From 5a7b7c011b866a88bde868add86d01f4d231d678 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 18 Mar 2019 11:21:27 +0000 Subject: [PATCH 1/2] Add the ability to receive uncompressed 16 bit big-endian (?) / 44,100 fps / interleaved stereo PCM. --- player.c | 109 ++++++++++++++++----------------- player.h | 9 ++- rtsp.c | 181 +++++++++++++++++++++++++++++++++++-------------------- 3 files changed, 180 insertions(+), 119 deletions(-) diff --git a/player.c b/player.c index 485166ee..63316684 100644 --- a/player.c +++ b/player.c @@ -215,7 +215,46 @@ void reset_input_flow_metrics(rtsp_conn_info *conn) { conn->initial_reference_timestamp = 0; } -static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_conn_info *conn) { +void unencrypted_packet_decode(unsigned char *packet, int length, short *dest, int *outsize, + int size_limit, rtsp_conn_info *conn) { + if (conn->stream.type == ast_apple_lossless) { +#ifdef CONFIG_APPLE_ALAC + if (config.use_apple_decoder) { + if (conn->decoder_in_use != 1 << decoder_apple_alac) { + debug(2, "Apple ALAC Decoder used on encrypted audio."); + conn->decoder_in_use = 1 << decoder_apple_alac; + } + apple_alac_decode_frame(packet, length, (unsigned char *)dest, outsize); + *outsize = *outsize * 4; // bring the size to bytes + } else +#endif + { + if (conn->decoder_in_use != 1 << decoder_hammerton) { + debug(2, "Hammerton Decoder used on encrypted audio."); + conn->decoder_in_use = 1 << decoder_hammerton; + } + alac_decode_frame(conn->decoder_info, packet, (unsigned char *)dest, outsize); + } + } else if (conn->stream.type == ast_uncompressed) { + int length_to_use = length; + if (length_to_use > size_limit) { + warn("unencrypted_packet_decode: uncompressed audio packet too long (size: %d bytes) to " + "process -- truncated", + length); + length_to_use = size_limit; + } + int i; + short *source = (short *)packet; + for (i = 0; i < (length_to_use / 2); i++) { + *dest = ntohs(*source); + dest++; + source++; + } + *outsize = length_to_use; + } +} + +int audio_packet_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_conn_info *conn) { // parameters: where the decoded stuff goes, its length in samples, // the incoming packet, the length of the incoming packet in bytes // destlen should contain the allowed max number of samples on entry @@ -230,7 +269,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co assert(len <= MAX_PACKET); int reply = 0; // everything okay int outsize = conn->input_bytes_per_frame * (*destlen); // the size the output should be, in bytes - int toutsize = outsize; + int maximum_possible_outsize = outsize; if (conn->stream.encrypted) { unsigned char iv[16]; @@ -246,48 +285,16 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co AES_cbc_encrypt(buf, packet, aeslen, &conn->aes, iv, AES_DECRYPT); #endif memcpy(packet + aeslen, buf + aeslen, len - aeslen); -#ifdef CONFIG_APPLE_ALAC - if (config.use_apple_decoder) { - if (conn->decoder_in_use != 1 << decoder_apple_alac) { - debug(2, "Apple ALAC Decoder used on encrypted audio."); - conn->decoder_in_use = 1 << decoder_apple_alac; - } - apple_alac_decode_frame(packet, len, (unsigned char *)dest, &outsize); - outsize = outsize * 4; // bring the size to bytes - } else -#endif - { - if (conn->decoder_in_use != 1 << decoder_hammerton) { - debug(2, "Hammerton Decoder used on encrypted audio."); - conn->decoder_in_use = 1 << decoder_hammerton; - } - alac_decode_frame(conn->decoder_info, packet, (unsigned char *)dest, &outsize); - } + unencrypted_packet_decode(packet, len, dest, &outsize, maximum_possible_outsize, conn); } else { -// not encrypted -#ifdef CONFIG_APPLE_ALAC - if (config.use_apple_decoder) { - if (conn->decoder_in_use != 1 << decoder_apple_alac) { - debug(2, "Apple ALAC Decoder used on unencrypted audio."); - conn->decoder_in_use = 1 << decoder_apple_alac; - } - apple_alac_decode_frame(buf, len, (unsigned char *)dest, &outsize); - outsize = outsize * 4; // bring the size to bytes - } else -#endif - { - if (conn->decoder_in_use != 1 << decoder_hammerton) { - debug(2, "Hammerton Decoder used on unencrypted audio."); - conn->decoder_in_use = 1 << decoder_hammerton; - } - alac_decode_frame(conn->decoder_info, buf, dest, &outsize); - } + // not encrypted + unencrypted_packet_decode(buf, len, dest, &outsize, maximum_possible_outsize, conn); } - if (outsize > toutsize) { + if (outsize > maximum_possible_outsize) { debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " "truncated, but buffer overflow possible! Encrypted = %d.", - outsize, toutsize, conn->stream.encrypted); + outsize, maximum_possible_outsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } @@ -299,7 +306,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co return reply; } -static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { +static int init_alac_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { // This is a guess, but the format of the fmtp looks identical to the format of an // ALACSpecificCOnfig @@ -380,14 +387,6 @@ static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) { alac_file *alac; - conn->max_frames_per_packet = fmtp[1]; // number of audio frames per packet. - - conn->input_rate = fmtp[11]; - conn->input_num_channels = fmtp[7]; - conn->input_bit_depth = fmtp[3]; - - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - alac = alac_create(conn->input_bit_depth, conn->input_num_channels); // no pthread cancellation point in here if (!alac) @@ -555,7 +554,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in if (abuf) { int datalen = conn->max_frames_per_packet; - if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) { + if (audio_packet_decode(abuf->data, &datalen, data, len, conn) == 0) { abuf->ready = 1; abuf->length = datalen; // abuf->timestamp = ltimestamp; @@ -1485,7 +1484,8 @@ void player_thread_cleanup_handler(void *arg) { conn->tbuf = NULL; } free_audio_buffers(conn); - terminate_decoders(conn); + if (conn->stream.type == ast_apple_lossless) + terminate_decoders(conn); clear_reference_timestamp(conn); conn->rtp_running = 0; @@ -1512,10 +1512,11 @@ void *player_thread_func(void *arg) { conn->latency = 88200; } - init_decoder((int32_t *)&conn->stream.fmtp, - conn); // this sets up incoming rate, bit depth, channels. - // No pthread cancellation point in here - // This must be after init_decoder + if (conn->stream.type == ast_apple_lossless) + init_alac_decoder((int32_t *)&conn->stream.fmtp, + conn); // this sets up incoming rate, bit depth, channels. + // No pthread cancellation point in here + // This must be after init_alac_decoder init_buffer(conn); // will need a corresponding deallocation. No cancellation points in here if (conn->stream.encrypted) { diff --git a/player.h b/player.h index 28b0341b..daff6236 100644 --- a/player.h +++ b/player.h @@ -48,7 +48,7 @@ typedef struct audio_buffer_entry { // decoded audio packets } abuf_t; // default buffer size -// This eeds to be a power of 2 because of the way BUFIDX(seqno) works. +// This needs to be a power of 2 because of the way BUFIDX(seqno) works. // 512 is the minimum for normal operation -- it gives 512*352/44100 or just over 4 seconds of // buffers. // For at least 10 seconds, you need to go to 2048. @@ -61,10 +61,17 @@ typedef struct audio_buffer_entry { // decoded audio packets #define BUFFER_FRAMES 1024 +enum audio_stream_type { + ast_unknown, + ast_uncompressed, // L16/44100/2 + ast_apple_lossless, +} ast_type; + typedef struct { int encrypted; uint8_t aesiv[16], aeskey[16]; int32_t fmtp[12]; + enum audio_stream_type type; } stream_cfg; typedef struct { diff --git a/rtsp.c b/rtsp.c index 8de83de3..8962d344 100644 --- a/rtsp.c +++ b/rtsp.c @@ -51,8 +51,8 @@ #endif #ifdef CONFIG_MBEDTLS -#include #include +#include #endif #ifdef CONFIG_POLARSSL @@ -292,9 +292,8 @@ void *player_watchdog_thread_code(void *arg) { conn->watchdog_barks++; if (conn->watchdog_barks == 1) { // debuglev = 3; // tell us everything. - debug(1, - "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " - "of the heart\".", + debug(1, "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " + "of the heart\".", conn->connection_number); conn->stop = 1; pthread_cancel(conn->thread); @@ -580,12 +579,12 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; - int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay + int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay char *buf = malloc(buflen + 1); // add a NUL at the end if (!buf) { warn("rtsp_read_request: can't get a buffer."); - return(rtsp_read_request_response_error); - } + return (rtsp_read_request_response_error); + } pthread_cleanup_push(malloc_cleanup, buf); ssize_t nread; ssize_t inbuf = 0; @@ -619,6 +618,10 @@ do { if (nread < 0) { if (errno == EINTR) continue; + if (errno == EAGAIN) { + debug(1, "Getting Error 11 -- EAGAIN from a blocking read!"); + continue; + } if (errno != ECONNRESET) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); @@ -787,7 +790,8 @@ int msg_write_response(int fd, rtsp_message *resp) { return -4; } if (reply != p - pkt) { - debug(1, "msg_write_response error -- requested bytes: %d not fully written: %d.",p - pkt, reply); + debug(1, "msg_write_response error -- requested bytes: %d not fully written: %d.", p - pkt, + reply); return -5; } return 0; @@ -842,10 +846,9 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message * rtsp_message *resp) { debug(3, "Connection %d: OPTIONS", conn->connection_number); resp->respcode = 200; - msg_add_header(resp, "Public", - "ANNOUNCE, SETUP, RECORD, " - "PAUSE, FLUSH, TEARDOWN, " - "OPTIONS, GET_PARAMETER, SET_PARAMETER"); + msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " + "PAUSE, FLUSH, TEARDOWN, " + "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, @@ -986,10 +989,10 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { msg_add_header(resp, "Session", "1"); resp->respcode = 200; // it all worked out okay - debug(1, "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: %d, Timing: %d and Audio: %d.", - conn->connection_number, - conn->dacp_id, &conn->client_ip_string, &conn->self_ip_string, - conn->local_control_port, conn->local_timing_port, + debug(1, "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: " + "%d, Timing: %d and Audio: %d.", + conn->connection_number, conn->dacp_id, &conn->client_ip_string, + &conn->self_ip_string, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); } else { @@ -1582,7 +1585,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m char *ct = msg_get_header(req, "Content-Type"); if (ct) { - // debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); +// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); #ifdef CONFIG_METADATA // It seems that the rtptime of the message is used as a kind of an ID that @@ -1741,6 +1744,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag } */ + conn->stream.type = ast_unknown; resp->respcode = 456; // 456 - Header Field Not Valid for Resource char *pssid = NULL; char *paesiv = NULL; @@ -1748,6 +1752,8 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag char *pfmtp = NULL; char *pminlatency = NULL; char *pmaxlatency = NULL; + // char *pAudioMediaInfo = NULL; + char *pUncompressedCDAudio = NULL; char *cp = req->content; int cp_left = req->contentlength; char *next; @@ -1755,27 +1761,62 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag next = nextline(cp, cp_left); cp_left -= next - cp; - if (!strncmp(cp, "o=iTunes", 7)) - pssid = cp + 8; + if (!strncmp(cp, "a=rtpmap:96 L16/44100/2", strlen("a=rtpmap:96 L16/44100/2"))) + pUncompressedCDAudio = cp + strlen("a=rtpmap:96 L16/44100/2"); - if (!strncmp(cp, "a=fmtp:", 7)) - pfmtp = cp + 7; + // if (!strncmp(cp, "m=audio", strlen("m=audio"))) + // pAudioMediaInfo = cp + strlen("m=audio"); - if (!strncmp(cp, "a=aesiv:", 8)) - paesiv = cp + 8; + if (!strncmp(cp, "o=iTunes", strlen("o=iTunes"))) + pssid = cp + strlen("o=iTunes"); - if (!strncmp(cp, "a=rsaaeskey:", 12)) - prsaaeskey = cp + 12; + if (!strncmp(cp, "a=fmtp:", strlen("a=fmtp:"))) + pfmtp = cp + strlen("a=fmtp:"); - if (!strncmp(cp, "a=min-latency:", 14)) - pminlatency = cp + 14; + if (!strncmp(cp, "a=aesiv:", strlen("a=aesiv:"))) + paesiv = cp + strlen("a=aesiv:"); - if (!strncmp(cp, "a=max-latency:", 14)) - pmaxlatency = cp + 14; + if (!strncmp(cp, "a=rsaaeskey:", strlen("a=rsaaeskey:"))) + prsaaeskey = cp + strlen("a=rsaaeskey:"); + + if (!strncmp(cp, "a=min-latency:", strlen("a=min-latency:"))) + pminlatency = cp + strlen("a=min-latency:"); + + if (!strncmp(cp, "a=max-latency:", strlen("a=max-latency:"))) + pmaxlatency = cp + strlen("a=max-latency:"); cp = next; } + if (pUncompressedCDAudio) { + debug(1, "pUncompressedCDAudio detected."); + conn->stream.type = ast_uncompressed; + conn->max_frames_per_packet = 352; // number of audio frames per packet. + conn->input_rate = 44100; + conn->input_num_channels = 2; + conn->input_bit_depth = 16; + conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); + + /* + int y = strlen(pAudioMediaInfo); + if (y > 0) { + char obf[4096]; + if (y > 4096) + y = 4096; + char *p = pAudioMediaInfo; + char *obfp = obf; + int obfc; + for (obfc = 0; obfc < y; obfc++) { + snprintf(obfp, 3, "%02X", (unsigned int)*p); + p++; + obfp += 2; + }; + *obfp = 0; + debug(1, "AudioMediaInfo: \"%s\".", obf); + } + */ + } + if (pssid) { uint32_t ssid = uatoi(pssid); debug(3, "Synchronisation Source Identifier: %08X,%u", ssid, ssid); @@ -1799,22 +1840,6 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag // debug(1,"Encrypted session requested"); } - if (!pfmtp) { - warn("FMTP params missing from the following ANNOUNCE message:"); - // print each line of the request content - // the problem is that nextline has replace all returns, newlines, etc. by - // NULLs - char *cp = req->content; - int cp_left = req->contentlength; - while (cp_left > 1) { - if (strlen(cp) != 0) - warn(" %s", cp); - cp += strlen(cp) + 1; - cp_left -= strlen(cp) + 1; - } - goto out; - } - if (conn->stream.encrypted) { int len, keylen; uint8_t *aesiv = base64_dec(paesiv, &len); @@ -1837,12 +1862,41 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag memcpy(conn->stream.aeskey, aeskey, 16); free(aeskey); } - unsigned int i; - for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) - conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); - // here we should check the sanity ot the fmtp values - // for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) - // debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]); + + if (pfmtp) { + conn->stream.type = ast_apple_lossless; + debug(1, "pAppleLossless detected."); + unsigned int i; + for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) + conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); + // here we should check the sanity ot the fmtp values + // for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) + // debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]); + + // set the parameters of the player (as distinct from the parameters of the decoder -- that's + // done later). + conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. + conn->input_rate = conn->stream.fmtp[11]; + conn->input_num_channels = conn->stream.fmtp[7]; + conn->input_bit_depth = conn->stream.fmtp[3]; + conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); + } + + if (conn->stream.type == ast_unknown) { + warn("Can not process the following ANNOUNCE message:"); + // print each line of the request content + // the problem is that nextline has replace all returns, newlines, etc. by + // NULLs + char *cp = req->content; + int cp_left = req->contentlength; + while (cp_left > 1) { + if (strlen(cp) != 0) + warn(" %s", cp); + cp += strlen(cp) + 1; + cp_left -= strlen(cp) + 1; + } + goto out; + } char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { @@ -2038,7 +2092,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #endif #ifdef CONFIG_MBEDTLS - #if MBEDTLS_VERSION_MINOR >= 7 +#if MBEDTLS_VERSION_MINOR >= 7 mbedtls_md5_context tctx; mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, (const unsigned char *)username, strlen(username)); @@ -2052,7 +2106,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri)); mbedtls_md5_finish_ret(&tctx, digest_mu); - #else +#else mbedtls_md5_context tctx; mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); @@ -2066,7 +2120,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { mbedtls_md5_update(&tctx, (unsigned char *)":", 1); mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); mbedtls_md5_finish(&tctx, digest_mu); - #endif +#endif #endif #ifdef CONFIG_POLARSSL @@ -2105,7 +2159,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #endif #ifdef CONFIG_MBEDTLS - #if MBEDTLS_VERSION_MINOR >= 7 +#if MBEDTLS_VERSION_MINOR >= 7 mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, buf, 32); mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); @@ -2115,7 +2169,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); mbedtls_md5_update_ret(&tctx, buf, 32); mbedtls_md5_finish_ret(&tctx, digest_total); - #else +#else mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, buf, 32); mbedtls_md5_update(&tctx, (unsigned char *)":", 1); @@ -2125,7 +2179,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); mbedtls_md5_update(&tctx, buf, 32); mbedtls_md5_finish(&tctx, digest_total); - #endif +#endif #endif #ifdef CONFIG_POLARSSL @@ -2194,7 +2248,8 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { // remove flow control and mutexes int rc = pthread_mutex_destroy(&conn->volume_control_mutex); if (rc) - debug(1, "Connection %d: error %d destroying volume_control_mutex.", conn->connection_number, rc); + debug(1, "Connection %d: error %d destroying volume_control_mutex.", conn->connection_number, + rc); rc = pthread_cond_destroy(&conn->flowcontrol); if (rc) debug(1, "Connection %d: error %d destroying flow control condition variable.", @@ -2285,9 +2340,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (strcmp(req->method, "OPTIONS") != 0) // the options message is very common, so don't log it until level 3 debug_level = 2; - debug(debug_level, - "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number, - req->method), + debug(debug_level, "Connection %d: Received an RTSP Packet of type \"%s\":", + conn->connection_number, req->method), debug_print_msg_headers(debug_level, req); apple_challenge(conn->fd, req, resp); @@ -2346,9 +2400,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { - debug(1, - "Connection %d: Unable to write an RTSP message response. Terminating the " - "connection.", + debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the " + "connection.", conn->connection_number); struct linger so_linger; so_linger.l_onoff = 1; // "true" From f76f2aa37000150c2d2beb6f20bdcddd8aea8522 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 18 Mar 2019 11:22:21 +0000 Subject: [PATCH 2/2] Add the ability to receive uncompressed 16 bit big-endian (?) / 44,100 fps / interleaved stereo PCM. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4bdd21d1..ccb7f0f9 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3rc1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3rc1pcm0], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h])