diff --git a/README.md b/README.md index c3e40785..e5c3a16d 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ Shairport Sync works by using timing information and timestamps present in data To maintain the exact latency required, if an output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”. -There are four default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and another matches the latency used by so-called "AirPlay" devices — iOS devices and iTunes and Quicktime Player when they are playing video. A third latency is used when the audio source is `forked-daapd`. The fourth latency is the default if no other latency is chosen and is used for older versions of iTunes. +The exact latency to be used is specified by the source when it negotiates with Shaiport Sync. Most sources set a latency of 88,200 frames -- exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of 99,577 frames (pretty close to the 99,400 frames estimated from listening tests!). Shairport Sync is a pretty substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. -Shairport Sync is designed for Linux and ALSA. It must have direct access to the output device, which must be a real sound card capable of working with 44,100 samples per second interleaved PCM stereo (you'll get a message in the logfile if there's a problem). +Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100 samples per second interleaved PCM stereo (you'll get a message in the logfile if there's a problem). For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. @@ -27,13 +27,13 @@ What else? * Hardware Mute — Shairport Sync will mute properly if the hardware supports it. * Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds. * Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) -* Metadata — Shairport Sync can be configured to deliver metadata, such as Album Name, Artist Name, Cover Art, etc. through a pipe to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. +* Metadata — Shairport Sync can be configured to deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes among others but the Music app in iOS 9 only supplies volume control metadata. * Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync. * Autotools and Libtool Support — One important difference between Shairport Sync and other versions of Shairport is that the Shairport Sync build process uses GNU autotools and libtool to examine and configure the build environment — very important for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available. Status ------ -Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. It will not work properly – if at all – with a PulseAudio (pseudo-)output device. +Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Wheezy and Jessie, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. It will not work properly – if at all – with a PulseAudio (pseudo-)output device. Using a port of the `alsa` system, Shairport Sync runs rather well on FreeBSD. Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound card, though the audio out of the card is of poor quality. USB-connected sound cards work well on recent versions of Raspbian; however older versions of Raspbian appear to suffer from a problem — see http://www.raspberrypi.org/forums/viewtopic.php?t=23544, so it is wise to update. Shairport Sync works well with the IQAudIO Pi-DAC — see http://www.iqaudio.com. @@ -41,7 +41,7 @@ At the time of writing, OpenWrt trunk does not support USB audio well on the Ras Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux and Fedora inside VMWare Fusion on a Mac, but synchronisation in inaccurate — possibly because the soundcard is being emulated. -Shairport Sync will output to alsa cards, to standard output and to pipes using appropriate backends. You can try compiling additional backends in as you wish, but it definitely will not work properly with them. Maybe someday... +Shairport Sync will output to `alsa` cards, to standard output and to pipes using appropriate backends. You can try compiling additional backends in as you wish, but it definitely will not work properly with them. Maybe someday... For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution. @@ -52,7 +52,7 @@ Building And Installing If you wish to install Shairport Sync on OpenWrt, Arch or Fedora platforms, please follow the appropriate instructions below. Otherwise follow the General Build Instructions. Then, when the progam has been installed, refer to the section on Configuring Shairport Sync that follows. **OpenWrt:** -If you're interested in Shairport Sync for OpenWrt, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt. OpenWrt doesn't support the IQaudIO Pi-DAC. +Thes is a Shairport Sync package in OpenWrt `trunk`. Also, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt, including one that builds back to `Attitude Adjustment`. **Arch Linux:** An Arch Linux installation package is available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD). @@ -156,7 +156,7 @@ use a specific sound card and mixer control, etc. — there are some examples in *Man Page* -You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/master/man/shairport-sync.html +You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html Configuring Shairport Sync @@ -310,15 +310,11 @@ alsa = { Latency ------- -Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. From listening tests, it seems that there are three latencies in current use: -* If the source is iTunes 10 or later, a latency of 99,400 frames seems to bring Shairport Sync into exact synchronisation both with the speakers on the iTunes computer itself and with AirPort Express receivers. -* If the source is an AirPlay device, the latency seems to be exactly 88,200 frames. AirPlay devices include AppleTV, iPod, iPad and iPhone and Quicktime Player on Mac. -* If the source is a `forked-daapd`-powered device, the latency seems to be exactly 99,400 frames. -* If the source cannot be identified as AirPlay or as iTunes 10 or later, then the default latency of 88,200 frames seems to work in general. Note that some third party programs masquerade as older versions of iTunes. +Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. -Shairport Sync uses the latencies described above as defaults. You shouldn't need to change them. +Shairport Sync uses latencies supplied by the source, typically either 88,200 or 99,577 frames. You shouldn't need to change them. (The `latencies` stanza in the configuration file and the various latency command-line options are deprecated.) -Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the `alsa` group `audio_backend_latency_offset`. Set this offset (in frames) to compensate for a fixed delay in the audio back end, for example, if the output device delays by 100 ms, set this to -4410. +Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset`. Set this offset (in frames) to compensate for a fixed delay in the audio back end, for example, if the output device delays by 100 ms, set this to -4410. Resynchronisation ------------- @@ -328,7 +324,7 @@ If synchronisation is lost — say due to a busy source or a congested network Tolerance --------- -Playback synchronisation is allowed to wander a small amount before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the statistics setting to monitor correction levels. Corrections should not greatly exceed net corrections. +Playback synchronisation is allowed to wander, or to "drift") a small amount before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the statistics setting to monitor correction levels. Corrections should not greatly exceed net corrections. * You can vary the tolerance with the `general` `drift` setting. Some Statistics @@ -337,7 +333,7 @@ If you turn on the `general` `statistics` setting, statistics like this will be `Sync error: -35.4 (frames); net correction: 24.2 (ppm); corrections: 24.2 (ppm); missing packets 0; late packets 5; too late packets 0; resend requests 6; min DAC queue size 4430.` -"Sync error" is the average deviation from exact synchronisation. The example above indicates that the output is on average 35.4 frames ahead of exact synchronisation. Sync is allowed to wander by the tolerance — 88 frames (± 2 milliseconds) by default — before a correction will be made. +"Sync error" is the average deviation from exact synchronisation. The example above indicates that the output is on average 35.4 frames ahead of exact synchronisation. Sync is allowed to drift by the `general` `drift` setting — 88 frames (± 2 milliseconds) by default — before a correction will be made. "Net correction" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The example above indicates that the output device is consuming frames 24.2 ppm faster than the source is generating them. @@ -345,6 +341,6 @@ If you turn on the `general` `statistics` setting, statistics like this will be For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from a second-generation iPod driving the Raspberry Pi which is connected over Ethernet. -It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Sometimes late packets can be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... +It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... "Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 samples, and will go low if the processor is very busy. If it goes below about 2,000 then it's a sign that the processor can't really keep up. diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cb653812..a45b9d56 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,19 @@ -HEAD +Version 2.5.0.7 ---- +**New Feature** +* Source-specified Latencies. The AirPlay protocol used by Shairport Sync allows the audio source to specify the exact delay or latency that should be applied to the audio stream. Until now, Shairport Sync ignored this information and used fixed preset latencies that were selected on the basis of the "User-Agent" setting. Using source-specified latencies means that Shairport Sync should be able adapt automatically to a wider range of sources. +Using source-specified latencies is now automatic unless non-standard static latencies have been specified in the configuration file or command line. Using non-standard latencies is usually done to compensate for delays in the back end of the system. For example, if the audio amplifier being driven by Shairport Sync has an inherent delay of its own -- as happens with many home theatre and surround sound systems -- then some users have reduced the latencies used by Shairport Sync to compensate. This usage is discouraged -- the `audio_backend_latency_offset` in the appropriate backend stanza (e.g. in the "alsa" stanza) should be used for this. Static latency settings are now deprecated, and will be removed in a future version of Shairport Sync. **Bug fixes** * Fix in the `shairport.c`: the USE_CUSTOM_LOCAL_STATE_DIR macro was still being used when it should have been USE_CUSTOM_PID_DIR. +* Fix a crashing bug -- if metadata was enabled but a pipename was not supplied, boom. + +**Other Changes** +* Volume ratios expressed in decibels are now consistently denominated in voltage decibels rather than power decibels. The rationale is that the levels refer to voltage levels, and power is proportional to the square of voltage. +Thus a ratio of levels of 65535 to 1 is 96.3 dB rather than the 48.15 dB used before. +* The latency figure returned to the source as part of the response to an rtsp request packet is 11,025, which may (?) be meant to indicate the minimum latency the device is capable of. +* An experimental handler for a GET_PARAMETER rtsp request has been added. It does nothing except log the occurence. +* The RTSP request dispatcher now logs an event whenever an unrecognised rtsp has been made. Version 2.5.0.2 ---- diff --git a/audio_alsa.c b/audio_alsa.c index d4e54973..e050e88a 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include "common.h" @@ -43,9 +44,9 @@ static void stop(void); static void flush(void); static uint32_t delay(void); static void volume(double vol); +static void linear_volume(double vol); static void parameters(audio_parameters *info); static int has_mute = 0; -static int has_db_vol = 0; static double set_volume; audio_output audio_alsa = { @@ -183,6 +184,8 @@ static int init(int argc, char **argv) { if (optind < argc) die("Invalid audio argument: %s", argv[optind]); + + debug(1,"Output device name is \"%s\".",alsa_out_dev); if (!hardware_mixer) return 0; @@ -191,13 +194,14 @@ static int init(int argc, char **argv) { alsa_mix_dev = alsa_out_dev; int ret = 0; - + snd_mixer_selem_id_alloca(&alsa_mix_sid); snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index); snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl); if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) die("Failed to open mixer"); + debug(1,"Mixer device name is \"%s\".",alsa_mix_dev); if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) die("Failed to attach mixer"); if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) @@ -206,19 +210,24 @@ static int init(int argc, char **argv) { ret = snd_mixer_load(alsa_mix_handle); if (ret < 0) die("Failed to load mixer element"); - alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); + + debug(1,"Mixer Control name is \"%s\".",alsa_mix_ctrl); + + alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); if (!alsa_mix_elem) die("Failed to find mixer element"); + + if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < 0) debug(1, "Can't read mixer's [linear] min and max volumes."); else { - if ((snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv, &alsa_mix_mindb) == 0) && - (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_maxv, &alsa_mix_maxdb) == 0)) { - has_db_vol = 1; + if (snd_mixer_selem_get_playback_dB_range (alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { + audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff audio_alsa.parameters = ¶meters; // likewise the parameters stuff - if (alsa_mix_mindb == -9999999) { - // trying to say that the lowest vol is mute, maybe? Raspberry Pi does this + if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) { + // Raspberry Pi does this + debug(1, "Lowest dB value is a mute."); if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1, &alsa_mix_mindb) == 0) debug(1, "Can't get dB value corresponding to a \"volume\" of 1."); @@ -226,8 +235,20 @@ static int init(int argc, char **argv) { debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); } else { - inform("note: the hardware mixer specified -- \"%s\" -- does not have dB volume, so it can not be used for volume control.",alsa_mix_ctrl); - } + // use the linear scale and do the db conversion ourselves + debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have a dB volume scale, so it can't be used.",alsa_mix_ctrl); + /* + debug(1, "Min and max volumes are %d and %d.",alsa_mix_minv,alsa_mix_maxv); + alsa_mix_maxdb = 0; + if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0)) + alsa_mix_mindb = -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0)); + else if (alsa_mix_maxv!=0) + alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0); + audio_alsa.volume = &linear_volume; // insert the linear volume function + audio_alsa.parameters = ¶meters; // likewise the parameters stuff + debug(1,"Max and min dB calculated are %d and %d.",alsa_mix_maxdb,alsa_mix_mindb); + */ + } } if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) { has_mute = 1; @@ -320,7 +341,7 @@ static void play(short buf[], int samples) { if (alsa_handle == NULL) { ret = open_alsa_device(); if ((ret == 0) && (audio_alsa.volume)) - volume(set_volume); + audio_alsa.volume(set_volume); } if (ret == 0) { pthread_mutex_lock(&alsa_mutex); @@ -390,6 +411,7 @@ static void parameters(audio_parameters *info) { } static void volume(double vol) { + // debug(1,"Volume called %f.",vol); set_volume = vol; double vol_setting = vol2attn(vol, alsa_mix_maxdb, alsa_mix_mindb); // debug(1,"Setting volume db to %f, for volume input of %f.",vol_setting/100,vol); @@ -398,3 +420,17 @@ static void volume(double vol) { if (has_mute) snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0)); } + +static void linear_volume(double vol) { + set_volume = vol; + double vol_setting = vol2attn(vol, 0, alsa_mix_mindb)/2000; + // debug(1,"Adjusted volume is %f.",vol_setting); + double linear_volume = pow(10, vol_setting); + // debug(1,"Linear volume is %f.",linear_volume); + long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume; + // debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol); + if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0) + die("Failed to set playback volume"); + if (has_mute) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0)); +} diff --git a/common.h b/common.h index 4a6c44f0..0f51a4b3 100644 --- a/common.h +++ b/common.h @@ -63,13 +63,13 @@ typedef struct { char *mdns_name; mdns_backend *mdns; int buffer_start_fill; - uint32_t latency; - uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution - uint32_t iTunesLatency; // supplied with --iTunesLatency option - uint32_t AirPlayLatency; // supplied with --AirPlayLatency option - uint32_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option + int32_t latency; + int32_t userSuppliedLatency; // overrides all other latencies -- use with caution + int32_t iTunesLatency; // supplied with --iTunesLatency option + int32_t AirPlayLatency; // supplied with --AirPlayLatency option + int32_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option int daemonise; - int statistics_requested; + int statistics_requested,use_negotiated_latencies; char *cmd_start, *cmd_stop; int cmd_blocking; int tolerance; // allow this much drift before attempting to correct it @@ -78,9 +78,9 @@ typedef struct { char *logfile; char *errfile; char *configfile; - uint32_t audio_backend_buffer_desired_length; // this will be the desired number of frames in the + int32_t audio_backend_buffer_desired_length; // this will be the desired number of frames in the // audio backend buffer -- the DAC buffer for ALSA - uint32_t audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency + int32_t audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency // there might be in the audio } shairport_cfg; diff --git a/configure.ac b/configure.ac index 4d6f2983..b4febda9 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], [2.5.0.5], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [2.5.0.7], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/player.c b/player.c index c86b9724..b98776b4 100644 --- a/player.c +++ b/player.c @@ -299,6 +299,9 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len) time_of_last_audio_packet = get_absolute_time_in_fp(); if (connection_state_to_output) { // if we are supposed to be processing these packets +// if (flush_rtp_timestamp != 0) +// debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); + if ((flush_rtp_timestamp != 0) && ((timestamp == flush_rtp_timestamp) || seq32_order(timestamp, flush_rtp_timestamp))) { debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %u, flushing to " @@ -489,16 +492,18 @@ static abuf_t *buffer_get_frame(void) { if (curframe->ready) { if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player + int have_sent_prefiller_silence; // set true when we have send some silent frames to the DAC + uint32_t reference_timestamp; + uint64_t reference_timestamp_time,remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); if (first_packet_timestamp == 0) { // if this is the very first packet // debug(1,"First frame seen, time %u, with %d // frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); - uint32_t reference_timestamp; - uint64_t reference_timestamp_time,remote_reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); if (reference_timestamp) { // if we have a reference time // debug(1,"First frame seen with timestamp..."); first_packet_timestamp = curframe->timestamp; // we will keep buffering until we are // supposed to start playing this + have_sent_prefiller_silence = 0; // Here, calculate when we should start playing. We need to know when to allow the // packets to be sent to the player. @@ -524,7 +529,6 @@ static abuf_t *buffer_get_frame(void) { // -4410 frames. int64_t delta = ((int64_t)first_packet_timestamp - (int64_t)reference_timestamp); - first_packet_time_to_play = reference_timestamp_time + ((delta + (int64_t)config.latency + (int64_t)config.audio_backend_latency_offset) @@ -541,6 +545,13 @@ static abuf_t *buffer_get_frame(void) { } if (first_packet_time_to_play != 0) { + // recalculate first_packet_time_to_play -- the latency might change + int64_t delta = ((int64_t)first_packet_timestamp - (int64_t)reference_timestamp); + first_packet_time_to_play = + reference_timestamp_time + + ((delta + (int64_t)config.latency + (int64_t)config.audio_backend_latency_offset) + << 32) / + 44100; uint32_t filler_size = frame_size; uint32_t max_dac_delay = 4410; @@ -560,7 +571,7 @@ static abuf_t *buffer_get_frame(void) { first_packet_time_to_play = 0; time_since_play_started = 0; } else { - if (config.output->delay) { + if ((config.output->delay) && (have_sent_prefiller_silence != 0)) { dac_delay = config.output->delay(); if (dac_delay == -1) { debug(1, "Error getting dac_delay in buffer_get_frame."); @@ -601,6 +612,7 @@ static abuf_t *buffer_get_frame(void) { // with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); config.output->play(silence, fs); free(silence); + have_sent_prefiller_silence = 1; if (ab_buffering == 0) { uint64_t reference_timestamp_time; // don't need this... get_reference_timestamp_stuff(&play_segment_reference_frame, &reference_timestamp_time, &play_segment_reference_frame_remote_time); @@ -1193,47 +1205,52 @@ void player_volume(double f) { // The volume ranges -144.0 (mute) or -30 -- 0. See // http://git.zx2c4.com/Airtunes2/about/#setting-volume // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30 - // equal increments - // So, we will pass this on without any weighting if we have a hardware mixer, as we expect the - // mixer to be calibrated in dB. - - // Here, we ask for an attenuation we will apply in software. The dB range of a value from 1 to - // 65536 is about 48.1 dB (log10 of 65536 is 4.8164). - // Thus, we ask our vol2attn function for an appropriate dB between -48.1 and 0 dB and translate + // equal increments. Since the human ear's response is roughly logarithmic, we imagine these to + // be power dB, i.e. from -30dB to 0dB. + + // So, if we have a hardware mixer, we will pass this on to its dB volume settings. + + // Without a hardware mixer, we have to do attenuation in software, so + // here, we ask for an attenuation we will apply to the signal amplitude in software. + // The dB range of a value from 1 to 65536 is about 96.3 dB (log10 of 65536 is 4.8164). + // Since the levels correspond with amplitude, they correspond to voltage, hence voltage dB, + // or 20 times the log of the ratio. Then multiplied by 100 for convenience. + // Thus, we ask our vol2attn function for an appropriate dB between -96.3 and 0 dB and translate // it back to a number. - double scaled_volume = vol2attn(f, 0, -4810); - double linear_volume = pow(10, scaled_volume / 1000); + double scaled_volume = vol2attn(f, 0, -9630); + double linear_volume = pow(10, scaled_volume / 2000); if (f == -144.0) linear_volume = 0.0; if (config.output->volume) { + // debug(1,"Set volume to %f.",f); config.output->volume(f); // volume will be sent as metadata by the config.output device - linear_volume = - 1.0; // no attenuation needed -- this value is used as a flag to avoid calculations + linear_volume = 1.0; // no attenuation needed -- this value is used as a flag to avoid calculations } if (config.output->parameters) config.output->parameters(&audio_information); else { audio_information.airplay_volume = f; - audio_information.minimum_volume_dB = -4810; + audio_information.minimum_volume_dB = -9630; audio_information.maximum_volume_dB = 0; audio_information.current_volume_dB = scaled_volume; audio_information.has_true_mute = 0; audio_information.is_muted = 0; } audio_information.valid = 1; + // debug(1,"Software volume set to %f on scale with a %f dB",f,linear_volume); pthread_mutex_lock(&vol_mutex); software_mixer_volume = linear_volume; fix_volume = 65536.0 * software_mixer_volume; pthread_mutex_unlock(&vol_mutex); #ifdef CONFIG_METADATA - char *dv = malloc(64); // will be freed in the metadata thread + char *dv = malloc(128); // will be freed in the metadata thread if (dv) { - memset(dv, 0, 64); - snprintf(dv, 63, "%.2f,%.2f,%.2f,%.2f", audio_information.airplay_volume, + memset(dv, 0, 128); + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", audio_information.airplay_volume, audio_information.current_volume_dB / 100.0, audio_information.minimum_volume_dB / 100.0, audio_information.maximum_volume_dB / 100.0); diff --git a/rtp.c b/rtp.c index e00a7536..8ab96589 100644 --- a/rtp.c +++ b/rtp.c @@ -193,7 +193,15 @@ static void *rtp_control_receiver(void *arg) { rtp_timestamp_less_latency = ntohl(*((uint32_t *)&packet[4])); sync_rtp_timestamp = ntohl(*((uint32_t *)&packet[16])); - + + if (config.use_negotiated_latencies) { + uint32_t la = sync_rtp_timestamp-rtp_timestamp_less_latency+11025; + if (la!=config.latency) { + config.latency = la; + debug(1,"Using negotiated latency of %u frames.",config.latency); + } + } + if (packet[0] & 0x10) { // if it's a packet right after a flush or resume sync_rtp_timestamp += 352; // add frame_size -- can't see a reference to this anywhere, diff --git a/rtsp.c b/rtsp.c index cbd1fa6f..e2eb53e9 100644 --- a/rtsp.c +++ b/rtsp.c @@ -582,9 +582,15 @@ static void msg_write_response(int fd, rtsp_message *resp) { } static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(1,"Handle Record"); + //debug(1,"Handle Record"); resp->respcode = 200; - msg_add_header(resp, "Audio-Latency", "88200"); + // I think this is for telling the client what the asbsolute minimum latency actually is, + // and when the client specifies a latency, it should be added to this figure. + + // Thus, AirPlay's latency figure of 77175, when added to 11025 gives you exactly 88200 + // and iTunes' latency figure of 88553, when added to 11025 gives you 99578, pretty close to the 99400 we guessed. + + msg_add_header(resp, "Audio-Latency", "11025"); char *p; uint32_t rtptime = 0; @@ -596,13 +602,14 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message p = strstr(hdr, "rtptime="); if (p) { p = strchr(p, '=') + 1; - if (p) + if (p) { rtptime = uatoi(p); // unsigned integer -- up to 2^32-1 + rtptime--; + // debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime); + player_flush(rtptime); + } } } - rtptime--; - debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime); - player_flush(rtptime); } static void handle_options(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { @@ -643,7 +650,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * } static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(1,"Handle Setup"); + // debug(1,"Handle Setup"); int cport, tport; int lsport, lcport, ltport; uint32_t active_remote = 0; @@ -667,13 +674,16 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * } #endif - // select latency + // This latency-setting mechanism is deprecated and will be removed. + // If no non-standard latency is chosen, automatic negotiated latency setting is permitted. + + // Select a static latency // if iTunes V10 or later is detected, use the iTunes latency setting // if AirPlay is detected, use the AirPlay latency setting // for everything else, use the general latency setting, if given, or // else use the default latency setting - config.latency = 88200; + config.latency = -1; if (config.userSuppliedLatency) config.latency = config.userSuppliedLatency; @@ -709,6 +719,12 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * debug(2, "Unrecognised User-Agent. Using latency of %d frames.", config.latency); } } + + if (config.latency==-1) { + // this means that no static latency was set, so we'll allow it to be set dynamically + config.latency=88200; // to be sure, to be sure + config.use_negotiated_latencies = 1; + } char *hdr = msg_get_header(req, "Transport"); if (!hdr) goto error; @@ -1090,6 +1106,12 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *re #endif +static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + debug(1, "received GET_PARAMETER request."); + resp->respcode = 200; +} + + static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // if (!req->contentlength) // debug(1, "received empty SET_PARAMETER request."); @@ -1231,7 +1253,7 @@ static struct method_handler { {"FLUSH", handle_flush}, {"TEARDOWN", handle_teardown}, {"SETUP", handle_setup}, - {"GET_PARAMETER", handle_ignore}, + {"GET_PARAMETER", handle_get_parameter}, {"SET_PARAMETER", handle_set_parameter}, {"RECORD", handle_record}, {NULL, NULL}}; @@ -1454,16 +1476,20 @@ static void *rtsp_conversation_thread_func(void *pconn) { goto respond; struct method_handler *mh; + int method_selected = 0; for (mh = method_handlers; mh->method; mh++) { if (!strcmp(mh->method, req->method)) { //debug(1,"RTSP Packet received of type \"%s\":",mh->method), //msg_print_debug_headers(req); + method_selected = 1; mh->handler(conn, req, resp); //debug(1,"RTSP Response:"); //msg_print_debug_headers(resp); break; } } + if (method_selected==0) + debug(1,"Unrecognised and unhandled rtsp request \"%s\".",req->method); respond: msg_write_response(conn->fd, resp); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index b2ac51a5..8a4dbf72 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -19,16 +19,6 @@ general = // ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. }; -// Latencies for different sources. These have been estimated from listening tests. -// It's probably better to compensate for a delay in the output device using the alsa "audio_backend_latency_offset" setting -- see below. -latencies = -{ -// default = 88200; // used for unrecognised sources and for iTunes up to and including iTunes 9.X. -// itunes = 99400; // used for iTunes 10 or later -// airplay = 88200; -// forkedDaapd = 99400; -}; - // How to deal with metadata, including artwork metadata = { @@ -75,3 +65,17 @@ stdout = // audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410. // audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size. }; + +// The following static latency settings are deprecated -- do not use them for new installations. Shairport Sync now sets latencies automatically. +// To compensate for a delay in the output device using the alsa/stdio/pipe "audio_backend_latency_offset" setting. + +// Static latencies for different sources. These have been estimated from listening tests. +latencies = +{ +// default = 88200; // used for unrecognised sources and for iTunes up to and including iTunes 9.X. +// itunes = 99400; // used for iTunes 10 or later +// airplay = 88200; +// forkedDaapd = 99400; +}; + + diff --git a/shairport.c b/shairport.c index 2561350a..beb8d21b 100644 --- a/shairport.c +++ b/shairport.c @@ -171,10 +171,10 @@ void usage(char *progname) { printf(" -a, --name=NAME set advertised name\n"); printf( " -A, --AirPlayLatency=FRAMES set the latency for audio sent from an AirPlay device.\n"); - printf(" The default value is %u frames.\n", config.AirPlayLatency); + printf(" The default value is %d frames.\n", config.AirPlayLatency); printf( " -i, --iTunesLatency=FRAMES set the latency for audio sent from iTunes 10 or later.\n"); - printf(" The default value is %u frames.\n", config.iTunesLatency); + printf(" The default value is %d frames.\n", config.iTunesLatency); printf(" -L, --latency=FRAMES set the latency for audio sent from an unknown device\n"); printf(" or from an old version of iTunes. Default is %d frames.\n", config.latency); @@ -399,19 +399,19 @@ int parse_options(int argc, char **argv) { die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } - /* Get the default latency. */ + /* Get the default latency. Deprecated! */ if (config_lookup_int(config.cfg, "latencies.default", &value)) - config.latency = value; + config.userSuppliedLatency = value; - /* Get the itunes latency. */ + /* Get the itunes latency. Deprecated! */ if (config_lookup_int(config.cfg, "latencies.itunes", &value)) config.iTunesLatency = value; - /* Get the AirPlay latency. */ + /* Get the AirPlay latency. Deprecated! */ if (config_lookup_int(config.cfg, "latencies.airplay", &value)) config.AirPlayLatency = value; - /* Get the forkedDaapd latency. */ + /* Get the forkedDaapd latency. Deprecated! */ if (config_lookup_int(config.cfg, "latencies.forkedDaapd", &value)) config.ForkedDaapdLatency = value; @@ -638,14 +638,14 @@ int main(int argc, char **argv) { config.configfile = configuration_file_path; config.statistics_requested = 0; // don't print stats in the log - config.latency = 88200; // AirPlay. Is also reset in rtsp.c when play is about to start + config.latency = -1; // -1 means not set. 88200 works well. This is also reset in rtsp.c when play is about to start config.userSuppliedLatency = 0; // zero means none supplied - config.iTunesLatency = 99400; // this seems to work pretty well for iTunes from Version 10 (?) + config.iTunesLatency = -1; // -1 means not supplied. 99400 seems to work pretty well for iTunes from Version 10 (?) // upwards-- two left-ear headphones, one from the iMac jack, one // from an NSLU2 running a cheap "3D Sound" USB Soundcard - config.AirPlayLatency = 88200; // this seems to work pretty well for AirPlay -- Syncs sound and + config.AirPlayLatency = -1; // -1 means not set. 88200 seems to work well for AirPlay -- Syncs sound and // vision on AppleTV, but also used for iPhone/iPod/iPad sources - config.ForkedDaapdLatency = 99400; // Seems to be right + config.ForkedDaapdLatency = -1; // -1 means not set. 99400 seems to be right config.resyncthreshold = 441 * 5; // this number of frames is 50 ms config.timeout = 120; // this number of seconds to wait for [more] audio before switching to idle. config.tolerance = 88; // this number of frames of error before attempting to correct it. @@ -823,6 +823,49 @@ int main(int argc, char **argv) { daemon_log(LOG_NOTICE, "startup"); + /* Mess around with the latency options */ + // Basically, we used to rely on static latencies -- 99400 for iTunes 10 or later and forkedDaapd, 88200 for everything else + // Nowadays we allow the source to set the latency, which works out at 99651 for iTunes 10 and forkedDaapd and 88220 for everything else + // What we want to do here is allow the source to set the latency unless the user has specified an non-standard latency. + // If the user has specified a standard latency, we suggest to them to stop doing it. + // If they specify a non-standard latency, we suggest the user to use the audio_backend_latency_offset instead. + + if (config.AirPlayLatency!=-1) { + if (config.AirPlayLatency==88200) { + inform("It is not necessary to set the AirPlay latency to 88200 -- you should remove this setting or configuration option, as it is deprecated."); + config.AirPlayLatency= -1; + } else { + inform("The AirPlay latency setting is deprecated, as Shairport Sync can now get the correct latency from the source."); + inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere."); + } + } + + if (config.iTunesLatency!=-1) { + if (config.iTunesLatency==99400) { + inform("It is not necessary to set the iTunes latency to 99400 -- you should remove this setting or configuration option, as it is deprecated and ignored."); + config.iTunesLatency= -1; + } else { + inform("The iTunes latency setting is deprecated, as Shairport Sync can now get the correct latency from the source."); + inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere."); + } + } + + if (config.ForkedDaapdLatency!=-1) { + if (config.ForkedDaapdLatency==99400) { + inform("It is not necessary to set the forkedDaapd latency to 99400 -- you should remove this setting or configuration option, as it is deprecated and ignored."); + config.ForkedDaapdLatency= -1; + } else { + inform("The forkedDaapd latency setting is deprecated, as Shairport Sync can now get the correct latency from the source."); + inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere."); + } + } + + if (config.userSuppliedLatency) { + inform("The default latency setting is deprecated, as Shairport Sync can now get the correct latency from the source."); + inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere."); + } + + /* Print out options */ debug(2, "statistics_requester status is %d.", config.statistics_requested); @@ -850,6 +893,7 @@ int main(int argc, char **argv) { debug(2, "audio backend desired buffer length is %d.", config.audio_backend_buffer_desired_length); debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); + debug(2, "use_negotiated_latencies %d.", config.use_negotiated_latencies); char *realConfigPath = realpath(config.configfile,NULL); if (realConfigPath) {