From bb807218467b8a157b8b672e130ae8769a508b02 Mon Sep 17 00:00:00 2001 From: MaZderMind Date: Tue, 2 Apr 2013 22:11:24 +0200 Subject: [PATCH 01/12] add default target w/ name 'all' --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index bb007670..7fe071be 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,9 @@ ifdef CONFIG_AO SRCS += audio_ao.c endif +# default target +all: shairport + shairport: $(SRCS) $(CC) $(CFLAGS) $(SRCS) $(LDFLAGS) -o shairport From 6e04651392a29dafbaf5e730e3925162a420e5f1 Mon Sep 17 00:00:00 2001 From: MaZderMind Date: Tue, 2 Apr 2013 22:11:56 +0200 Subject: [PATCH 02/12] don't fail clean target, when binary does not exist --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7fe071be..0af4d7f4 100644 --- a/Makefile +++ b/Makefile @@ -20,4 +20,4 @@ shairport: $(SRCS) $(CC) $(CFLAGS) $(SRCS) $(LDFLAGS) -o shairport clean: - rm shairport + rm -f shairport From 44a25abaf86893534da8aefd76e03e81dc5ec4dd Mon Sep 17 00:00:00 2001 From: MaZderMind Date: Tue, 2 Apr 2013 22:33:50 +0200 Subject: [PATCH 03/12] change getopt return from char to int --- audio_ao.c | 3 ++- shairport.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/audio_ao.c b/audio_ao.c index 67a51ea1..c1d7a3d0 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -49,7 +49,8 @@ static int init(int argc, char **argv) { optind = 0; // some platforms apparently require optreset = 1; - which? - char opt, *mid; + int opt; + char *mid; while ((opt = getopt(argc, argv, "d:i:n:o:")) > 0) { switch (opt) { case 'd': diff --git a/shairport.c b/shairport.c index b38808e5..1138405b 100644 --- a/shairport.c +++ b/shairport.c @@ -73,7 +73,7 @@ void usage(char *progname) { } int parse_options(int argc, char **argv) { - char opt; + int opt; while ((opt = getopt(argc, argv, "+hvp:a:o:b:")) > 0) { switch (opt) { default: From 68431528b33f2bd0bac85aefe7541c2ffc01c863 Mon Sep 17 00:00:00 2001 From: MaZderMind Date: Tue, 2 Apr 2013 23:11:36 +0200 Subject: [PATCH 04/12] remove perl-script from readme, mention build process and iPhones --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d2224a0a..139ecab7 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,20 @@ By [James Laird](mailto:jhl@mafipulation.org) ([announcement](http://mafipulatio What it is ---------- -This program emulates an AirPort Express for the purpose of streaming music from iTunes and compatible iPods. It implements a server for the Apple RAOP protocol. +This program emulates an AirPort Express for the purpose of streaming music from iTunes and compatible iPods and iPhones. It implements a server for the Apple RAOP protocol. ShairPort does not support AirPlay v2 (video and photo streaming). It supports multiple simultaneous streams, if your audio output chain (as detected by libao) does so. How to use it ------------- -`perl shairport.pl`. See INSTALL.md for further information. +``` +./configure +make clean all +./shairport -a 'AP Name' +``` -The triangle-in-rectangle AirTunes (now AirPlay) logo will appear in the iTunes status bar of any machine on the network, or on iPod play controls screen. Choose your access point name to start streaming to the ShairPort instance. +The triangle-in-rectangle AirTunes (now AirPlay) logo will appear in the iTunes status bar of any machine on the network, or on iPod/iPhone play controls screen. Choose your access point name to start streaming to the ShairPort instance. Thanks ------ From 4daa5a2ecc06e1ef3587fd0f4d43450057c4b348 Mon Sep 17 00:00:00 2001 From: James Laird Date: Wed, 3 Apr 2013 19:45:09 +1100 Subject: [PATCH 05/12] readme: ditch simultaneous streams --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 139ecab7..0066ce1f 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ What it is This program emulates an AirPort Express for the purpose of streaming music from iTunes and compatible iPods and iPhones. It implements a server for the Apple RAOP protocol. ShairPort does not support AirPlay v2 (video and photo streaming). -It supports multiple simultaneous streams, if your audio output chain (as detected by libao) does so. - How to use it ------------- ``` From 89611b826f11fff780f5c83e5d52f80d4ae7e1c7 Mon Sep 17 00:00:00 2001 From: James Laird Date: Wed, 3 Apr 2013 20:27:34 +1100 Subject: [PATCH 06/12] rtsp: add a Session header to keep iTunes happy With thanks to Quentin Smart (sm3rt) for the fix. --- rtsp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rtsp.c b/rtsp.c index 2f6d59d9..1f386073 100644 --- a/rtsp.c +++ b/rtsp.c @@ -361,6 +361,8 @@ static void handle_setup(rtsp_conn_info *conn, sprintf(resphdr + strlen(resphdr), ";server_port=%d", sport); msg_add_header(resp, "Transport", resphdr); + msg_add_header(resp, "Session", "1"); + resp->respcode = 200; } From f01b957fcdbcc7767a56f004494bed35d9d4ab08 Mon Sep 17 00:00:00 2001 From: Quentin Smart Date: Wed, 3 Apr 2013 23:14:31 +1300 Subject: [PATCH 07/12] rtsp: fix endian bug in challenge computation on MIPS platform the IP address was reversed... --- rtsp.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/rtsp.c b/rtsp.c index 1f386073..cc3c2550 100644 --- a/rtsp.c +++ b/rtsp.c @@ -479,11 +479,8 @@ static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) { #endif { struct sockaddr_in *sa = (struct sockaddr_in*)(&fdsa); - unsigned int ip = sa->sin_addr.s_addr; - for (i=0; i<4; i++) { - *bp++ = ip & 0xff; - ip >>= 8; - } + memcpy(bp, &sa->sin_addr.s_addr, 4); + bp += 4; } for (i=0; i<6; i++) From 6090b490ed297fbc0fe60800a50ebe45b8dee144 Mon Sep 17 00:00:00 2001 From: James Laird Date: Wed, 3 Apr 2013 21:38:32 +1100 Subject: [PATCH 08/12] rtp: don't resync on mystery resend-related packet --- rtp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtp.c b/rtp.c index 30803050..e900e853 100644 --- a/rtp.c +++ b/rtp.c @@ -72,10 +72,10 @@ static void *rtp_receiver(void *arg) { continue; } if (type == 0x56 && seqno == 0) { - debug("Suspected resync request packet received.\n"); - player_flush(); + debug("resend-related request packet received, ignoring.\n"); continue; } + debug("Unknown RTP packet of type 0x%02X length %d seqno %d\n", type, nread, seqno); } warn("Unknown RTP packet of type 0x%02X length %d\n", type, nread); } From 0f7b32ab6d4bd3b752ea60125f1b0c11271e02b9 Mon Sep 17 00:00:00 2001 From: James Laird Date: Wed, 3 Apr 2013 21:54:45 +1100 Subject: [PATCH 09/12] debug: multilevel debug functions --- TODO | 8 ++++++-- common.c | 5 ++--- common.h | 2 +- player.c | 8 ++++---- rtp.c | 10 +++++----- rtsp.c | 14 +++++++------- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/TODO b/TODO index 3fa418ae..0c3a58d9 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,8 @@ -- makefile - documentation - PKGBUILD, initscript, systemd, launchd plist -- tidy debug printing +- alsa +- pipe +- avahi +- volume +- supervisor? +- pico mdns? diff --git a/common.c b/common.c index 027610ec..9824e56c 100644 --- a/common.c +++ b/common.c @@ -56,10 +56,9 @@ void warn(char *format, ...) { va_end(args); } -void debug(char *format, ...) { - if (!debuglev) +void debug(int level, char *format, ...) { + if (level > debuglev) return; - fprintf(stderr, "DEBUG: "); va_list args; va_start(args, format); vfprintf(stderr, format, args); diff --git a/common.h b/common.h index 76c91de6..f4ec5fbf 100644 --- a/common.h +++ b/common.h @@ -29,7 +29,7 @@ typedef struct { extern int debuglev; void die(char *format, ...); void warn(char *format, ...); -void debug(char *format, ...); +void debug(int level, char *format, ...); uint8_t *base64_dec(char *input, int *outlen); char *base64_enc(uint8_t *input, int length); diff --git a/player.c b/player.c index 18632768..5b0bd6d9 100644 --- a/player.c +++ b/player.c @@ -218,7 +218,7 @@ void player_put_packet(seq_t seqno, uint8_t *data, int len) { pthread_mutex_lock(&ab_mutex); if (ab_buffering && buf_fill >= config.buffer_start_fill) { - debug("buffering over. starting play\n"); + debug(1, "buffering over. starting play\n"); ab_buffering = 0; pthread_cond_signal(&ab_buffer_ready); } @@ -318,7 +318,7 @@ static void bf_est_update(short fill) { bf_est_drift = biquad_filt(&bf_drift_lpf, CONTROL_B*(adj_error + err_deriv) + bf_est_drift); - debug("bf %d err %f drift %f desiring %f ed %f estd %f\n", + debug(3, "bf %d err %f drift %f desiring %f ed %f estd %f\n", fill, bf_est_err, bf_est_drift, desired_fill, err_deriv, err_deriv + adj_error); bf_playback_rate = 1.0 + adj_error + bf_est_drift; @@ -401,12 +401,12 @@ static int stuff_buffer(double playback_rate, short *inptr, short *outptr) { }; if (stuff) { if (stuff==1) { - debug("+++++++++\n"); + debug(2, "+++++++++\n"); // interpolate one sample *outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1); *outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1); } else if (stuff==-1) { - debug("---------\n"); + debug(2, "---------\n"); inptr++; inptr++; } diff --git a/rtp.c b/rtp.c index e900e853..7dac5b37 100644 --- a/rtp.c +++ b/rtp.c @@ -72,15 +72,15 @@ static void *rtp_receiver(void *arg) { continue; } if (type == 0x56 && seqno == 0) { - debug("resend-related request packet received, ignoring.\n"); + debug(2, "resend-related request packet received, ignoring.\n"); continue; } - debug("Unknown RTP packet of type 0x%02X length %d seqno %d\n", type, nread, seqno); + debug(1, "Unknown RTP packet of type 0x%02X length %d seqno %d\n", type, nread, seqno); } warn("Unknown RTP packet of type 0x%02X length %d\n", type, nread); } - debug("RTP thread interrupted. terminating.\n"); + debug(1, "RTP thread interrupted. terminating.\n"); close(sock); return NULL; @@ -128,7 +128,7 @@ int rtp_setup(SOCKADDR *remote, int cport, int tport) { if (running) die("rtp_setup called with active stream!\n"); - debug("rtp_setup: cport=%d tport=%d\n", cport, tport); + debug(1, "rtp_setup: cport=%d tport=%d\n", cport, tport); // we do our own timing and ignore the timing port. // an audio perfectionist may wish to learn the protocol. @@ -147,7 +147,7 @@ int rtp_setup(SOCKADDR *remote, int cport, int tport) { int sport = bind_port(remote); - debug("rtp listening on port %d\n", sport); + debug(1, "rtp listening on port %d\n", sport); please_shutdown = 0; pthread_create(&rtp_thread, NULL, &rtp_receiver, NULL); diff --git a/rtsp.c b/rtsp.c index cc3c2550..06e87bba 100644 --- a/rtsp.c +++ b/rtsp.c @@ -61,7 +61,7 @@ static inline int rtsp_playing(void) { static void rtsp_take_player(void) { if (pthread_mutex_trylock(&playing_mutex)) { - debug("shutting down playing thread\n"); + debug(1, "shutting down playing thread\n"); // XXX minor race condition between please_shutdown and signal delivery please_shutdown = 1; pthread_kill(playing_thread, SIGUSR1); @@ -161,7 +161,7 @@ static int msg_handle_line(rtsp_message **pmsg, char *line) { *pmsg = msg; char *sp, *p; - debug("received request: %s\n", line); + debug(1, "received request: %s\n", line); p = strtok_r(line, " ", &sp); if (!p) @@ -191,7 +191,7 @@ static int msg_handle_line(rtsp_message **pmsg, char *line) { *p = 0; p += 2; msg_add_header(msg, line, p); - debug(" %s: %s\n", line, p); + debug(2, " %s: %s\n", line, p); return -1; } else { char *cl = msg_get_header(msg, "Content-Length"); @@ -219,12 +219,12 @@ static rtsp_message * rtsp_read_request(int fd) { while (msg_size < 0) { if (please_shutdown) { - debug("RTSP shutdown requested\n"); + debug(1, "RTSP shutdown requested\n"); goto shutdown; } nread = read(fd, buf+inbuf, buflen - inbuf); if (!nread) { - debug("RTSP connection closed\n"); + debug(1, "RTSP connection closed\n"); goto shutdown; } if (nread < 0) { @@ -291,10 +291,10 @@ static void msg_write_response(int fd, rtsp_message *resp) { "RTSP/1.0 %d %s\r\n", resp->respcode, resp->respcode==200 ? "OK" : "Error"); write(fd, rbuf, nrbuf); - debug("sending response: %s", rbuf); + debug(1, "sending response: %s", rbuf); int i; for (i=0; inheaders; i++) { - debug(" %s: %s\n", resp->name[i], resp->value[i]); + debug(2, " %s: %s\n", resp->name[i], resp->value[i]); write(fd, resp->name[i], strlen(resp->name[i])); write(fd, ": ", 2); write(fd, resp->value[i], strlen(resp->value[i])); From 7a2434aef7455972aabf83dba8303b529e839cf0 Mon Sep 17 00:00:00 2001 From: James Laird Date: Wed, 3 Apr 2013 21:54:58 +1100 Subject: [PATCH 10/12] player: talk about established buffer fill preference --- player.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/player.c b/player.c index 5b0bd6d9..b22c458b 100644 --- a/player.c +++ b/player.c @@ -302,10 +302,20 @@ static void bf_est_reset(short fill) { } static void bf_est_update(short fill) { + // the rate-matching system needs to decide how full to keep the buffer. + // the initial fill is present when the system starts to output samples, + // but most output chains will instantly gobble their own buffer's worth of + // data. we average for a while to decide where to draw the line. if (fill_count < 1000) { desired_fill += (double)fill/1000.0; fill_count++; return; + } else if (fill_count == 1000) { + // this information could be used to help estimate our effective latency? + debug(1, "established desired fill of %f frames, " + "so output chain buffered about %f frames\n", desired_fill, + config.buffer_start_fill - desired_fill); + fill_count++; } #define CONTROL_A (1e-4) From d3e89828c5a598f68c9fbdc403f955a661438a85 Mon Sep 17 00:00:00 2001 From: James Laird Date: Wed, 3 Apr 2013 23:34:58 +1100 Subject: [PATCH 11/12] rtsp: handle volume messages --- rtsp.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 06e87bba..63a8eb94 100644 --- a/rtsp.c +++ b/rtsp.c @@ -371,6 +371,32 @@ static void handle_ignore(rtsp_conn_info *conn, resp->respcode = 200; } +static void handle_set_parameter(rtsp_conn_info *conn, + rtsp_message *req, rtsp_message *resp) { + if (!req->contentlength) + return; + + char *cp = req->content; + int cp_left = req->contentlength; + char *next; + while (cp_left && cp) { + next = nextline(cp, cp_left); + cp_left -= next-cp; + + if (!strncmp(cp, "volume: ", 8)) { + float volume = atof(cp + 8); + debug(1, "volume: %f\n", volume); + player_volume(volume); + } else { + debug(1, "unrecognised parameter: >>%s<< (%d)\n", cp, strlen(cp)); + } + cp = next; + } + + + resp->respcode = 200; +} + static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { @@ -441,7 +467,7 @@ static struct method_handler { {"TEARDOWN", handle_teardown}, {"SETUP", handle_setup}, {"GET_PARAMETER", handle_ignore}, - {"SET_PARAMETER", handle_ignore}, // XXX + {"SET_PARAMETER", handle_set_parameter}, // XXX {"RECORD", handle_ignore}, {NULL, NULL} }; From 171e5d42ef4494bd52d3b5b702156b3f259721e2 Mon Sep 17 00:00:00 2001 From: James Laird Date: Wed, 3 Apr 2013 23:35:14 +1100 Subject: [PATCH 12/12] rtsp: safer buffer handling --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 63a8eb94..5b7ba6d1 100644 --- a/rtsp.c +++ b/rtsp.c @@ -406,7 +406,7 @@ static void handle_announce(rtsp_conn_info *conn, char *cp = req->content; int cp_left = req->contentlength; char *next; - while (cp) { + while (cp_left && cp) { next = nextline(cp, cp_left); cp_left -= next-cp;