瀏覽代碼

tweak BT + first AirPlay commit

philippe44 5 年之前
父節點
當前提交
e9731ee232

+ 1 - 0
.gitignore

@@ -66,3 +66,4 @@ libs/
 /cdump.cmd
 /_*
 sdkconfig
+*_history/

+ 0 - 65
components/airplay/airplay_sink.c

@@ -1,65 +0,0 @@
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-#include <stdlib.h>
-
-#include "mdns.h"
-#include "nvs.h"
-#include "tcpip_adapter.h"
-#include "esp_log.h"
-#include "esp_console.h"
-#include "esp_pthread.h"
-#include "esp_system.h"
-#include "freertos/timers.h"
-#include "airplay_sink.h"
-
-#include "trace.h"
-
-static const char * TAG = "platform";
-extern char current_namespace[];
-
-void airplay_sink_init(void) {
-    const char *hostname;
-	char *airplay_name, sink_name[32] = CONFIG_AIRPLAY_NAME;
-	nvs_handle nvs;
-				
-	tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &hostname);
-
-    //initialize mDNS
-    ESP_ERROR_CHECK( mdns_init() );
-    ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
-        
-    //structure with TXT records
-    mdns_txt_item_t serviceTxtData[] = {
-		{"am", "esp32"},
-		{"tp", "UDP"},
-		{"sm","false"}, 
-		{"sv","false"}, 
-		{"ek","1"},
-		{"et","0,1"},
-		{"md","0,1,2"},
-		{"cn","0,1"},
-		{"ch","2"},
-		{"ss","16"},
-		{"sr","44100"},
-		{"vn","3"},
-		{"txtvers","1"},
-	};
-	
-	if (nvs_open(current_namespace, NVS_READONLY, &nvs) == ESP_OK) {
-		size_t len = 31;
-		nvs_get_str(nvs, "airplay_sink_name", sink_name, &len);
-		nvs_close(nvs);
-	}	
-	
-	// AirPlay wants mDNS name to be MAC@name
-	uint8_t mac[6];	
-    esp_read_mac(mac, ESP_MAC_WIFI_STA);
-    asprintf(&airplay_name, "%02X%02X%02X%02X%02X%02X@%s",  mac[3], mac[4], mac[5], mac[3], mac[4], mac[5], sink_name);
-	
-	ESP_LOGI(TAG, "mdns hostname set to: [%s] with servicename %s", hostname, sink_name);
-
-    //initialize service
-    ESP_ERROR_CHECK( mdns_service_add(airplay_name, "_raop", "_tcp", 6000, serviceTxtData, sizeof(serviceTxtData) / sizeof(mdns_txt_item_t)) );
-	free(airplay_name);
-}

+ 0 - 22
components/airplay/airplay_sink.h

@@ -1,22 +0,0 @@
-/*
-   This example code is in the Public Domain (or CC0 licensed, at your option.)
-
-   Unless required by applicable law or agreed to in writing, this
-   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
-   CONDITIONS OF ANY KIND, either express or implied.
-*/
-
-#ifndef __AIRPLAY_SINK_H__
-#define __AIRPLAY_SINK_H__
-
-#include <stdint.h>
-
-//typedef enum { 	BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE, 
-				//BT_SINK_RATE, BT_SINK_VOLUME,  } bt_sink_cmd_t;
-
-/**
- * @brief     init sink mode (need to be provided)
- */
-void airplay_sink_init(void);
-
-#endif /* __AIRPLAY_SINK_H__*/

+ 1 - 1
components/driver_bt/bt_app_sink.c

@@ -355,7 +355,7 @@ static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
     }
 }
 
-void bt_sink_init(void (*cmd_cb)(bt_sink_cmd_t cmd, ...), void (*data_cb)(const uint8_t *data, uint32_t len))
+void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb)
 {
 	esp_err_t err;
 	

+ 9 - 1
components/driver_bt/bt_app_sink.h

@@ -13,10 +13,18 @@
 
 typedef enum { 	BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE, 
 				BT_SINK_RATE, BT_SINK_VOLUME,  } bt_sink_cmd_t;
+				
+typedef void (*bt_cmd_cb_t)(bt_sink_cmd_t cmd, ...);
+typedef void (*bt_data_cb_t)(const uint8_t *data, uint32_t len);
 
 /**
  * @brief     init sink mode (need to be provided)
  */
-void bt_sink_init(void (*cmd_cb)(bt_sink_cmd_t cmd, ...), void (*data_cb)(const uint8_t *data, uint32_t len));
+void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb);
+
+/**
+ * @brief     local command mode (stop, play, volume ...)
+ */
+void bt_sink_cmd(bt_sink_cmd_t event, ...);
 
 #endif /* __BT_APP_SINK_H__*/

+ 1135 - 0
components/raop/alac.c

@@ -0,0 +1,1135 @@
+/*
+ * ALAC (Apple Lossless Audio Codec) decoder
+ * Copyright (c) 2005 David Hammerton
+ * All rights reserved.
+ *
+ * This is the actual decoder.
+ *
+ * http://crazney.net/programs/itunes/alac.html
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+static const int host_bigendian = 0;
+#else
+static const int host_bigendian = 1;
+#endif
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "alac.h"
+
+extern int _fprintf(FILE *file, ...);
+
+#define _Swap32(v) do { \
+                   v = (((v) & 0x000000FF) << 0x18) | \
+                       (((v) & 0x0000FF00) << 0x08) | \
+                       (((v) & 0x00FF0000) >> 0x08) | \
+                       (((v) & 0xFF000000) >> 0x18); } while(0)
+
+#define _Swap16(v) do { \
+                   v = (((v) & 0x00FF) << 0x08) | \
+                       (((v) & 0xFF00) >> 0x08); } while (0)
+
+struct {signed int x:24;} se_struct_24;
+#define SignExtend24(val) (se_struct_24.x = val)
+
+void allocate_buffers(alac_file *alac)
+{
+    alac->predicterror_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4);
+    alac->predicterror_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4);
+
+    alac->outputsamples_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4);
+    alac->outputsamples_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4);
+
+    alac->uncompressed_bytes_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4);
+    alac->uncompressed_bytes_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4);
+}
+
+void alac_set_info(alac_file *alac, char *inputbuffer)
+{
+  char *ptr = inputbuffer;
+  ptr += 4; /* size */
+  ptr += 4; /* frma */
+  ptr += 4; /* alac */
+  ptr += 4; /* size */
+  ptr += 4; /* alac */
+
+  ptr += 4; /* 0 ? */
+
+  alac->setinfo_max_samples_per_frame = *(uint32_t*)ptr; /* buffer size / 2 ? */
+  if (!host_bigendian)
+      _Swap32(alac->setinfo_max_samples_per_frame);
+  ptr += 4;
+  alac->setinfo_7a = *(uint8_t*)ptr;
+  ptr += 1;
+  alac->setinfo_sample_size = *(uint8_t*)ptr;
+  ptr += 1;
+  alac->setinfo_rice_historymult = *(uint8_t*)ptr;
+  ptr += 1;
+  alac->setinfo_rice_initialhistory = *(uint8_t*)ptr;
+  ptr += 1;
+  alac->setinfo_rice_kmodifier = *(uint8_t*)ptr;
+  ptr += 1;
+  alac->setinfo_7f = *(uint8_t*)ptr;
+  ptr += 1;
+  alac->setinfo_80 = *(uint16_t*)ptr;
+  if (!host_bigendian)
+      _Swap16(alac->setinfo_80);
+  ptr += 2;
+  alac->setinfo_82 = *(uint32_t*)ptr;
+  if (!host_bigendian)
+      _Swap32(alac->setinfo_82);
+  ptr += 4;
+  alac->setinfo_86 = *(uint32_t*)ptr;
+  if (!host_bigendian)
+      _Swap32(alac->setinfo_86);
+  ptr += 4;
+  alac->setinfo_8a_rate = *(uint32_t*)ptr;
+  if (!host_bigendian)
+      _Swap32(alac->setinfo_8a_rate);
+
+  allocate_buffers(alac);
+
+}
+
+/* stream reading */
+
+/* supports reading 1 to 16 bits, in big endian format */
+static uint32_t readbits_16(alac_file *alac, int bits)
+{
+    uint32_t result;
+    int new_accumulator;
+
+    result = (alac->input_buffer[0] << 16) |
+             (alac->input_buffer[1] << 8) |
+             (alac->input_buffer[2]);
+
+    /* shift left by the number of bits we've already read,
+     * so that the top 'n' bits of the 24 bits we read will
+     * be the return bits */
+    result = result << alac->input_buffer_bitaccumulator;
+
+    result = result & 0x00ffffff;
+
+    /* and then only want the top 'n' bits from that, where
+     * n is 'bits' */
+    result = result >> (24 - bits);
+
+    new_accumulator = (alac->input_buffer_bitaccumulator + bits);
+
+    /* increase the buffer pointer if we've read over n bytes. */
+    alac->input_buffer += (new_accumulator >> 3);
+
+    /* and the remainder goes back into the bit accumulator */
+    alac->input_buffer_bitaccumulator = (new_accumulator & 7);
+
+    return result;
+}
+
+/* supports reading 1 to 32 bits, in big endian format */
+static uint32_t readbits(alac_file *alac, int bits)
+{
+    int32_t result = 0;
+
+    if (bits > 16)
+    {
+        bits -= 16;
+        result = readbits_16(alac, 16) << bits;
+    }
+
+    result |= readbits_16(alac, bits);
+
+    return result;
+}
+
+/* reads a single bit */
+static int readbit(alac_file *alac)
+{
+    int result;
+    int new_accumulator;
+
+    result = alac->input_buffer[0];
+
+    result = result << alac->input_buffer_bitaccumulator;
+
+    result = result >> 7 & 1;
+
+    new_accumulator = (alac->input_buffer_bitaccumulator + 1);
+
+    alac->input_buffer += (new_accumulator / 8);
+
+    alac->input_buffer_bitaccumulator = (new_accumulator % 8);
+
+    return result;
+}
+
+static void unreadbits(alac_file *alac, int bits)
+{
+    int new_accumulator = (alac->input_buffer_bitaccumulator - bits);
+
+    alac->input_buffer += (new_accumulator >> 3);
+
+    alac->input_buffer_bitaccumulator = (new_accumulator & 7);
+    if (alac->input_buffer_bitaccumulator < 0)
+        alac->input_buffer_bitaccumulator *= -1;
+}
+
+/* various implementations of count_leading_zero:
+ * the first one is the original one, the simplest and most
+ * obvious for what it's doing. never use this.
+ * then there are the asm ones. fill in as necessary
+ * and finally an unrolled and optimised c version
+ * to fall back to
+ */
+#if 0
+/* hideously inefficient. could use a bitmask search,
+ * alternatively bsr on x86,
+ */
+static int count_leading_zeros(int32_t input)
+{
+    int i = 0;
+    while (!(0x80000000 & input) && i < 32)
+    {
+        i++;
+        input = input << 1;
+    }
+    return i;
+}
+#elif defined(__GNUC__)
+/* for some reason the unrolled version (below) is
+ * actually faster than this. yay intel!
+ */
+static int count_leading_zeros(int input)
+{
+    return __builtin_clz(input);
+}
+#elif (defined(_MSC_VER) || defined (__BORLANDC__)) && defined(_M_IX86)
+static int count_leading_zeros(int input)
+{
+    int output = 0;
+    if (!input) return 32;
+    __asm
+    {
+        mov eax, input;
+        mov edx, 0x1f;
+        bsr ecx, eax;
+        sub edx, ecx;
+        mov output, edx;
+    }
+    return output;
+}
+#else
+#warning using generic count leading zeroes. You may wish to write one for your CPU / compiler
+static int count_leading_zeros(int input)
+{
+	int output = 0;
+    int curbyte = 0;
+
+    curbyte = input >> 24;
+    if (curbyte) goto found;
+    output += 8;
+
+    curbyte = input >> 16;
+    if (curbyte & 0xff) goto found;
+    output += 8;
+
+    curbyte = input >> 8;
+    if (curbyte & 0xff) goto found;
+    output += 8;
+
+    curbyte = input;
+    if (curbyte & 0xff) goto found;
+    output += 8;
+
+    return output;
+
+found:
+    if (!(curbyte & 0xf0))
+    {
+        output += 4;
+    }
+    else
+        curbyte >>= 4;
+
+    if (curbyte & 0x8)
+        return output;
+    if (curbyte & 0x4)
+        return output + 1;
+    if (curbyte & 0x2)
+        return output + 2;
+    if (curbyte & 0x1)
+        return output + 3;
+
+    /* shouldn't get here: */
+    return output + 4;
+}
+#endif
+
+#define RICE_THRESHOLD 8 // maximum number of bits for a rice prefix.
+
+static int32_t entropy_decode_value(alac_file* alac,
+                             int readSampleSize,
+							 int k,
+                             int rice_kmodifier_mask)
+{
+    int32_t x = 0; // decoded value
+
+    // read x, number of 1s before 0 represent the rice value.
+    while (x <= RICE_THRESHOLD && readbit(alac))
+    {
+        x++;
+    }
+
+    if (x > RICE_THRESHOLD)
+    {
+        // read the number from the bit stream (raw value)
+        int32_t value;
+
+        value = readbits(alac, readSampleSize);
+
+        // mask value
+        value &= (((uint32_t)0xffffffff) >> (32 - readSampleSize));
+
+        x = value;
+    }
+    else
+    {
+        if (k != 1)
+        {
+            int extraBits = readbits(alac, k);
+
+            // x = x * (2^k - 1)
+            x *= (((1 << k) - 1) & rice_kmodifier_mask);
+
+            if (extraBits > 1)
+                x += extraBits - 1;
+            else
+                unreadbits(alac, 1);
+        }
+    }
+
+    return x;
+}
+
+static void entropy_rice_decode(alac_file* alac,
+                         int32_t* outputBuffer,
+                         int outputSize,
+                         int readSampleSize,
+                         int rice_initialhistory,
+						 int rice_kmodifier,
+                         int rice_historymult,
+                         int rice_kmodifier_mask)
+{
+    int             outputCount;
+    int             history = rice_initialhistory;
+    int             signModifier = 0;
+
+    for (outputCount = 0; outputCount < outputSize; outputCount++)
+    {
+        int32_t     decodedValue;
+        int32_t     finalValue;
+        int32_t     k;
+
+        k = 31 - rice_kmodifier - count_leading_zeros((history >> 9) + 3);
+
+        if (k < 0) k += rice_kmodifier;
+        else k = rice_kmodifier;
+
+        // note: don't use rice_kmodifier_mask here (set mask to 0xFFFFFFFF)
+        decodedValue = entropy_decode_value(alac, readSampleSize, k, 0xFFFFFFFF);
+
+        decodedValue += signModifier;
+        finalValue = (decodedValue + 1) / 2; // inc by 1 and shift out sign bit
+        if (decodedValue & 1) // the sign is stored in the low bit
+            finalValue *= -1;
+
+        outputBuffer[outputCount] = finalValue;
+
+        signModifier = 0;
+
+        // update history
+        history += (decodedValue * rice_historymult)
+                - ((history * rice_historymult) >> 9);
+
+        if (decodedValue > 0xFFFF)
+            history = 0xFFFF;
+
+        // special case, for compressed blocks of 0
+        if ((history < 128) && (outputCount + 1 < outputSize))
+        {
+            int32_t     blockSize;
+
+            signModifier = 1;
+
+            k = count_leading_zeros(history) + ((history + 16) / 64) - 24;
+
+			// note: blockSize is always 16bit
+            blockSize = entropy_decode_value(alac, 16, k, rice_kmodifier_mask);
+
+            // got blockSize 0s
+            if (blockSize > 0)
+            {
+                memset(&outputBuffer[outputCount + 1], 0, blockSize * sizeof(*outputBuffer));
+                outputCount += blockSize;
+            }
+
+            if (blockSize > 0xFFFF)
+                signModifier = 0;
+
+            history = 0;
+        }
+    }
+}
+
+#define SIGN_EXTENDED32(val, bits) ((val << (32 - bits)) >> (32 - bits))
+
+#define SIGN_ONLY(v) \
+                     ((v < 0) ? (-1) : \
+                                ((v > 0) ? (1) : \
+                                           (0)))
+
+static void predictor_decompress_fir_adapt(int32_t *error_buffer,
+                                           int32_t *buffer_out,
+                                           int output_size,
+                                           int readsamplesize,
+                                           int16_t *predictor_coef_table,
+                                           int predictor_coef_num,
+                                           int predictor_quantitization)
+{
+    int i;
+
+    /* first sample always copies */
+    *buffer_out = *error_buffer;
+
+    if (!predictor_coef_num)
+    {
+        if (output_size <= 1) return;
+        memcpy(buffer_out+1, error_buffer+1, (output_size-1) * 4);
+        return;
+    }
+
+    if (predictor_coef_num == 0x1f) /* 11111 - max value of predictor_coef_num */
+    { /* second-best case scenario for fir decompression,
+	   * error describes a small difference from the previous sample only
+       */
+        if (output_size <= 1) return;
+        for (i = 0; i < output_size - 1; i++)
+        {
+            int32_t prev_value;
+            int32_t error_value;
+
+            prev_value = buffer_out[i];
+            error_value = error_buffer[i+1];
+            buffer_out[i+1] = SIGN_EXTENDED32((prev_value + error_value), readsamplesize);
+        }
+        return;
+    }
+
+    /* read warm-up samples */
+    if (predictor_coef_num > 0)
+    {
+        int i;
+        for (i = 0; i < predictor_coef_num; i++)
+        {
+            int32_t val;
+
+            val = buffer_out[i] + error_buffer[i+1];
+
+            val = SIGN_EXTENDED32(val, readsamplesize);
+
+            buffer_out[i+1] = val;
+        }
+    }
+
+#if 0
+    /* 4 and 8 are very common cases (the only ones i've seen). these
+     * should be unrolled and optimised
+     */
+    if (predictor_coef_num == 4)
+    {
+        /* FIXME: optimised general case */
+        return;
+    }
+
+    if (predictor_coef_table == 8)
+    {
+        /* FIXME: optimised general case */
+        return;
+    }
+#endif
+
+
+    /* general case */
+    if (predictor_coef_num > 0)
+    {
+        for (i = predictor_coef_num + 1;
+             i < output_size;
+             i++)
+        {
+            int j;
+            int sum = 0;
+            int outval;
+            int error_val = error_buffer[i];
+
+            for (j = 0; j < predictor_coef_num; j++)
+            {
+                sum += (buffer_out[predictor_coef_num-j] - buffer_out[0]) *
+                       predictor_coef_table[j];
+            }
+
+            outval = (1 << (predictor_quantitization-1)) + sum;
+            outval = outval >> predictor_quantitization;
+            outval = outval + buffer_out[0] + error_val;
+            outval = SIGN_EXTENDED32(outval, readsamplesize);
+
+            buffer_out[predictor_coef_num+1] = outval;
+
+            if (error_val > 0)
+            {
+                int predictor_num = predictor_coef_num - 1;
+
+                while (predictor_num >= 0 && error_val > 0)
+                {
+                    int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num];
+                    int sign = SIGN_ONLY(val);
+
+                    predictor_coef_table[predictor_num] -= sign;
+
+                    val *= sign; /* absolute value */
+
+                    error_val -= ((val >> predictor_quantitization) *
+                                  (predictor_coef_num - predictor_num));
+
+                    predictor_num--;
+                }
+            }
+            else if (error_val < 0)
+			{
+                int predictor_num = predictor_coef_num - 1;
+
+                while (predictor_num >= 0 && error_val < 0)
+                {
+                    int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num];
+                    int sign = - SIGN_ONLY(val);
+
+                    predictor_coef_table[predictor_num] -= sign;
+
+                    val *= sign; /* neg value */
+
+                    error_val -= ((val >> predictor_quantitization) *
+                                  (predictor_coef_num - predictor_num));
+
+                    predictor_num--;
+                }
+            }
+
+            buffer_out++;
+        }
+    }
+}
+
+static void deinterlace_16(int32_t *buffer_a, int32_t *buffer_b,
+                    int16_t *buffer_out,
+                    int numchannels, int numsamples,
+                    uint8_t interlacing_shift,
+                    uint8_t interlacing_leftweight)
+{
+    int i;
+    if (numsamples <= 0) return;
+
+    /* weighted interlacing */
+    if (interlacing_leftweight)
+    {
+        for (i = 0; i < numsamples; i++)
+        {
+            int32_t difference, midright;
+            int16_t left;
+            int16_t right;
+
+            midright = buffer_a[i];
+            difference = buffer_b[i];
+
+
+            right = midright - ((difference * interlacing_leftweight) >> interlacing_shift);
+			left = right + difference;
+
+            /* output is always little endian */
+            if (host_bigendian)
+            {
+                _Swap16(left);
+                _Swap16(right);
+            }
+
+            buffer_out[i*numchannels] = left;
+            buffer_out[i*numchannels + 1] = right;
+        }
+
+        return;
+    }
+
+    /* otherwise basic interlacing took place */
+    for (i = 0; i < numsamples; i++)
+    {
+        int16_t left, right;
+
+        left = buffer_a[i];
+        right = buffer_b[i];
+
+        /* output is always little endian */
+        if (host_bigendian)
+        {
+            _Swap16(left);
+            _Swap16(right);
+        }
+
+        buffer_out[i*numchannels] = left;
+        buffer_out[i*numchannels + 1] = right;
+    }
+}
+
+static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b,
+                    int uncompressed_bytes,
+                    int32_t *uncompressed_bytes_buffer_a, int32_t *uncompressed_bytes_buffer_b,
+                    void *buffer_out,
+                    int numchannels, int numsamples,
+                    uint8_t interlacing_shift,
+                    uint8_t interlacing_leftweight)
+{
+    int i;
+    if (numsamples <= 0) return;
+
+	/* weighted interlacing */
+    if (interlacing_leftweight)
+    {
+        for (i = 0; i < numsamples; i++)
+        {
+            int32_t difference, midright;
+            int32_t left;
+            int32_t right;
+
+            midright = buffer_a[i];
+            difference = buffer_b[i];
+
+            right = midright - ((difference * interlacing_leftweight) >> interlacing_shift);
+            left = right + difference;
+
+            if (uncompressed_bytes)
+            {
+                uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8));
+                left <<= (uncompressed_bytes * 8);
+                right <<= (uncompressed_bytes * 8);
+
+                left |= uncompressed_bytes_buffer_a[i] & mask;
+                right |= uncompressed_bytes_buffer_b[i] & mask;
+            }
+
+            ((uint8_t*)buffer_out)[i * numchannels * 3] = (left) & 0xFF;
+            ((uint8_t*)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF;
+            ((uint8_t*)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF;
+
+            ((uint8_t*)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF;
+            ((uint8_t*)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF;
+            ((uint8_t*)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF;
+        }
+
+        return;
+    }
+
+    /* otherwise basic interlacing took place */
+    for (i = 0; i < numsamples; i++)
+    {
+        int32_t left, right;
+
+        left = buffer_a[i];
+        right = buffer_b[i];
+
+        if (uncompressed_bytes)
+        {
+			uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8));
+            left <<= (uncompressed_bytes * 8);
+            right <<= (uncompressed_bytes * 8);
+
+            left |= uncompressed_bytes_buffer_a[i] & mask;
+            right |= uncompressed_bytes_buffer_b[i] & mask;
+        }
+
+        ((uint8_t*)buffer_out)[i * numchannels * 3] = (left) & 0xFF;
+        ((uint8_t*)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF;
+        ((uint8_t*)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF;
+
+        ((uint8_t*)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF;
+        ((uint8_t*)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF;
+        ((uint8_t*)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF;
+
+    }
+
+}
+
+void decode_frame(alac_file *alac,
+                  unsigned char *inbuffer,
+                  void *outbuffer, int *outputsize)
+{
+    int channels;
+    int32_t outputsamples = alac->setinfo_max_samples_per_frame;
+
+    /* setup the stream */
+    alac->input_buffer = inbuffer;
+    alac->input_buffer_bitaccumulator = 0;
+
+    channels = readbits(alac, 3);
+
+    *outputsize = outputsamples * alac->bytespersample;
+
+    switch(channels)
+    {
+    case 0: /* 1 channel */
+    {
+        int hassize;
+        int isnotcompressed;
+        int readsamplesize;
+
+        int uncompressed_bytes;
+        int ricemodifier;
+
+        /* 2^result = something to do with output waiting.
+		 * perhaps matters if we read > 1 frame in a pass?
+         */
+        readbits(alac, 4);
+
+        readbits(alac, 12); /* unknown, skip 12 bits */
+
+        hassize = readbits(alac, 1); /* the output sample size is stored soon */
+
+        uncompressed_bytes = readbits(alac, 2); /* number of bytes in the (compressed) stream that are not compressed */
+
+        isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */
+
+        if (hassize)
+        {
+            /* now read the number of samples,
+             * as a 32bit integer */
+            outputsamples = readbits(alac, 32);
+            *outputsize = outputsamples * alac->bytespersample;
+        }
+
+        readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8);
+
+        if (!isnotcompressed)
+        { /* so it is compressed */
+            int16_t predictor_coef_table[32];
+            int predictor_coef_num;
+            int prediction_type;
+            int prediction_quantitization;
+            int i;
+
+            /* skip 16 bits, not sure what they are. seem to be used in
+             * two channel case */
+            readbits(alac, 8);
+            readbits(alac, 8);
+
+            prediction_type = readbits(alac, 4);
+            prediction_quantitization = readbits(alac, 4);
+
+            ricemodifier = readbits(alac, 3);
+            predictor_coef_num = readbits(alac, 5);
+
+            /* read the predictor table */
+            for (i = 0; i < predictor_coef_num; i++)
+            {
+                predictor_coef_table[i] = (int16_t)readbits(alac, 16);
+            }
+
+			if (uncompressed_bytes)
+            {
+                int i;
+                for (i = 0; i < outputsamples; i++)
+                {
+                    alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8);
+                }
+            }
+
+            entropy_rice_decode(alac,
+                                alac->predicterror_buffer_a,
+                                outputsamples,
+                                readsamplesize,
+                                alac->setinfo_rice_initialhistory,
+                                alac->setinfo_rice_kmodifier,
+                                ricemodifier * alac->setinfo_rice_historymult / 4,
+                                (1 << alac->setinfo_rice_kmodifier) - 1);
+
+            if (prediction_type == 0)
+            { /* adaptive fir */
+                predictor_decompress_fir_adapt(alac->predicterror_buffer_a,
+                                               alac->outputsamples_buffer_a,
+                                               outputsamples,
+                                               readsamplesize,
+                                               predictor_coef_table,
+                                               predictor_coef_num,
+                                               prediction_quantitization);
+            }
+            else
+            {
+                _fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type);
+                /* i think the only other prediction type (or perhaps this is just a
+                 * boolean?) runs adaptive fir twice.. like:
+                 * predictor_decompress_fir_adapt(predictor_error, tempout, ...)
+                 * predictor_decompress_fir_adapt(predictor_error, outputsamples ...)
+                 * little strange..
+                 */
+            }
+
+        }
+        else
+        { /* not compressed, easy case */
+            if (alac->setinfo_sample_size <= 16)
+            {
+                int i;
+                for (i = 0; i < outputsamples; i++)
+                {
+					int32_t audiobits = readbits(alac, alac->setinfo_sample_size);
+
+                    audiobits = SIGN_EXTENDED32(audiobits, alac->setinfo_sample_size);
+
+                    alac->outputsamples_buffer_a[i] = audiobits;
+                }
+            }
+            else
+            {
+                int i;
+                for (i = 0; i < outputsamples; i++)
+                {
+                    int32_t audiobits;
+
+                    audiobits = readbits(alac, 16);
+                    /* special case of sign extension..
+                     * as we'll be ORing the low 16bits into this */
+                    audiobits = audiobits << (alac->setinfo_sample_size - 16);
+                    audiobits |= readbits(alac, alac->setinfo_sample_size - 16);
+                    audiobits = SignExtend24(audiobits);
+
+                    alac->outputsamples_buffer_a[i] = audiobits;
+                }
+            }
+            uncompressed_bytes = 0; // always 0 for uncompressed
+        }
+
+        switch(alac->setinfo_sample_size)
+        {
+        case 16:
+        {
+            int i;
+            for (i = 0; i < outputsamples; i++)
+            {
+                int16_t sample = alac->outputsamples_buffer_a[i];
+                if (host_bigendian)
+                    _Swap16(sample);
+                ((int16_t*)outbuffer)[i * alac->numchannels] = sample;
+            }
+            break;
+        }
+        case 24:
+        {
+            int i;
+            for (i = 0; i < outputsamples; i++)
+            {
+                int32_t sample = alac->outputsamples_buffer_a[i];
+
+                if (uncompressed_bytes)
+                {
+                    uint32_t mask;
+                    sample = sample << (uncompressed_bytes * 8);
+                    mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8));
+                    sample |= alac->uncompressed_bytes_buffer_a[i] & mask;
+                }
+
+                ((uint8_t*)outbuffer)[i * alac->numchannels * 3] = (sample) & 0xFF;
+                ((uint8_t*)outbuffer)[i * alac->numchannels * 3 + 1] = (sample >> 8) & 0xFF;
+                ((uint8_t*)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF;
+            }
+            break;
+        }
+        case 20:
+        case 32:
+            _fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size);
+            break;
+        default:
+            break;
+        }
+        break;
+    }
+    case 1: /* 2 channels */
+    {
+        int hassize;
+        int isnotcompressed;
+        int readsamplesize;
+
+        int uncompressed_bytes;
+
+        uint8_t interlacing_shift;
+        uint8_t interlacing_leftweight;
+
+        /* 2^result = something to do with output waiting.
+         * perhaps matters if we read > 1 frame in a pass?
+         */
+        readbits(alac, 4);
+
+        readbits(alac, 12); /* unknown, skip 12 bits */
+
+        hassize = readbits(alac, 1); /* the output sample size is stored soon */
+
+        uncompressed_bytes = readbits(alac, 2); /* the number of bytes in the (compressed) stream that are not compressed */
+
+        isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */
+
+        if (hassize)
+        {
+            /* now read the number of samples,
+             * as a 32bit integer */
+            outputsamples = readbits(alac, 32);
+            *outputsize = outputsamples * alac->bytespersample;
+        }
+
+        readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8) + 1;
+
+        if (!isnotcompressed)
+        { /* compressed */
+            int16_t predictor_coef_table_a[32];
+            int predictor_coef_num_a;
+            int prediction_type_a;
+            int prediction_quantitization_a;
+            int ricemodifier_a;
+
+            int16_t predictor_coef_table_b[32];
+            int predictor_coef_num_b;
+            int prediction_type_b;
+            int prediction_quantitization_b;
+            int ricemodifier_b;
+
+            int i;
+
+            interlacing_shift = readbits(alac, 8);
+            interlacing_leftweight = readbits(alac, 8);
+
+            /******** channel 1 ***********/
+            prediction_type_a = readbits(alac, 4);
+            prediction_quantitization_a = readbits(alac, 4);
+
+            ricemodifier_a = readbits(alac, 3);
+            predictor_coef_num_a = readbits(alac, 5);
+
+            /* read the predictor table */
+            for (i = 0; i < predictor_coef_num_a; i++)
+            {
+                predictor_coef_table_a[i] = (int16_t)readbits(alac, 16);
+            }
+
+            /******** channel 2 *********/
+            prediction_type_b = readbits(alac, 4);
+            prediction_quantitization_b = readbits(alac, 4);
+
+			ricemodifier_b = readbits(alac, 3);
+            predictor_coef_num_b = readbits(alac, 5);
+
+            /* read the predictor table */
+            for (i = 0; i < predictor_coef_num_b; i++)
+            {
+                predictor_coef_table_b[i] = (int16_t)readbits(alac, 16);
+            }
+
+            /*********************/
+            if (uncompressed_bytes)
+            { /* see mono case */
+                int i;
+                for (i = 0; i < outputsamples; i++)
+                {
+                    alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8);
+                    alac->uncompressed_bytes_buffer_b[i] = readbits(alac, uncompressed_bytes * 8);
+                }
+            }
+
+            /* channel 1 */
+            entropy_rice_decode(alac,
+                                alac->predicterror_buffer_a,
+                                outputsamples,
+                                readsamplesize,
+                                alac->setinfo_rice_initialhistory,
+                                alac->setinfo_rice_kmodifier,
+                                ricemodifier_a * alac->setinfo_rice_historymult / 4,
+                                (1 << alac->setinfo_rice_kmodifier) - 1);
+
+            if (prediction_type_a == 0)
+            { /* adaptive fir */
+                predictor_decompress_fir_adapt(alac->predicterror_buffer_a,
+                                               alac->outputsamples_buffer_a,
+                                               outputsamples,
+                                               readsamplesize,
+                                               predictor_coef_table_a,
+                                               predictor_coef_num_a,
+                                               prediction_quantitization_a);
+            }
+            else
+            { /* see mono case */
+                _fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type_a);
+            }
+
+            /* channel 2 */
+            entropy_rice_decode(alac,
+								alac->predicterror_buffer_b,
+                                outputsamples,
+                                readsamplesize,
+                                alac->setinfo_rice_initialhistory,
+                                alac->setinfo_rice_kmodifier,
+                                ricemodifier_b * alac->setinfo_rice_historymult / 4,
+                                (1 << alac->setinfo_rice_kmodifier) - 1);
+
+            if (prediction_type_b == 0)
+            { /* adaptive fir */
+                predictor_decompress_fir_adapt(alac->predicterror_buffer_b,
+                                               alac->outputsamples_buffer_b,
+                                               outputsamples,
+                                               readsamplesize,
+                                               predictor_coef_table_b,
+                                               predictor_coef_num_b,
+                                               prediction_quantitization_b);
+            }
+            else
+            {
+                _fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type_b);
+            }
+        }
+        else
+        { /* not compressed, easy case */
+            if (alac->setinfo_sample_size <= 16)
+            {
+                int i;
+                for (i = 0; i < outputsamples; i++)
+                {
+                    int32_t audiobits_a, audiobits_b;
+
+                    audiobits_a = readbits(alac, alac->setinfo_sample_size);
+                    audiobits_b = readbits(alac, alac->setinfo_sample_size);
+
+                    audiobits_a = SIGN_EXTENDED32(audiobits_a, alac->setinfo_sample_size);
+                    audiobits_b = SIGN_EXTENDED32(audiobits_b, alac->setinfo_sample_size);
+
+                    alac->outputsamples_buffer_a[i] = audiobits_a;
+                    alac->outputsamples_buffer_b[i] = audiobits_b;
+                }
+            }
+            else
+            {
+                int i;
+                for (i = 0; i < outputsamples; i++)
+                {
+					int32_t audiobits_a, audiobits_b;
+
+                    audiobits_a = readbits(alac, 16);
+                    audiobits_a = audiobits_a << (alac->setinfo_sample_size - 16);
+                    audiobits_a |= readbits(alac, alac->setinfo_sample_size - 16);
+                    audiobits_a = SignExtend24(audiobits_a);
+
+                    audiobits_b = readbits(alac, 16);
+                    audiobits_b = audiobits_b << (alac->setinfo_sample_size - 16);
+                    audiobits_b |= readbits(alac, alac->setinfo_sample_size - 16);
+                    audiobits_b = SignExtend24(audiobits_b);
+
+                    alac->outputsamples_buffer_a[i] = audiobits_a;
+                    alac->outputsamples_buffer_b[i] = audiobits_b;
+                }
+            }
+            uncompressed_bytes = 0; // always 0 for uncompressed
+            interlacing_shift = 0;
+            interlacing_leftweight = 0;
+        }
+
+        switch(alac->setinfo_sample_size)
+        {
+        case 16:
+        {
+            deinterlace_16(alac->outputsamples_buffer_a,
+                           alac->outputsamples_buffer_b,
+                           (int16_t*)outbuffer,
+                           alac->numchannels,
+                           outputsamples,
+                           interlacing_shift,
+                           interlacing_leftweight);
+            break;
+        }
+        case 24:
+        {
+            deinterlace_24(alac->outputsamples_buffer_a,
+						   alac->outputsamples_buffer_b,
+                           uncompressed_bytes,
+                           alac->uncompressed_bytes_buffer_a,
+                           alac->uncompressed_bytes_buffer_b,
+                           (int16_t*)outbuffer,
+                           alac->numchannels,
+                           outputsamples,
+                           interlacing_shift,
+                           interlacing_leftweight);
+            break;
+		}
+        case 20:
+        case 32:
+			_fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size);
+            break;
+        default:
+            break;
+        }
+
+        break;
+    }
+    }
+}
+
+alac_file *create_alac(int samplesize, int numchannels)
+{
+	alac_file *newfile = malloc(sizeof(alac_file));
+
+	newfile->samplesize = samplesize;
+	newfile->numchannels = numchannels;
+	newfile->bytespersample = (samplesize / 8) * numchannels;
+
+	return newfile;
+}
+
+void delete_alac(alac_file *alac)
+{
+	free(alac->predicterror_buffer_a);
+	free(alac->predicterror_buffer_b);
+
+	free(alac->outputsamples_buffer_a);
+	free(alac->outputsamples_buffer_b);
+
+	free(alac->uncompressed_bytes_buffer_a);
+	free(alac->uncompressed_bytes_buffer_b);
+
+	free(alac);
+}
+
+

+ 55 - 0
components/raop/alac.h

@@ -0,0 +1,55 @@
+#ifndef __ALAC__DECOMP_H
+#define __ALAC__DECOMP_H
+
+typedef struct alac_file alac_file;
+
+alac_file *create_alac(int samplesize, int numchannels);
+void delete_alac(alac_file *alac);
+void decode_frame(alac_file *alac,
+                  unsigned char *inbuffer,
+                  void *outbuffer, int *outputsize);
+void alac_set_info(alac_file *alac, char *inputbuffer);
+void allocate_buffers(alac_file *alac);
+
+struct alac_file
+{
+    unsigned char *input_buffer;
+    int input_buffer_bitaccumulator; /* used so we can do arbitary
+                                        bit reads */
+
+    int samplesize;
+    int numchannels;
+    int bytespersample;
+
+
+    /* buffers */
+    int32_t *predicterror_buffer_a;
+    int32_t *predicterror_buffer_b;
+
+    int32_t *outputsamples_buffer_a;
+    int32_t *outputsamples_buffer_b;
+
+    int32_t *uncompressed_bytes_buffer_a;
+    int32_t *uncompressed_bytes_buffer_b;
+
+
+
+  /* stuff from setinfo */
+  uint32_t setinfo_max_samples_per_frame; /* 0x1000 = 4096 */    /* max samples per frame? */
+  uint8_t setinfo_7a; /* 0x00 */
+  uint8_t setinfo_sample_size; /* 0x10 */
+  uint8_t setinfo_rice_historymult; /* 0x28 */
+  uint8_t setinfo_rice_initialhistory; /* 0x0a */
+  uint8_t setinfo_rice_kmodifier; /* 0x0e */
+  uint8_t setinfo_7f; /* 0x02 */
+  uint16_t setinfo_80; /* 0x00ff */
+  uint32_t setinfo_82; /* 0x000020e7 */ /* max sample size?? */
+  uint32_t setinfo_86; /* 0x00069fe4 */ /* bit rate (avarge)?? */
+  uint32_t setinfo_8a_rate; /* 0x0000ac44 */
+  /* end setinfo stuff */
+
+};
+
+
+#endif /* __ALAC__DECOMP_H */
+

+ 4 - 1
components/airplay/component.mk → components/raop/component.mk

@@ -7,4 +7,7 @@
 # please read the SDK documents if you need to do this.
 #
 
-CFLAGS += 	-I$(COMPONENT_PATH)/../tools
+CFLAGS += -fstack-usage \
+	-I$(COMPONENT_PATH)/../tools	\
+	-I$(COMPONENT_PATH)/../codecs/inc/alac
+	

+ 545 - 0
components/raop/dmap_parser.c

@@ -0,0 +1,545 @@
+#include "dmap_parser.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#define DMAP_STRINGIFY_(x) #x
+#define DMAP_STRINGIFY(x) DMAP_STRINGIFY_(x)
+
+typedef enum {
+	DMAP_UNKNOWN,
+	DMAP_UINT,
+	DMAP_INT,
+	DMAP_STR,
+	DMAP_DATA,
+	DMAP_DATE,
+	DMAP_VERS,
+	DMAP_DICT,
+	DMAP_ITEM
+} DMAP_TYPE;
+
+typedef struct {
+	/**
+	 * The four-character code used in the encoded message.
+	 */
+	const char *code;
+
+	/**
+	 * The type of data associated with the content code.
+	 */
+	DMAP_TYPE type;
+
+	/**
+	 * For listings, the type of their listing item children.
+	 *
+	 * Listing items (mlit) can be of any type, and as with other content codes
+	 * their type information is not encoded in the message. Parsers must
+	 * determine the type of the listing items based on their parent context.
+	 */
+	DMAP_TYPE list_item_type;
+
+	/**
+	 * A human-readable name for the content code.
+	 */
+	const char *name;
+} dmap_field;
+
+static const dmap_field dmap_fields[] = {
+	{ "abal",    DMAP_DICT, DMAP_STR,  "daap.browsealbumlisting" },
+	{ "abar",    DMAP_DICT, DMAP_STR,  "daap.browseartistlisting" },
+	{ "abcp",    DMAP_DICT, DMAP_STR,  "daap.browsecomposerlisting" },
+	{ "abgn",    DMAP_DICT, DMAP_STR,  "daap.browsegenrelisting" },
+	{ "abpl",    DMAP_UINT, 0,         "daap.baseplaylist" },
+	{ "abro",    DMAP_DICT, 0,         "daap.databasebrowse" },
+	{ "adbs",    DMAP_DICT, 0,         "daap.databasesongs" },
+	{ "aeAD",    DMAP_DICT, 0,         "com.apple.itunes.adam-ids-array" },
+	{ "aeAI",    DMAP_UINT, 0,         "com.apple.itunes.itms-artistid" },
+	{ "aeCD",    DMAP_DATA, 0,         "com.apple.itunes.flat-chapter-data" },
+	{ "aeCF",    DMAP_UINT, 0,         "com.apple.itunes.cloud-flavor-id" },
+	{ "aeCI",    DMAP_UINT, 0,         "com.apple.itunes.itms-composerid" },
+	{ "aeCK",    DMAP_UINT, 0,         "com.apple.itunes.cloud-library-kind" },
+	{ "aeCM",    DMAP_UINT, 0,         "com.apple.itunes.cloud-match-type" },
+	{ "aeCR",    DMAP_STR,  0,         "com.apple.itunes.content-rating" } ,
+	{ "aeCS",    DMAP_UINT, 0,         "com.apple.itunes.artworkchecksum" },
+	{ "aeCU",    DMAP_UINT, 0,         "com.apple.itunes.cloud-user-id" },
+	{ "aeCd",    DMAP_UINT, 0,         "com.apple.itunes.cloud-id" },
+	{ "aeDE",    DMAP_STR,  0,         "com.apple.itunes.longest-content-description" },
+	{ "aeDL",    DMAP_UINT, 0,         "com.apple.itunes.drm-downloader-user-id" },
+	{ "aeDP",    DMAP_UINT, 0,         "com.apple.itunes.drm-platform-id" },
+	{ "aeDR",    DMAP_UINT, 0,         "com.apple.itunes.drm-user-id" },
+	{ "aeDV",    DMAP_UINT, 0,         "com.apple.itunes.drm-versions" },
+	{ "aeEN",    DMAP_STR,  0,         "com.apple.itunes.episode-num-str" },
+	{ "aeES",    DMAP_UINT, 0,         "com.apple.itunes.episode-sort" },
+	{ "aeFA",    DMAP_UINT, 0,         "com.apple.itunes.drm-family-id" },
+	{ "aeGD",    DMAP_UINT, 0,         "com.apple.itunes.gapless-enc-dr" } ,
+	{ "aeGE",    DMAP_UINT, 0,         "com.apple.itunes.gapless-enc-del" },
+	{ "aeGH",    DMAP_UINT, 0,         "com.apple.itunes.gapless-heur" },
+	{ "aeGI",    DMAP_UINT, 0,         "com.apple.itunes.itms-genreid" },
+	{ "aeGR",    DMAP_UINT, 0,         "com.apple.itunes.gapless-resy" },
+	{ "aeGU",    DMAP_UINT, 0,         "com.apple.itunes.gapless-dur" },
+	{ "aeGs",    DMAP_UINT, 0,         "com.apple.itunes.can-be-genius-seed" },
+	{ "aeHC",    DMAP_UINT, 0,         "com.apple.itunes.has-chapter-data" },
+	{ "aeHD",    DMAP_UINT, 0,         "com.apple.itunes.is-hd-video" },
+	{ "aeHV",    DMAP_UINT, 0,         "com.apple.itunes.has-video" },
+	{ "aeK1",    DMAP_UINT, 0,         "com.apple.itunes.drm-key1-id" },
+	{ "aeK2",    DMAP_UINT, 0,         "com.apple.itunes.drm-key2-id" },
+	{ "aeMC",    DMAP_UINT, 0,         "com.apple.itunes.playlist-contains-media-type-count" },
+	{ "aeMK",    DMAP_UINT, 0,         "com.apple.itunes.mediakind" },
+	{ "aeMX",    DMAP_STR,  0,         "com.apple.itunes.movie-info-xml" },
+	{ "aeMk",    DMAP_UINT, 0,         "com.apple.itunes.extended-media-kind" },
+	{ "aeND",    DMAP_UINT, 0,         "com.apple.itunes.non-drm-user-id" },
+	{ "aeNN",    DMAP_STR,  0,         "com.apple.itunes.network-name" },
+	{ "aeNV",    DMAP_UINT, 0,         "com.apple.itunes.norm-volume" },
+	{ "aePC",    DMAP_UINT, 0,         "com.apple.itunes.is-podcast" },
+	{ "aePI",    DMAP_UINT, 0,         "com.apple.itunes.itms-playlistid" },
+	{ "aePP",    DMAP_UINT, 0,         "com.apple.itunes.is-podcast-playlist" },
+	{ "aePS",    DMAP_UINT, 0,         "com.apple.itunes.special-playlist" },
+	{ "aeRD",    DMAP_UINT, 0,         "com.apple.itunes.rental-duration" },
+	{ "aeRP",    DMAP_UINT, 0,         "com.apple.itunes.rental-pb-start" },
+	{ "aeRS",    DMAP_UINT, 0,         "com.apple.itunes.rental-start" },
+	{ "aeRU",    DMAP_UINT, 0,         "com.apple.itunes.rental-pb-duration" },
+	{ "aeRf",    DMAP_UINT, 0,         "com.apple.itunes.is-featured" },
+	{ "aeSE",    DMAP_UINT, 0,         "com.apple.itunes.store-pers-id" },
+	{ "aeSF",    DMAP_UINT, 0,         "com.apple.itunes.itms-storefrontid" },
+	{ "aeSG",    DMAP_UINT, 0,         "com.apple.itunes.saved-genius" },
+	{ "aeSI",    DMAP_UINT, 0,         "com.apple.itunes.itms-songid" },
+	{ "aeSN",    DMAP_STR,  0,         "com.apple.itunes.series-name" },
+	{ "aeSP",    DMAP_UINT, 0,         "com.apple.itunes.smart-playlist" },
+	{ "aeSU",    DMAP_UINT, 0,         "com.apple.itunes.season-num" },
+	{ "aeSV",    DMAP_VERS, 0,         "com.apple.itunes.music-sharing-version" },
+	{ "aeXD",    DMAP_STR,  0,         "com.apple.itunes.xid" },
+	{ "aecp",    DMAP_STR,  0,         "com.apple.itunes.collection-description" },
+	{ "aels",    DMAP_UINT, 0,         "com.apple.itunes.liked-state" },
+	{ "aemi",    DMAP_DICT, 0,         "com.apple.itunes.media-kind-listing-item" },
+	{ "aeml",    DMAP_DICT, 0,         "com.apple.itunes.media-kind-listing" },
+	{ "agac",    DMAP_UINT, 0,         "daap.groupalbumcount" },
+	{ "agma",    DMAP_UINT, 0,         "daap.groupmatchedqueryalbumcount" },
+	{ "agmi",    DMAP_UINT, 0,         "daap.groupmatchedqueryitemcount" },
+	{ "agrp",    DMAP_STR,  0,         "daap.songgrouping" },
+	{ "ajAE",    DMAP_UINT, 0,         "com.apple.itunes.store.ams-episode-type" },
+	{ "ajAS",    DMAP_UINT, 0,         "com.apple.itunes.store.ams-episode-sort-order" },
+	{ "ajAT",    DMAP_UINT, 0,         "com.apple.itunes.store.ams-show-type" },
+	{ "ajAV",    DMAP_UINT, 0,         "com.apple.itunes.store.is-ams-video" },
+	{ "ajal",    DMAP_UINT, 0,         "com.apple.itunes.store.album-liked-state" },
+	{ "ajcA",    DMAP_UINT, 0,         "com.apple.itunes.store.show-composer-as-artist" },
+	{ "ajca",    DMAP_UINT, 0,         "com.apple.itunes.store.show-composer-as-artist" },
+	{ "ajuw",    DMAP_UINT, 0,         "com.apple.itunes.store.use-work-name-as-display-name" },
+	{ "amvc",    DMAP_UINT, 0,         "daap.songmovementcount" },
+	{ "amvm",    DMAP_STR,  0,         "daap.songmovementname" },
+	{ "amvn",    DMAP_UINT, 0,         "daap.songmovementnumber" },
+	{ "aply",    DMAP_DICT, 0,         "daap.databaseplaylists" },
+	{ "aprm",    DMAP_UINT, 0,         "daap.playlistrepeatmode" },
+	{ "apro",    DMAP_VERS, 0,         "daap.protocolversion" },
+	{ "apsm",    DMAP_UINT, 0,         "daap.playlistshufflemode" },
+	{ "apso",    DMAP_DICT, 0,         "daap.playlistsongs" },
+	{ "arif",    DMAP_DICT, 0,         "daap.resolveinfo" },
+	{ "arsv",    DMAP_DICT, 0,         "daap.resolve" },
+	{ "asaa",    DMAP_STR,  0,         "daap.songalbumartist" },
+	{ "asac",    DMAP_UINT, 0,         "daap.songartworkcount" },
+	{ "asai",    DMAP_UINT, 0,         "daap.songalbumid" },
+	{ "asal",    DMAP_STR,  0,         "daap.songalbum" },
+	{ "asar",    DMAP_STR,  0,         "daap.songartist" },
+	{ "asas",    DMAP_UINT, 0,         "daap.songalbumuserratingstatus" },
+	{ "asbk",    DMAP_UINT, 0,         "daap.bookmarkable" },
+	{ "asbo",    DMAP_UINT, 0,         "daap.songbookmark" },
+	{ "asbr",    DMAP_UINT, 0,         "daap.songbitrate" },
+	{ "asbt",    DMAP_UINT, 0,         "daap.songbeatsperminute" },
+	{ "ascd",    DMAP_UINT, 0,         "daap.songcodectype" },
+	{ "ascm",    DMAP_STR,  0,         "daap.songcomment" },
+	{ "ascn",    DMAP_STR,  0,         "daap.songcontentdescription" },
+	{ "asco",    DMAP_UINT, 0,         "daap.songcompilation" },
+	{ "ascp",    DMAP_STR,  0,         "daap.songcomposer" },
+	{ "ascr",    DMAP_UINT, 0,         "daap.songcontentrating" },
+	{ "ascs",    DMAP_UINT, 0,         "daap.songcodecsubtype" },
+	{ "asct",    DMAP_STR,  0,         "daap.songcategory" },
+	{ "asda",    DMAP_DATE, 0,         "daap.songdateadded" },
+	{ "asdb",    DMAP_UINT, 0,         "daap.songdisabled" },
+	{ "asdc",    DMAP_UINT, 0,         "daap.songdisccount" },
+	{ "asdk",    DMAP_UINT, 0,         "daap.songdatakind" },
+	{ "asdm",    DMAP_DATE, 0,         "daap.songdatemodified" },
+	{ "asdn",    DMAP_UINT, 0,         "daap.songdiscnumber" },
+	{ "asdp",    DMAP_DATE, 0,         "daap.songdatepurchased" },
+	{ "asdr",    DMAP_DATE, 0,         "daap.songdatereleased" },
+	{ "asdt",    DMAP_STR,  0,         "daap.songdescription" },
+	{ "ased",    DMAP_UINT, 0,         "daap.songextradata" },
+	{ "aseq",    DMAP_STR,  0,         "daap.songeqpreset" },
+	{ "ases",    DMAP_UINT, 0,         "daap.songexcludefromshuffle" },
+	{ "asfm",    DMAP_STR,  0,         "daap.songformat" },
+	{ "asgn",    DMAP_STR,  0,         "daap.songgenre" },
+	{ "asgp",    DMAP_UINT, 0,         "daap.songgapless" },
+	{ "asgr",    DMAP_UINT, 0,         "daap.supportsgroups" },
+	{ "ashp",    DMAP_UINT, 0,         "daap.songhasbeenplayed" },
+	{ "askd",    DMAP_DATE, 0,         "daap.songlastskipdate" },
+	{ "askp",    DMAP_UINT, 0,         "daap.songuserskipcount" },
+	{ "asky",    DMAP_STR,  0,         "daap.songkeywords" },
+	{ "aslc",    DMAP_STR,  0,         "daap.songlongcontentdescription" },
+	{ "aslr",    DMAP_UINT, 0,         "daap.songalbumuserrating" },
+	{ "asls",    DMAP_UINT, 0,         "daap.songlongsize" },
+	{ "aspc",    DMAP_UINT, 0,         "daap.songuserplaycount" },
+	{ "aspl",    DMAP_DATE, 0,         "daap.songdateplayed" },
+	{ "aspu",    DMAP_STR,  0,         "daap.songpodcasturl" },
+	{ "asri",    DMAP_UINT, 0,         "daap.songartistid" },
+	{ "asrs",    DMAP_UINT, 0,         "daap.songuserratingstatus" },
+	{ "asrv",    DMAP_INT,  0,         "daap.songrelativevolume" },
+	{ "assa",    DMAP_STR,  0,         "daap.sortartist" },
+	{ "assc",    DMAP_STR,  0,         "daap.sortcomposer" },
+	{ "assl",    DMAP_STR,  0,         "daap.sortalbumartist" },
+	{ "assn",    DMAP_STR,  0,         "daap.sortname" },
+	{ "assp",    DMAP_UINT, 0,         "daap.songstoptime" },
+	{ "assr",    DMAP_UINT, 0,         "daap.songsamplerate" },
+	{ "asss",    DMAP_STR,  0,         "daap.sortseriesname" },
+	{ "asst",    DMAP_UINT, 0,         "daap.songstarttime" },
+	{ "assu",    DMAP_STR,  0,         "daap.sortalbum" },
+	{ "assz",    DMAP_UINT, 0,         "daap.songsize" },
+	{ "astc",    DMAP_UINT, 0,         "daap.songtrackcount" },
+	{ "astm",    DMAP_UINT, 0,         "daap.songtime" },
+	{ "astn",    DMAP_UINT, 0,         "daap.songtracknumber" },
+	{ "asul",    DMAP_STR,  0,         "daap.songdataurl" },
+	{ "asur",    DMAP_UINT, 0,         "daap.songuserrating" },
+	{ "asvc",    DMAP_UINT, 0,         "daap.songprimaryvideocodec" },
+	{ "asyr",    DMAP_UINT, 0,         "daap.songyear" },
+	{ "ated",    DMAP_UINT, 0,         "daap.supportsextradata" },
+	{ "avdb",    DMAP_DICT, 0,         "daap.serverdatabases" },
+	{ "awrk",    DMAP_STR,  0,         "daap.songwork" },
+	{ "caar",    DMAP_UINT, 0,         "dacp.availablerepeatstates" },
+	{ "caas",    DMAP_UINT, 0,         "dacp.availableshufflestates" },
+	{ "caci",    DMAP_DICT, 0,         "caci" },
+	{ "cafe",    DMAP_UINT, 0,         "dacp.fullscreenenabled" },
+	{ "cafs",    DMAP_UINT, 0,         "dacp.fullscreen" },
+	{ "caia",    DMAP_UINT, 0,         "dacp.isactive" },
+	{ "cana",    DMAP_STR,  0,         "dacp.nowplayingartist" },
+	{ "cang",    DMAP_STR,  0,         "dacp.nowplayinggenre" },
+	{ "canl",    DMAP_STR,  0,         "dacp.nowplayingalbum" },
+	{ "cann",    DMAP_STR,  0,         "dacp.nowplayingname" },
+	{ "canp",    DMAP_UINT, 0,         "dacp.nowplayingids" },
+	{ "cant",    DMAP_UINT, 0,         "dacp.nowplayingtime" },
+	{ "capr",    DMAP_VERS, 0,         "dacp.protocolversion" },
+	{ "caps",    DMAP_UINT, 0,         "dacp.playerstate" },
+	{ "carp",    DMAP_UINT, 0,         "dacp.repeatstate" },
+	{ "cash",    DMAP_UINT, 0,         "dacp.shufflestate" },
+	{ "casp",    DMAP_DICT, 0,         "dacp.speakers" },
+	{ "cast",    DMAP_UINT, 0,         "dacp.songtime" },
+	{ "cavc",    DMAP_UINT, 0,         "dacp.volumecontrollable" },
+	{ "cave",    DMAP_UINT, 0,         "dacp.visualizerenabled" },
+	{ "cavs",    DMAP_UINT, 0,         "dacp.visualizer" },
+	{ "ceJC",    DMAP_UINT, 0,         "com.apple.itunes.jukebox-client-vote" },
+	{ "ceJI",    DMAP_UINT, 0,         "com.apple.itunes.jukebox-current" },
+	{ "ceJS",    DMAP_UINT, 0,         "com.apple.itunes.jukebox-score" },
+	{ "ceJV",    DMAP_UINT, 0,         "com.apple.itunes.jukebox-vote" },
+	{ "ceQR",    DMAP_DICT, 0,         "com.apple.itunes.playqueue-contents-response" },
+	{ "ceQa",    DMAP_STR,  0,         "com.apple.itunes.playqueue-album" },
+	{ "ceQg",    DMAP_STR,  0,         "com.apple.itunes.playqueue-genre" },
+	{ "ceQn",    DMAP_STR,  0,         "com.apple.itunes.playqueue-name" },
+	{ "ceQr",    DMAP_STR,  0,         "com.apple.itunes.playqueue-artist" },
+	{ "cmgt",    DMAP_DICT, 0,         "dmcp.getpropertyresponse" },
+	{ "cmmk",    DMAP_UINT, 0,         "dmcp.mediakind" },
+	{ "cmpr",    DMAP_VERS, 0,         "dmcp.protocolversion" },
+	{ "cmsr",    DMAP_UINT, 0,         "dmcp.serverrevision" },
+	{ "cmst",    DMAP_DICT, 0,         "dmcp.playstatus" },
+	{ "cmvo",    DMAP_UINT, 0,         "dmcp.volume" },
+	{ "f\215ch", DMAP_UINT, 0,         "dmap.haschildcontainers" },
+	{ "ipsa",    DMAP_DICT, 0,         "dpap.iphotoslideshowadvancedoptions" },
+	{ "ipsl",    DMAP_DICT, 0,         "dpap.iphotoslideshowoptions" },
+	{ "mbcl",    DMAP_DICT, 0,         "dmap.bag" },
+	{ "mccr",    DMAP_DICT, 0,         "dmap.contentcodesresponse" },
+	{ "mcna",    DMAP_STR,  0,         "dmap.contentcodesname" },
+	{ "mcnm",    DMAP_UINT, 0,         "dmap.contentcodesnumber" },
+	{ "mcon",    DMAP_DICT, 0,         "dmap.container" },
+	{ "mctc",    DMAP_UINT, 0,         "dmap.containercount" },
+	{ "mcti",    DMAP_UINT, 0,         "dmap.containeritemid" },
+	{ "mcty",    DMAP_UINT, 0,         "dmap.contentcodestype" },
+	{ "mdbk",    DMAP_UINT, 0,         "dmap.databasekind" },
+	{ "mdcl",    DMAP_DICT, 0,         "dmap.dictionary" },
+	{ "mdst",    DMAP_UINT, 0,         "dmap.downloadstatus" },
+	{ "meds",    DMAP_UINT, 0,         "dmap.editcommandssupported" },
+	{ "meia",    DMAP_UINT, 0,         "dmap.itemdateadded" },
+	{ "meip",    DMAP_UINT, 0,         "dmap.itemdateplayed" },
+	{ "mext",    DMAP_UINT, 0,         "dmap.objectextradata" },
+	{ "miid",    DMAP_UINT, 0,         "dmap.itemid" },
+	{ "mikd",    DMAP_UINT, 0,         "dmap.itemkind" },
+	{ "mimc",    DMAP_UINT, 0,         "dmap.itemcount" },
+	{ "minm",    DMAP_STR,  0,         "dmap.itemname" },
+	{ "mlcl",    DMAP_DICT, DMAP_DICT, "dmap.listing" },
+	{ "mlid",    DMAP_UINT, 0,         "dmap.sessionid" },
+	{ "mlit",    DMAP_ITEM, 0,         "dmap.listingitem" },
+	{ "mlog",    DMAP_DICT, 0,         "dmap.loginresponse" },
+	{ "mpco",    DMAP_UINT, 0,         "dmap.parentcontainerid" },
+	{ "mper",    DMAP_UINT, 0,         "dmap.persistentid" },
+	{ "mpro",    DMAP_VERS, 0,         "dmap.protocolversion" },
+	{ "mrco",    DMAP_UINT, 0,         "dmap.returnedcount" },
+	{ "mrpr",    DMAP_UINT, 0,         "dmap.remotepersistentid" },
+	{ "msal",    DMAP_UINT, 0,         "dmap.supportsautologout" },
+	{ "msas",    DMAP_UINT, 0,         "dmap.authenticationschemes" },
+	{ "msau",    DMAP_UINT, 0,         "dmap.authenticationmethod" },
+	{ "msbr",    DMAP_UINT, 0,         "dmap.supportsbrowse" },
+	{ "msdc",    DMAP_UINT, 0,         "dmap.databasescount" },
+	{ "msex",    DMAP_UINT, 0,         "dmap.supportsextensions" },
+	{ "msix",    DMAP_UINT, 0,         "dmap.supportsindex" },
+	{ "mslr",    DMAP_UINT, 0,         "dmap.loginrequired" },
+	{ "msma",    DMAP_UINT, 0,         "dmap.machineaddress" },
+	{ "msml",    DMAP_DICT, 0,         "msml" },
+	{ "mspi",    DMAP_UINT, 0,         "dmap.supportspersistentids" },
+	{ "msqy",    DMAP_UINT, 0,         "dmap.supportsquery" },
+	{ "msrs",    DMAP_UINT, 0,         "dmap.supportsresolve" },
+	{ "msrv",    DMAP_DICT, 0,         "dmap.serverinforesponse" },
+	{ "mstc",    DMAP_DATE, 0,         "dmap.utctime" },
+	{ "mstm",    DMAP_UINT, 0,         "dmap.timeoutinterval" },
+	{ "msto",    DMAP_INT,  0,         "dmap.utcoffset" },
+	{ "msts",    DMAP_STR,  0,         "dmap.statusstring" },
+	{ "mstt",    DMAP_UINT, 0,         "dmap.status" },
+	{ "msup",    DMAP_UINT, 0,         "dmap.supportsupdate" },
+	{ "mtco",    DMAP_UINT, 0,         "dmap.specifiedtotalcount" },
+	{ "mudl",    DMAP_DICT, 0,         "dmap.deletedidlisting" },
+	{ "mupd",    DMAP_DICT, 0,         "dmap.updateresponse" },
+	{ "musr",    DMAP_UINT, 0,         "dmap.serverrevision" },
+	{ "muty",    DMAP_UINT, 0,         "dmap.updatetype" },
+	{ "pasp",    DMAP_STR,  0,         "dpap.aspectratio" },
+	{ "pcmt",    DMAP_STR,  0,         "dpap.imagecomments" },
+	{ "peak",    DMAP_UINT, 0,         "com.apple.itunes.photos.album-kind" },
+	{ "peed",    DMAP_DATE, 0,         "com.apple.itunes.photos.exposure-date" },
+	{ "pefc",    DMAP_DICT, 0,         "com.apple.itunes.photos.faces" },
+	{ "peki",    DMAP_UINT, 0,         "com.apple.itunes.photos.key-image-id" },
+	{ "pekm",    DMAP_DICT, 0,         "com.apple.itunes.photos.key-image" },
+	{ "pemd",    DMAP_DATE, 0,         "com.apple.itunes.photos.modification-date" },
+	{ "pfai",    DMAP_DICT, 0,         "dpap.failureids" },
+	{ "pfdt",    DMAP_DICT, 0,         "dpap.filedata" },
+	{ "pfmt",    DMAP_STR,  0,         "dpap.imageformat" },
+	{ "phgt",    DMAP_UINT, 0,         "dpap.imagepixelheight" },
+	{ "picd",    DMAP_DATE, 0,         "dpap.creationdate" },
+	{ "pifs",    DMAP_UINT, 0,         "dpap.imagefilesize" },
+	{ "pimf",    DMAP_STR,  0,         "dpap.imagefilename" },
+	{ "plsz",    DMAP_UINT, 0,         "dpap.imagelargefilesize" },
+	{ "ppro",    DMAP_VERS, 0,         "dpap.protocolversion" },
+	{ "prat",    DMAP_UINT, 0,         "dpap.imagerating" },
+	{ "pret",    DMAP_DICT, 0,         "dpap.retryids" },
+	{ "pwth",    DMAP_UINT, 0,         "dpap.imagepixelwidth" }
+};
+static const size_t dmap_field_count = sizeof(dmap_fields) / sizeof(dmap_field);
+
+typedef int (*sort_func) (const void *, const void *);
+
+int dmap_version(void) {
+	return DMAP_VERSION;
+}
+
+const char *dmap_version_string(void) {
+	return DMAP_STRINGIFY(DMAP_VERSION_MAJOR) "."
+	       DMAP_STRINGIFY(DMAP_VERSION_MINOR) "."
+	       DMAP_STRINGIFY(DMAP_VERSION_PATCH);
+}
+
+static int dmap_field_sort(const dmap_field *a, const dmap_field *b) {
+	return memcmp(a->code, b->code, 4);
+}
+
+static const dmap_field *dmap_field_from_code(const char *code) {
+	dmap_field key;
+	key.code = code;
+	return bsearch(&key, dmap_fields, dmap_field_count, sizeof(dmap_field), (sort_func)dmap_field_sort);
+}
+
+const char *dmap_name_from_code(const char *code) {
+	const dmap_field *field;
+	if (!code)
+		return NULL;
+
+	field = dmap_field_from_code(code);
+	return field ? field->name : NULL;
+}
+
+static uint16_t dmap_read_u16(const char *buf) {
+	return (uint16_t)(((buf[0] & 0xff) << 8) | (buf[1] & 0xff));
+}
+
+static int16_t dmap_read_i16(const char *buf) {
+	return (int16_t)dmap_read_u16(buf);
+}
+
+static uint32_t dmap_read_u32(const char *buf) {
+	return ((uint32_t)(buf[0] & 0xff) << 24) |
+	((uint32_t)(buf[1] & 0xff) << 16) |
+	((uint32_t)(buf[2] & 0xff) <<  8) |
+	((uint32_t)(buf[3] & 0xff));
+}
+
+static int32_t dmap_read_i32(const char *buf) {
+	return (int32_t)dmap_read_u32(buf);
+}
+
+static uint64_t dmap_read_u64(const char *buf) {
+	return ((uint64_t)(buf[0] & 0xff) << 56) |
+	((uint64_t)(buf[1] & 0xff) << 48) |
+	((uint64_t)(buf[2] & 0xff) << 40) |
+	((uint64_t)(buf[3] & 0xff) << 32) |
+	((uint64_t)(buf[4] & 0xff) << 24) |
+	((uint64_t)(buf[5] & 0xff) << 16) |
+	((uint64_t)(buf[6] & 0xff) <<  8) |
+	((uint64_t)(buf[7] & 0xff));
+}
+
+static int64_t dmap_read_i64(const char *buf) {
+	return (int64_t)dmap_read_u64(buf);
+}
+
+static int dmap_parse_internal(const dmap_settings *settings, const char *buf, size_t len, const dmap_field *parent) {
+	const dmap_field *field;
+	DMAP_TYPE field_type;
+	size_t field_len;
+	const char *field_name;
+	const char *p = buf;
+	const char *end = buf + len;
+	char code[5] = {0};
+
+	if (!settings || !buf)
+		return -1;
+
+	while (end - p >= 8) {
+		memcpy(code, p, 4);
+		field = dmap_field_from_code(code);
+		p += 4;
+
+		field_len = dmap_read_u32(p);
+		p += 4;
+
+		if (p + field_len > end)
+			return -1;
+
+		if (field) {
+			field_type = field->type;
+			field_name = field->name;
+
+			if (field_type == DMAP_ITEM) {
+				if (parent != NULL && parent->list_item_type) {
+					field_type = parent->list_item_type;
+				} else {
+					field_type = DMAP_DICT;
+				}
+			}
+		} else {
+			/* Make a best guess of the type */
+			field_type = DMAP_UNKNOWN;
+			field_name = code;
+
+			if (field_len >= 8) {
+				/* Look for a four char code followed by a length within the current field */
+				if (isalpha(p[0] & 0xff) &&
+				    isalpha(p[1] & 0xff) &&
+				    isalpha(p[2] & 0xff) &&
+				    isalpha(p[3] & 0xff)) {
+					if (dmap_read_u32(p + 4) < field_len)
+						field_type = DMAP_DICT;
+				}
+			}
+
+			if (field_type == DMAP_UNKNOWN) {
+				size_t i;
+				int is_string = 1;
+				for (i=0; i < field_len; i++) {
+					if (!isprint(p[i] & 0xff)) {
+						is_string = 0;
+						break;
+					}
+				}
+
+				field_type = is_string ? DMAP_STR : DMAP_UINT;
+			}
+		}
+
+		switch (field_type) {
+			case DMAP_UINT:
+				/* Determine the integer's type based on its size */
+				switch (field_len) {
+					case 1:
+						if (settings->on_uint32)
+							settings->on_uint32(settings->ctx, code, field_name, (unsigned char)*p);
+						break;
+					case 2:
+						if (settings->on_uint32)
+							settings->on_uint32(settings->ctx, code, field_name, dmap_read_u16(p));
+						break;
+					case 4:
+						if (settings->on_uint32)
+							settings->on_uint32(settings->ctx, code, field_name, dmap_read_u32(p));
+						break;
+					case 8:
+						if (settings->on_uint64)
+							settings->on_uint64(settings->ctx, code, field_name, dmap_read_u64(p));
+						break;
+					default:
+						if (settings->on_data)
+							settings->on_data(settings->ctx, code, field_name, p, field_len);
+						break;
+				}
+				break;
+			case DMAP_INT:
+				switch (field_len) {
+					case 1:
+						if (settings->on_int32)
+							settings->on_int32(settings->ctx, code, field_name, *p);
+						break;
+					case 2:
+						if (settings->on_int32)
+							settings->on_int32(settings->ctx, code, field_name, dmap_read_i16(p));
+						break;
+					case 4:
+						if (settings->on_int32)
+							settings->on_int32(settings->ctx, code, field_name, dmap_read_i32(p));
+						break;
+					case 8:
+						if (settings->on_int64)
+							settings->on_int64(settings->ctx, code, field_name, dmap_read_i64(p));
+						break;
+					default:
+						if (settings->on_data)
+							settings->on_data(settings->ctx, code, field_name, p, field_len);
+						break;
+				}
+				break;
+			case DMAP_STR:
+				if (settings->on_string)
+					settings->on_string(settings->ctx, code, field_name, p, field_len);
+				break;
+			case DMAP_DATA:
+				if (settings->on_data)
+					settings->on_data(settings->ctx, code, field_name, p, field_len);
+				break;
+			case DMAP_DATE:
+				/* Seconds since epoch */
+				if (settings->on_date)
+					settings->on_date(settings->ctx, code, field_name, dmap_read_u32(p));
+				break;
+			case DMAP_VERS:
+				if (settings->on_string && field_len >= 4) {
+					char version[20];
+					sprintf(version, "%u.%u", dmap_read_u16(p), dmap_read_u16(p+2));
+					settings->on_string(settings->ctx, code, field_name, version, strlen(version));
+				}
+				break;
+			case DMAP_DICT:
+				if (settings->on_dict_start)
+					settings->on_dict_start(settings->ctx, code, field_name);
+				if (dmap_parse_internal(settings, p, field_len, field) != 0)
+					return -1;
+				if (settings->on_dict_end)
+					settings->on_dict_end(settings->ctx, code, field_name);
+				break;
+			case DMAP_ITEM:
+				/* Unreachable: listing item types are always mapped to another type */
+				abort();
+			case DMAP_UNKNOWN:
+				break;
+		}
+
+		p += field_len;
+	}
+
+	if (p != end)
+		return -1;
+
+	return 0;
+}
+
+int dmap_parse(const dmap_settings *settings, const char *buf, size_t len) {
+	return dmap_parse_internal(settings, buf, len, NULL);
+}

+ 90 - 0
components/raop/dmap_parser.h

@@ -0,0 +1,90 @@
+#ifndef dmap_parser_h
+#define dmap_parser_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#define DMAP_VERSION_MAJOR 1
+#define DMAP_VERSION_MINOR 2
+#define DMAP_VERSION_PATCH 1
+
+#define DMAP_VERSION (DMAP_VERSION_MAJOR * 1000000 + \
+                      DMAP_VERSION_MINOR * 1000 + \
+                      DMAP_VERSION_PATCH)
+
+/*
+ * Callbacks invoked during parsing.
+ *
+ * @param ctx  The context pointer specified in the dmap_settings structure.
+ * @param code The content code from the message.
+ * @param name The name associated with the content code, if known. If there is
+ *             no known name this parameter contains the same value as the code
+ *             parameter.
+ */
+typedef void (*dmap_dict_cb)   (void *ctx, const char *code, const char *name);
+typedef void (*dmap_int32_cb)  (void *ctx, const char *code, const char *name, int32_t value);
+typedef void (*dmap_int64_cb)  (void *ctx, const char *code, const char *name, int64_t value);
+typedef void (*dmap_uint32_cb) (void *ctx, const char *code, const char *name, uint32_t value);
+typedef void (*dmap_uint64_cb) (void *ctx, const char *code, const char *name, uint64_t value);
+typedef void (*dmap_data_cb)   (void *ctx, const char *code, const char *name, const char *buf, size_t len);
+
+typedef struct {
+	/* Callbacks to indicate the start and end of dictionary fields. */
+	dmap_dict_cb   on_dict_start;
+	dmap_dict_cb   on_dict_end;
+
+	/* Callbacks for field data. */
+	dmap_int32_cb  on_int32;
+	dmap_int64_cb  on_int64;
+	dmap_uint32_cb on_uint32;
+	dmap_uint64_cb on_uint64;
+	dmap_uint32_cb on_date;
+	dmap_data_cb   on_string;
+	dmap_data_cb   on_data;
+
+	/** A context pointer passed to each callback function. */
+	void *ctx;
+} dmap_settings;
+
+/**
+ * Returns the library version number.
+ *
+ * The version number format is (major * 1000000) + (minor * 1000) + patch.
+ * For example, the value for version 1.2.3 is 1002003.
+ */
+int dmap_version(void);
+
+/**
+ * Returns the library version as a string.
+ */
+const char *dmap_version_string(void);
+
+/**
+ * Returns the name associated with the provided content code, or NULL if there
+ * is no known name.
+ *
+ * For example, if given the code "minm" this function returns "dmap.itemname".
+ */
+const char *dmap_name_from_code(const char *code);
+
+/**
+ * Parses a DMAP message buffer using the provided settings.
+ *
+ * @param settings A dmap_settings structure populated with the callbacks to
+ *                 invoke during parsing.
+ * @param buf      Pointer to a DMAP message buffer. The buffer must contain a
+ *                 complete message.
+ * @param len      The length of the DMAP message buffer.
+ *
+ * @return 0 if parsing was successful, or -1 if an error occurred.
+ */
+int dmap_parse(const dmap_settings *settings, const char *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+#endif

+ 40 - 0
components/raop/log_util.h

@@ -0,0 +1,40 @@
+/*
+ *  logging utility
+ *
+ *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
+ *  (c) Philippe 2016-2017, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __LOG_UTIL_H
+#define __LOG_UTIL_H
+
+#include "platform.h"
+
+typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
+
+const char *logtime(void);
+void logprint(const char *fmt, ...);
+log_level debug2level(char *level);
+char *level2debug(log_level level);
+
+#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define LOG_WARN(fmt, ...)  if (*loglevel >= lWARN)  logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define LOG_INFO(fmt, ...)  if (*loglevel >= lINFO)  logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define LOG_DEBUG(fmt, ...) if (*loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define LOG_SDEBUG(fmt, ...) if (*loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
+
+#endif

+ 127 - 0
components/raop/platform.h

@@ -0,0 +1,127 @@
+/*
+ *  platform setting definition
+ *
+ *  (c) Philippe, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __PLATFORM_H
+#define __PLATFORM_H
+
+#ifdef WIN32
+#define LINUX     0
+#define WIN       1
+#else
+#define LINUX     1
+#define WIN       0
+#endif
+
+#include <stdbool.h>
+#include <signal.h>
+#include <sys/stat.h>
+
+#ifdef WIN32
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <io.h>
+#include <iphlpapi.h>
+#include <sys/timeb.h>
+
+typedef unsigned __int8  u8_t;
+typedef unsigned __int16 u16_t;
+typedef unsigned __int32 u32_t;
+typedef unsigned __int64 u64_t;
+typedef __int16 s16_t;
+typedef __int32 s32_t;
+typedef __int64 s64_t;
+
+#define inline __inline
+
+int gettimeofday(struct timeval *tv, struct timezone *tz);
+char *strcasestr(const char *haystack, const char *needle);
+
+#define usleep(x) 		Sleep((x)/1000)
+
#define sleep(x) 		Sleep((x)*1000)
+#define last_error() 	WSAGetLastError()
+#define ERROR_WOULDBLOCK WSAEWOULDBLOCK
+#define open 			_open
+#define read 			_read
+#define poll 			WSAPoll
+#define snprintf 		_snprintf
+#define strcasecmp 		stricmp
+#define _random(x) 		random(x)
+#define VALGRIND_MAKE_MEM_DEFINED(x,y)
+#define S_ADDR(X) X.S_un.S_addr
+
+#define in_addr_t 	u32_t
+#define socklen_t 	int
+#define ssize_t 	int
+
+#define RTLD_NOW 0
+
+#else
+
+#include <strings.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <inttypes.h>
+/*
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/time.h>
+#include <netdb.h>
+*/
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <lwip/inet.h>
+#include <pthread.h>
+#include <errno.h>
+
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+
+typedef int16_t   s16_t;
+typedef int32_t   s32_t;
+typedef int64_t   s64_t;
+typedef uint8_t   u8_t;
+typedef uint16_t   u16_t;
+typedef uint32_t   u32_t;
+typedef unsigned long long u64_t;
+
+#define last_error() errno
+#define ERROR_WOULDBLOCK EWOULDBLOCK
+
+char *strlwr(char *str);
+#define _random(x) random()
+#define closesocket(s) close(s)
+#define S_ADDR(X) X.s_addr
+
+#endif
+
+typedef struct ntp_s {
+	u32_t seconds;
+	u32_t fraction;
+
} ntp_t;
+
+u64_t timeval_to_ntp(struct timeval tv, struct ntp_s *ntp);
+u64_t get_ntp(struct ntp_s *ntp);
+// we expect somebody to provide the ms clock, system-wide
+u32_t _gettime_ms_(void);
+#define gettime_ms _gettime_ms_
+
+#endif     // __PLATFORM

+ 823 - 0
components/raop/raop.c

@@ -0,0 +1,823 @@
+/*
+ *
+ *  (c) Philippe 2019, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+
+#include "platform.h"
+
+#ifdef WIN32
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+#include <openssl/engine.h>
+#include "mdns.h"
+#include "mdnsd.h"
+#include "mdnssd-itf.h"
+#else
+#include "esp_pthread.h"
+#include "mdns.h"
+#include "mbedtls/version.h"
+#include <mbedtls/x509.h>
+#endif
+
+#include "util.h"
+#include "raop.h"
+#include "rtp.h"
+#include "dmap_parser.h"
+#include "log_util.h"
+
+typedef struct raop_ctx_s {
+#ifdef WIN32
+	struct mdns_service *svc;
+	struct mdnsd *svr;
+#endif
+	struct in_addr host;	// IP of bridge
+	short unsigned port;    // RTSP port for AirPlay
+	int sock;               // socket of the above
+	struct in_addr peer;	// IP of the iDevice (airplay sender)
+	bool running;
+	bool drift;
+#ifdef WIN32
+	pthread_t thread, search_thread;
+#else
+	TaskHandle_t thread, search_thread, joiner;
+#endif
+	unsigned char mac[6];
+	int latency;
+	struct {
+		char *aesiv, *aeskey;
+		char *fmtp;
+	} rtsp;
+	struct rtp_s *rtp;
+	raop_cmd_cb_t	cmd_cb;
+	raop_data_cb_t	data_cb;
+	/*
+	struct {
+		char				DACPid[32], id[32];
+		struct in_addr		host;
+		u16_t				port;
+		struct mDNShandle_s *handle;
+	} active_remote;
+	*/
+	void *owner;
+} raop_ctx_t;
+
+extern struct mdnsd* glmDNSServer;
+extern log_level	raop_loglevel;
+static log_level 	*loglevel = &raop_loglevel;
+
+static void*	rtsp_thread(void *arg);
+static bool 	handle_rtsp(raop_ctx_t *ctx, int sock);
+
+static char*	rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
+static int  	base64_pad(char *src, char **padded);
+static int 		base64_encode(const void *data, int size, char **str);
+static int 		base64_decode(const char *str, void *data);
+static void* 	search_remote(void *args);
+
+extern char private_key[];
+
enum { RSA_MODE_KEY, RSA_MODE_AUTH };
+
+
static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len);
+
+/*----------------------------------------------------------------------------*/
+struct raop_ctx_s *raop_create(struct in_addr host, char *name,
+						unsigned char mac[6], int latency,
+						raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
+	struct raop_ctx_s *ctx = malloc(sizeof(struct raop_ctx_s));
+	struct sockaddr_in addr;
+	char id[64];
+
#ifdef WIN32
+
	socklen_t nlen = sizeof(struct sockaddr);
+
	char *txt[] = { "am=esp32", "tp=UDP", "sm=false", "sv=false", "ek=1",
+					"et=0,1", "md=0,1,2", "cn=0,1", "ch=2",
+					"ss=16", "sr=44100", "vn=3", "txtvers=1",
+					NULL };
+#else
+	mdns_txt_item_t txt[] = {
+		{"am", "esp32"},
+		{"tp", "UDP"},
+		{"sm","false"},
+		{"sv","false"},
+		{"ek","1"},
+		{"et","0,1"},
+		{"md","0,1,2"},
+		{"cn","0,1"},
+		{"ch","2"},
+		{"ss","16"},
+		{"sr","44100"},
+		{"vn","3"},
+		{"txtvers","1"},
+	};
+
+#endif
+
+	if (!ctx) return NULL;
+
+	// make sure we have a clean context
+	memset(ctx, 0, sizeof(raop_ctx_t));
+
+#ifdef WIN32
+	ctx->svr = glmDNSServer;
+#endif
+	ctx->host = host;
+	ctx->sock = socket(AF_INET, SOCK_STREAM, 0);
+	ctx->cmd_cb = cmd_cb;
+	ctx->data_cb = data_cb;
+	ctx->drift = false;
+	ctx->latency = min(latency, 44100);
+	if (ctx->sock == -1) {
+		LOG_ERROR("Cannot create listening socket", NULL);
+		free(ctx);
+		return NULL;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_addr.s_addr = host.s_addr;
+	addr.sin_family = AF_INET;
+#ifdef WIN32
+	addr.sin_port = 0;
+#else
+	ctx->port = 5000;
+	addr.sin_port = htons(ctx->port);
+#endif
+
+	if (bind(ctx->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0 || listen(ctx->sock, 1)) {
+		LOG_ERROR("Cannot bind or listen RTSP listener: %s", strerror(errno));
+		free(ctx);
+		closesocket(ctx->sock);
+		return NULL;
+	}
+
+#ifdef WIN32
+	getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
+	ctx->port = ntohs(addr.sin_port);
+
#endif
+
+
	ctx->running = true;
+
	memcpy(ctx->mac, mac, 6);
+
	snprintf(id, 64, "%02X%02X%02X%02X%02X%02X@%s",  mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], name);
+
#ifdef WIN32
+
	// seems that Windows snprintf does not add NULL char if actual size > max
+
	id[63] = '\0';
+
	ctx->svc = mdnsd_register_svc(ctx->svr, id, "_raop._tcp.local", ctx->port, NULL, (const char**) txt);
+
	pthread_create(&ctx->thread, NULL, &rtsp_thread, ctx);
+
#else
+
	LOG_INFO("starting mDNS with %s", id);
+
	ESP_ERROR_CHECK( mdns_service_add(id, "_raop", "_tcp", ctx->port, txt, sizeof(txt) / sizeof(mdns_txt_item_t)) );
+
	xTaskCreate((TaskFunction_t) rtsp_thread, "RTSP_thread", 8*1024, ctx, ESP_TASK_PRIO_MIN + 1, &ctx->thread);
+
#endif
+
+	return ctx;
+}
+
+
+/*----------------------------------------------------------------------------*/
+void raop_delete(struct raop_ctx_s *ctx) {
+	int sock;
+	struct sockaddr addr;
+	socklen_t nlen = sizeof(struct sockaddr);
+
+	if (!ctx) return;
+
+	ctx->running = false;
+
+	// wake-up thread by connecting socket, needed for freeBSD
+	sock = socket(AF_INET, SOCK_STREAM, 0);
+	getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
+	connect(sock, (struct sockaddr*) &addr, sizeof(addr));
+	closesocket(sock);
+
+#ifdef WIN32
+	pthread_join(ctx->thread, NULL);
+#else
+	ctx->joiner = xTaskGetCurrentTaskHandle();
+	xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
+#endif
+
+	rtp_end(ctx->rtp);
+
+#ifdef WIN32
+	shutdown(ctx->sock, SD_BOTH);
+#else
+	shutdown(ctx->sock, SHUT_RDWR);
+#endif
+	closesocket(ctx->sock);
+
+	/*
+	// terminate search, but do not reclaim memory of pthread if never launched
+	if (ctx->active_remote.handle) {
+		close_mDNS(ctx->active_remote.handle);
+		pthread_join(ctx->search_thread, NULL);
+	}
+	*/
+
+	NFREE(ctx->rtsp.aeskey);
+	NFREE(ctx->rtsp.aesiv);
+	NFREE(ctx->rtsp.fmtp);
+
+	// stop broadcasting devices
+#ifdef WIN32
+	mdns_service_remove(ctx->svr, ctx->svc);
+	mdnsd_stop(ctx->svr);
+#endif
+
+	free(ctx);
+}
+
+
+/*----------------------------------------------------------------------------*/
+void  raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
+/*
+	struct sockaddr_in addr;
+	int sock;
+	char *command = NULL;
+
+	// first notify the remote controller (if any)
+	switch(event) {
+		case RAOP_PAUSE:
+			command = strdup("pause");
+			break;
+		case RAOP_PLAY:
+			command = strdup("play");
+			break;
+		case RAOP_STOP:
+			command = strdup("stop");
+			break;
+		case RAOP_VOLUME: {
+			float Volume = *((float*) param);
+			Volume = Volume ? (Volume - 1) * 30 : -144;
+			asprintf(&command,"setproperty?dmcp.device-volume=%0.4lf", Volume);
+			break;
+		}
+		default:
+			break;
+	}
+
+	// no command to send to remote or no remote found yet
+	if (!command || !ctx->active_remote.port) {
+
		NFREE(command);
+
		return;
+
	}
+
+	sock = socket(AF_INET, SOCK_STREAM, 0);
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = S_ADDR(ctx->active_remote.host);
+	addr.sin_port = htons(ctx->active_remote.port);
+
+	if (!connect(sock, (struct sockaddr*) &addr, sizeof(addr))) {
+		char *method, *buf, resp[512] = "";
+		int len;
+		key_data_t headers[4] = { {NULL, NULL} };
+
+		asprintf(&method, "GET /ctrl-int/1/%s HTTP/1.0", command);
+		kd_add(headers, "Active-Remote", ctx->active_remote.id);
+		kd_add(headers, "Connection", "close");
+
+		buf = http_send(sock, method, headers);
+		len = recv(sock, resp, 512, 0);
+		if (len > 0) resp[len-1] = '\0';
+		LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
+
+		NFREE(method);
+		NFREE(buf);
+		kd_free(headers);
+	}
+
+	free(command);
+
+	closesocket(sock);
+*/
+	// then notify local system
+	ctx->cmd_cb(event, param);
+}
+
+/*----------------------------------------------------------------------------*/
+static void *rtsp_thread(void *arg) {
+	raop_ctx_t *ctx = (raop_ctx_t*) arg;
+	int  sock = -1;
+
+	while (ctx->running) {
+		fd_set rfds;
+		struct timeval timeout = {0, 100*1000};
+		int n;
+		bool res = false;
+
+		if (sock == -1) {
+			struct sockaddr_in peer;
+			socklen_t addrlen = sizeof(struct sockaddr_in);
+
+			sock = accept(ctx->sock, (struct sockaddr*) &peer, &addrlen);
+			ctx->peer.s_addr = peer.sin_addr.s_addr;
+
+			if (sock != -1 && ctx->running) {
+				LOG_INFO("got RTSP connection %u", sock);
+			} else continue;
+		}
+
+		FD_ZERO(&rfds);
+		FD_SET(sock, &rfds);
+
+		n = select(sock + 1, &rfds, NULL, NULL, &timeout);
+
+		if (!n) continue;
+
+		if (n > 0) res = handle_rtsp(ctx, sock);
+
+		if (n < 0 || !res) {
+			closesocket(sock);
+			LOG_INFO("RTSP close %u", sock);
+			sock = -1;
+		}
+	}
+
+	if (sock != -1) closesocket(sock);
+
+#ifndef WIN32
+	xTaskNotify(ctx->joiner, 0, eNoAction);
+	vTaskDelete(NULL);
+#endif
+
+	return NULL;
+}
+
+
+/*----------------------------------------------------------------------------*/
+static bool handle_rtsp(raop_ctx_t *ctx, int sock)
+{
+	char *buf = NULL, *body = NULL, method[16] = "";
+	key_data_t headers[16], resp[8] = { {NULL, NULL} };
+	int len;
+	bool success = true;
+
+	if (!http_parse(sock, method, headers, &body, &len)) {
+		NFREE(body);
+		kd_free(headers);
+		return false;
+	}
+
+	if (strcmp(method, "OPTIONS")) {
+		LOG_INFO("[%p]: received %s", ctx, method);
+	}
+
+	if ((buf = kd_lookup(headers, "Apple-Challenge")) != NULL) {
+		int n;
+		char *buf_pad, *p, *data_b64 = NULL, data[32];
+
+		LOG_INFO("[%p]: challenge %s", ctx, buf);
+
+		// need to pad the base64 string as apple device don't
+		base64_pad(buf, &buf_pad);
+
+		p = data + min(base64_decode(buf_pad, data), 32-10);
+		p = (char*) memcpy(p, &S_ADDR(ctx->host), 4) + 4;
+		p = (char*) memcpy(p, ctx->mac, 6) + 6;
+		memset(p, 0, 32 - (p - data));
+		p = rsa_apply((unsigned char*) data, 32, &n, RSA_MODE_AUTH);
+		n = base64_encode(p, n, &data_b64);
+
+		// remove padding as well (seems to be optional now)
+		for (n = strlen(data_b64) - 1; n > 0 && data_b64[n] == '='; data_b64[n--] = '\0');
+
+		kd_add(resp, "Apple-Response", data_b64);
+
+		NFREE(p);
+		NFREE(buf_pad);
+		NFREE(data_b64);
+	}
+
+	if (!strcmp(method, "OPTIONS")) {
+
+		kd_add(resp, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
+
+	} else if (!strcmp(method, "ANNOUNCE")) {
+		char *padded, *p;
+
+		NFREE(ctx->rtsp.aeskey);
+		NFREE(ctx->rtsp.aesiv);
+		NFREE(ctx->rtsp.fmtp);
+
+		if ((p = strcasestr(body, "rsaaeskey")) != NULL) {
+			unsigned char *aeskey;
+			int len, outlen;
+
+			p = strextract(p, ":", "\r\n");
+			base64_pad(p, &padded);
+			aeskey = malloc(strlen(padded));
+			len = base64_decode(padded, aeskey);
+			ctx->rtsp.aeskey = rsa_apply(aeskey, len, &outlen, RSA_MODE_KEY);
+
+			NFREE(p);
+			NFREE(aeskey);
+			NFREE(padded);
+		}
+
+		if ((p = strcasestr(body, "aesiv")) != NULL) {
+			p = strextract(p, ":", "\r\n");
+			base64_pad(p, &padded);
+			ctx->rtsp.aesiv = malloc(strlen(padded));
+			base64_decode(padded, ctx->rtsp.aesiv);
+
+			NFREE(p);
+			NFREE(padded);
+		}
+
+		if ((p = strcasestr(body, "fmtp")) != NULL) {
+			p = strextract(p, ":", "\r\n");
+			ctx->rtsp.fmtp = strdup(p);
+			NFREE(p);
+		}
+
+		// on announce, search remote
+		/*
+		if ((buf = kd_lookup(headers, "DACP-ID")) != NULL) strcpy(ctx->active_remote.DACPid, buf);
+		if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
+
+		ctx->active_remote.handle = init_mDNS(false, ctx->host);
+		pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
+		*/
+
+	} else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) {
+		char *p;
+		rtp_resp_t rtp = { 0 };
+		short unsigned tport = 0, cport = 0;
+
+		if ((p = strcasestr(buf, "timing_port")) != NULL) sscanf(p, "%*[^=]=%hu", &tport);
+		if ((p = strcasestr(buf, "control_port")) != NULL) sscanf(p, "%*[^=]=%hu", &cport);
+
+		rtp = rtp_init(ctx->peer, false, ctx->drift, true, ctx->latency,
+							ctx->rtsp.aeskey, ctx->rtsp.aesiv, ctx->rtsp.fmtp,
+							cport, tport, ctx->data_cb);
+
+		ctx->rtp = rtp.ctx;
+
+		if (cport * tport * rtp.cport * rtp.tport * rtp.aport && rtp.ctx) {
+			char *transport;
+			asprintf(&transport, "RTP/AVP/UDP;unicast;mode=record;control_port=%u;timing_port=%u;server_port=%u", rtp.cport, rtp.tport, rtp.aport);
+			LOG_DEBUG("[%p]: audio=(%hu:%hu), timing=(%hu:%hu), control=(%hu:%hu)", ctx, 0, rtp.aport, tport, rtp.tport, cport, rtp.cport);
+			kd_add(resp, "Transport", transport);
+			kd_add(resp, "Session", "DEADBEEF");
+			free(transport);
+		} else {
+			success = false;
+			LOG_INFO("[%p]: cannot start session, missing ports", ctx);
+		}
+
+	} else if (!strcmp(method, "RECORD")) {
+		unsigned short seqno = 0;
+		unsigned rtptime = 0;
+		char *p;
+
+		if (ctx->latency) {
+			char latency[6];
+			snprintf(latency, 6, "%u", ctx->latency);
+			kd_add(resp, "Audio-Latency", latency);
+		}
+
+		buf = kd_lookup(headers, "RTP-Info");
+		if ((p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
+		if ((p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
+
+		if (ctx->rtp) rtp_record(ctx->rtp, seqno, rtptime);
+
+		ctx->cmd_cb(RAOP_STREAM, NULL);
+
+	}  else if (!strcmp(method, "FLUSH")) {
+		unsigned short seqno = 0;
+		unsigned rtptime = 0;
+		char *p;
+
+		buf = kd_lookup(headers, "RTP-Info");
+		if ((p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
+		if ((p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
+
+		// only send FLUSH if useful (discards frames above buffer head and top)
+		if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime))
+			ctx->cmd_cb(RAOP_FLUSH, NULL);
+
+	}  else if (!strcmp(method, "TEARDOWN")) {
+
+		rtp_end(ctx->rtp);
+
+		ctx->rtp = NULL;
+
+		/*
+		// need to make sure no search is on-going and reclaim pthread memory
+		if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle);
+		pthread_join(ctx->search_thread, NULL);
+		memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
+		*/
+
+		NFREE(ctx->rtsp.aeskey);
+		NFREE(ctx->rtsp.aesiv);
+		NFREE(ctx->rtsp.fmtp);
+
+		ctx->cmd_cb(RAOP_STOP, NULL);
+
+	} if (!strcmp(method, "SET_PARAMETER")) {
+		char *p;
+
+		if ((p = strcasestr(body, "volume")) != NULL) {
+			float volume;
+
+			sscanf(p, "%*[^:]:%f", &volume);
+			LOG_INFO("[%p]: SET PARAMETER volume %f", ctx, volume);
+			volume = (volume == -144.0) ? 0 : (1 + volume / 30);
+			ctx->cmd_cb(RAOP_VOLUME, &volume);
+		}
+/*
+		if (((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) {
+			struct metadata_s metadata;
+			dmap_settings settings = {
+				NULL, NULL, NULL, NULL,	NULL, NULL,	NULL, on_dmap_string, NULL,
+				NULL
+			};
+
+			settings.ctx = &metadata;
+			memset(&metadata, 0, sizeof(struct metadata_s));
+			if (!dmap_parse(&settings, body, len)) {
+				LOG_INFO("[%p]: received metadata\n\tartist: %s\n\talbum:  %s\n\ttitle:  %s",
+						 ctx, metadata.artist, metadata.album, metadata.title);
+				free_metadata(&metadata);
+			}
+		}
+*/
+	}
+
+	// don't need to free "buf" because kd_lookup return a pointer, not a strdup
+
+	kd_add(resp, "Audio-Jack-Status", "connected; type=analog");
+	kd_add(resp, "CSeq", kd_lookup(headers, "CSeq"));
+
+	if (success) buf = http_send(sock, "RTSP/1.0 200 OK", resp);
+	else buf = http_send(sock, "RTSP/1.0 500 ERROR", NULL);
+
+	if (strcmp(method, "OPTIONS")) {
+		LOG_INFO("[%p]: responding:\n%s", ctx, buf ? buf : "<void>");
+	}
+
+	NFREE(body);
+	NFREE(buf);
+	kd_free(resp);
+	kd_free(headers);
+
+	return true;
+}
+
+/*----------------------------------------------------------------------------*/
+/*
+bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
+	mDNSservice_t *s;
+	raop_ctx_t *ctx = (raop_ctx_t*) cookie;
+
+	// see if we have found an active remote for our ID
+	for (s = slist; s; s = s->next) {
+		if (strcasestr(s->name, ctx->active_remote.DACPid)) {
+			ctx->active_remote.host = s->addr;
+			ctx->active_remote.port = s->port;
+			LOG_INFO("[%p]: found ActiveRemote for %s at %s:%u", ctx, ctx->active_remote.DACPid,
+								inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
+			*stop = true;
+			break;
+		}
+	}
+
+	// let caller clear list
+	return false;
+}
+*/
+
+
+/*----------------------------------------------------------------------------*/
+/*
+static void* search_remote(void *args) {
+	raop_ctx_t *ctx = (raop_ctx_t*) args;
+
+	query_mDNS(ctx->active_remote.handle, "_dacp._tcp.local", 0, 0, &search_remote_cb, (void*) ctx);
+
+	return NULL;
+}
+
*/
+
+
/*----------------------------------------------------------------------------*/
+static char *rsa_apply(unsigned char *input, int inlen, int *outlen, int mode)
+{
+	static char super_secret_key[] =
+	"-----BEGIN RSA PRIVATE KEY-----\n"
+	"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
+	"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
+	"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
+	"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
+	"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
+	"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
+	"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
+	"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
+	"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
+	"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
+	"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
+	"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
+	"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
+	"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
+	"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
+	"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
+	"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
+	"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
+	"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
+	"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
+	"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
+	"-----END RSA PRIVATE KEY-----";
+#ifdef WIN32
+	unsigned char *out;
+	RSA *rsa;
+
+	BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
+	rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
+	BIO_free(bmem);
+
+	out = malloc(RSA_size(rsa));
+	switch (mode) {
+		case RSA_MODE_AUTH:
+			*outlen = RSA_private_encrypt(inlen, input, out, rsa,
+										  RSA_PKCS1_PADDING);
+			break;
+		case RSA_MODE_KEY:
+			*outlen = RSA_private_decrypt(inlen, input, out, rsa,
+										  RSA_PKCS1_OAEP_PADDING);
+			break;
+	}
+
+	RSA_free(rsa);
+
+	return (char*) out;
+#else
+	mbedtls_pk_context pkctx;
+	mbedtls_rsa_context *trsa;
+	size_t olen;
+
+	/*
+	we should do entropy initialization & pass a rng function but this
+	consumes a ton of stack and there is no security concern here. Anyway,
+	mbedtls takes a lot of stack, unfortunately ...
+	*/
+
+	mbedtls_pk_init(&pkctx);
+	mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key,
+		sizeof(super_secret_key), NULL, 0);
+
+	uint8_t *outbuf = NULL;
+	trsa = mbedtls_pk_rsa(pkctx);
+
+	switch (mode) {
+	case RSA_MODE_AUTH:
+		mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
+		outbuf = malloc(trsa->len);
+		mbedtls_rsa_pkcs1_encrypt(trsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, inlen, input, outbuf);
+		*outlen = trsa->len;
+		break;
+	case RSA_MODE_KEY:
+		mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
+		outbuf = malloc(trsa->len);
+		mbedtls_rsa_pkcs1_decrypt(trsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, &olen, input, outbuf, trsa->len);
+		*outlen = olen;
+		break;
+	}
+
+	mbedtls_pk_free(&pkctx);
+
+	return (char*) outbuf;
+#endif
+}
+
+#define DECODE_ERROR 0xffffffff
+
+static char base64_chars[] =
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*----------------------------------------------------------------------------*/
+static int  base64_pad(char *src, char **padded)
+{
+	int n;
+
+	n = strlen(src) + strlen(src) % 4;
+	*padded = malloc(n + 1);
+	memset(*padded, '=', n);
+	memcpy(*padded, src, strlen(src));
+	(*padded)[n] = '\0';
+
+	return strlen(*padded);
+}
+
+/*----------------------------------------------------------------------------*/
+static int pos(char c)
+{
+	char *p;
+	for (p = base64_chars; *p; p++)
+	if (*p == c)
+		return p - base64_chars;
+	return -1;
+}
+
+/*----------------------------------------------------------------------------*/
+static int base64_encode(const void *data, int size, char **str)
+{
+	char *s, *p;
+	int i;
+	int c;
+	const unsigned char *q;
+
+	p = s = (char *) malloc(size * 4 / 3 + 4);
+	if (p == NULL) return -1;
+	q = (const unsigned char *) data;
+	i = 0;
+	for (i = 0; i < size;) {
+		c = q[i++];
+		c *= 256;
+		if (i < size) c += q[i];
+		i++;
+		c *= 256;
+		if (i < size) c += q[i];
+		i++;
+		p[0] = base64_chars[(c & 0x00fc0000) >> 18];
+		p[1] = base64_chars[(c & 0x0003f000) >> 12];
+		p[2] = base64_chars[(c & 0x00000fc0) >> 6];
+		p[3] = base64_chars[(c & 0x0000003f) >> 0];
+		if (i > size) p[3] = '=';
+		if (i > size + 1) p[2] = '=';
+		p += 4;
+	}
+	*p = 0;
+	*str = s;
+	return strlen(s);
+}
+
+/*----------------------------------------------------------------------------*/
+static unsigned int token_decode(const char *token)
+{
+	int i;
+	unsigned int val = 0;
+	int marker = 0;
+	if (strlen(token) < 4)
+	return DECODE_ERROR;
+	for (i = 0; i < 4; i++) {
+	val *= 64;
+	if (token[i] == '=')
+		marker++;
+	else if (marker > 0)
+		return DECODE_ERROR;
+	else
+		val += pos(token[i]);
+	}
+	if (marker > 2)
+	return DECODE_ERROR;
+	return (marker << 24) | val;
+}
+
+/*----------------------------------------------------------------------------*/
+static int base64_decode(const char *str, void *data)
+{
+	const char *p;
+	unsigned char *q;
+
+	q = data;
+	for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
+	unsigned int val = token_decode(p);
+	unsigned int marker = (val >> 24) & 0xff;
+	if (val == DECODE_ERROR)
+		return -1;
+	*q++ = (val >> 16) & 0xff;
+	if (marker < 2)
+		*q++ = (val >> 8) & 0xff;
+	if (marker < 1)
+		*q++ = val & 0xff;
+	}
+	return q - (unsigned char *) data;
+}
+
+/*----------------------------------------------------------------------------*/
+static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len) {
+	struct metadata_s *metadata = (struct metadata_s *) ctx;
+
+	if (!strcasecmp(code, "asar")) metadata->artist = strndup(buf, len);
+	else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len);
+	else if (!strcasecmp(code, "minm")) metadata->title = strndup(buf, len);
+}
+

+ 32 - 0
components/raop/raop.h

@@ -0,0 +1,32 @@
+/*
+ *  AirCast: Chromecast to AirPlay
+ *
+ *  (c) Philippe 2016-2017, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __RAOP_H
+#define __RAOP_H
+
+#include "platform.h"
+#include "raop_sink.h"
+
+struct raop_ctx_s*   raop_create(struct in_addr host, char *name, unsigned char mac[6], int latency,
+							     raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
+void  		  raop_delete(struct raop_ctx_s *ctx);
+void		  raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param);
+
+#endif

+ 68 - 0
components/raop/raop_sink.c

@@ -0,0 +1,68 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "mdns.h"
+#include "nvs.h"
+#include "tcpip_adapter.h"
+#include "esp_log.h"
+#include "esp_console.h"
+#include "esp_pthread.h"
+#include "esp_system.h"
+#include "freertos/timers.h"
+
+#include "raop.h"
+
+#include "log_util.h"
+
+#include "trace.h"
+
+static const char * TAG = "platform";
+extern char current_namespace[];
+
+log_level	raop_loglevel = lINFO;
+log_level	util_loglevel;
+
+static log_level *loglevel = &raop_loglevel;
+static struct raop_ctx_s *raop;
+
+/****************************************************************************************
+ * Airplay sink initialization
+ */
+void raop_sink_init(raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
+    const char *hostname;
+	char sink_name[64-6] = CONFIG_AIRPLAY_NAME;
+	nvs_handle nvs;
+	tcpip_adapter_ip_info_t ipInfo; 
+	struct in_addr host;
+   	
+	// get various IP info
+	tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo);
+	tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &hostname);
+	host.s_addr = ipInfo.ip.addr;
+
+    //initialize mDNS
+    ESP_ERROR_CHECK( mdns_init() );
+    ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
+        
+    if (nvs_open(current_namespace, NVS_READONLY, &nvs) == ESP_OK) {
+		size_t len = sizeof(sink_name) - 1;
+		nvs_get_str(nvs, "airplay_sink_name", sink_name, &len);
+		nvs_close(nvs);
+	}	
+	
+	ESP_LOGI(TAG, "mdns hostname set to: [%s] with servicename %s", hostname, sink_name);
+
+    //initialize service
+	uint8_t mac[6];	
+    esp_read_mac(mac, ESP_MAC_WIFI_STA);
+	raop = raop_create(host, sink_name, mac, 44100, cmd_cb, data_cb);
+}
+
+/****************************************************************************************
+ * Airplay local command (stop, start, volume ...)
+ */
+void raop_sink_cmd(raop_event_t event, void *param) {
+	raop_cmd(raop, event, param);
+}

+ 31 - 0
components/raop/raop_sink.h

@@ -0,0 +1,31 @@
+/*
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#ifndef RAOP_SINK_H
+#define RAOP_SINK_H
+
+#include <stdint.h>
+
+typedef enum { RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_PAUSE, RAOP_STOP, RAOP_VOLUME } raop_event_t ;
+
+typedef void (*raop_cmd_cb_t)(raop_event_t event, void *param);
+typedef void (*raop_data_cb_t)(const u8_t *data, size_t len);
+
+/**
+ * @brief     init sink mode (need to be provided)
+ */
+
+void raop_sink_init(raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
+
+/**
+ * @brief     init sink mode (need to be provided)
+ */
+
+void raop_sink_cmd(raop_event_t event, void *param);
+
+#endif /* RAOP_SINK_H*/

+ 893 - 0
components/raop/rtp.c

@@ -0,0 +1,893 @@
+/*
+ * HairTunes - RAOP packet handler and slave-clocked replay engine
+ * Copyright (c) James Laird 2011
+ * All rights reserved.
+ *
+ * Modularisation: philippe_44@outlook.com, 2019
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <pthread.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "platform.h"
+#include "rtp.h"
+#include "log_util.h"
+#include "util.h"
+
+#ifdef WIN32
+#include <openssl/aes.h>
+#include "alac.h"
+#else
+#include "esp_pthread.h"
+#include "esp_system.h"
+#include <mbedtls/version.h>
+#include <mbedtls/aes.h>
+//#include "alac_wrapper.h"
+#include "alac.h"
+#endif
+
+#define NTP2MS(ntp) ((((ntp) >> 10) * 1000L) >> 22)
+#define MS2NTP(ms) (((((u64_t) (ms)) << 22) / 1000) << 10)
+#define NTP2TS(ntp, rate) ((((ntp) >> 16) * (rate)) >> 16)
+#define TS2NTP(ts, rate)  (((((u64_t) (ts)) << 16) / (rate)) << 16)
+#define MS2TS(ms, rate) ((((u64_t) (ms)) * (rate)) / 1000)
+#define TS2MS(ts, rate) NTP2MS(TS2NTP(ts,rate))
+
+
#define GAP_THRES	8
+
#define GAP_COUNT	20
+
+extern log_level 	raop_loglevel;
+static log_level 	*loglevel = &raop_loglevel;
+
+//#define __RTP_STORE
+
+// default buffer size
+#define BUFFER_FRAMES (44100 / 352 + 1)
+#define MAX_PACKET    1408
+
+#define RTP_SYNC	(0x01)
+#define NTP_SYNC	(0x02)
+
+#define RESEND_TO	200
+
+enum { DATA, CONTROL, TIMING };
+
+static const u8_t silence_frame[MAX_PACKET] = { 0 };
+
+typedef u16_t seq_t;
+typedef struct audio_buffer_entry {   // decoded audio packets
+	int ready;
+	u32_t rtptime, last_resend;
+	s16_t *data;
+	int len;
+} abuf_t;
+
+typedef struct rtp_s {
+#ifdef __RTP_STORE
+	FILE *rtpIN, *rtpOUT;
+#endif
+	bool running;
+	unsigned char aesiv[16];
+#ifdef WIN32
+	AES_KEY aes;
+#else
+	mbedtls_aes_context aes;
+#endif
+	bool decrypt, range;
+	int frame_size, frame_duration;
+	int in_frames, out_frames;
+	struct in_addr host;
+	struct sockaddr_in rtp_host;
+	struct {
+		unsigned short rport, lport;
+		int sock;
+	} rtp_sockets[3]; 					 // data, control, timing
+	struct timing_s {
+		bool drift;
+		u64_t local, remote;
+		u32_t count, gap_count;
+		s64_t gap_sum, gap_adjust;
+	} timing;
+	struct {
+		u32_t 	rtp, time;
+		u8_t  	status;
+		bool	first, required;
+	} synchro;
+	struct {
+		u32_t time;
+		seq_t seqno;
+		u32_t rtptime;
+	} record;
+	int latency;			// rtp hold depth in samples
+	u32_t resent_frames;	// total recovered frames
+	u32_t silent_frames;	// total silence frames
+	u32_t filled_frames;    // silence frames in current silence episode
+	int skip;				// number of frames to skip to keep sync alignement
+	abuf_t audio_buffer[BUFFER_FRAMES];
+	seq_t ab_read, ab_write;
+	pthread_mutex_t ab_mutex;
+#ifdef WIN32
+	pthread_t rtp_thread;
+#else
+	TaskHandle_t rtp_thread, joiner;
+#endif
+	alac_file *alac_codec;
+	int flush_seqno;
+	bool playing;
+	rtp_data_cb_t callback;
+} rtp_t;
+
+
+#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
+static void 	buffer_alloc(abuf_t *audio_buffer, int size);
+static void 	buffer_release(abuf_t *audio_buffer);
+static void 	buffer_reset(abuf_t *audio_buffer);
+static void 	buffer_push_packet(rtp_t *ctx);
+static bool 	rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
+static bool 	rtp_request_timing(rtp_t *ctx);
+static void*	rtp_thread_func(void *arg);
+static int	  	seq_order(seq_t a, seq_t b);
+
+/*---------------------------------------------------------------------------*/
+static alac_file* alac_init(int fmtp[32]) {
+	alac_file *alac;
+	int sample_size = fmtp[3];
+
+	if (sample_size != 16) {
+		LOG_ERROR("sample size must be 16 %d", sample_size);
+		return false;
+	}
+
+	alac = create_alac(sample_size, 2);
+
+	if (!alac) {
+		LOG_ERROR("cannot create alac codec", NULL);
+		return NULL;
+	}
+
+	alac->setinfo_max_samples_per_frame = fmtp[1];
+	alac->setinfo_7a 				= fmtp[2];
+	alac->setinfo_sample_size 		= sample_size;
+	alac->setinfo_rice_historymult = fmtp[4];
+	alac->setinfo_rice_initialhistory = fmtp[5];
+	alac->setinfo_rice_kmodifier 	= fmtp[6];
+	alac->setinfo_7f 				= fmtp[7];
+	alac->setinfo_80 				= fmtp[8];
+	alac->setinfo_82 			    = fmtp[9];
+	alac->setinfo_86 				= fmtp[10];
+	alac->setinfo_8a_rate			= fmtp[11];
+	allocate_buffers(alac);
+
+	return alac;
+}
+
+/*---------------------------------------------------------------------------*/
+rtp_resp_t rtp_init(struct in_addr host, bool sync, bool drift, bool range,
+								int latency, char *aeskey, char *aesiv, char *fmtpstr,
+								short unsigned pCtrlPort, short unsigned pTimingPort,
+								rtp_data_cb_t callback)
+{
+	int i = 0;
+	char *arg;
+	int fmtp[12];
+	bool rc = true;
+	rtp_t *ctx = calloc(1, sizeof(rtp_t));
+	rtp_resp_t resp = { 0, 0, 0, NULL };
+
+	if (!ctx) return resp;
+
+	ctx->host = host;
+	ctx->decrypt = false;
+	ctx->callback = callback;
+	ctx->rtp_host.sin_family = AF_INET;
+	ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
+	pthread_mutex_init(&ctx->ab_mutex, 0);
+	ctx->flush_seqno = -1;
+	ctx->latency = latency;
+	ctx->synchro.required = sync;
+	ctx->timing.drift = drift;
+	ctx->range = range;
+
+	// write pointer = last written, read pointer = next to read so fill = w-r+1
+	ctx->ab_read = ctx->ab_write + 1;
+
+#ifdef __RTP_STORE
+	ctx->rtpIN = fopen("airplay.rtpin", "wb");
+	ctx->rtpOUT = fopen("airplay.rtpout", "wb");
+#endif
+
+	ctx->rtp_sockets[CONTROL].rport = pCtrlPort;
+	ctx->rtp_sockets[TIMING].rport = pTimingPort;
+
+	if (aesiv && aeskey) {
+		memcpy(ctx->aesiv, aesiv, 16);
+#ifdef WIN32
+		AES_set_decrypt_key((unsigned char*) aeskey, 128, &ctx->aes);
+#else
+		memset(&ctx->aes, 0, sizeof(mbedtls_aes_context));
+		mbedtls_aes_setkey_dec(&ctx->aes, (unsigned char*) aeskey, 128);
+#endif
+		ctx->decrypt = true;
+	}
+
+	memset(fmtp, 0, sizeof(fmtp));
+	while ((arg = strsep(&fmtpstr, " \t")) != NULL) fmtp[i++] = atoi(arg);
+
+	ctx->frame_size = fmtp[1];
+	ctx->frame_duration = (ctx->frame_size * 1000) / 44100;
+
+	// alac decoder
+	ctx->alac_codec = alac_init(fmtp);
+	rc &= ctx->alac_codec != NULL;
+
+	buffer_alloc(ctx->audio_buffer, ctx->frame_size*4);
+
+	// create rtp ports
+	for (i = 0; i < 3; i++) {
+		ctx->rtp_sockets[i].sock = bind_socket(&ctx->rtp_sockets[i].lport, SOCK_DGRAM);
+		rc &= ctx->rtp_sockets[i].sock > 0;
+	}
+
+	// create http port and start listening
+	resp.cport = ctx->rtp_sockets[CONTROL].lport;
+	resp.tport = ctx->rtp_sockets[TIMING].lport;
+	resp.aport = ctx->rtp_sockets[DATA].lport;
+
+	if (rc) {
+		ctx->running = true;
+#ifdef WIN32
+		pthread_create(&ctx->rtp_thread, NULL, rtp_thread_func, (void *) ctx);
+#else
+		xTaskCreate((TaskFunction_t) rtp_thread_func, "RTP_thread", 4096, ctx, configMAX_PRIORITIES - 3, &ctx->rtp_thread);
+#endif
+	} else {
+		rtp_end(ctx);
+		ctx = NULL;
+	}
+
+	resp.ctx = ctx;
+
+	return resp;
+}
+
+/*---------------------------------------------------------------------------*/
+void rtp_end(rtp_t *ctx)
+{
+	int i;
+
+	if (!ctx) return;
+
+	if (ctx->running) {
+		ctx->running = false;
+#ifdef WIN32
+		pthread_join(ctx->rtp_thread, NULL);
+#else
+		ctx->joiner = xTaskGetCurrentTaskHandle();
+		xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
+#endif
+	}
+
+	for (i = 0; i < 3; i++) shutdown_socket(ctx->rtp_sockets[i].sock);
+
+	delete_alac(ctx->alac_codec);
+
+	buffer_release(ctx->audio_buffer);
+	free(ctx);
+
+#ifdef __RTP_STORE
+	fclose(ctx->rtpIN);
+	fclose(ctx->rtpOUT);
+#endif
+}
+
+/*---------------------------------------------------------------------------*/
+bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime)
+{
+	bool rc = true;
+	u32_t now = gettime_ms();
+
+	if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
+		rc = false;
+		LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
+	} else {
+		pthread_mutex_lock(&ctx->ab_mutex);
+		buffer_reset(ctx->audio_buffer);
+		ctx->playing = false;
+		ctx->flush_seqno = seqno;
+		ctx->synchro.first = false;
+		pthread_mutex_unlock(&ctx->ab_mutex);
+	}
+
+	LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
+
+	return rc;
+}
+
+/*---------------------------------------------------------------------------*/
+void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime)
+{
+	ctx->record.seqno = seqno;
+	ctx->record.rtptime = rtptime;
+	ctx->record.time = gettime_ms();
+
+	LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_alloc(abuf_t *audio_buffer, int size) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) {
+		audio_buffer[i].data = malloc(size);
+		audio_buffer[i].ready = 0;
+	}
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_release(abuf_t *audio_buffer) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) {
+		free(audio_buffer[i].data);
+	}
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_reset(abuf_t *audio_buffer) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) audio_buffer[i].ready = 0;
+}
+
+/*---------------------------------------------------------------------------*/
+// the sequence numbers will wrap pretty often.
+// this returns true if the second arg is after the first
+static int seq_order(seq_t a, seq_t b) {
+	s16_t d = b - a;
+	return d > 0;
+}
+
+/*---------------------------------------------------------------------------*/
+static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, int *outsize) {
+	unsigned char packet[MAX_PACKET];
+	unsigned char iv[16];
+	int aeslen;
+	assert(len<=MAX_PACKET);
+
+	if (ctx->decrypt) {
+		aeslen = len & ~0xf;
+		memcpy(iv, ctx->aesiv, sizeof(iv));
+#ifdef WIN32
+		AES_cbc_encrypt((unsigned char*)buf, packet, aeslen, &ctx->aes, iv, AES_DECRYPT);
+#else
+		mbedtls_aes_crypt_cbc(&ctx->aes, MBEDTLS_AES_DECRYPT, aeslen, iv, (unsigned char*) buf, packet);
+#endif
+		memcpy(packet+aeslen, buf+aeslen, len-aeslen);
+		decode_frame(ctx->alac_codec, packet, dest, outsize);
+	} else decode_frame(ctx->alac_codec, (unsigned char*) buf, dest, outsize);
+}
+
+
+/*---------------------------------------------------------------------------*/
+static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
+	abuf_t *abuf = NULL;
+
+	pthread_mutex_lock(&ctx->ab_mutex);
+
+	if (!ctx->playing) {
+		if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
+		   ((ctx->synchro.required && ctx->synchro.first) || !ctx->synchro.required)) {
+			ctx->ab_write = seqno-1;
+			ctx->ab_read = seqno;
+			ctx->skip = 0;
+			ctx->flush_seqno = -1;
+			ctx->playing = true;
+			ctx->synchro.first = false;
+			ctx->resent_frames = ctx->silent_frames = 0;
+		} else {
+			pthread_mutex_unlock(&ctx->ab_mutex);
+			return;
+		}
+	}
+
+	if (seqno == ctx->ab_write+1) {
+		// expected packet
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		ctx->ab_write = seqno;
+		LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
+	} else if (seq_order(ctx->ab_write, seqno)) {
+		// newer than expected
+		if (seqno - ctx->ab_write - 1 > ctx->latency / ctx->frame_size) {
+			// only get rtp latency-1 frames back (last one is seqno)
+			LOG_WARN("[%p] too many missing frames %hu", ctx, seqno - ctx->ab_write - 1);
+			ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
+		}
+		if (seqno - ctx->ab_read + 1 > ctx->latency / ctx->frame_size) {
+			// if ab_read is lagging more than http latency, advance it
+			LOG_WARN("[%p] on hold for too long %hu", ctx, seqno - ctx->ab_read + 1);
+			ctx->ab_read = seqno - ctx->latency / ctx->frame_size + 1;
+		}
+		if (rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1)) {
+			seq_t i;
+			u32_t now = gettime_ms();
+			for (i = ctx->ab_write + 1; i <= seqno-1; i++) {
+				ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
+				ctx->audio_buffer[BUFIDX(i)].last_resend = now;
+			}
+		}
+		LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		ctx->ab_write = seqno;
+	} else if (seq_order(ctx->ab_read, seqno + 1)) {
+		// recovered packet, not yet sent
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+	} else {
+		// too late
+		LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+	}
+
+	if (ctx->in_frames++ > 1000) {
+		LOG_INFO("[%p]: fill [level:%hd] [W:%hu R:%hu]", ctx, (seq_t) (ctx->ab_write - ctx->ab_read + 1), ctx->ab_write, ctx->ab_read);
+		ctx->in_frames = 0;
+	}
+
+	if (abuf) {
+		alac_decode(ctx, abuf->data, data, len, &abuf->len);
+		abuf->ready = 1;
+		// this is the local rtptime when this frame is expected to play
+		abuf->rtptime = rtptime;
+#ifdef __RTP_STORE
+		fwrite(data, len, 1, ctx->rtpIN);
+		fwrite(abuf->data, abuf->len, 1, ctx->rtpOUT);
+#endif
+	}
+
+	buffer_push_packet(ctx);
+
+	pthread_mutex_unlock(&ctx->ab_mutex);
+}
+
+/*---------------------------------------------------------------------------*/
+// push as many frames as possible through callback
+static void buffer_push_packet(rtp_t *ctx) {
+	abuf_t *curframe = NULL;
+	u32_t now, playtime;
+	int i;
+
+	// not ready to play yet
+	if (!ctx->playing ||  ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
+
+	// maybe re-evaluate time in loop in case data callback blocks ...
+	now = gettime_ms();
+
+	// there is always at least one frame in the buffer
+	do {
+
+		curframe = ctx->audio_buffer + BUFIDX(ctx->ab_read);
+		playtime = ctx->synchro.time + (((s32_t)(curframe->rtptime - ctx->synchro.rtp)) * 1000) / 44100;
+
+		/*
+		if (now > playtime + ctx->frame_duration) {
+			//LOG_INFO("[%p]: discarded frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
+		} else if (curframe->ready) {
+			ctx->callback((const u8_t*) curframe->data, curframe->len);
+		} else if (now >= playtime) {
+			LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
+			ctx->callback(silence_frame, ctx->frame_size * 4);
+			ctx->silent_frames++;
+		} else break;
+		*/
+
+		if (curframe->ready) {
+			ctx->callback((const u8_t*) curframe->data, curframe->len);
+		} else if (now >= playtime) {
+			LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
+			ctx->callback(silence_frame, ctx->frame_size * 4);
+			ctx->silent_frames++;
+		} else break;
+
+		ctx->ab_read++;
+		ctx->out_frames++;
+
+	} while (ctx->ab_write - ctx->ab_read + 1 > 0);
+
+	if (ctx->out_frames > 1000) {
+		LOG_INFO("[%p]: drain [level:%hd gap:%d] [W:%hu R:%hu] [R:%u S:%u F:%u]",
+				ctx, ctx->ab_write - ctx->ab_read, playtime - now, ctx->ab_write, ctx->ab_read,
+				ctx->resent_frames, ctx->silent_frames, ctx->filled_frames);
+		ctx->out_frames = 0;
+	}
+
+	LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
+
+	// each missing packet will be requested up to (latency_frames / 16) times
+	for (i = 16; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
+		abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
+		if (!frame->ready && now - frame->last_resend > RESEND_TO) {
+			rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
+			frame->last_resend = now;
+		}
+	}
+
}
+

+
+/*---------------------------------------------------------------------------*/
+static void *rtp_thread_func(void *arg) {
+	fd_set fds;
+	int i, sock = -1;
+	int count = 0;
+	bool ntp_sent;
+	char *packet = malloc(MAX_PACKET);
+	rtp_t *ctx = (rtp_t*) arg;
+
+	for (i = 0; i < 3; i++) {
+		if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
+		// send synchro requets 3 times
+		ntp_sent = rtp_request_timing(ctx);
+	}
+
+	while (ctx->running) {
+		ssize_t plen;
+		char type;
+		socklen_t rtp_client_len = sizeof(struct sockaddr_storage);
+		int idx = 0;
+		char *pktp = packet;
+		struct timeval timeout = {0, 50*1000};
+
+		FD_ZERO(&fds);
+		for (i = 0; i < 3; i++)	{ FD_SET(ctx->rtp_sockets[i].sock, &fds); }
+
+		if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue;
+
+		for (i = 0; i < 3; i++)
+			if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i;
+
+		plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, 0, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len);
+
+		if (!ntp_sent) {
+			LOG_WARN("[%p]: NTP request not send yet", ctx);
+			ntp_sent = rtp_request_timing(ctx);
+		}
+
+		if (plen < 0) continue;
+		assert(plen <= MAX_PACKET);
+
+		type = packet[1] & ~0x80;
+		pktp = packet;
+
+		switch (type) {
+			seq_t seqno;
+			unsigned rtptime;
+
+			// re-sent packet
+			case 0x56: {
+				pktp += 4;
+				plen -= 4;
+			}
+
+			// data packet
+			case 0x60: {
+				seqno = ntohs(*(u16_t*)(pktp+2));
+				rtptime = ntohl(*(u32_t*)(pktp+4));
+
+				// adjust pointer and length
+				pktp += 12;
+				plen -= 12;
+
+				LOG_SDEBUG("[%p]: seqno:%hu rtp:%u (type: %x, first: %u)", ctx, seqno, rtptime, type, packet[1] & 0x80);
+
+				// check if packet contains enough content to be reasonable
+				if (plen < 16) break;
+
+				if ((packet[1] & 0x80) && (type != 0x56)) {
+					LOG_INFO("[%p]: 1st audio packet received", ctx);
+				}
+
+				buffer_put_packet(ctx, seqno, rtptime, packet[1] & 0x80, pktp, plen);
+
+				break;
+			}
+
+			// sync packet
+			case 0x54: {
+				u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
+				u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
+				u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
+
+				pthread_mutex_lock(&ctx->ab_mutex);
+
+				// re-align timestamp and expected local playback time
+				if (!ctx->latency) ctx->latency = rtp_now - rtp_now_latency;
+				ctx->synchro.rtp = rtp_now - ctx->latency;
+				ctx->synchro.time = ctx->timing.local + (u32_t) NTP2MS(remote - ctx->timing.remote);
+
+				// now we are synced on RTP frames
+				ctx->synchro.status |= RTP_SYNC;
+
+				// 1st sync packet received (signals a restart of playback)
+				if (packet[0] & 0x10) {
+					ctx->synchro.first = true;
+					LOG_INFO("[%p]: 1st sync packet received", ctx);
+				}
+
+				pthread_mutex_unlock(&ctx->ab_mutex);
+
+				LOG_DEBUG("[%p]: sync packet rtp_latency:%u rtp:%u remote ntp:%Lx, local time %u (now:%u)",
+						  ctx, rtp_now_latency, rtp_now, remote, ctx->synchro.time, gettime_ms());
+
+				if (!count--) {
+					rtp_request_timing(ctx);
+					count = 3;
+				}
+
+				break;
+			}
+
+			// NTP timing packet
+			case 0x53: {
+				u64_t expected;
+				s64_t delta = 0;
+				u32_t reference   = ntohl(*(u32_t*)(pktp+12)); // only low 32 bits in our case
+				u64_t remote 	  =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
+				u32_t roundtrip   = gettime_ms() - reference;
+
+				// better discard sync packets when roundtrip is suspicious
+				if (roundtrip > 100) {
+					LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip);
+					break;
+				}
+
+				/*
+				  The expected elapsed remote time should be exactly the same as
+				  elapsed local time between the two request, corrected by the
+				  drifting
+				*/
+				expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
+
+				ctx->timing.remote = remote;
+				ctx->timing.local = reference;
+				ctx->timing.count++;
+
+				if (!ctx->timing.drift && (ctx->synchro.status & NTP_SYNC)) {
+					delta = NTP2MS((s64_t) expected - (s64_t) ctx->timing.remote);
+					ctx->timing.gap_sum += delta;
+
+					pthread_mutex_lock(&ctx->ab_mutex);
+
+					/*
+					 if expected time is more than remote, then our time is
+					 running faster and we are transmitting frames too quickly,
+					 so we'll run out of frames, need to add one
+					*/
+					if (ctx->timing.gap_sum > GAP_THRES && ctx->timing.gap_count++ > GAP_COUNT) {
+						LOG_INFO("[%p]: Sending packets too fast %Ld [W:%hu R:%hu]", ctx, ctx->timing.gap_sum, ctx->ab_write, ctx->ab_read);
+						ctx->ab_read--;
+						ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready = 1;
+						ctx->timing.gap_sum -= GAP_THRES;
+						ctx->timing.gap_adjust -= GAP_THRES;
+					/*
+					 if expected time is less than remote, then our time is
+					 running slower and we are transmitting frames too slowly,
+					 so we'll overflow frames buffer, need to remove one
+					*/
+					} else if (ctx->timing.gap_sum < -GAP_THRES && ctx->timing.gap_count++ > GAP_COUNT) {
+						if (seq_order(ctx->ab_read, ctx->ab_write + 1)) {
+							ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready = 0;
+							ctx->ab_read++;
+						} else ctx->skip++;
+						ctx->timing.gap_sum += GAP_THRES;
+						ctx->timing.gap_adjust += GAP_THRES;
+						LOG_INFO("[%p]: Sending packets too slow %Ld (skip: %d)  [W:%hu R:%hu]", ctx, ctx->timing.gap_sum, ctx->skip, ctx->ab_write, ctx->ab_read);
+					}
+
+					if (llabs(ctx->timing.gap_sum) < 8) ctx->timing.gap_count = 0;
+
+					pthread_mutex_unlock(&ctx->ab_mutex);
+				}
+
+				// now we are synced on NTP (mutex not needed)
+				ctx->synchro.status |= NTP_SYNC;
+
+				LOG_DEBUG("[%p]: Timing references local:%Lu, remote:%Lx (delta:%Ld, sum:%Ld, adjust:%Ld, gaps:%d)",
+						  ctx, ctx->timing.local, ctx->timing.remote, delta, ctx->timing.gap_sum, ctx->timing.gap_adjust, ctx->timing.gap_count);
+
+				break;
+			}
+		}
+	}
+
+	free(packet);
+	LOG_INFO("[%p]: terminating", ctx);
+
+#ifndef WIN32
+	xTaskNotify(ctx->joiner, 0, eNoAction);
+	vTaskDelete(NULL);
+#endif
+
+	return NULL;
+}
+
+/*---------------------------------------------------------------------------*/
+static bool rtp_request_timing(rtp_t *ctx) {
+	unsigned char req[32];
+	u32_t now = gettime_ms();
+	int i;
+	struct sockaddr_in host;
+
+	LOG_DEBUG("[%p]: timing request now:%u (port: %hu)", ctx, now, ctx->rtp_sockets[TIMING].rport);
+
+	req[0] = 0x80;
+	req[1] = 0x52|0x80;
+	*(u16_t*)(req+2) = htons(7);
+	*(u32_t*)(req+4) = htonl(0);  // dummy
+	for (i = 0; i < 16; i++) req[i+8] = 0;
+	*(u32_t*)(req+24) = 0;
+	*(u32_t*)(req+28) = htonl(now); // this is not a real NTP, but a 32 ms counter in the low part of the NTP
+
+	if (ctx->host.s_addr != INADDR_ANY) {
+		host.sin_family = AF_INET;
+		host.sin_addr =	ctx->host;
+	} else host = ctx->rtp_host;
+
+	// no address from sender, need to wait for 1st packet to be received
+	if (host.sin_addr.s_addr == INADDR_ANY) return false;
+
+	host.sin_port = htons(ctx->rtp_sockets[TIMING].rport);
+
+	if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), 0, (struct sockaddr*) &host, sizeof(host))) {
+		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
+	}
+
+	return true;
+}
+
+/*---------------------------------------------------------------------------*/
+static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
+	unsigned char req[8];    // *not* a standard RTCP NACK
+
+	// do not request silly ranges (happens in case of network large blackouts)
+	if (seq_order(last, first) || last - first > BUFFER_FRAMES / 2) return false;
+
+	ctx->resent_frames += last - first + 1;
+
+	LOG_DEBUG("resend request [W:%hu R:%hu first=%hu last=%hu]", ctx->ab_write, ctx->ab_read, first, last);
+
+	req[0] = 0x80;
+	req[1] = 0x55|0x80;  // Apple 'resend'
+	*(u16_t*)(req+2) = htons(1);  // our seqnum
+	*(u16_t*)(req+4) = htons(first);  // missed seqnum
+	*(u16_t*)(req+6) = htons(last-first+1);  // count
+
+	ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
+
+	if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), 0, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
+		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
+	}
+
+	return true;
+}
+
+
+#if 0
+/*---------------------------------------------------------------------------*/
+// get the next frame, when available. return 0 if underrun/stream reset.
+static short *_buffer_get_frame(rtp_t *ctx, int *len) {
+	short buf_fill;
+	abuf_t *curframe = 0;
+	int i;
+	u32_t now, playtime;
+
+	if (!ctx->playing) return NULL;
+
+	// skip frames if we are running late and skip could not be done in SYNC
+	while (ctx->skip && seq_order(ctx->ab_read, ctx->ab_write + 1)) {
+		ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready = 0;
+		ctx->ab_read++;
+		ctx->skip--;
+		LOG_INFO("[%p]: Sending packets too slow (skip: %d) [W:%hu R:%hu]", ctx, ctx->skip, ctx->ab_write, ctx->ab_read);
+	}
+
+	buf_fill = ctx->ab_write - ctx->ab_read + 1;
+
+	if (buf_fill >= BUFFER_FRAMES) {
+		LOG_ERROR("[%p]: Buffer overrun %hu", ctx, buf_fill);
+		ctx->ab_read = ctx->ab_write - (BUFFER_FRAMES - 64);
+		buf_fill = ctx->ab_write - ctx->ab_read + 1;
+	}
+
+	now = gettime_ms();
+	curframe = ctx->audio_buffer + BUFIDX(ctx->ab_read);
+
+	// use next frame when buffer is empty or silence continues to be sent
+	if (!buf_fill) curframe->rtptime = ctx->audio_buffer[BUFIDX(ctx->ab_read - 1)].rtptime + ctx->frame_size;
+
+	playtime = ctx->synchro.time + (((s32_t)(curframe->rtptime - ctx->synchro.rtp))*1000)/44100;
+
+	LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
+
+	// wait if not ready but have time, otherwise send silence
+	if (!buf_fill || ctx->synchro.status != (RTP_SYNC | NTP_SYNC) || (now < playtime && !curframe->ready)) {
+		LOG_SDEBUG("[%p]: waiting (fill:%hd, W:%hu R:%hu) now:%u, playtime:%u, wait:%d", ctx, buf_fill, ctx->ab_write, ctx->ab_read, now, playtime, playtime - now);
+		// look for "blocking" frames at the top of the queue and try to catch-up
+		for (i = 0; i < min(16, buf_fill); i++) {
+			abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
+			if (!frame->ready && now - frame->last_resend > RESEND_TO) {
+				rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
+				frame->last_resend = now;
+			}
+		}
+		return NULL;
+	}
+
+	// when silence is inserted at the top, need to move write pointer
+	if (!buf_fill) {
+		if (!ctx->filled_frames) {
+			LOG_WARN("[%p]: start silence (late %d ms) [W:%hu R:%hu]", ctx, now - playtime, ctx->ab_write, ctx->ab_read);
+		}
+		ctx->ab_write++;
+		ctx->filled_frames++;
+	} else ctx->filled_frames = 0;
+
+	if (!(ctx->out_frames++ & 0x1ff)) {
+		LOG_INFO("[%p]: drain [level:%hd gap:%d] [W:%hu R:%hu] [R:%u S:%u F:%u]",
+					ctx, buf_fill-1, playtime - now, ctx->ab_write, ctx->ab_read,
+					ctx->resent_frames, ctx->silent_frames, ctx->filled_frames);
+	}
+
+	// each missing packet will be requested up to (latency_frames / 16) times
+	for (i = 16; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
+		abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
+		if (!frame->ready && now - frame->last_resend > RESEND_TO) {
+			rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
+			frame->last_resend = now;
+		}
+	}
+
+
	if (!curframe->ready) {
+		LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
+		memset(curframe->data, 0, ctx->frame_size*4);
+		curframe->len = ctx->frame_size * 4;
+		ctx->silent_frames++;
+	} else {
+		LOG_SDEBUG("[%p]: prepared frame (fill:%hd, W:%hu R:%hu)", ctx, buf_fill-1, ctx->ab_write, ctx->ab_read);
+	}
+
+	*len = curframe->len;
+	curframe->ready = 0;
+	ctx->ab_read++;
+
+	return curframe->data;
+}
+#endif
+
+
+
+

+ 21 - 0
components/raop/rtp.h

@@ -0,0 +1,21 @@
+#ifndef _HAIRTUNES_H_
+#define _HAIRTUNES_H_
+
+#include "util.h"
+
+typedef struct {
+	unsigned short cport, tport, aport;
+	struct rtp_s *ctx;
+} rtp_resp_t;
+
+typedef	void		(*rtp_data_cb_t)(const u8_t *data, size_t len);
+
+rtp_resp_t 			rtp_init(struct in_addr host, bool sync, bool drift, bool range, int latency,
+							char *aeskey, char *aesiv, char *fmtpstr,
+							short unsigned pCtrlPort, short unsigned pTimingPort, rtp_data_cb_t data_cb);
+void			 	rtp_end(struct rtp_s *ctx);
+bool 				rtp_flush(struct rtp_s *ctx, unsigned short seqno, unsigned rtptime);
+void 				rtp_record(struct rtp_s *ctx, unsigned short seqno, unsigned rtptime);
+void 				rtp_metadata(struct rtp_s *ctx, struct metadata_s *metadata);
+
+#endif

+ 601 - 0
components/raop/util.c

@@ -0,0 +1,601 @@
+/*
+ *  AirConnect: Chromecast & UPnP to AirPlay
+ *
+ *  (c) Philippe 2016-2017, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "platform.h"
+
+#ifdef WIN32
+#include <iphlpapi.h>
+#else
+/*
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netdb.h>
+*/
+#include <ctype.h>
+#endif
+
+#include <stdarg.h>
+
+#include "pthread.h"
+#include "util.h"
+#include "log_util.h"
+
+/*----------------------------------------------------------------------------*/
+/* globals */
+/*----------------------------------------------------------------------------*/
+
+extern log_level	util_loglevel;
+
+/*----------------------------------------------------------------------------*/
+/* locals */
+/*----------------------------------------------------------------------------*/
+static log_level 		*loglevel = &util_loglevel;
+
+static char *ltrim(char *s);
+static int read_line(int fd, char *line, int maxlen, int timeout);
+
+/*----------------------------------------------------------------------------*/
+/* 																			  */
+/* NETWORKING utils															  */
+/* 																			  */
+/*----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+#define MAX_INTERFACES 256
+#define DEFAULT_INTERFACE 1
+#if !defined(WIN32)
+#define INVALID_SOCKET (-1)
+#endif
+in_addr_t get_localhost(char **name)
+{
+#ifdef WIN32
+	char buf[256];
+	struct hostent *h = NULL;
+	struct sockaddr_in LocalAddr;
+
+	memset(&LocalAddr, 0, sizeof(LocalAddr));
+
+	gethostname(buf, 256);
+	h = gethostbyname(buf);
+
+	if (name) *name = strdup(buf);
+
+	if (h != NULL) {
+		memcpy(&LocalAddr.sin_addr, h->h_addr_list[0], 4);
+		return LocalAddr.sin_addr.s_addr;
+	}
+	else return INADDR_ANY;
+#else
+	// missing platform here ...
+	return INADDR_ANY;
+#endif
+}
+
+
+/*----------------------------------------------------------------------------*/
+#ifdef WIN32
+void winsock_init(void) {
+	WSADATA wsaData;
+	WORD wVersionRequested = MAKEWORD(2, 2);
+	int WSerr = WSAStartup(wVersionRequested, &wsaData);
+	if (WSerr != 0) {
+		LOG_ERROR("Bad winsock version", NULL);
+		exit(1);
+	}
+}
+
+/*----------------------------------------------------------------------------*/
+void winsock_close(void) {
+	WSACleanup();
+}
+#endif
+
+
+/*----------------------------------------------------------------------------*/
+int shutdown_socket(int sd)
+{
+	if (sd <= 0) return -1;
+
+#ifdef WIN32
+	shutdown(sd, SD_BOTH);
+#else
+	shutdown(sd, SHUT_RDWR);
+#endif
+
+	LOG_DEBUG("closed socket %d", sd);
+
+	return closesocket(sd);
+}
+
+
+/*----------------------------------------------------------------------------*/
+int bind_socket(unsigned short *port, int mode)
+{
+	int sock;
+	socklen_t len = sizeof(struct sockaddr);
+	struct sockaddr_in addr;
+
+	if ((sock = socket(AF_INET, mode, 0)) < 0) {
+		LOG_ERROR("cannot create socket %d", sock);
+		return sock;
+	}
+
+	/*  Populate socket address structure  */
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family      = AF_INET;
+	addr.sin_addr.s_addr = htonl(INADDR_ANY);
+	addr.sin_port        = htons(*port);
+#ifdef SIN_LEN
+	si.sin_len = sizeof(si);
+#endif
+
+	if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
+		closesocket(sock);
+		LOG_ERROR("cannot bind socket %d", sock);
+		return -1;
+	}
+
+	if (!*port) {
+		getsockname(sock, (struct sockaddr *) &addr, &len);
+		*port = ntohs(addr.sin_port);
+	}
+
+	LOG_DEBUG("socket binding %d on port %d", sock, *port);
+
+	return sock;
+}
+
+
+/*----------------------------------------------------------------------------*/
+int conn_socket(unsigned short port)
+{
+	struct sockaddr_in addr;
+	int sd;
+
+	sd = socket(AF_INET, SOCK_STREAM, 0);
+
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+	addr.sin_port = htons(port);
+
+	if (sd < 0 || connect(sd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		close(sd);
+		return -1;
+	}
+
+	LOG_DEBUG("created socket %d", sd);
+
+	return sd;
+}
+
+
+
+/*----------------------------------------------------------------------------*/
+/* 																			  */
+/* SYSTEM utils															 	  */
+/* 																			  */
+/*----------------------------------------------------------------------------*/
+
+#ifdef WIN32
+/*----------------------------------------------------------------------------*/
+void *dlopen(const char *filename, int flag) {
+	SetLastError(0);
+	return LoadLibrary((LPCTSTR)filename);
+}
+
+/*----------------------------------------------------------------------------*/
+void *dlsym(void *handle, const char *symbol) {
+	SetLastError(0);
+	return (void *)GetProcAddress(handle, symbol);
+}
+
+/*----------------------------------------------------------------------------*/
+char *dlerror(void) {
+	static char ret[32];
+	int last = GetLastError();
+	if (last) {
+		sprintf(ret, "code: %i", last);
+		SetLastError(0);
+		return ret;
+	}
+	return NULL;
+}
+#endif
+
+
+/*----------------------------------------------------------------------------*/
+/* 																			  */
+/* STDLIB extensions													 	  */
+/* 																			  */
+/*----------------------------------------------------------------------------*/
+
+#ifdef WIN32
+/*---------------------------------------------------------------------------*/
+char *strcasestr(const char *haystack, const char *needle) {
+	size_t length_needle;
+	size_t length_haystack;
+	size_t i;
+
+	if (!haystack || !needle)
+		return NULL;
+
+	length_needle = strlen(needle);
+	length_haystack = strlen(haystack);
+
+	if (length_haystack < length_needle) return NULL;
+
+	length_haystack -= length_needle - 1;
+
+	for (i = 0; i < length_haystack; i++)
+	{
+		size_t j;
+
+		for (j = 0; j < length_needle; j++)
+		{
+			unsigned char c1;
+			unsigned char c2;
+
+			c1 = haystack[i+j];
+			c2 = needle[j];
+			if (toupper(c1) != toupper(c2))
+				goto next;
+		}
+		return (char *) haystack + i;
+		next:
+			;
+	}
+
+	return NULL;
+}
+
+/*---------------------------------------------------------------------------*/
+char* strsep(char** stringp, const char* delim)
+{
+  char* start = *stringp;
+  char* p;
+
+  p = (start != NULL) ? strpbrk(start, delim) : NULL;
+
+  if (p == NULL)  {
+	*stringp = NULL;
+  } else {
+	*p = '\0';
+	*stringp = p + 1;
+  }
+
+  return start;
+}
+
+/*---------------------------------------------------------------------------*/
+char *strndup(const char *s, size_t n) {
+	char *p = malloc(n + 1);
+	strncpy(p, s, n);
+	p[n] = '\0';
+
+	return p;
+}
+#endif
+
+
+/*----------------------------------------------------------------------------*/
+char* strextract(char *s1, char *beg, char *end)
+{
+	char *p1, *p2, *res;
+
+	p1 = strcasestr(s1, beg);
+	if (!p1) return NULL;
+
+	p1 += strlen(beg);
+	p2 = strcasestr(p1, end);
+	if (!p2) return strdup(p1);
+
+	res = malloc(p2 - p1 + 1);
+	memcpy(res, p1, p2 - p1);
+	res[p2 - p1] = '\0';
+
+	return res;
+}
+
+
+#ifdef WIN32
+/*----------------------------------------------------------------------------*/
+int asprintf(char **strp, const char *fmt, ...)
+{
+	va_list args, cp;
+	int len, ret = 0;
+
+	va_start(args, fmt);
+	len = vsnprintf(NULL, 0, fmt, args);
+	*strp = malloc(len + 1);
+
+	if (*strp) ret = vsprintf(*strp, fmt, args);
+
+	va_end(args);
+
+	return ret;
+}
+#endif
+
+/*---------------------------------------------------------------------------*/
+static char *ltrim(char *s)
+{
+	while(isspace((int) *s)) s++;
+	return s;
+}
+
+/*----------------------------------------------------------------------------*/
+/* 																			  */
+/* HTTP management														 	  */
+/* 																			  */
+/*----------------------------------------------------------------------------*/
+
+/*----------------------------------------------------------------------------*/
+bool http_parse(int sock, char *method, key_data_t *rkd, char **body, int *len)
+{
+	char line[256], *dp;
+	unsigned j;
+	int i, timeout = 100;
+
+	rkd[0].key = NULL;
+
+	if ((i = read_line(sock, line, sizeof(line), timeout)) <= 0) {
+		if (i < 0) {
+			LOG_ERROR("cannot read method", NULL);
+		}
+		return false;
+	}
+
+	if (!sscanf(line, "%s", method)) {
+		LOG_ERROR("missing method", NULL);
+		return false;
+	}
+
+	i = *len = 0;
+
+	while (read_line(sock, line, sizeof(line), timeout) > 0) {
+
+		LOG_SDEBUG("sock: %u, received %s", line);
+
+		// line folding should be deprecated
+		if (i && rkd[i].key && (line[0] == ' ' || line[0] == '\t')) {
+			for(j = 0; j < strlen(line); j++) if (line[j] != ' ' && line[j] != '\t') break;
+			rkd[i].data = realloc(rkd[i].data, strlen(rkd[i].data) + strlen(line + j) + 1);
+			strcat(rkd[i].data, line + j);
+			continue;
+		}
+
+		dp = strstr(line,":");
+
+		if (!dp){
+			LOG_ERROR("Request failed, bad header", NULL);
+			kd_free(rkd);
+			return false;
+		}
+
+		*dp = 0;
+		rkd[i].key = strdup(line);
+		rkd[i].data = strdup(ltrim(dp + 1));
+
+		if (!strcasecmp(rkd[i].key, "Content-Length")) *len = atol(rkd[i].data);
+
+		i++;
+		rkd[i].key = NULL;
+	}
+
+	if (*len) {
+		int size = 0;
+
+		*body = malloc(*len + 1);
+		while (*body && size < *len) {
+			int bytes = recv(sock, *body + size, *len - size, 0);
+			if (bytes <= 0) break;
+			size += bytes;
+		}
+
+		(*body)[*len] = '\0';
+
+		if (!*body || size != *len) {
+			LOG_ERROR("content length receive error %d %d", *len, size);
+		}
+	}
+
+	return true;
+}
+
+
+/*----------------------------------------------------------------------------*/
+static int read_line(int fd, char *line, int maxlen, int timeout)
+{
+	int i,rval;
+	int count=0;
+	struct pollfd pfds;
+	char ch;
+
+	*line = 0;
+	pfds.fd = fd;
+	pfds.events = POLLIN;
+
+	for(i = 0; i < maxlen; i++){
+		if (poll(&pfds, 1, timeout)) rval=recv(fd, &ch, 1, 0);
+		else return 0;
+
+		if (rval == -1) {
+			if (errno == EAGAIN) return 0;
+			LOG_ERROR("fd: %d read error: %s", fd, strerror(errno));
+			return -1;
+		}
+
+		if (rval == 0) {
+			LOG_INFO("disconnected on the other end %u", fd);
+			return 0;
+		}
+
+		if (ch == '\n') {
+			*line=0;
+			return count;
+		}
+
+		if (ch=='\r') continue;
+
+		*line++=ch;
+		count++;
+		if (count >= maxlen-1) break;
+	}
+
+	*line = 0;
+	return count;
+}
+
+
+/*----------------------------------------------------------------------------*/
+char *http_send(int sock, char *method, key_data_t *rkd)
+{
+	unsigned sent, len;
+	char *resp = kd_dump(rkd);
+	char *data = malloc(strlen(method) + 2 + strlen(resp) + 2 + 1);
+
+	len = sprintf(data, "%s\r\n%s\r\n", method, resp);
+	NFREE(resp);
+
+	sent = send(sock, data, len, 0);
+
+	if (sent != len) {
+		LOG_ERROR("HTTP send() error:%s %u (strlen=%u)", data, sent, len);
+		NFREE(data);
+	}
+
+	return data;
+}
+
+
+/*----------------------------------------------------------------------------*/
+char *kd_lookup(key_data_t *kd, char *key)
+{
+	int i = 0;
+	while (kd && kd[i].key){
+		if (!strcasecmp(kd[i].key, key)) return kd[i].data;
+		i++;
+	}
+	return NULL;
+}
+
+
+/*----------------------------------------------------------------------------*/
+bool kd_add(key_data_t *kd, char *key, char *data)
+{
+	int i = 0;
+	while (kd && kd[i].key) i++;
+
+	kd[i].key = strdup(key);
+	kd[i].data = strdup(data);
+	kd[i+1].key = NULL;
+
+	return NULL;
+}
+
+
+/*----------------------------------------------------------------------------*/
+void kd_free(key_data_t *kd)
+{
+	int i = 0;
+	while (kd && kd[i].key){
+		free(kd[i].key);
+		if (kd[i].data) free(kd[i].data);
+		i++;
+	}
+
+	kd[0].key = NULL;
+}
+
+
+/*----------------------------------------------------------------------------*/
+char *kd_dump(key_data_t *kd)
+{
+	int i = 0;
+	int pos = 0, size = 0;
+	char *str = NULL;
+
+	if (!kd || !kd[0].key) return strdup("\r\n");
+
+	while (kd && kd[i].key) {
+		char *buf;
+		int len;
+
+		len = asprintf(&buf, "%s: %s\r\n", kd[i].key, kd[i].data);
+
+		while (pos + len >= size) {
+			void *p = realloc(str, size + 1024);
+			size += 1024;
+			if (!p) {
+				free(str);
+				return NULL;
+			}
+			str = p;
+		}
+
+		memcpy(str + pos, buf, len);
+
+		pos += len;
+		free(buf);
+		i++;
+	}
+
+	str[pos] = '\0';
+
+	return str;
+}
+
+/*--------------------------------------------------------------------------*/
+void free_metadata(struct metadata_s *metadata)
+{
+	NFREE(metadata->artist);
+	NFREE(metadata->album);
+	NFREE(metadata->title);
+	NFREE(metadata->genre);
+	NFREE(metadata->path);
+	NFREE(metadata->artwork);
+	NFREE(metadata->remote_title);
+}
+
+
/*----------------------------------------------------------------------------*/
+

+int _fprintf(FILE *file, ...)
+{
+	va_list args;
+	char *fmt;
+	int n;
+
+	va_start(args, file);
+	fmt = va_arg(args, char*);
+
+	n = vfprintf(file, fmt, args);
+	va_end(args);
+	return n;
+}
+
+
+
+
+
+
+
+
+

+ 85 - 0
components/raop/util.h

@@ -0,0 +1,85 @@
+/*
+ *  Misc utilities
+ *
+ *  (c) Adrian Smith 2012-2014, triode1@btinternet.com
+ *  (c) Philippe 2016-2017, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __UTIL_H
+#define __UTIL_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "platform.h"
+#include "pthread.h"
+
+#define NFREE(p) if (p) { free(p); p = NULL; }
+
+typedef struct metadata_s {
+	char *artist;
+	char *album;
+	char *title;
+	char *genre;
+	char *path;
+	char *artwork;
+	char *remote_title;
+	u32_t track;
+	u32_t duration;
+	u32_t track_hash;
+	u32_t sample_rate;
+	u8_t  sample_size;
+	u8_t  channels;
+} metadata_t;
+
+/*
+void 		free_metadata(struct metadata_s *metadata);
+void 		dup_metadata(struct metadata_s *dst, struct metadata_s *src);
+*/
+
+
u32_t 		gettime_ms(void);
+
+#ifdef WIN32
+char* 		strsep(char** stringp, const char* delim);
+char 		*strndup(const char *s, size_t n);
+int 		asprintf(char **strp, const char *fmt, ...);
+void 		winsock_init(void);
+void 		winsock_close(void);
+
#else
+char 		*strlwr(char *str);
+#endif
+char* 		strextract(char *s1, char *beg, char *end);
+in_addr_t 	get_localhost(char **name);
+void 		get_mac(u8_t mac[]);
+int 		shutdown_socket(int sd);
+int 		bind_socket(short unsigned *port, int mode);
+int 		conn_socket(unsigned short port);

typedef struct {
+
	char *key;
+	char *data;
+} key_data_t;
+
+bool 		http_parse(int sock, char *method, key_data_t *rkd, char **body, int *len);
char*		http_send(int sock, char *method, key_data_t *rkd);
+
+char*		kd_lookup(key_data_t *kd, char *key);
+bool 		kd_add(key_data_t *kd, char *key, char *value);
+char* 		kd_dump(key_data_t *kd);
+void 		kd_free(key_data_t *kd);
+
+int 		_fprintf(FILE *file, ...);
+
#endif
+

+ 1 - 1
components/squeezelite/component.mk

@@ -14,7 +14,7 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON
 	-I$(COMPONENT_PATH)/../codecs/inc/opus 		\
 	-I$(COMPONENT_PATH)/../codecs/inc/opusfile	\
 	-I$(COMPONENT_PATH)/../driver_bt			\
-	-I$(COMPONENT_PATH)/../airplay
+	-I$(COMPONENT_PATH)/../raop
 
 #	-I$(COMPONENT_PATH)/../codecs/inc/faad2
 

+ 80 - 10
components/squeezelite/decode_external.c

@@ -21,7 +21,7 @@
  
 #include "squeezelite.h"
 #include "bt_app_sink.h"
-#include "airplay_sink.h"
+#include "raop_sink.h"
 
 #define LOCK_O   mutex_lock(outputbuf->mutex)
 #define UNLOCK_O mutex_unlock(outputbuf->mutex)
@@ -34,16 +34,19 @@ extern struct buffer *outputbuf;
 // this is the only system-wide loglevel variable
 extern log_level loglevel;
 
+static raop_event_t	raop_state;
+static bool raop_expect_stop = false;
+
 /****************************************************************************************
- * BT sink data handler
+ * Common sink data handler
  */
-static void bt_sink_data_handler(const uint8_t *data, uint32_t len)
+static void sink_data_handler(const uint8_t *data, uint32_t len)
 {
-    size_t bytes;
-	
+    size_t bytes, space;
+		
 	// would be better to lock decoder, but really, it does not matter
 	if (decode.state != DECODE_STOPPED) {
-		LOG_SDEBUG("Cannot use BT sink while LMS is controlling player");
+		LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
 		return;
 	} 
 	
@@ -63,13 +66,15 @@ static void bt_sink_data_handler(const uint8_t *data, uint32_t len)
 		}
 #endif	
 		_buf_inc_writep(outputbuf, bytes);
+		space = _buf_space(outputbuf);
+		
 		len -= bytes;
 		data += bytes;
 				
 		UNLOCK_O;
 		
 		// allow i2s to empty the buffer if needed
-		if (len) usleep(50000);
+		if (len && !space) usleep(50000);
 	}	
 }
 
@@ -81,6 +86,7 @@ static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
 	va_list args;
 	
 	LOCK_D;
+	
 	if (decode.state != DECODE_STOPPED) {
 		LOG_WARN("Cannot use BT sink while LMS is controlling player");
 		UNLOCK_D;
@@ -128,14 +134,78 @@ static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
 
 	va_end(args);
 }
- 
+
+/****************************************************************************************
+ * AirPlay sink command handler
+ */
+void raop_sink_cmd_handler(raop_event_t event, void *param)
+{
+	LOCK_D;
+	
+	if (decode.state != DECODE_STOPPED) {
+		LOG_WARN("Cannot use Airplay sink while LMS is controlling player");
+		UNLOCK_D;
+		return;
+	} 	
+	
+	if (event != RAOP_VOLUME) LOCK_O;
+	
+	// this is async, so player might have been deleted
+	switch (event) {
+		case RAOP_STREAM:
+			// a PLAY will come later, so we'll do the load at that time
+			LOG_INFO("Stream", NULL);
+			raop_state = event;
+			output.external = true;
+			output.current_sample_rate = 44100;
+			output.state = OUTPUT_BUFFER;
+			output.threshold = 5;
+			break;
+		case RAOP_STOP:
+			LOG_INFO("Stop", NULL);
+			output.external = false;
+			output.state = OUTPUT_OFF;
+			raop_state = event;
+			break;
+		case RAOP_FLUSH:
+			LOG_INFO("Flush", NULL);
+			raop_expect_stop = true;
+			raop_state = event;
+			output.state = OUTPUT_STOPPED;
+			break;
+		case RAOP_PLAY: {
+			LOG_INFO("Play", NULL);
+			// this where we need the OUTPUT_START_AT
+			if (raop_state != RAOP_PLAY) {
+				output.external = true;
+				output.state = OUTPUT_RUNNING;
+			}
+			raop_state = event;
+			break;
+		}
+		case RAOP_VOLUME: {
+			float volume = *((float*) param);
+			LOG_INFO("Volume[0..1] %0.4f", volume);
+			volume *= 65536;
+			set_volume((u16_t) volume, (u16_t) volume);
+			break;
+		}
+		default:
+			break;
+	}
+	
+	if (event != RAOP_VOLUME) UNLOCK_O;
+	
+	UNLOCK_D;
+}
+
 /****************************************************************************************
  * We provide the generic codec register option
  */
 void register_other(void) {
 #ifdef CONFIG_BT_SINK	
 	if (!strcasestr(output.device, "BT ")) {
-		bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler);
+		bt_sink_init(bt_sink_cmd_handler, sink_data_handler);
 		LOG_INFO("Initializing BT sink");
 	} else {
 		LOG_WARN("Cannot be a BT sink and source");
@@ -143,7 +213,7 @@ void register_other(void) {
 #endif	
 #ifdef CONFIG_AIRPLAY_SINK
 	if (!strcasestr(output.device, "BT ")) {
-		airplay_sink_init();
+		raop_sink_init(raop_sink_cmd_handler, sink_data_handler);
 		LOG_INFO("Initializing AirPlay sink");		
 	} else {
 		LOG_WARN("Cannot be an AirPlay sink and BT source");