diff --git a/Makefile b/Makefile index bb007670..0af4d7f4 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,11 @@ ifdef CONFIG_AO SRCS += audio_ao.c endif +# default target +all: shairport + shairport: $(SRCS) $(CC) $(CFLAGS) $(SRCS) $(LDFLAGS) -o shairport clean: - rm shairport + rm -f shairport diff --git a/README.md b/README.md index d2224a0a..0066ce1f 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,18 @@ 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 ------ 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/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/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..b22c458b 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); } @@ -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) @@ -318,7 +328,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 +411,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 e1b96d7d..978b96b9 100644 --- a/rtp.c +++ b/rtp.c @@ -74,15 +74,15 @@ static void *rtp_receiver(void *arg) { continue; } if (type == 0x56 && seqno == 0) { - debug("Suspected resync request packet received.\n"); - player_flush(); + debug(2, "resend-related request packet received, ignoring.\n"); continue; } + 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; @@ -130,7 +130,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. @@ -149,7 +149,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 421dd2c0..4e0880a3 100644 --- a/rtsp.c +++ b/rtsp.c @@ -68,7 +68,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); @@ -168,7 +168,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) @@ -198,7 +198,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"); @@ -226,12 +226,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) { @@ -298,10 +298,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])); @@ -368,6 +368,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; } @@ -376,6 +378,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) { @@ -385,7 +413,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; @@ -446,7 +474,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} }; @@ -484,11 +512,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++) 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: