diff --git a/.travis.yml b/.travis.yml index aded32f9..37c4fd66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,26 +7,10 @@ env: - CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemv" - CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd" - CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-stdio --with-avahi --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-stdio --with-avahi --with-ssl=polarssl --with-metadata --with-systemd" - - CFG="--with-pipe --with-avahi --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-pipe --with-avahi --with-ssl=polarssl --with-metadata --with-systemd" - - CFG="--with-ao --with-avahi --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-ao --with-avahi --with-ssl=polarssl --with-metadata --with-systemd" - CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemv" - CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemv" - CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd" - CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd" - - CFG="--with-stdio --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-stdio --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd" - - CFG="--with-pipe --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-pipe --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd" - - CFG="--with-ao --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd" - - CFG="--with-ao --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd" script: - autoreconf -fi @@ -35,7 +19,7 @@ script: before_install: - sudo apt-get -qq update - - sudo apt-get install -y libdaemon-dev avahi-daemon libavahi-client-dev + - sudo apt-get install -y libdaemon-dev avahi-daemon libavahi-client-dev addons: apt: diff --git a/LICENSES b/LICENSES index 63ff1826..583fdf4f 100644 --- a/LICENSES +++ b/LICENSES @@ -11,6 +11,9 @@ tinysvcmdns.c, tinysvcmdns.h: Shairport: Copyright (c) 2011-2013 James Laird +Shairport Sync: + Copyright (c) 2014–2017 Mike Brady + 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 diff --git a/Makefile.am b/Makefile.am index 3ad62609..c6e4d5e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,9 +1,14 @@ SUBDIRS = man bin_PROGRAMS = shairport-sync -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c AM_CFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" +AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" + +if USE_APPLE_ALAC + shairport_sync_SOURCES += apple_alac.cpp +endif if USE_CUSTOMPIDDIR AM_CFLAGS+= \ @@ -42,6 +47,10 @@ if USE_AO shairport_sync_SOURCES += audio_ao.c endif +if USE_SOUNDIO +shairport_sync_SOURCES += audio_soundio.c +endif + if USE_PULSE shairport_sync_SOURCES += audio_pulse.c endif diff --git a/README.md b/README.md index 14d6dc38..fc1ceb79 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,21 @@ Shairport Sync ============= -Shairport Sync is an AirPlay audio player — it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player and ForkedDaapd, among others. +**Welcome to Version 3 of Shairport Sync. This is a big update from version 2. Please see the Release Notes for more information.** + +Shairport Sync is an AirPlay audio player – it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player and ForkedDaapd, among others. Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.) Shairport Sync does not support AirPlay video or photo streaming. This is the stable "master" branch. Changes and updates are incorporated into this branch relatively slowly. To access the development version, where all the latest changes are made first, please switch to the "development" branch. -**Please note that, between 2.8.4 and 2.8.5, there is a change in the standard ./configure arguments which you should not ignore!** - +**Please note that, between 2.8.4 and 2.8.5, there is a change in the standard ./configure arguments which you should not ignore!** If you are updating Shairport Sync from a version that is 2.8.4 or older, please read the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page very carefully. More Information ---------- Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to play audio at exactly the right time. It does this by monitoring and controlling the *latency* — the time-gap between when a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC). -The latency to be used is specified by the source when it negotiates with Shairport 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. +The latency to be used is specified by the source when it negotiates with Shairport Sync. Most sources set a latency of exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. Timestamps are measured relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. This is done by synchronising the two clocks, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. @@ -22,7 +23,7 @@ To maintain the exact latency required, if an output device is running slow rela Shairport Sync is a 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 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). +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, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (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. @@ -30,6 +31,8 @@ What else? -------------- * Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. * Hardware Mute — Shairport Sync will mute properly if the hardware supports it. +* Support for the Apple ALAC decoder. +* Bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits. * 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 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 and the Music app in iOS. @@ -40,7 +43,7 @@ Status ------ 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 (see the note below on configuring the Raspberry Pi to make best use of it). USB-connected sound cards work well, though [very] old 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. +Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card, though the audio out of the card is of poor quality – see the note below on configuring the Raspberry Pi to make best use of it. At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi. @@ -88,11 +91,12 @@ Please see the guide at [CYGWIN.md](https://github.com/mikebrady/shairport-sync/ Sincere thanks to all package contributors! **General Build Instructions** +The following procedures will install the shairport-sync application into your system. Before continuing, you should check to see if shairport-sync is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions. The following libraries are required: -* OpenSSL or PolarSSL +* OpenSSL or mbed TLS (PolarSSL is supported but deprecated) * Avahi * ALSA * libdaemon @@ -104,17 +108,20 @@ The following libraries are required: Optional: * libsoxr +* libalac (This is a library containing the Apple ALAC decoder.) -Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or PolarSSL. Libsoxr is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. +Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. Debian, Ubuntu and Raspbian users can get the basics with: - `apt-get install build-essential git` – these may already be installed. - `apt-get install autoconf automake libtool libdaemon-dev libasound2-dev libpopt-dev libconfig-dev` - `apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended). -- `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use PolarSSL otherwise. -- `apt-get install libpolarssl-dev` if you want to use PolarSSL, or use OpenSSL/libcrypto otherwise. -- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspioan Jessie; if not, instructions for how to build it from source for Raspian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md). +- `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise. +- `apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. (You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. It is suggested you use mbed TLS instead.) +- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspbian Jessie; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/development/LIBSOXR.md). + +If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information. **Download Shairport Sync:** @@ -133,7 +140,7 @@ $ autoreconf -i -f - `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout). - `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe. - `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15. -- `--with-ssl=openssl` or `--with-ssl=polarssl` for encryption and related utilities using either OpenSSL or PolarSSL. +- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL. - `--with-soxr` for libsoxr-based resampling. - `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location. - `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. @@ -141,6 +148,7 @@ $ autoreconf -i -f - `--with-systemd` to install a systemd service description at the `make install` stage. Default is not to to install. - `--with-configfile` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten. - `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use. +- `--with-apple-alac` to include the Apple ALAC Decoder. **Determine if it's a `systemd` or a "System V" installation:** @@ -207,7 +215,7 @@ To enable Shairport Sync to start automatically at system startup, enter: `$ sudo systemctl enable shairport-sync` **Install into a System V system** -If you are installing onto a System V system: +If you are installing onto a System V system, enter: ``` $ sudo make install @@ -272,11 +280,11 @@ Please note that the full path to the programs must be specified, and script fil Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatability, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method. **Raspberry Pi** -The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. This provides a low-quality output that is nevertheless useful for testing purposes and may be adequate for [very] casual listening. It is not HiFi -- it is quite noisy and can't play anything above about 15kHz. A further problem is that it declares itself to have a very large mixer volume control range -- all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 30dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` stanza to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 30, the top 30 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -26dB, below which muting will occur. +The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. This provides a low-quality output that is nevertheless useful for testing purposes and may be adequate for [very] casual listening. It is not HiFi – it is quite noisy and can't play anything above about 15kHz. A further problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 30dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 30, the top 30 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -26dB, below which muting will occur. From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume. -Another setting to consider is the `general` `drift` setting: you should set it to a larger number, such as 352, to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC. +Another setting to consider is the `general` `drift_tolerance_in_seconds` setting: you should set it to a larger tolerance, say 10 milliseconds – `drift_tolerance_in_seconds=0.010;` – to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC. *Command Line Arguments* @@ -388,38 +396,50 @@ 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. -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 now obselete and are deprecated.) +Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them. -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. +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_in_seconds`. 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 -0.1. Resynchronisation ------------- Shairport Sync actively maintains synchronisation with the source. -If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 50 ms — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above. -* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold` setting. +If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above. +* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting. Tolerance --------- -Playback synchronisation is allowed to wander — 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. +Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, 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_tolerance_in_seconds` setting. Some Statistics --------------- -If you turn on the `general` `statistics` setting, statistics like this will be printed at intervals on the console (or in the logfile if running in daemon mode): +If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file: +``` +sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy +``` +This will be followed by the statistics themselves at regular intervals, for example: +``` + -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 + -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267 + -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268 + -2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267 + -2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268 + -2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267 + -2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267 + -2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266 +``` -`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 in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) 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 in ppm" 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 first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm. -"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. +"Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. -"Corrections" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. - -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. +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 an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+. 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. +"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 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up. Troubleshooting --------------- diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5e14d9ed..18996d0e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,104 @@ +Version 3.0 +==== + +Big Update +---- +Version 3 brings in support for 24-bit and 32-bit (and 8 bit!) DACs and for DACs running at multiples of 44,100 samples per second. + +The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC. + +Here is the list of new features: + +**New Features** +* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. +* 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. It's only worth doing if 44,100 samples per second output is not available. +* Internal processing including software volume control and interpolation is done after sample size and rate conversion. +* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac), to be installed. Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. +* Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. +* Choose Network Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. +* Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3. +* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details. + + +Pesky Changes You Cannot Ignore +---- +* Processor load is up by about 11%. +* Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details. +* Sox-based interpolation at higher sample rates may overload your CPU -- you might have to choose between higher sample rates and sox-based interpolation. + + +Version 3.0rc0 – Release Candidate 0 +---- +Note: all Version 3 changes are summarized above. + +**New Feature** +* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details. + +**Other changes** +* Updates to `man` page and [README](https://github.com/mikebrady/shairport-sync/blob/development/README.md). Reports of typos or suggestions for improvement are welcome! + +Version 3.0d24 – Development Version +---- +Note: all Version 3 changes are summarized above. + +**New Feature** +* Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3. + +Version 3.0d23 – Development Version +---- +Note: all Version 3 changes are summarized above. + +**New Feature** +* Choose Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically. + +Version 3.0d22 – Development Version +---- +Note: all Version 3 changes are summarized above. + +**Bug Fix** +* Fixed a bug which prevented successful building in the OpenWrt build system. The problem was caused by an `#include apple_alac.h` in `player.c` which was actioned even if the apple alac decoder was not selected. This caused the OpenWrt build system to expect the standard C++ library – required by the apple alac code – to be referenced, but it was not specified on the build manifest and therefore stopped the build. The solution was to make the `#include` conditional on selecting the apple alac decoder. + +Version 3.0d21 – Development Version +---- +Note: all Version 3 changes are summarized above. + +**Bug Fix** +* Fixed a bug which turned off resync by default. Duh. + +Version 3.0d20 – Development Version +---- +Note: all Version 3 changes are summarized above. + +**Bug Fix** +* Fix a small and generally silent error in configure.ac so that it only looks for the systemd direcotry if systemd has been chosen. It caused a warning when cross-compiling. + +Version 3.0d19 – Development Version +---- +Note: all Version 3 changes are summarized above. + +**New Feature** +* Reduces processor load back to V2.X levels by using a precalculated array of pseudorandom numbers to do dithering. Doesn't seem to make any audible difference. + +Version 3.0d18 – Development Version +---- +Note: all Version 3 changes are summarized above. + +**New Features** +* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. (Other back ends are not updated yet.) +* 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. +* Internal processing including software volume control and interpolation is done after sample size and rate conversion. +* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file. +* Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further. +* Settings that were denominated in frames are now deprecated but still honoured. Deprecation warnings are issued. + +Pesky Changes You Cannot Ignore +==== +* Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details. +* Sox-based interpolation at higher sample rates may overload your CPU -- yopu might have to choose between higher sample rates and sox-based interpolation. + +**Bugs** +* Documentation is not updated. + Version 2.8.6 – Stable Candidate ---- @@ -19,7 +120,6 @@ The enhancements and bug fixes in 2.8.5 were made in versions 2.8.4.1 to 2.8.4.8 For advice on updating an installation you built yourself, please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page. -======= Version 2.8.4.8 – Development Version ---- **Enhancements** diff --git a/alac.c b/alac.c index cf72781a..c814ee3f 100644 --- a/alac.c +++ b/alac.c @@ -35,59 +35,60 @@ static const int host_bigendian = 0; #include #include #ifdef _WIN32 - #include "stdint_win.h" +#include "stdint_win.h" #else - #include +#include #endif #include "alac.h" -#define _Swap32(v) do { \ - v = (((v) & 0x000000FF) << 0x18) | \ - (((v) & 0x0000FF00) << 0x08) | \ - (((v) & 0x00FF0000) >> 0x08) | \ - (((v) & 0xFF000000) >> 0x18); } while(0) +#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) +#define _Swap16(v) \ + do { \ + v = (((v)&0x00FF) << 0x08) | (((v)&0xFF00) >> 0x08); \ + } while (0) -struct {signed int x:24;} se_struct_24; +struct { + signed int x : 24; +} se_struct_24; #define SignExtend24(val) (se_struct_24.x = val) void alac_free(alac_file *alac) { - if (alac->predicterror_buffer_a) - free(alac->predicterror_buffer_a); - if (alac->predicterror_buffer_b) - free(alac->predicterror_buffer_b); + if (alac->predicterror_buffer_a) + free(alac->predicterror_buffer_a); + if (alac->predicterror_buffer_b) + free(alac->predicterror_buffer_b); - if (alac->outputsamples_buffer_a) - free(alac->outputsamples_buffer_a); - if (alac->outputsamples_buffer_b) - free(alac->outputsamples_buffer_b); + if (alac->outputsamples_buffer_a) + free(alac->outputsamples_buffer_a); + if (alac->outputsamples_buffer_b) + free(alac->outputsamples_buffer_b); - if (alac->uncompressed_bytes_buffer_a) - free(alac->uncompressed_bytes_buffer_a); - if (alac->uncompressed_bytes_buffer_b) - free(alac->uncompressed_bytes_buffer_b); + if (alac->uncompressed_bytes_buffer_a) + free(alac->uncompressed_bytes_buffer_a); + if (alac->uncompressed_bytes_buffer_b) + free(alac->uncompressed_bytes_buffer_b); - free(alac); + free(alac); } -void alac_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); +void alac_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->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); + 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) -{ +void alac_set_info(alac_file *alac, char *inputbuffer) { char *ptr = inputbuffer; ptr += 4; /* size */ ptr += 4; /* frma */ @@ -97,122 +98,114 @@ void alac_set_info(alac_file *alac, char *inputbuffer) ptr += 4; /* 0 ? */ - alac->setinfo_max_samples_per_frame = *(uint32_t*)ptr; /* buffer size / 2 ? */ + alac->setinfo_max_samples_per_frame = *(uint32_t *)ptr; /* buffer size / 2 ? */ if (!host_bigendian) - _Swap32(alac->setinfo_max_samples_per_frame); + _Swap32(alac->setinfo_max_samples_per_frame); ptr += 4; - alac->setinfo_7a = *(uint8_t*)ptr; + alac->setinfo_7a = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_sample_size = *(uint8_t*)ptr; + alac->setinfo_sample_size = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_rice_historymult = *(uint8_t*)ptr; + alac->setinfo_rice_historymult = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_rice_initialhistory = *(uint8_t*)ptr; + alac->setinfo_rice_initialhistory = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_rice_kmodifier = *(uint8_t*)ptr; + alac->setinfo_rice_kmodifier = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_7f = *(uint8_t*)ptr; + alac->setinfo_7f = *(uint8_t *)ptr; ptr += 1; - alac->setinfo_80 = *(uint16_t*)ptr; + alac->setinfo_80 = *(uint16_t *)ptr; if (!host_bigendian) - _Swap16(alac->setinfo_80); + _Swap16(alac->setinfo_80); ptr += 2; - alac->setinfo_82 = *(uint32_t*)ptr; + alac->setinfo_82 = *(uint32_t *)ptr; if (!host_bigendian) - _Swap32(alac->setinfo_82); + _Swap32(alac->setinfo_82); ptr += 4; - alac->setinfo_86 = *(uint32_t*)ptr; + alac->setinfo_86 = *(uint32_t *)ptr; if (!host_bigendian) - _Swap32(alac->setinfo_86); + _Swap32(alac->setinfo_86); ptr += 4; - alac->setinfo_8a_rate = *(uint32_t*)ptr; + alac->setinfo_8a_rate = *(uint32_t *)ptr; if (!host_bigendian) - _Swap32(alac->setinfo_8a_rate); + _Swap32(alac->setinfo_8a_rate); alac_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; +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]); + 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; + /* 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; + result = result & 0x00ffffff; - /* and then only want the top 'n' bits from that, where - * n is 'bits' */ - result = result >> (24 - bits); + /* 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); + new_accumulator = (alac->input_buffer_bitaccumulator + bits); - /* increase the buffer pointer if we've read over n bytes. */ - alac->input_buffer += (new_accumulator >> 3); + /* 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); + /* and the remainder goes back into the bit accumulator */ + alac->input_buffer_bitaccumulator = (new_accumulator & 7); - return result; + 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; +static uint32_t readbits(alac_file *alac, int bits) { + int32_t result = 0; - if (bits > 16) - { - bits -= 16; - result = readbits_16(alac, 16) << bits; - } + if (bits > 16) { + bits -= 16; + result = readbits_16(alac, 16) << bits; + } - result |= readbits_16(alac, bits); + result |= readbits_16(alac, bits); - return result; + return result; } /* reads a single bit */ -static int readbit(alac_file *alac) -{ - int result; - int new_accumulator; +static int readbit(alac_file *alac) { + int result; + int new_accumulator; - result = alac->input_buffer[0]; + result = alac->input_buffer[0]; - result = result << alac->input_buffer_bitaccumulator; + result = result << alac->input_buffer_bitaccumulator; - result = result >> 7 & 1; + result = result >> 7 & 1; - new_accumulator = (alac->input_buffer_bitaccumulator + 1); + new_accumulator = (alac->input_buffer_bitaccumulator + 1); - alac->input_buffer += (new_accumulator / 8); + alac->input_buffer += (new_accumulator / 8); - alac->input_buffer_bitaccumulator = (new_accumulator % 8); + alac->input_buffer_bitaccumulator = (new_accumulator % 8); - return result; + return result; } -static void unreadbits(alac_file *alac, int bits) -{ - int new_accumulator = (alac->input_buffer_bitaccumulator - bits); +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 += (new_accumulator >> 3); - alac->input_buffer_bitaccumulator = (new_accumulator & 7); - if (alac->input_buffer_bitaccumulator < 0) - alac->input_buffer_bitaccumulator *= -1; + alac->input_buffer_bitaccumulator = (new_accumulator & 7); + if (alac->input_buffer_bitaccumulator < 0) + alac->input_buffer_bitaccumulator *= -1; } /* various implementations of count_leading_zero: @@ -240,247 +233,220 @@ static int count_leading_zeros(int32_t input) /* 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); -} +static int count_leading_zeros(int input) { return __builtin_clz(input); } #elif defined(_MSC_VER) && defined(_M_IX86) -static int count_leading_zeros(int input) -{ - int output = 0; - if (!input) return 32; - __asm - { +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; + } + 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; +static int count_leading_zeros(int input) { + int output = 0; + int curbyte = 0; - curbyte = input >> 24; - if (curbyte) goto found; - output += 8; + curbyte = input >> 24; + if (curbyte) + goto found; + output += 8; - curbyte = input >> 16; - if (curbyte & 0xff) 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 >> 8; + if (curbyte & 0xff) + goto found; + output += 8; - curbyte = input; - if (curbyte & 0xff) goto found; - output += 8; + curbyte = input; + if (curbyte & 0xff) + goto found; + output += 8; - return output; + return output; found: - if (!(curbyte & 0xf0)) - { - output += 4; - } - else - curbyte >>= 4; + 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; + 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; + /* 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 +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++; + // 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); } + } - 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; + 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; +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; + 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); + k = 31 - rice_kmodifier - count_leading_zeros((history >> 9) + 3); - if (k < 0) k += rice_kmodifier; - else k = rice_kmodifier; + 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); + // 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; + 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; + 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; - // 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; - } + history = 0; } + } } #define SIGN_EXTENDED32(val, bits) ((val << (32 - bits)) >> (32 - bits)) -#define SIGN_ONLY(v) \ - ((v < 0) ? (-1) : \ - ((v > 0) ? (1) : \ - (0))) +#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) -{ +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; - /* first sample always copies */ - *buffer_out = *error_buffer; + val = buffer_out[i] + error_buffer[i + 1]; - if (!predictor_coef_num) - { - if (output_size <= 1) return; - memcpy(buffer_out+1, error_buffer+1, (output_size-1) * 4); - return; - } + val = SIGN_EXTENDED32(val, readsamplesize); - 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; - } + buffer_out[i + 1] = val; } + } #if 0 /* 4 and 8 are very common cases (the only ones i've seen). these @@ -499,657 +465,545 @@ static void predictor_decompress_fir_adapt(int32_t *error_buffer, } #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]; - /* 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]; + } - 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); - 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; - buffer_out[predictor_coef_num+1] = outval; + if (error_val > 0) { + int predictor_num = predictor_coef_num - 1; - 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); - 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; - predictor_coef_table[predictor_num] -= sign; + val *= sign; /* absolute value */ - val *= sign; /* absolute value */ + error_val -= ((val >> predictor_quantitization) * (predictor_coef_num - predictor_num)); - 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++; + 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; +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; + /* 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]; + midright = buffer_a[i]; + difference = buffer_b[i]; + right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); + left = right + difference; - right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); - left = right + difference; + /* output is always little endian */ + if (host_bigendian) { + _Swap16(left); + _Swap16(right); + } - /* output is always little endian */ - if (host_bigendian) - { - _Swap16(left); - _Swap16(right); - } - - buffer_out[i*numchannels] = left; - buffer_out[i*numchannels + 1] = right; - } - - return; + buffer_out[i * numchannels] = left; + buffer_out[i * numchannels + 1] = right; } - /* otherwise basic interlacing took place */ - for (i = 0; i < numsamples; i++) - { - int16_t left, right; + return; + } - left = buffer_a[i]; - right = buffer_b[i]; + /* otherwise basic interlacing took place */ + for (i = 0; i < numsamples; i++) { + int16_t left, right; - /* output is always little endian */ + 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 alac_decode_frame(alac_file *alac, unsigned char *inbuffer, void *outbuffer, int *outputsize) { + int outbuffer_allocation_size = *outputsize; // initial value + 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; + if (*outputsize > outbuffer_allocation_size) { + fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E1.\n"); + *outputsize = 0; + return; + } + + 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; + if (*outputsize > outbuffer_allocation_size) { + fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E2.\n"); + *outputsize = 0; + return; + } + } + + 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 prediction type for compressed case: %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(left); - _Swap16(right); - } - - buffer_out[i*numchannels] = left; - buffer_out[i*numchannels + 1] = right; + _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]; -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; + 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; + if (*outputsize > outbuffer_allocation_size) { + fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E3.\n"); + *outputsize = 0; return; + } } - /* otherwise basic interlacing took place */ - for (i = 0; i < numsamples; i++) - { - int32_t left, right; + readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8) + 1; - left = buffer_a[i]; - right = buffer_b[i]; + 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; - if (uncompressed_bytes) - { - uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); - left <<= (uncompressed_bytes * 8); - right <<= (uncompressed_bytes * 8); + int16_t predictor_coef_table_b[32]; + int predictor_coef_num_b; + int prediction_type_b; + int prediction_quantitization_b; + int ricemodifier_b; - left |= uncompressed_bytes_buffer_a[i] & mask; - right |= uncompressed_bytes_buffer_b[i] & mask; + 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); } + } - ((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; + /* 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); - ((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; + 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 prediction type on channel 1: %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 prediction type on channel 2: %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; + } + } } -void alac_decode_frame(alac_file *alac, - unsigned char *inbuffer, - void *outbuffer, int *outputsize) -{ - int outbuffer_allocation_size = *outputsize; // initial value - int channels; - int32_t outputsamples = alac->setinfo_max_samples_per_frame; +alac_file *alac_create(int samplesize, int numchannels) { + alac_file *newfile = malloc(sizeof(alac_file)); - /* setup the stream */ - alac->input_buffer = inbuffer; - alac->input_buffer_bitaccumulator = 0; + memset(newfile, 0, sizeof(alac_file)); - channels = readbits(alac, 3); + newfile->samplesize = samplesize; + newfile->numchannels = numchannels; + newfile->bytespersample = (samplesize / 8) * numchannels; - *outputsize = outputsamples * alac->bytespersample; - if (*outputsize>outbuffer_allocation_size) { - fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E1.\n"); - *outputsize = 0; - return; - } - - 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; - if (*outputsize>outbuffer_allocation_size) { - fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E2.\n"); - *outputsize = 0; - return; - } - - } - - 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 prediction type for compressed case: %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; - if (*outputsize>outbuffer_allocation_size) { - fprintf(stderr, "FIXME: Not enough space if the output buffer for audio frame - E3.\n"); - *outputsize = 0; - return; - } - } - - 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 prediction type on channel 1: %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 prediction type on channel 2: %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; - } - } + return newfile; } - -alac_file *alac_create(int samplesize, int numchannels) -{ - alac_file *newfile = malloc(sizeof(alac_file)); - - memset(newfile, 0, sizeof(alac_file)); - - newfile->samplesize = samplesize; - newfile->numchannels = numchannels; - newfile->bytespersample = (samplesize / 8) * numchannels; - - return newfile; -} - diff --git a/alac.h b/alac.h index 09666a1d..a56c29f1 100644 --- a/alac.h +++ b/alac.h @@ -6,52 +6,43 @@ typedef struct alac_file alac_file; alac_file *alac_create(int samplesize, int numchannels); -void alac_decode_frame(alac_file *alac, - unsigned char *inbuffer, - void *outbuffer, int *outputsize); +void alac_decode_frame(alac_file *alac, unsigned char *inbuffer, void *outbuffer, int *outputsize); void alac_set_info(alac_file *alac, char *inputbuffer); void alac_allocate_buffers(alac_file *alac); void alac_free(alac_file *alac); -struct alac_file -{ - unsigned char *input_buffer; - int input_buffer_bitaccumulator; /* used so we can do arbitary - bit reads */ +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; + int samplesize; + int numchannels; + int bytespersample; + /* buffers */ + int32_t *predicterror_buffer_a; + int32_t *predicterror_buffer_b; - /* 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; - + 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 */ + 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 */ - diff --git a/apple_alac.cpp b/apple_alac.cpp new file mode 100644 index 00000000..0ee30290 --- /dev/null +++ b/apple_alac.cpp @@ -0,0 +1,55 @@ +#include + +// these are headers for the ALAC decoder, utilities and endian utilities +#include +#include +#include + +#include "apple_alac.h" +#include "config.h" + +typedef struct magicCookie { + ALACSpecificConfig config; + ALACAudioChannelLayout channelLayoutInfo; // seems to be unused +} magicCookie; + +magicCookie cookie; +ALACDecoder *theDecoder; + +extern "C" int apple_alac_init(int32_t fmtp[12]) { + + memset(&cookie, 0, sizeof(magicCookie)); + + // create a magic cookie for the decoder from the fmtp information. It seems to be in the same + // format as a simple magic cookie + + cookie.config.frameLength = Swap32NtoB(352); + cookie.config.compatibleVersion = fmtp[2]; // should be zero, uint8_t + cookie.config.bitDepth = fmtp[3]; // uint8_t expected to be 16 + cookie.config.pb = fmtp[4]; // uint8_t should be 40; + cookie.config.mb = fmtp[5]; // uint8_t should be 10; + cookie.config.kb = fmtp[6]; // uint8_t should be 14; + cookie.config.numChannels = fmtp[7]; // uint8_t expected to be 2 + cookie.config.maxRun = Swap16NtoB(fmtp[8]); // uint16_t expected to be 255 + cookie.config.maxFrameBytes = Swap32NtoB(fmtp[9]); // uint32_t should be 0; + cookie.config.avgBitRate = Swap32NtoB(fmtp[10]); // uint32_t should be 0;; + cookie.config.sampleRate = Swap32NtoB(fmtp[11]); // uint32_t expected to be 44100; + + theDecoder = new ALACDecoder; + theDecoder->Init(&cookie, sizeof(magicCookie)); + + return 0; +} + +extern "C" int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, + unsigned char *dest, int *outsize) { + uint32_t numFrames = 0; + BitBuffer theInputBuffer; + BitBufferInit(&theInputBuffer, sampleBuffer, bufferLength); + theDecoder->Decode(&theInputBuffer, dest, Swap32BtoN(cookie.config.frameLength), + cookie.config.numChannels, &numFrames); + *outsize = numFrames; + return 0; +} + +extern "C" int apple_alac_terminate() { delete (theDecoder); } diff --git a/apple_alac.h b/apple_alac.h new file mode 100644 index 00000000..eb8bcf6c --- /dev/null +++ b/apple_alac.h @@ -0,0 +1,20 @@ +#ifndef __APPLE_ALAC_H +#define __APPLE_ALAC_H + +#include "config.h" +#include + +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +EXTERNC int apple_alac_init(int32_t fmtp[12]); +EXTERNC int apple_alac_terminate(); +EXTERNC int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength, + unsigned char *dest, int *outsize); + +#undef EXTERNC + +#endif /* __APPLE_ALAC_H */ diff --git a/audio.c b/audio.c index 6c2c6fce..915cb802 100644 --- a/audio.c +++ b/audio.c @@ -24,10 +24,10 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include #include "audio.h" #include "config.h" +#include +#include #ifdef CONFIG_SNDIO extern audio_output audio_sndio; @@ -35,6 +35,9 @@ extern audio_output audio_sndio; #ifdef CONFIG_AO extern audio_output audio_ao; #endif +#ifdef CONFIG_SOUNDIO +extern audio_output audio_soundio; +#endif #ifdef CONFIG_PULSE extern audio_output audio_pulse; #endif @@ -64,10 +67,13 @@ static audio_output *outputs[] = { #ifdef CONFIG_AO &audio_ao, #endif +#ifdef CONFIG_SOUNDIO + &audio_soundio, +#endif #ifdef CONFIG_DUMMY &audio_dummy, #endif -#ifdef CONFIG_PIPE +#ifdef CONFIG_PIPE &audio_pipe, #endif #ifdef CONFIG_STDOUT diff --git a/audio.h b/audio.h index 4c86d7db..31fa1069 100644 --- a/audio.h +++ b/audio.h @@ -1,8 +1,8 @@ #ifndef _AUDIO_H #define _AUDIO_H -#include #include +#include typedef struct { double current_volume_dB; @@ -19,7 +19,7 @@ typedef struct { // at end of program void (*deinit)(void); - void (*start)(int sample_rate); + void (*start)(int sample_rate, int sample_format); // block of samples void (*play)(short buf[], int samples); @@ -32,17 +32,17 @@ typedef struct { // almost certainly wrong if the buffer is empty, so put silent buffers into it to make it busy. // will change dynamically, so keep watching it. Implemented in ALSA only. // returns a negative error code if there's a problem - int (*delay)(long* the_delay); // snd_pcm_sframes_t is a signed long + int (*delay)(long *the_delay); // snd_pcm_sframes_t is a signed long // may be NULL, in which case soft volume is applied void (*volume)(double vol); // may be NULL, in which case soft volume parameters are used void (*parameters)(audio_parameters *info); - + // may be NULL, in which case software muting is used. void (*mute)(int do_mute); - + } audio_output; audio_output *audio_get_output(char *name); diff --git a/audio_alsa.c b/audio_alsa.c index 0e9de51c..e3458cab 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -26,23 +26,23 @@ #define ALSA_PCM_NEW_HW_PARAMS_API +#include "audio.h" +#include "common.h" +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include "common.h" -#include "audio.h" static void help(void); static int init(int argc, char **argv); static void deinit(void); -static void start(int sample_rate); +static void start(int i_sample_rate, int i_sample_format); static void play(short buf[], int samples); static void stop(void); static void flush(void); -int delay(long* the_delay); +int delay(long *the_delay); static void volume(double vol); static void linear_volume(double vol); static void parameters(audio_parameters *info); @@ -50,24 +50,23 @@ static void mute(int do_mute); static double set_volume; static int output_method_signalled = 0; -audio_output audio_alsa = { - .name = "alsa", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = &flush, - .delay = &delay, - .play = &play, - .mute = &mute, - .volume = &volume, - .parameters = ¶meters -}; +audio_output audio_alsa = {.name = "alsa", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = &flush, + .delay = &delay, + .play = &play, + .mute = &mute, + .volume = &volume, + .parameters = ¶meters}; static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; static unsigned int desired_sample_rate; +static snd_pcm_format_t sample_format; static snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; @@ -86,15 +85,15 @@ static int alsa_mix_index = 0; static int hardware_mixer = 0; static int has_softvol = 0; -static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, snd_pcm_uframes_t) = snd_pcm_writei; +static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, + snd_pcm_uframes_t) = snd_pcm_writei; static int play_number; static int64_t accumulated_delay, accumulated_da_delay; int alsa_characteristics_already_listed = 0; -static snd_pcm_uframes_t period_size_requested,buffer_size_requested; -static int set_period_size_request,set_buffer_size_request; - +static snd_pcm_uframes_t period_size_requested, buffer_size_requested; +static int set_period_size_request, set_buffer_size_request; static void help(void) { printf(" -d output-device set the output device [default*|...]\n" @@ -135,40 +134,72 @@ static int init(int argc, char **argv) { // debug(2,"audio_alsa init called."); const char *str; int value; + double dvalue; - set_period_size_request = 0; - set_buffer_size_request = 0; - - config.audio_backend_latency_offset = 0; // this is the default for ALSA - config.audio_backend_buffer_desired_length = - 6615; // default for alsa with a software mixer + set_period_size_request = 0; + set_buffer_size_request = 0; + + config.audio_backend_latency_offset = 0; + config.audio_backend_buffer_desired_length = 0.15; // get settings from settings file first, allow them to be overridden by // command line options if (config.cfg != NULL) { + /* Get the desired buffer size setting. */ - if (config_lookup_int(config.cfg, - "alsa.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 66150)) + if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 66150)) { + inform("The setting alsa.audio_backend_buffer_desired_length is deprecated. " + "Use alsa.audio_backend_buffer_desired_length_in_seconds instead."); die("Invalid alsa audio backend buffer desired length \"%d\". It " "should be between 0 and " "66150, default is 6615", value); - else { - config.audio_backend_buffer_desired_length = value; + } else { + inform("The setting alsa.audio_backend_buffer_desired_length is deprecated. " + "Use alsa.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0 * value / 44100; + } + } + + /* Get the desired buffer size setting. */ + if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > 1.5)) { + die("Invalid alsa audio backend buffer desired time \"%f\". It " + "should be between 0 and " + "1.5, default is 0.15 seconds", + dvalue); + } else { + config.audio_backend_buffer_desired_length = dvalue; } } /* Get the latency offset. */ - if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", - &value)) { - if ((value < -66150) || (value > 66150)) + if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) { + if ((value < -66150) || (value > 66150)) { + inform("The setting alsa.audio_backend_latency_offset is deprecated. " + "Use alsa.audio_backend_latency_offset_in_seconds instead."); die("Invalid alsa audio backend buffer latency offset \"%d\". It " "should be between -66150 and +66150, default is 0", value); - else - config.audio_backend_latency_offset = value; + } else { + inform("The setting alsa.audio_backend_latency_offset is deprecated. " + "Use alsa.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0 * value / 44100; + } + } + + /* Get the latency offset. */ + if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset_in_seconds", &dvalue)) { + if ((dvalue < -1.0) || (dvalue > 1.5)) { + die("Invalid alsa audio backend buffer latency offset time \"%f\". It " + "should be between -1.0 and +1.5, default is 0 seconds", + dvalue); + } else { + config.audio_backend_latency_offset = dvalue; + } } /* Get the Output Device Name. */ @@ -194,7 +225,7 @@ static int init(int argc, char **argv) { alsa_mix_ctrl = (char *)str; hardware_mixer = 1; } - + /* Get the disable_synchronization setting. */ if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) { if (strcasecmp(str, "no") == 0) @@ -204,7 +235,45 @@ static int init(int argc, char **argv) { else die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\""); } - + + /* Get the output format, using the same names as aplay does*/ + if (config_lookup_string(config.cfg, "alsa.output_format", &str)) { + if (strcasecmp(str, "S16") == 0) + config.output_format = SPS_FORMAT_S16; + else if (strcasecmp(str, "S24") == 0) + config.output_format = SPS_FORMAT_S24; + else if (strcasecmp(str, "S24_3LE") == 0) + config.output_format = SPS_FORMAT_S24_3LE; + else if (strcasecmp(str, "S24_3BE") == 0) + config.output_format = SPS_FORMAT_S24_3BE; + else if (strcasecmp(str, "S32") == 0) + config.output_format = SPS_FORMAT_S32; + else if (strcasecmp(str, "U8") == 0) + config.output_format = SPS_FORMAT_U8; + else if (strcasecmp(str, "S8") == 0) + config.output_format = SPS_FORMAT_S8; + else + die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", " + "\"S24_3LE\", \"S24_3BE\" or " + "\"S32\"", + str); + } + + /* Get the output rate, which must be a multiple of 44,100*/ + if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) { + debug(1, "Value read for output rate is %d.", value); + switch (value) { + case 44100: + case 88200: + case 176400: + case 352800: + config.output_rate = value; + break; + default: + die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800", value); + } + } + /* Get the use_mmap_if_available setting. */ if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) { if (strcasecmp(str, "no") == 0) @@ -215,10 +284,9 @@ static int init(int argc, char **argv) { die("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\""); } /* Get the optional period size value */ - if (config_lookup_int(config.cfg, "alsa.period_size", - &value)) { + if (config_lookup_int(config.cfg, "alsa.period_size", &value)) { set_period_size_request = 1; - debug(1,"Value read for period size is %d.",value); + debug(1, "Value read for period size is %d.", value); if (value < 0) die("Invalid alsa period size setting \"%d\". It " "must be greater than 0.", @@ -228,10 +296,9 @@ static int init(int argc, char **argv) { } /* Get the optional buffer size value */ - if (config_lookup_int(config.cfg, "alsa.buffer_size", - &value)) { + if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) { set_buffer_size_request = 1; - debug(1,"Value read for buffer size is %d.",value); + debug(1, "Value read for buffer size is %d.", value); if (value < 0) die("Invalid alsa buffer size setting \"%d\". It " "must be greater than 0.", @@ -240,8 +307,6 @@ static int init(int argc, char **argv) { buffer_size_requested = value; } } - - optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour argv--; // so we shift the arguments to satisfy getopt() @@ -282,7 +347,7 @@ static int init(int argc, char **argv) { debug(1, "Output device name is \"%s\".", alsa_out_dev); if (hardware_mixer) { - + if (alsa_mix_dev == NULL) alsa_mix_dev = alsa_out_dev; @@ -290,25 +355,24 @@ static int init(int argc, char **argv) { open_mixer(); - if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, - &alsa_mix_maxv) < 0) + 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_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, - &alsa_mix_maxdb) == 0) { + 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.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 == SND_CTL_TLV_DB_GAIN_MUTE) { // Raspberry Pi does this debug(1, "Lowest dB value is a mute -- try minimum volume +1"); - if (snd_mixer_selem_ask_playback_vol_dB( - alsa_mix_elem, alsa_mix_minv + 1, &alsa_mix_mindb) != 0) + 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 minimum volume + 1."); } - 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); + 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 { // use the linear scale and do the db conversion ourselves debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have " @@ -323,16 +387,12 @@ static int init(int argc, char **argv) { snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl); - if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, - &alsa_mix_maxdb) == 0) { - debug(1, "Volume control \"%s\" has dB volume from %f to %f.", - alsa_mix_ctrl, - (1.0 * alsa_mix_mindb) / 100.0, - (1.0 * alsa_mix_maxdb) / 100.0); + if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { + debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl, + (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0); has_softvol = 1; } else { - debug(1, "Cannot get the dB range from the volume control \"%s\"", - alsa_mix_ctrl); + debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl); } /* @@ -352,14 +412,13 @@ static int init(int argc, char **argv) { } } if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) { - audio_alsa.mute = - &mute; // insert the mute function now we know it can do muting stuff + audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff debug(1, "Has mute ability."); } snd_mixer_close(alsa_mix_handle); } - + alsa_mix_handle = NULL; pthread_mutex_unlock(&alsa_mutex); return 0; @@ -374,7 +433,7 @@ static void deinit(void) { } int open_alsa_device(void) { - + const snd_pcm_uframes_t minimal_buffer_headroom = 352 * 2; // we accept this much headroom in the hardware buffer, but we'll // accept less @@ -386,10 +445,16 @@ int open_alsa_device(void) { // snd_pcm_uframes_t frames = 441 * 10; snd_pcm_uframes_t buffer_size, actual_buffer_length; snd_pcm_access_t access; - - // ensure no calls are made to the alsa device enquiring about the buffer length if synchronisation is disabled. - if (config.no_sync!=0) - audio_alsa.delay = NULL; + + // ensure no calls are made to the alsa device enquiring about the buffer length if + // synchronisation is disabled. + if (config.no_sync != 0) + audio_alsa.delay = NULL; + + // ensure no calls are made to the alsa device enquiring about the buffer length if + // synchronisation is disabled. + if (config.no_sync != 0) + audio_alsa.delay = NULL; ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) @@ -406,78 +471,106 @@ int open_alsa_device(void) { alsa_out_dev); } - if ((config.no_mmap == 0) && (snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) >= 0)) { - if (output_method_signalled==0) { - debug(1,"Output written using MMAP"); - output_method_signalled=1; - } + if ((config.no_mmap == 0) && + (snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) >= + 0)) { + if (output_method_signalled == 0) { + debug(1, "Output written using MMAP"); + output_method_signalled = 1; + } access = SND_PCM_ACCESS_MMAP_INTERLEAVED; alsa_pcm_write = snd_pcm_mmap_writei; } else { - if (output_method_signalled==0) { - debug(1,"Output written with RW"); - output_method_signalled=1; - } + if (output_method_signalled == 0) { + debug(1, "Output written with RW"); + output_method_signalled = 1; + } access = SND_PCM_ACCESS_RW_INTERLEAVED; alsa_pcm_write = snd_pcm_writei; } ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, access); if (ret < 0) { - die("audio_alsa: Access type not available for device \"%s\": %s", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev, + snd_strerror(ret)); } - - ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, - SND_PCM_FORMAT_S16); + snd_pcm_format_t sf; + switch (sample_format) { + case SPS_FORMAT_S8: + sf = SND_PCM_FORMAT_S8; + break; + case SPS_FORMAT_U8: + sf = SND_PCM_FORMAT_U8; + break; + case SPS_FORMAT_S16: + sf = SND_PCM_FORMAT_S16; + break; + case SPS_FORMAT_S24: + sf = SND_PCM_FORMAT_S24; + break; + case SPS_FORMAT_S24_3LE: + sf = SND_PCM_FORMAT_S24_3LE; + break; + case SPS_FORMAT_S24_3BE: + sf = SND_PCM_FORMAT_S24_3BE; + break; + case SPS_FORMAT_S32: + sf = SND_PCM_FORMAT_S32; + break; + } + ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); if (ret < 0) { - die("audio_alsa: Sample format not available for device \"%s\": %s", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Sample format %d not available for device \"%s\": %s", sf, alsa_out_dev, + snd_strerror(ret)); } ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); if (ret < 0) { - die("audio_alsa: Channels count (2) not available for device \"%s\": %s", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev, + snd_strerror(ret)); } - ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, - &my_sample_rate, &dir); + ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir); if (ret < 0) { - die("audio_alsa: Rate %iHz not available for playback: %s", - desired_sample_rate, snd_strerror(ret)); + die("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate, + snd_strerror(ret)); } - if (set_period_size_request!=0) { - debug(1,"Attempting to set the period size"); - ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested, &dir); - if (ret < 0) { - die("audio_alsa: cannot set period size of %lu: %s", - period_size_requested, snd_strerror(ret)); - snd_pcm_uframes_t actual_period_size; - snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); - if (actual_period_size!=period_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual setting: %lu",period_size_requested,actual_period_size); - } + if (set_period_size_request != 0) { + debug(1, "Attempting to set the period size"); + ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested, + &dir); + if (ret < 0) { + die("audio_alsa: cannot set period size of %lu: %s", period_size_requested, + snd_strerror(ret)); + snd_pcm_uframes_t actual_period_size; + snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); + if (actual_period_size != period_size_requested) + inform("Actual period size set to a different value than requested. Requested: %lu, actual " + "setting: %lu", + period_size_requested, actual_period_size); + } } - if (set_buffer_size_request!=0) { - debug(1,"Attempting to set the buffer size to %lu",buffer_size_requested); - ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested); - if (ret < 0) { - die("audio_alsa: cannot set buffer size of %lu: %s", - buffer_size_requested, snd_strerror(ret)); - snd_pcm_uframes_t actual_buffer_size; - snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); - if (actual_buffer_size!=buffer_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual setting: %lu",buffer_size,actual_buffer_size); - } + if (set_buffer_size_request != 0) { + debug(1, "Attempting to set the buffer size to %lu", buffer_size_requested); + ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested); + if (ret < 0) { + die("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested, + snd_strerror(ret)); + snd_pcm_uframes_t actual_buffer_size; + snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); + if (actual_buffer_size != buffer_size_requested) + inform("Actual period size set to a different value than requested. Requested: %lu, actual " + "setting: %lu", + buffer_size, actual_buffer_size); + } } ret = snd_pcm_hw_params(alsa_handle, alsa_params); if (ret < 0) { - die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev, + snd_strerror(ret)); } if (my_sample_rate != desired_sample_rate) { @@ -486,12 +579,11 @@ int open_alsa_device(void) { ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length); if (ret < 0) { - die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", - alsa_out_dev, snd_strerror(ret)); + die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev, + snd_strerror(ret)); } - if (actual_buffer_length < - config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { + if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) { /* // the dac buffer is too small, so let's try to set it buffer_size = @@ -514,146 +606,158 @@ int open_alsa_device(void) { buffer_size); } */ - debug(1,"The alsa buffer is to small (%lu bytes) to accommodate the desired backend buffer length (%ld) you have chosen.",actual_buffer_length,config.audio_backend_buffer_desired_length); + debug(1, "The alsa buffer is to small (%lu bytes) to accommodate the desired backend buffer " + "length (%ld) you have chosen.", + actual_buffer_length, config.audio_backend_buffer_desired_length); } - - if (alsa_characteristics_already_listed==0) { - alsa_characteristics_already_listed=1; - int log_level = 2; // the level at which debug information should be output - int rc; - snd_pcm_access_t access_type; - snd_pcm_format_t format_type; - snd_pcm_subformat_t subformat_type; - unsigned int val, val2; - unsigned int uval, uval2; - int sval; - int dir; - snd_pcm_uframes_t frames; - debug(log_level,"PCM handle name = '%s'", - snd_pcm_name(alsa_handle)); - -// ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); -// if (ret < 0) { -// die("audio_alsa: Cannpot get configuration for device \"%s\": no configurations " -// "available", -// alsa_out_dev); -// } + if (alsa_characteristics_already_listed == 0) { + alsa_characteristics_already_listed = 1; + int log_level = 2; // the level at which debug information should be output + int rc; + snd_pcm_access_t access_type; + snd_pcm_format_t format_type; + snd_pcm_subformat_t subformat_type; + unsigned int val, val2; + unsigned int uval, uval2; + int sval; + int dir; + snd_pcm_uframes_t frames; - debug(log_level,"alsa device parameters:"); + debug(log_level, "PCM handle name = '%s'", snd_pcm_name(alsa_handle)); - snd_pcm_hw_params_get_access(alsa_params,&access_type); - debug(log_level," access type = %s", snd_pcm_access_name(access_type)); + // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); + // if (ret < 0) { + // die("audio_alsa: Cannpot get configuration for device \"%s\": no + // configurations + //" + // "available", + // alsa_out_dev); + // } - snd_pcm_hw_params_get_format(alsa_params,&format_type); - debug(log_level," format = '%s' (%s)",snd_pcm_format_name(format_type),snd_pcm_format_description(format_type)); + debug(log_level, "alsa device parameters:"); - snd_pcm_hw_params_get_subformat(alsa_params,&subformat_type); - debug(log_level," subformat = '%s' (%s)",snd_pcm_subformat_name(subformat_type),snd_pcm_subformat_description(subformat_type)); + snd_pcm_hw_params_get_access(alsa_params, &access_type); + debug(log_level, " access type = %s", snd_pcm_access_name(access_type)); - snd_pcm_hw_params_get_channels(alsa_params, &uval); - debug(log_level," number of channels = %u", uval); + snd_pcm_hw_params_get_format(alsa_params, &format_type); + debug(log_level, " format = '%s' (%s)", snd_pcm_format_name(format_type), + snd_pcm_format_description(format_type)); - sval = snd_pcm_hw_params_get_sbits(alsa_params); - debug(log_level," number of significant bits = %d", sval); - - snd_pcm_hw_params_get_rate(alsa_params, &uval, &dir); - switch (dir) { - case -1: - debug(log_level," rate = %u frames per second (<).", uval); - break; - case 0: - debug(log_level," rate = %u frames per second (precisely).", uval); - break; - case 1: - debug(log_level," rate = %u frames per second (>).", uval); - break; - } + snd_pcm_hw_params_get_subformat(alsa_params, &subformat_type); + debug(log_level, " subformat = '%s' (%s)", snd_pcm_subformat_name(subformat_type), + snd_pcm_subformat_description(subformat_type)); - if (snd_pcm_hw_params_get_rate_numden(alsa_params,&uval, &uval2)==0) - debug(log_level," precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval, uval2, ((double)uval)/uval2); - else - debug(log_level," precise (rational) rate information unavailable."); + snd_pcm_hw_params_get_channels(alsa_params, &uval); + debug(log_level, " number of channels = %u", uval); - snd_pcm_hw_params_get_period_time(alsa_params,&uval, &dir); - switch (dir) { - case -1: - debug(log_level," period_time = %u us (<).", uval); - break; - case 0: - debug(log_level," period_time = %u us (precisely).", uval); - break; - case 1: - debug(log_level," period_time = %u us (>).", uval); - break; - } + sval = snd_pcm_hw_params_get_sbits(alsa_params); + debug(log_level, " number of significant bits = %d", sval); - snd_pcm_hw_params_get_period_size(alsa_params,&frames, &dir); - switch (dir) { - case -1: - debug(log_level," period_size = %lu frames (<).", frames); - break; - case 0: - debug(log_level," period_size = %lu frames (precisely).", frames); - break; - case 1: - debug(log_level," period_size = %lu frames (>).", frames); - break; - } + snd_pcm_hw_params_get_rate(alsa_params, &uval, &dir); + switch (dir) { + case -1: + debug(log_level, " rate = %u frames per second (<).", uval); + break; + case 0: + debug(log_level, " rate = %u frames per second (precisely).", uval); + break; + case 1: + debug(log_level, " rate = %u frames per second (>).", uval); + break; + } - snd_pcm_hw_params_get_buffer_time(alsa_params,&uval, &dir); - switch (dir) { - case -1: - debug(log_level," buffer_time = %u us (<).", uval); - break; - case 0: - debug(log_level," buffer_time = %u us (precisely).", uval); - break; - case 1: - debug(log_level," buffer_time = %u us (>).", uval); - break; - } + if (snd_pcm_hw_params_get_rate_numden(alsa_params, &uval, &uval2) == 0) + debug(log_level, " precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval, + uval2, ((double)uval) / uval2); + else + debug(log_level, " precise (rational) rate information unavailable."); + + snd_pcm_hw_params_get_period_time(alsa_params, &uval, &dir); + switch (dir) { + case -1: + debug(log_level, " period_time = %u us (<).", uval); + break; + case 0: + debug(log_level, " period_time = %u us (precisely).", uval); + break; + case 1: + debug(log_level, " period_time = %u us (>).", uval); + break; + } + + snd_pcm_hw_params_get_period_size(alsa_params, &frames, &dir); + switch (dir) { + case -1: + debug(log_level, " period_size = %lu frames (<).", frames); + break; + case 0: + debug(log_level, " period_size = %lu frames (precisely).", frames); + break; + case 1: + debug(log_level, " period_size = %lu frames (>).", frames); + break; + } + + snd_pcm_hw_params_get_buffer_time(alsa_params, &uval, &dir); + switch (dir) { + case -1: + debug(log_level, " buffer_time = %u us (<).", uval); + break; + case 0: + debug(log_level, " buffer_time = %u us (precisely).", uval); + break; + case 1: + debug(log_level, " buffer_time = %u us (>).", uval); + break; + } + + snd_pcm_hw_params_get_buffer_size(alsa_params, &frames); + switch (dir) { + case -1: + debug(log_level, " buffer_size = %lu frames (<).", frames); + break; + case 0: + debug(log_level, " buffer_size = %lu frames (precisely).", frames); + break; + case 1: + debug(log_level, " buffer_size = %lu frames (>).", frames); + break; + } + + snd_pcm_hw_params_get_periods(alsa_params, &uval, &dir); + switch (dir) { + case -1: + debug(log_level, " periods_per_buffer = %u (<).", uval); + break; + case 0: + debug(log_level, " periods_per_buffer = %u (precisely).", uval); + break; + case 1: + debug(log_level, " periods_per_buffer = %u (>).", uval); + break; + } + } - snd_pcm_hw_params_get_buffer_size(alsa_params,&frames); - switch (dir) { - case -1: - debug(log_level," buffer_size = %lu frames (<).", frames); - break; - case 0: - debug(log_level," buffer_size = %lu frames (precisely).", frames); - break; - case 1: - debug(log_level," buffer_size = %lu frames (>).", frames); - break; - } - - snd_pcm_hw_params_get_periods(alsa_params, &uval, &dir); - switch (dir) { - case -1: - debug(log_level," periods_per_buffer = %u (<).", uval); - break; - case 0: - debug(log_level," periods_per_buffer = %u (precisely).", uval); - break; - case 1: - debug(log_level," periods_per_buffer = %u (>).", uval); - break; - } - } - return (0); } -static void start(int sample_rate) { +static void start(int i_sample_rate, int i_sample_format) { // debug(2,"audio_alsa start called."); - if (sample_rate != 44100) - die("Unexpected sample rate %d -- only 44,100 supported!", sample_rate); - desired_sample_rate = sample_rate; // must be a variable + if (i_sample_rate == 0) + desired_sample_rate = 44100; // default + else + desired_sample_rate = i_sample_rate; // must be a variable + + if (i_sample_format == 0) + sample_format = SPS_FORMAT_S16; // default + else + sample_format = i_sample_format; } -int delay(long* the_delay) { - //snd_pcm_sframes_t is a signed long -- hence the return of a "long" - int reply; +int delay(long *the_delay) { + // snd_pcm_sframes_t is a signed long -- hence the return of a "long" + int reply; // debug(3,"audio_alsa delay called."); if (alsa_handle == NULL) { return -ENODEV; @@ -673,12 +777,11 @@ int delay(long* the_delay) { reply = 0; // no error } else { if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) { - *the_delay = 0; - reply = 0; // no error + *the_delay = 0; + reply = 0; // no error } else { reply = -EIO; - debug(1, "Error -- ALSA delay(): bad state: %d.", - snd_pcm_state(alsa_handle)); + debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle)); } if ((derr = snd_pcm_prepare(alsa_handle))) { ignore = snd_pcm_recover(alsa_handle, derr, 1); @@ -687,8 +790,8 @@ int delay(long* the_delay) { } pthread_mutex_unlock(&alsa_mutex); // here, occasionally pretend there's a problem with pcm_get_delay() - //if ((random() % 100000) < 3) // keep it pretty rare - // reply = -EPERM; // pretend something bad has happened + // if ((random() % 100000) < 3) // keep it pretty rare + // reply = -EPERM; // pretend something bad has happened return reply; } } @@ -700,7 +803,7 @@ static void play(short buf[], int samples) { pthread_mutex_lock(&alsa_mutex); ret = open_alsa_device(); if (hardware_mixer) - open_mixer(); + open_mixer(); pthread_mutex_unlock(&alsa_mutex); if ((hardware_mixer) && (ret == 0) && (audio_alsa.volume)) audio_alsa.volume(set_volume); @@ -711,17 +814,17 @@ static void play(short buf[], int samples) { int err, ignore; if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { - if (buf==NULL) - debug(1,"NULL buffer passed to pcm_writei -- skipping it"); - if (samples==0) - debug(1,"empty buffer being passed to pcm_writei -- skipping it"); - if ((samples!=0) && (buf!=NULL)) { - err = alsa_pcm_write(alsa_handle, (char *)buf, samples); - if (err < 0) { - debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, - snd_strerror(err)); - ignore = snd_pcm_recover(alsa_handle, err, 1); - } + if (buf == NULL) + debug(1, "NULL buffer passed to pcm_writei -- skipping it"); + if (samples == 0) + debug(1, "empty buffer being passed to pcm_writei -- skipping it"); + if ((samples != 0) && (buf != NULL)) { + err = alsa_pcm_write(alsa_handle, (char *)buf, samples); + if (err < 0) { + debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, + snd_strerror(err)); + ignore = snd_pcm_recover(alsa_handle, err, 1); + } } } else { debug(1, "Error -- ALSA device in incorrect state (%d) for play.", @@ -759,8 +862,7 @@ static void flush(void) { */ if (!((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING))) - debug(1, "Flush returning unexpected state -- %d.", - snd_pcm_state(alsa_handle)); + debug(1, "Flush returning unexpected state -- %d.", snd_pcm_state(alsa_handle)); // flush also closes the device snd_pcm_close(alsa_handle); @@ -786,7 +888,7 @@ static void parameters(audio_parameters *info) { static void volume(double vol) { pthread_mutex_lock(&alsa_mutex); - debug(2, "Setting volume db to %f.", vol); + debug(2, "Setting volume db to %f.", vol); set_volume = vol; if (hardware_mixer && alsa_mix_handle) { if (has_softvol) { @@ -794,17 +896,17 @@ static void volume(double vol) { snd_ctl_elem_value_t *value; long raw; - if (snd_ctl_convert_from_dB(ctl, elem_id, (long) vol, &raw, 0) < 0) - debug(1, "Failed converting dB gain to raw volume value for the " - "software volume control."); + if (snd_ctl_convert_from_dB(ctl, elem_id, (long)vol, &raw, 0) < 0) + debug(1, "Failed converting dB gain to raw volume value for the " + "software volume control."); snd_ctl_elem_value_alloca(&value); snd_ctl_elem_value_set_id(value, elem_id); snd_ctl_elem_value_set_integer(value, 0, raw); snd_ctl_elem_value_set_integer(value, 1, raw); if (snd_ctl_elem_write(ctl, value) < 0) - debug(1, "Failed to set playback dB volume for the software volume " - "control."); + debug(1, "Failed to set playback dB volume for the software volume " + "control."); } } else { if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 0) != 0) { @@ -824,8 +926,7 @@ static void linear_volume(double vol) { if (hardware_mixer && alsa_mix_handle) { double linear_volume = pow(10, vol); // debug(1,"Linear volume is %f.",linear_volume); - long int_vol = - alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * 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 (alsa_mix_handle) { if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0) diff --git a/audio_ao.c b/audio_ao.c index 41b823b0..87f5f749 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -24,12 +24,12 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include "audio.h" +#include "common.h" +#include +#include #include #include -#include -#include -#include "common.h" -#include "audio.h" ao_device *dev = NULL; @@ -43,38 +43,73 @@ static void help(void) { static int init(int argc, char **argv) { const char *str; int value; + double dvalue; ao_initialize(); int driver = ao_default_driver_id(); ao_option *ao_opts = NULL; - config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; - + // get settings from settings file first, allow them to be overridden by command line options if (config.cfg != NULL) { /* Get the desired buffer size setting. */ if (config_lookup_int(config.cfg, "ao.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 66150)) - die("Invalid a0 audio backend buffer desired length \"%d\". It should be between 0 and " - "66150, default is 44100", + if ((value < 0) || (value > 66150)) { + inform("The setting ao.audio_backend_buffer_desired_length is deprecated. " + "Use ao.audio_backend_buffer_desired_length_in_seconds instead."); + die("Invalid ao audio backend buffer desired length \"%d\". It " + "should be between 0 and " + "66150, default is 6615", value); - else { - config.audio_backend_buffer_desired_length = value; + } else { + inform("The setting ao.audio_backend_buffer_desired_length is deprecated. " + "Use ao.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0 * value / 44100; } } - + + /* Get the desired buffer size setting. */ + if (config_lookup_float(config.cfg, "ao.audio_backend_buffer_desired_length_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > 1.5)) { + die("Invalid ao audio backend buffer desired time \"%f\". It " + "should be between 0 and " + "1.5, default is 0.15 seconds", + dvalue); + } else { + config.audio_backend_buffer_desired_length = dvalue; + } + } + /* Get the latency offset. */ if (config_lookup_int(config.cfg, "ao.audio_backend_latency_offset", &value)) { - if ((value < -66150) || (value > 66150)) - die("Invalid ao audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", + if ((value < -66150) || (value > 66150)) { + inform("The setting ao.audio_backend_latency_offset is deprecated. " + "Use ao.audio_backend_latency_offset_in_seconds instead."); + die("Invalid ao audio backend buffer latency offset \"%d\". It " + "should be between -66150 and +66150, default is 0", value); - else - config.audio_backend_latency_offset = value; + } else { + inform("The setting ao.audio_backend_latency_offset is deprecated. " + "Use ao.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0 * value / 44100; + } + } + + /* Get the latency offset. */ + if (config_lookup_float(config.cfg, "ao.audio_backend_latency_offset_in_seconds", &dvalue)) { + if ((dvalue < -1.0) || (dvalue > 1.5)) { + die("Invalid ao audio backend buffer latency offset time \"%f\". It " + "should be between -1.0 and +1.5, default is 0 seconds", + dvalue); + } else { + config.audio_backend_latency_offset = dvalue; + } } } - optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour argv--; // so we shift the arguments to satisfy getopt() argc++; @@ -134,9 +169,11 @@ static void deinit(void) { ao_shutdown(); } -static void start(int sample_rate) { - if (sample_rate != 44100) +static void start(int sample_rate, int sample_format) { + if (sample_rate != 0) die("unexpected sample rate!"); + if (sample_format != 0) + die("unexpected sample format!"); } static void play(short buf[], int samples) { ao_play(dev, (char *)buf, samples * 4); } diff --git a/audio_dummy.c b/audio_dummy.c index 659a7202..98d14efb 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -28,11 +28,11 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include "common.h" #include "audio.h" +#include "common.h" +#include +#include +#include int Fs; long long starttime, samples_played; @@ -41,7 +41,7 @@ static int init(int argc, char **argv) { return 0; } static void deinit(void) {} -static void start(int sample_rate) { +static void start(int sample_rate, int sample_format) { Fs = sample_rate; starttime = 0; samples_played = 0; diff --git a/audio_pipe.c b/audio_pipe.c index 17bf3103..22cac672 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -28,22 +28,22 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include +#include "audio.h" +#include "common.h" +#include #include #include +#include #include -#include -#include #include -#include "common.h" -#include "audio.h" +#include +#include static int fd = -1; char *pipename = NULL; -static void start(int sample_rate) { +static void start(int sample_rate, int sample_format) { // this will leave fd as -1 if a reader hasn't been attached fd = open(pipename, O_WRONLY | O_NONBLOCK); } @@ -51,7 +51,7 @@ static void start(int sample_rate) { static void play(short buf[], int samples) { // if the file is not open, try to open it. if (fd == -1) { - fd = open(pipename, O_WRONLY | O_NONBLOCK); + fd = open(pipename, O_WRONLY | O_NONBLOCK); } // if it's got a reader, write to it. if (fd != -1) { @@ -60,17 +60,18 @@ static void play(short buf[], int samples) { } static void stop(void) { -// Don't close the pipe just because a play session has stopped. -// if (fd > 0) -// close(fd); + // Don't close the pipe just because a play session has stopped. + // if (fd > 0) + // close(fd); } static int init(int argc, char **argv) { debug(1, "pipe init"); const char *str; int value; - - config.audio_backend_buffer_desired_length = 44100; // one second. + double dvalue; + + config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; if (config.cfg != NULL) { @@ -79,36 +80,71 @@ static int init(int argc, char **argv) { if (config_lookup_string(config.cfg, "pipe.name", &str)) { pipename = (char *)str; } - + if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0)) die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); /* Get the desired buffer size setting. */ if (config_lookup_int(config.cfg, "pipe.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 132300)) - die("Invalid pipe audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100", + if ((value < 0) || (value > 66150)) { + inform("The setting pipe.audio_backend_buffer_desired_length is deprecated. " + "Use pipe.audio_backend_buffer_desired_length_in_seconds instead."); + die("Invalid pipe audio backend buffer desired length \"%d\". It " + "should be between 0 and " + "66150, default is 6615", value); - else - config.audio_backend_buffer_desired_length = value; + } else { + inform("The setting pipe.audio_backend_buffer_desired_length is deprecated. " + "Use pipe.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0 * value / 44100; + } + } + + /* Get the desired buffer size setting. */ + if (config_lookup_float(config.cfg, "pipe.audio_backend_buffer_desired_length_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > 1.5)) { + die("Invalid pipe audio backend buffer desired time \"%f\". It " + "should be between 0 and " + "1.5, default is 0.15 seconds", + dvalue); + } else { + config.audio_backend_buffer_desired_length = dvalue; + } } /* Get the latency offset. */ if (config_lookup_int(config.cfg, "pipe.audio_backend_latency_offset", &value)) { - if ((value < -66150) || (value > 66150)) - die("Invalid pipe audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", + if ((value < -66150) || (value > 66150)) { + inform("The setting pipe.audio_backend_latency_offset is deprecated. " + "Use pipe.audio_backend_latency_offset_in_seconds instead."); + die("Invalid pipe audio backend buffer latency offset \"%d\". It " + "should be between -66150 and +66150, default is 0", value); - else - config.audio_backend_latency_offset = value; + } else { + inform("The setting pipe.audio_backend_latency_offset is deprecated. " + "Use pipe.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0 * value / 44100; + } + } + + /* Get the latency offset. */ + if (config_lookup_float(config.cfg, "pipe.audio_backend_latency_offset_in_seconds", &dvalue)) { + if ((dvalue < -1.0) || (dvalue > 1.5)) { + die("Invalid pipe audio backend buffer latency offset time \"%f\". It " + "should be between -1.0 and +1.5, default is 0 seconds", + dvalue); + } else { + config.audio_backend_latency_offset = dvalue; + } } } - if ((pipename == NULL) && (argc != 1)) die("bad or missing argument(s) to pipe"); if (argc == 1) pipename = strdup(argv[0]); - - + // here, create the pipe if (mkfifo(pipename, 0644) && errno != EEXIST) die("Could not create output pipe \"%s\"", pipename); @@ -119,13 +155,11 @@ static int init(int argc, char **argv) { } static void deinit(void) { - if (fd > 0) + if (fd > 0) close(fd); } -static void help(void) { - printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); -} +static void help(void) { printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); } audio_output audio_pipe = {.name = "pipe", .help = &help, diff --git a/audio_pulse.c b/audio_pulse.c index 6fa94e97..083f863b 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -28,14 +28,14 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include "audio.h" +#include "common.h" +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include "common.h" -#include "audio.h" static pa_simple *pa_dev = NULL; @@ -110,9 +110,11 @@ static void deinit(void) { pa_dev = NULL; } -static void start(int sample_rate) { - if (sample_rate != 44100) +static void start(int sample_rate, int sample_format) { + if (sample_rate != 0) die("unexpected sample rate!"); + if (sample_format != 0) + die("unexpected sample format!"); } static void play(short buf[], int samples) { diff --git a/audio_sndio.c b/audio_sndio.c index 7093261e..357e405b 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -19,10 +19,10 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "audio.h" +#include #include #include -#include -#include "audio.h" static struct sio_hdl *sio; static struct sio_par par; @@ -56,6 +56,8 @@ static void deinit(void) { sio_close(sio); } static void start(int sample_rate) { if (sample_rate != par.rate) die("unexpected sample rate!"); + if (sample_format != 0) + die("unexpected sample format!"); sio_start(sio); } @@ -86,4 +88,4 @@ audio_output audio_sndio = {.name = "sndio", .play = &play, .volume = &volume, .parameters = NULL, - .mute= NULL}; + .mute = NULL}; diff --git a/audio_soundio.c b/audio_soundio.c new file mode 100644 index 00000000..17f09562 --- /dev/null +++ b/audio_soundio.c @@ -0,0 +1,215 @@ +#include "audio.h" +#include "common.h" +#include +#include +#include +#include + +#include + +int Fs; +long long starttime, samples_played; + +struct SoundIoOutStream *outstream; +struct SoundIo *soundio; +struct SoundIoDevice *device; +struct SoundIoRingBuffer *ring_buffer = NULL; + +static int min_int(int a, int b) { return (a < b) ? a : b; } + +static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, + int frame_count_max) { + struct SoundIoChannelArea *areas; + int frame_count; + int err; + + char *read_ptr = soundio_ring_buffer_read_ptr(ring_buffer); + int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer); + int fill_count = fill_bytes / outstream->bytes_per_frame; + + debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , " + "outstream->bytes_per_frame: %d", + frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame); + + if (frame_count_min > fill_count) { + int frame_count = frame_count_min; + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) { + debug(0, "[--->>] begin write error: %s", soundio_strerror(err)); + } + for (int frame = 0; frame < frame_count; frame += 1) { + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + memset(areas[ch].ptr, 0, outstream->bytes_per_sample); + areas[ch].ptr += areas[ch].step; + } + } + if ((err = soundio_outstream_end_write(outstream))) + debug(0, "[--->>] end write error: %s", soundio_strerror(err)); + return; + } + + int read_count = min_int(frame_count_max, fill_count); + int frames_left = read_count; + + while (frames_left > 0) { + int frame_count = frames_left; + + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) + debug(0, "[--->>] begin write error: %s", soundio_strerror(err)); + + if (frame_count <= 0) + break; + + for (int frame = 0; frame < frame_count; frame += 1) { + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + memcpy(areas[ch].ptr, read_ptr, outstream->bytes_per_sample); + areas[ch].ptr += areas[ch].step; + read_ptr += outstream->bytes_per_sample; + } + } + + if ((err = soundio_outstream_end_write(outstream))) + debug(0, "[--->>] end write error: %s", soundio_strerror(err)); + + frames_left -= frame_count; + } + + debug(3, "[--->>] Wrote: %d", read_count * outstream->bytes_per_frame); + soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream->bytes_per_frame); +} + +static void underflow_callback(struct SoundIoOutStream *outstream) { + static int count = 0; + debug(0, "underflow %d\n", ++count); +} + +static int init(int argc, char **argv) { + int err; + config.audio_backend_buffer_desired_length = 2.0; + + soundio = soundio_create(); + if (!soundio) { + debug(0, "out of memory\n"); + return 1; + } + if ((err = soundio_connect_backend(soundio, SoundIoBackendCoreAudio))) { + debug(0, "error connecting: %s", soundio_strerror(err)); + return 1; + } + soundio_flush_events(soundio); + + int default_out_device_index = soundio_default_output_device_index(soundio); + if (default_out_device_index < 0) { + debug(0, "no output device found"); + return 1; + } + + device = soundio_get_output_device(soundio, default_out_device_index); + if (!device) { + debug(0, "out of memory"); + return 1; + } + debug(0, "Output device: %s\n", device->name); + return 0; +} + +static void deinit(void) { + soundio_ring_buffer_destroy(ring_buffer); + soundio_device_unref(device); + soundio_destroy(soundio); + debug(0, "soundio audio deinit\n"); +} + +static void start(int sample_rate, int sample_format) { + Fs = sample_rate; + starttime = 0; + samples_played = 0; + int err; + + debug(1, "soundion rate: %d, format: %d", sample_rate, sample_format); + + // soundio_device_sort_channel_layouts(device); + + outstream = soundio_outstream_create(device); + outstream->format = SoundIoFormatS16NE; + outstream->sample_rate = sample_rate; + outstream->layout.channel_count = 2; + outstream->write_callback = write_callback; + outstream->underflow_callback = underflow_callback; + // outstream->software_latency = 0; + + if ((err = soundio_outstream_open(outstream))) { + debug(0, "unable to open device: %s", soundio_strerror(err)); + } + if (outstream->layout_error) + debug(0, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error)); + + int capacity = outstream->sample_rate * outstream->bytes_per_frame; + ring_buffer = soundio_ring_buffer_create(soundio, capacity); + if (!ring_buffer) + debug(0, "unable to create ring buffer: out of memory"); + char *buf = soundio_ring_buffer_write_ptr(ring_buffer); + memset(buf, 0, capacity); + soundio_ring_buffer_advance_write_ptr(ring_buffer, capacity); + + if ((err = soundio_outstream_start(outstream))) { + debug(0, "unable to start outstream: %s", soundio_strerror(err)); + } + + debug(1, "libsoundio output started\n"); +} + +static void play(short buf[], int samples) { + int err; + int free_bytes = soundio_ring_buffer_free_count(ring_buffer); + int written_bytes = 0; + int write_bytes = 0; + int left_bytes = samples * outstream->bytes_per_frame; + char *write_ptr = soundio_ring_buffer_write_ptr(ring_buffer); + + debug(3, "[<<---] samples: %d , size: %d", samples, left_bytes); + write_bytes = min_int(left_bytes, free_bytes); + debug(3, "[<<---] left_bytes: %d, write_bytes: %d, free_bytes: %d\n", left_bytes, write_bytes, + free_bytes); + + if (write_bytes) { + memcpy(write_ptr, (char *)buf, write_bytes); + written_bytes += write_bytes; + soundio_ring_buffer_advance_write_ptr(ring_buffer, write_bytes); + debug(3, "[<<---] Written to buffer : %d\n", written_bytes); + } +} + +static void parameters(audio_parameters *info) { + info->minimum_volume_dB = -30.0; + info->maximum_volume_dB = 0.0; + debug(2, "Parameters\n"); + debug(2, "Current Volume dB: %f\n", info->current_volume_dB); + debug(2, "Minimum Volume dB: %d\n", info->minimum_volume_dB); + debug(2, "Maximum Volume dB: %d\n", info->maximum_volume_dB); +} + +static void stop(void) { + soundio_outstream_destroy(outstream); + soundio_ring_buffer_clear(ring_buffer); + debug(1, "libsoundio output stopped\n"); +} + +static void flush(void) { + soundio_ring_buffer_clear(ring_buffer); + debug(1, "libsoundio output flushed\n"); +} + +static void help(void) { printf(" There are no options for libsoundio.\n"); } + +audio_output audio_soundio = {.name = "soundio", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = &flush, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = ¶meters, + .mute = NULL}; diff --git a/audio_stdout.c b/audio_stdout.c index b8b0aa53..2e79f76b 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -27,24 +27,20 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include +#include "audio.h" +#include "common.h" +#include #include #include +#include #include -#include -#include "common.h" -#include "audio.h" +#include static int fd = -1; -static void start(int sample_rate) { - fd = STDOUT_FILENO; -} +static void start(int sample_rate, int sample_format) { fd = STDOUT_FILENO; } -static void play(short buf[], int samples) { - int ignore = write(fd, buf, samples * 4); -} +static void play(short buf[], int samples) { int ignore = write(fd, buf, samples * 4); } static void stop(void) { // don't close stdout @@ -53,27 +49,66 @@ static void stop(void) { static int init(int argc, char **argv) { const char *str; int value; + double dvalue; - config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; if (config.cfg != NULL) { /* Get the desired buffer size setting. */ if (config_lookup_int(config.cfg, "stdout.audio_backend_buffer_desired_length", &value)) { - if ((value < 0) || (value > 132300)) - die("Invalid stdout audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100", + if ((value < 0) || (value > 66150)) { + inform("The setting audio_backend_buffer_desired_length is deprecated. " + "Use audio_backend_buffer_desired_length_in_seconds instead."); + die("Invalid stdout audio backend buffer desired length \"%d\". It " + "should be between 0 and " + "66150, default is 6615", value); - else - config.audio_backend_buffer_desired_length = value; + } else { + inform("The stdout.setting audio_backend_buffer_desired_length is deprecated. " + "Use stdout.audio_backend_buffer_desired_length_in_seconds instead."); + config.audio_backend_buffer_desired_length = 1.0 * value / 44100; + } + } + + /* Get the desired buffer size setting. */ + if (config_lookup_float(config.cfg, "stdout.audio_backend_buffer_desired_length_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > 1.5)) { + die("Invalid stdout audio backend buffer desired time \"%f\". It " + "should be between 0 and " + "1.5, default is 0.15 seconds", + dvalue); + } else { + config.audio_backend_buffer_desired_length = dvalue; + } } /* Get the latency offset. */ if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", &value)) { - if ((value < -66150) || (value > 66150)) - die("Invalid stdout audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", + if ((value < -66150) || (value > 66150)) { + inform("The setting stdout.audio_backend_latency_offset is deprecated. " + "Use stdout.audio_backend_latency_offset_in_seconds instead."); + die("Invalid stdout audio backend buffer latency offset \"%d\". It " + "should be between -66150 and +66150, default is 0", value); - else - config.audio_backend_latency_offset = value; + } else { + inform("The setting stdout.audio_backend_latency_offset is deprecated. " + "Use stdout.audio_backend_latency_offset_in_seconds instead."); + config.audio_backend_latency_offset = 1.0 * value / 44100; + } + } + + /* Get the latency offset. */ + if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset_in_seconds", + &dvalue)) { + if ((dvalue < -1.0) || (dvalue > 1.5)) { + die("Invalid stdout audio backend buffer latency offset time \"%f\". It " + "should be between -1.0 and +1.5, default is 0 seconds", + dvalue); + } else { + config.audio_backend_latency_offset = dvalue; + } } } return 0; @@ -83,19 +118,17 @@ static void deinit(void) { // don't close stdout } -static void help(void) { - printf(" stdout takes no arguments\n"); -} +static void help(void) { printf(" stdout takes no arguments\n"); } audio_output audio_stdout = {.name = "stdout", - .help = &help, - .init = &init, - .deinit = &deinit, - .start = &start, - .stop = &stop, - .flush = NULL, - .delay = NULL, - .play = &play, - .volume = NULL, - .parameters = NULL, - .mute = NULL}; + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = NULL, + .mute = NULL}; diff --git a/common.c b/common.c index bf721273..53d7715e 100644 --- a/common.c +++ b/common.c @@ -25,20 +25,20 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include #include -#include -#include -#include +#include #include +#include +#include +#include +#include #include #include +#include +#include -#include #include "common.h" +#include #ifdef COMPILE_FOR_OSX #include @@ -47,27 +47,36 @@ #endif #ifdef HAVE_LIBSSL -#include -#include -#include #include #include +#include +#include +#include #endif #ifdef HAVE_LIBPOLARSSL -#include -#include -#include -#include -#include "polarssl/entropy.h" #include "polarssl/ctr_drbg.h" +#include "polarssl/entropy.h" +#include +#include +#include +#include #if POLARSSL_VERSION_NUMBER >= 0x01030000 #include "polarssl/compat-1.2.h" #endif #endif -#include "common.h" +#ifdef HAVE_LIBMBEDTLS +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/entropy.h" +#include +#include +#include +#include + +#endif + #include // true if Shairport Sync is supposed to be sending output to the output device, false otherwise @@ -126,6 +135,59 @@ void inform(char *format, ...) { daemon_log(LOG_INFO, "%s", s); } +#ifdef HAVE_LIBMBEDTLS +char *base64_enc(uint8_t *input, int length) { + char *buf = NULL; + size_t dlen = 0; + int rc = mbedtls_base64_encode(NULL, 0, &dlen, input, length); + if (rc && (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)) + debug(1, "Error %d getting length of base64 encode.", rc); + else { + buf = (char *)malloc(dlen); + rc = mbedtls_base64_encode((unsigned char *)buf, dlen, &dlen, input, length); + if (rc != 0) + debug(1, "Error %d encoding base64.", rc); + } + return buf; +} + +uint8_t *base64_dec(char *input, int *outlen) { + // slight problem here is that Apple cut the padding off their challenges. We must restore it + // before passing it in to the decoder, it seems + uint8_t *buf = NULL; + size_t dlen = 0; + int inbufsize = ((strlen(input) + 3) / 4) * 4; // this is the size of the input buffer we will + // send to the decoder, but we need space for 3 + // extra "="s and a NULL + char *inbuf = malloc(inbufsize + 4); + if (inbuf == 0) + debug(1, "Can't malloc memory for inbuf in base64_decode."); + else { + strcpy(inbuf, input); + strcat(inbuf, "==="); + // debug(1,"base64_dec called with string \"%s\", length %d, filled string: \"%s\", length %d.", + // input,strlen(input),inbuf,inbufsize); + int rc = mbedtls_base64_decode(NULL, 0, &dlen, (unsigned char *)inbuf, inbufsize); + if (rc && (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)) + debug(1, "Error %d getting decode length, result is %d.", rc, dlen); + else { + // debug(1,"Decode size is %d.",dlen); + buf = malloc(dlen); + if (buf == 0) + debug(1, "Can't allocate memory in base64_dec."); + else { + rc = mbedtls_base64_decode(buf, dlen, &dlen, (unsigned char *)inbuf, inbufsize); + if (rc != 0) + debug(1, "Error %d in base64_dec.", rc); + } + } + free(inbuf); + } + *outlen = dlen; + return buf; +} +#endif + #ifdef HAVE_LIBPOLARSSL char *base64_enc(uint8_t *input, int length) { char *buf = NULL; @@ -280,6 +342,63 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { } #endif +#ifdef HAVE_LIBMBEDTLS +uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { + mbedtls_pk_context pkctx; + mbedtls_rsa_context *trsa; + const char *pers = "rsa_encrypt"; + size_t olen = *outlen; + int rc; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + + mbedtls_entropy_init(&entropy); + + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, + strlen(pers)); + + mbedtls_pk_init(&pkctx); + + rc = mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key, sizeof(super_secret_key), + NULL, 0); + if (rc != 0) + debug(1, "Error %d reading the private key.", rc); + + 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); + rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, + inlen, input, outbuf); + if (rc != 0) + debug(1, "mbedtls_pk_encrypt error %d.", rc); + *outlen = trsa->len; + break; + case RSA_MODE_KEY: + mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1); + outbuf = malloc(trsa->len); + rc = mbedtls_rsa_pkcs1_decrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, + &olen, input, outbuf, trsa->len); + if (rc != 0) + debug(1, "mbedtls_pk_decrypt error %d.", rc); + *outlen = olen; + break; + default: + die("bad rsa mode"); + } + + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + mbedtls_pk_free(&pkctx); + return outbuf; +} +#endif + #ifdef HAVE_LIBPOLARSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { rsa_context trsa; @@ -440,7 +559,7 @@ double vol2attn(double vol, long max_db, long min_db) { double vol_setting = 0; if ((vol <= 0.0) && (vol >= -30.0)) { - long range_db = max_db - min_db; // this will be a positive nunmber + long range_db = max_db - min_db; // this will be a positive number // debug(1,"Volume min %ddB, max %ddB, range %ddB.",min_db,max_db,range_db); // double first_slope = -3000.0; // this is the slope of the attenuation at the high end -- 30dB // for the full rotation. @@ -479,7 +598,8 @@ uint64_t get_absolute_time_in_fp() { struct timespec tn; // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt clock_gettime(CLOCK_MONOTONIC, &tn); - time_now_fp = ((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000; // types okay + time_now_fp = + ((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000; // types okay #endif #ifdef COMPILE_FOR_OSX uint64_t time_now_mach; @@ -514,13 +634,13 @@ uint64_t get_absolute_time_in_fp() { } ssize_t non_blocking_write(int fd, const void *buf, size_t count) { - void *ibuf = (void *)buf; - size_t bytes_remaining = count; - int rc = 0; + void *ibuf = (void *)buf; + size_t bytes_remaining = count; + int rc = 0; struct pollfd ufds[1]; - while ((bytes_remaining>0) && (rc==0)) { - // check that we can do some writing - ufds[0].fd = fd; + while ((bytes_remaining > 0) && (rc == 0)) { + // check that we can do some writing + ufds[0].fd = fd; ufds[0].events = POLLOUT; rc = poll(ufds, 1, 5000); if (rc < 0) { @@ -528,51 +648,117 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { } else if (rc == 0) { // warn("non-blocking write timeout waiting for pipe to become ready for writing"); rc = -2; - } else { //rc > 0, implying it might be ready - size_t bytes_written = write(fd,ibuf,bytes_remaining); - if (bytes_written==-1) { - // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno)); - rc = -1; - } else { - ibuf += bytes_written; - bytes_remaining -= bytes_written; - } + } else { // rc > 0, implying it might be ready + size_t bytes_written = write(fd, ibuf, bytes_remaining); + if (bytes_written == -1) { + // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno)); + rc = -1; + } else { + ibuf += bytes_written; + bytes_remaining -= bytes_written; + } } - } - if (rc==0) - return count-bytes_remaining; // this is just to mimic a normal write/3. - else - return rc; + } + if (rc == 0) + return count - bytes_remaining; // this is just to mimic a normal write/3. + else + return rc; // return write(fd,buf,count); } -/* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ +/* from + * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 + */ -char *str_replace ( const char *string, const char *substr, const char *replacement ){ +char *str_replace(const char *string, const char *substr, const char *replacement) { char *tok = NULL; char *newstr = NULL; char *oldstr = NULL; char *head = NULL; - + /* if either substr or replacement is NULL, duplicate string a let caller handle it */ - if ( substr == NULL || replacement == NULL ) return strdup (string); - newstr = strdup (string); + if (substr == NULL || replacement == NULL) + return strdup(string); + newstr = strdup(string); head = newstr; - while ( (tok = strstr ( head, substr ))){ + while ((tok = strstr(head, substr))) { oldstr = newstr; - newstr = malloc ( strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1 ); + newstr = malloc(strlen(oldstr) - strlen(substr) + strlen(replacement) + 1); /*failed to alloc mem, free old string and return NULL */ - if ( newstr == NULL ){ - free (oldstr); + if (newstr == NULL) { + free(oldstr); return NULL; } - memcpy ( newstr, oldstr, tok - oldstr ); - memcpy ( newstr + (tok - oldstr), replacement, strlen ( replacement ) ); - memcpy ( newstr + (tok - oldstr) + strlen( replacement ), tok + strlen ( substr ), strlen ( oldstr ) - strlen ( substr ) - ( tok - oldstr ) ); - memset ( newstr + strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) , 0, 1 ); + memcpy(newstr, oldstr, tok - oldstr); + memcpy(newstr + (tok - oldstr), replacement, strlen(replacement)); + memcpy(newstr + (tok - oldstr) + strlen(replacement), tok + strlen(substr), + strlen(oldstr) - strlen(substr) - (tok - oldstr)); + memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement), 0, 1); /* move back head right after the last replacement */ - head = newstr + (tok - oldstr) + strlen( replacement ); - free (oldstr); + head = newstr + (tok - oldstr) + strlen(replacement); + free(oldstr); } return newstr; } + +/* from http://burtleburtle.net/bob/rand/smallprng.html */ + +// typedef uint64_t u8; +typedef struct ranctx { + uint64_t a; + uint64_t b; + uint64_t c; + uint64_t d; +} ranctx; + +static struct ranctx rx; + +#define rot(x, k) (((x) << (k)) | ((x) >> (64 - (k)))) +uint64_t ranval(ranctx *x) { + uint64_t e = x->a - rot(x->b, 7); + x->a = x->b ^ rot(x->c, 13); + x->b = x->c + rot(x->d, 37); + x->c = x->d + e; + x->d = e + x->a; + return x->d; +} + +void raninit(ranctx *x, uint64_t seed) { + uint64_t i; + x->a = 0xf1ea5eed, x->b = x->c = x->d = seed; + for (i = 0; i < 20; ++i) { + (void)ranval(x); + } +} + +void r64init(uint64_t seed) { raninit(&rx, seed); } + +uint64_t r64u() { return (ranval(&rx)); } + +int64_t r64i() { return (ranval(&rx) >> 1); } + +/* generate an array of 64-bit random numbers */ +const int ranarraylength = 1009; // these will be 8-byte numbers. + +uint64_t *ranarray; + +int ranarraynext; + +void ranarrayinit() { + ranarray = (uint64_t *)malloc(ranarraylength * sizeof(uint64_t)); + int i; + for (i = 0; i < ranarraylength; i++) + ranarray[i] = r64u(); + ranarraynext = 0; +} + +uint64_t ranarrayval() { + uint64_t v = ranarray[ranarraynext]; + ranarraynext = (ranarraynext++) % ranarraylength; +} + +void r64arrayinit() { ranarrayinit(); } + +uint64_t ranarray64u() { return (ranarrayval()); } + +int64_t ranarray64i() { return (ranarrayval(&rx) >> 1); } diff --git a/common.h b/common.h index 32ee301d..3a1cab58 100644 --- a/common.h +++ b/common.h @@ -1,12 +1,12 @@ #ifndef _COMMON_H #define _COMMON_H +#include #include #include -#include -#include "config.h" #include "audio.h" +#include "config.h" #include "mdns.h" #if defined(__APPLE__) && defined(__MACH__) @@ -46,12 +46,34 @@ enum stuffing_type { enum playback_mode_type { ST_stereo = 0, ST_mono, + ST_reverse_stereo, + ST_left_only, + ST_right_only, } playback_mode_type; +enum decoders_supported_type { + decoder_hammerton = 0, + decoder_apple_alac, +} decoders_supported_type; + +// the following enum must _exactly match the snd_pcm_format_t definition in the alsa pcm.h file. + +enum sps_format_t { + SPS_FORMAT_UNKNOWN = 0, + SPS_FORMAT_S8, + SPS_FORMAT_U8, + SPS_FORMAT_S16, + SPS_FORMAT_S24, + SPS_FORMAT_S24_3LE, + SPS_FORMAT_S24_3BE, + SPS_FORMAT_S32, +} sps_format_t; + typedef struct { config_t *cfg; char *password; - char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running on host %h" + char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running + // on host %h" #ifdef CONFIG_METADATA int metadata_enabled; char *metadata_pipename; @@ -65,10 +87,13 @@ typedef struct { int udp_port_base; int udp_port_range; int ignore_volume_control; - int no_sync; // disable synchronisation, even if it's available - int no_mmap; // disable use of mmap-based output, even if it's available - int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never - // resync. + int volume_max_db_set; // set to 1 if a maximum volume db has been set + int volume_max_db; + int no_sync; // disable synchronisation, even if it's available + int no_mmap; // disable use of mmap-based output, even if it's available + double resyncthreshold; // if it get's out of whack my more than this number of seconds, resync. + // Zero means never + // resync. int allow_session_interruption; int timeout; // while in play mode, exit if no packets of audio come in for more than this number // of seconds . Zero means never exit. @@ -79,27 +104,37 @@ typedef struct { char *mdns_name; mdns_backend *mdns; int buffer_start_fill; - 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 + int64_t latency; + int64_t userSuppliedLatency; // overrides all other latencies -- use with caution + int64_t iTunesLatency; // supplied with --iTunesLatency option + int64_t AirPlayLatency; // supplied with --AirPlayLatency option + int64_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option int daemonise; - int statistics_requested,use_negotiated_latencies; + int statistics_requested, use_negotiated_latencies; enum playback_mode_type playback_mode; char *cmd_start, *cmd_stop; int cmd_blocking; - int tolerance; // allow this much drift before attempting to correct it + double tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; + int decoders_supported; + int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by + // David Hammerton char *pidfile; // char *logfile; // char *errfile; char *configfile; - char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by default “_raop._tcp.”. - long audio_backend_buffer_desired_length; // this will be the desired number of frames in the - // audio backend buffer -- the DAC buffer for ALSA - long audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency there might be in the audio path - uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's native range. + char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by + // default “_raop._tcp.”. + char *interface; // a string containg the interface name, or NULL if nothing specified + int interface_index; // only valid if the interface string is non-NULL + double audio_backend_buffer_desired_length; // this will be the length in seconds of the + // audio backend buffer -- the DAC buffer for ALSA + double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any + // fixed latency there might be in the audio path + uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's + // native range. + enum sps_format_t output_format; + int output_rate; } shairport_cfg; // true if Shairport Sync is supposed to be sending output to the output device, false otherwise @@ -110,8 +145,20 @@ void set_requested_connection_state_to_output(int v); ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places -/* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */ -char *str_replace ( const char *string, const char *substr, const char *replacement ); +/* from + * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 + */ +char *str_replace(const char *string, const char *substr, const char *replacement); + +// based on http://burtleburtle.net/bob/rand/smallprng.html + +void r64init(uint64_t seed); +uint64_t r64u(); +int64_t r64i(); + +void r64arrayinit(); +uint64_t ranarray64u(); +int64_t ranarray64i(); int debuglev; void die(char *format, ...); @@ -144,7 +191,7 @@ config_t config_file_stuff; int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative int64_t session_corrections; -uint32_t play_segment_reference_frame; +int64_t play_segment_reference_frame; uint64_t play_segment_reference_frame_remote_time; void command_start(void); diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 86ad20c5..c616ed68 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -2,7 +2,7 @@ .SH NAME shairport-sync \- Synchronised Audio Player for iTunes / AirPlay .SH SYNOPSIS -\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--forkedDaapdLatency=\fB\fIlatency\fB]\fB [--get-cover-art]\fB [-i \fB\fIlatency\fB]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB +\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--get-cover-art]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB shairport-sync -D\fB @@ -63,7 +63,7 @@ These are the settings available within the \fBgeneral\f1 group: \fBname=\f1\fI"service_name"\f1\fB;\f1 Use this \fIservice_name\f1 to identify this player in iTunes, etc. -The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "2.8.4" and \fB%V\f1 for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata". +The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc". The default is "%H", which is replaced by the hostname with the first letter capitalised. .TP @@ -91,11 +91,11 @@ When shairport-sync starts to play audio, it establises three UDP connections to \fBudp_port_range=\f1\fIrange\f1\fB;\f1 Use this in conjunction with the prevous setting to specify the \fIrange\f1 of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found. .TP -\fBdrift=\f1\fIframes\f1\fB;\f1 -Allow playback to drift up to \fIframes\f1 out of exact synchronization 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 \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections. +\fBdrift_tolerance_in_seconds=\f1\fIseconds\f1\fB;\f1 +Allow playback to drift up to \fIseconds\f1 out of exact synchronization before attempting to correct it. The default is 0.002 seconds, i.e. 2 milliseconds. 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 \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections. This setting replaces the deprecated \fBdrift\f1 setting. .TP -\fBresync_threshold=\f1\fIthreshold\f1\fB;\f1 -Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fI0\f1 to disable resynchronisation. +\fBresync_threshold_in_seconds=\f1\fIthreshold\f1\fB;\f1 +Resynchronise if timings differ by more than \fIthreshold\f1 seconds. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 0.050 seconds, i.e. 50 milliseconds. Specify 0.0 to disable resynchronisation. This setting replaces the deprecated \fBresync_threshold\f1 setting. .TP \fBlog_verbosity=\f1\fI0\f1\fB;\f1 Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1. @@ -103,6 +103,9 @@ Use this to specify how much debugging information should be output or logged. T \fBignore_volume_control=\f1\fI"choice"\f1\fB;\f1 Set this \fIchoice\f1 to \fI"yes"\f1 if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is \fI"no"\f1. .TP +\fBvolume_max_db=\f1\fIdBvalue\f1\fB;\f1 +Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB. +.TP \fBvolume_range_db=\f1\fIdBvalue\f1\fB;\f1 Use this \fIdBvalue\f1 to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume. @@ -118,7 +121,13 @@ If you omit this setting, the native range of the mixer is used. Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is \fI"_raop._tcp"\f1. .TP \fBplayback_mode=\f1\fI"mode"\f1\fB;\f1 -The \fImode\f1 can be \fI"stereo"\f1 or \fI"mono"\f1. Default is \fI"stereo"\f1. +The \fImode\f1 can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". +.TP +\fBinterface=\f1\fI"name"\f1\fB;\f1 +Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour. +.TP +\fBalac_decoder=\f1\fI"decodername"\f1\fB;\f1 +This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work. .TP \fB"ALSA" SETTINGS\f1 These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as \fBalsamixer\f1 or \fBaplay\f1 to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the \fBalsa\f1 group settings: @@ -134,11 +143,17 @@ This setting is deprecated and is ignored. For your information, its functionali \fBmixer_device=\f1\fI"mixer_device"\f1\fB;\f1 By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device. .TP -\fBaudio_backend_latency_offset=\f1\fIoffset\f1\fB;\f1 -Set this \fIoffset\f1, 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 \fI-4410\f1. Default is \fI0\f1. +\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset\f1\fB;\f1 +Set this \fIoffset\f1, in seconds, to compensate for a fixed delay in the audio back end. For example, if the output device delays by 100 ms, set this to -0.1. .TP -\fBaudio_backend_buffer_desired_length=\f1\fIlength\f1\fB;\f1 -Use this to set the desired number frames to be in the output device's hardware output buffer. The default is \fI6,615\f1 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying, or it may exceed the hardware buffer size. It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata. +\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIlength\f1\fB;\f1 +Use this to set the desired length, in seconds, of the queue of audio frames in the output device's hardware output buffer. The default is 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying, or it may exceed the hardware buffer size. It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata. +.TP +\fBoutput_rate=\f1\fIframe rate\f1\fB;\f1 +Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available. +.TP +\fBoutput_format=\f1\fI"format"\f1\fB;\f1 +Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify. "S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16". If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither. .TP \fBdisable_synchronization=\f1\fI"no"\f1\fB;\f1 This is an advanced setting and is for debugging only. Set to \fI"yes"\f1 to disable synchronization. Default is \fI"no"\f1. If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to audio buffer overflow or underflow. @@ -157,44 +172,44 @@ These settings are for the PIPE backend, used to route audio to a named unix pip Use the \fIname\f1 setting to set the name and location of the pipe. -There are two further settings affecting timing that might be useful if the pipe reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length\f1 setting affects the nominal output buffer size. +There are two further settings affecting timing that might be useful if the pipe reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size. These are the settings available within the \fBpipe\f1 group: .TP \fBname=\f1\fI"/path/to/pipe"\f1\fB;\f1 Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it. .TP -\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 -Packets of audio frames are written to the pipe synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. +\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1 +Packets of audio frames are written to the pipe synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0. .TP -\fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 44,100 frames. +\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1 +Use this setting, in seconds, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 1 second. Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 1.0 seconds. .TP \fB"STDOUT" SETTINGS\f1 -These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, interleaved stereo. +These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format, usually PCM 16 bit little endian, 44,100 samples per second, interleaved stereo. -There are two settings affecting timing that might be useful if the stdout reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length\f1 setting affects the nominal output buffer size. +There are two settings affecting timing that might be useful if the stdout reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size. These are the settings available within the \fBstdout\f1 group: .TP -\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 -Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. +\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1 +Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0. .TP -\fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100 frames. +\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1 +Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 1 second. Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 1.0 seconds. .TP \fB"AO" SETTINGS\f1 These settings are for the AO backend, used for the libao audio library. -There are two settings affecting timing. The \fIaudio_backend_latency_offset\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length\f1 setting affects the nominal output buffer size. +There are two settings affecting timing. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size. These are the settings available within the \fBao\f1 group: .TP -\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1 -Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0. +\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1 +Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0. .TP -\fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1 -Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the libao system. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 44,100 frames. +\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1 +Use this setting, in seconds, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the libao system. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 1 second. Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 1.0 seconds. .TP \fB"METADATA" SETTINGS\f1 shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It sends the metadata to a pipe, by default \fI/tmp/shairport-sync-metadata\f1. To process metadata, shairport-sync must have been compiled with metadata support included. You can check that this is so by running the command \fB$ shairport-sync -V\f1; the identification string will contain the word \fBmetadata\f1. @@ -238,29 +253,6 @@ If \fBchoice\f1 is set to "yes", then another source will be able to interrupt a .TP \fBsession_timeout=\f1\fIseconds\f1\fB;\f1 If a play session has been established and the source disappears without warning (such as a device going out of range of a network) then wait for \fIseconds\f1 seconds before ending the session. Once the session has terminated, other devices can use it. The default is 120 seconds. -.TP -\fB"LATENCIES" SETTINGS\f1 -The latencies settings are now deprecated. Do not use them for new installations. They will be removed from a future version of shairport-sync. - -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 now sets latencies automatically using information supplied by the source, typically either 88,200 or 99,577 frames. - -The following relates to the old scheme of using fixed latencies, which ignored the latency information supplied by the source. There are four default latency settings. 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, including 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. - -Note: If you are thinking of changing latencies to compensate for a delay in the audio output device, then instead of changing these individual latencies, use the \fBaudio_backend_latency_offset\f1 setting in the \fBalsa\f1 group (or the appropriate other group if you're not outputing through the alsa backend). -.TP -\fBitunes=\f1\fIlatency\f1\fB;\f1 -This is the \fIlatency\f1, in frames, used for iTunes 10 or later. Default is 99,400. -.TP -\fBairplay=\f1\fIlatency\f1\fB;\f1 -This is the \fIlatency\f1, in frames, used for AirPlay devices, including iOS devices and iTunes and Quicktime Player when they are playing video. Default is 88,200. -.TP -\fBforkedDaapd=\f1\fIlatency\f1\fB;\f1 -This is the \fIlatency\f1, in frames, used for forkedDaapd sources. Default is 99,400. -.TP -\fBdefault=\f1\fIlatency\f1\fB;\f1 -This is the \fIlatency\f1, in frames, used when the source is unrecognised. Default is 88,200. .SH OPTIONS This section is about the command-line options available in shairport-sync. @@ -273,15 +265,10 @@ There are two kinds of command-line options for shairport-sync: regular \fBprogr These command-line options are used by shairport-sync itself. .TP \fB-a \f1\fIservice name\f1\fB | --name=\f1\fIservice name\f1 -Use this \fIservice name\f1 to identify this player in iTunes, etc. The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "2.8.4" and \fB%V\f1 for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata". +Use this \fIservice name\f1 to identify this player in iTunes, etc. The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc". The default is "%H", which is replaced by the hostname with the first letter capitalised. .TP -\fB-A | --AirPlayLatency=\f1\fIlatency\f1 -Use this \fIlatency\f1, in frames, for audio streamed from an AirPlay device. The default is 88,200 frames, where there are 44,100 frames to the second. - -Please note that this feature is deprecated and will be removed in a future version of shairport-sync. -.TP \fB-B \f1\fIprogram\f1\fB | --on-start=\f1\fIprogram\f1 Execute \fIprogram\f1 when playback is about to begin. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have \fI#!/bin/sh\f1 (or whatever is appropriate) in the headline. @@ -303,11 +290,6 @@ Execute \fIprogram\f1 when playback has ended. Specify the full path to the prog If you want shairport-sync to wait until the command has completed before continuing, select the \fB-w\f1 option as well. .TP -\fB--forkedDaapdLatency=\f1\fIlatency\f1 -Use this \fIlatency\f1, in frames, for audio streamed from a forked-daapd based source. The default is 99,400 frames, where there are 44,100 frames to the second. - -Please note that this feature is deprecated and will be removed in a future version of shairport-sync. -.TP \fB--get-coverart\f1 This option requires the \fB--meta-dir\f1 option to be set, and enables shairport-sync to request cover art from the source and to transmit it through the metadata pipe. @@ -316,11 +298,6 @@ Please note that cover art data may be very large, and may place too great a bur \fB-h | --help\f1 Print brief help message and exit. .TP -\fB-i | --iTunesLatency=\f1\fIlatency\f1 -Use this \fIlatency\f1, in frames, for audio streamed from an iTunes source, where iTunes is Version 10 or later. The default is 99,400 frames, where there are 44,100 frames to the second. If the source is iTunes but is earler than Version 10, the \fIdefault latency\f1 is used (see the \fB-L\f1 option). Some third party programs masquerade as older versions of iTunes. - -Please note that this feature is deprecated and will be removed in a future version of shairport-sync. -.TP \fB-k | --kill\f1 Kill the shairport-sync daemon and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option). .TP @@ -350,7 +327,7 @@ Reconnect the shairport-sync daemon to the output device and exit. It may take a Please note that this feature is deprecated and will be removed in a future version of shairport-sync. .TP \fB-r \f1\fIthreshold\f1\fB | --resync=\f1\fIthreshold\f1 -Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation. +Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation. This setting is deprecated and will be removed in a future version of shairport-sync. .TP \fB--statistics\f1 Print some statistics in the standard output, or in the logfile if in daemon mode. @@ -364,7 +341,7 @@ Exit play mode if the stream disappears for more than \fItimeout\f1 seconds. When shairport-sync plays an audio stream, it starts a play session and will return a busy signal to any other sources that attempt to use it. If the audio stream disappears for longer than \fItimeout\f1 seconds, the play session will be terminated. If you specify a timeout time of \fB0\f1, shairport-sync will never signal that it is busy and will not prevent other sources from "barging in" on an existing play session. The default value is 120 seconds. .TP \fB--tolerance=\f1\fIframes\f1 -Allow playback to be up to \fIframes\f1 out of exact synchronization 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 \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections. +Allow playback to be up to \fIframes\f1 out of exact synchronization 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 \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections. This setting is deprecated and will be removed in a future version of shairport-sync. .TP \fB-V | --version\f1 Print version information and exit. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 96885703..db1ce722 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -4,7 +4,7 @@ 16 bits, the range of + // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from + // -32768 to +32767 + + // See the original paper at + // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf + // by Lipshitz, Wannamaker and Vanderkooy, 1992. + + int64_t dither_mask; + switch (format) { + case SPS_FORMAT_S32: + dither_mask = (int64_t)1 << (64 + 1 - 32); + break; + case SPS_FORMAT_S24: + case SPS_FORMAT_S24_3LE: + case SPS_FORMAT_S24_3BE: + dither_mask = (int64_t)1 << (64 + 1 - 24); + break; + case SPS_FORMAT_S16: + dither_mask = (int64_t)1 << (64 + 1 - 16); + break; + case SPS_FORMAT_S8: + case SPS_FORMAT_U8: + dither_mask = (int64_t)1 << (64 + 1 - 8); + break; + } + dither_mask -= 1; + // int64_t r = r64i(); + int64_t r = ranarray64i(); // use an array of precalculated pseudorandom numbers rather than + // calculating them on the fly. Should be easier on low-powered + // processors + + int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask); + previous_random_number = r; + // add dither, allowing for clipping + if (tpdf >= 0) { + if (INT64_MAX - tpdf >= hyper_sample) + hyper_sample += tpdf; + else + hyper_sample = INT64_MAX; + } else { + if (INT64_MIN - tpdf <= hyper_sample) + hyper_sample += tpdf; + else + hyper_sample = INT64_MIN; + } + // dither is complete here + } + + // move the result to the desired position in the int64_t + char *op = *outp; + uint8_t byt; + switch (format) { + case SPS_FORMAT_S32: + hyper_sample >>= (64 - 32); + *(int32_t *)op = hyper_sample; + result = 4; + break; + case SPS_FORMAT_S24_3LE: + hyper_sample >>= (64 - 24); + byt = (uint8_t)hyper_sample; + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 8); + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 16); + *op++ = byt; + result = 3; + break; + case SPS_FORMAT_S24_3BE: + hyper_sample >>= (64 - 24); + byt = (uint8_t)(hyper_sample >> 16); + *op++ = byt; + byt = (uint8_t)(hyper_sample >> 8); + *op++ = byt; + byt = (uint8_t)hyper_sample; + *op++ = byt; + result = 3; + break; + case SPS_FORMAT_S24: + hyper_sample >>= (64 - 24); + *(int32_t *)op = hyper_sample; + result = 4; + break; + case SPS_FORMAT_S16: + hyper_sample >>= (64 - 16); + *(int16_t *)op = (int16_t)hyper_sample; + result = 2; + break; + case SPS_FORMAT_S8: + hyper_sample >>= (int8_t)(64 - 8); + *op = hyper_sample; + result = 1; + break; + case SPS_FORMAT_U8: + hyper_sample >>= (uint8_t)(64 - 8); + hyper_sample += 128; + *op = hyper_sample; + result = 1; + break; + } + + *outp += result; } static inline short dithered_vol(short sample) { @@ -445,28 +760,31 @@ static inline short dithered_vol(short sample) { out = (long)sample * fix_volume; if (fix_volume < 0x10000) { - - // add a TPDF dither -- see http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf - // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25 - - // I think, for a 32 --> 16 bits, the range of - // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from -32768 to +32767 - - // See the original paper at http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf - // by Lipshitz, Wannamaker and Vanderkooy, 1992. - - long tpdf = rand_in_range(65536+1) - rand_in_range(65536+1); - // Check there's no clipping -- if there is, - if (tpdf>=0) { - if (LONG_MAX-tpdf>=out) - out += tpdf; - else - out = LONG_MAX; + + // add a TPDF dither -- see + // http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf + // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25 + + // I think, for a 32 --> 16 bits, the range of + // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from + // -32768 to +32767 + + // See the original paper at + // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf + // by Lipshitz, Wannamaker and Vanderkooy, 1992. + + long tpdf = rand_in_range(65536 + 1) - rand_in_range(65536 + 1); + // Check there's no clipping -- if there is, + if (tpdf >= 0) { + if (LONG_MAX - tpdf >= out) + out += tpdf; + else + out = LONG_MAX; } else { - if (LONG_MIN-tpdf<=out) - out += tpdf; - else - out = LONG_MIN; + if (LONG_MIN - tpdf <= out) + out += tpdf; + else + out = LONG_MIN; } } return out >> 16; @@ -484,7 +802,7 @@ static abuf_t *buffer_get_frame(void) { pthread_mutex_lock(&ab_mutex); int wait; - long dac_delay = 0; // long because alsa returns a long + long dac_delay = 0; // long because alsa returns a long do { // get the time local_time_now = get_absolute_time_in_fp(); // type okay @@ -527,11 +845,14 @@ static abuf_t *buffer_get_frame(void) { flush_requested = 0; } pthread_mutex_unlock(&flush_mutex); + uint32_t flush_limit = 0; if (ab_synced) { do { curframe = audio_buffer + BUFIDX(ab_read); - if ((ab_read!=ab_write) && (curframe->ready)) { // it could be synced and empty, under exceptional circumstances, with the frame unused, thus apparently ready + if ((ab_read != ab_write) && (curframe->ready)) { // it could be synced and empty, under + // exceptional circumstances, with the + // frame unused, thus apparently ready if (curframe->sequence_number != ab_read) { // some kind of sync problem has occurred. @@ -546,18 +867,14 @@ static abuf_t *buffer_get_frame(void) { } } - if ((flush_rtp_timestamp != 0) && - ((curframe->timestamp == flush_rtp_timestamp) || - seq32_order(curframe->timestamp, flush_rtp_timestamp))) { - debug(1, "Dropping flushed packet seqno %u, timestamp %u", curframe->sequence_number, + if ((flush_rtp_timestamp != 0) && (curframe->timestamp <= flush_rtp_timestamp)) { + debug(1, "Dropping flushed packet seqno %u, timestamp %lld", curframe->sequence_number, curframe->timestamp); curframe->ready = 0; flush_limit++; ab_read = SUCCESSOR(ab_read); } - if ((flush_rtp_timestamp != 0) && - (!seq32_order(curframe->timestamp, - flush_rtp_timestamp))) // if we have gone past the flush boundary time + if (curframe->timestamp > flush_rtp_timestamp) flush_rtp_timestamp = 0; } } while ((flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); @@ -570,12 +887,15 @@ static abuf_t *buffer_get_frame(void) { curframe = audio_buffer + BUFIDX(ab_read); if (curframe->ready) { - notified_buffer_empty=0; // at least one buffer now -- diagnostic only. + notified_buffer_empty = 0; // at least one buffer now -- diagnostic only. 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); + int have_sent_prefiller_silence; // set true when we have sent some silent frames to the + // DAC + int64_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); + reference_timestamp *= output_sample_ratio; 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)); @@ -608,47 +928,57 @@ static abuf_t *buffer_get_frame(void) { // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. // -4410 frames. - int64_t delta = ((int64_t)first_packet_timestamp - (int64_t)reference_timestamp)+config.latency+config.audio_backend_latency_offset; // uint32_t to int64_t is okay and int32t to int64t promotion is okay. - - if (delta>=0) { - uint64_t delta_fp_sec = (delta << 32) / 44100; // int64_t which is positive - first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; + debug(1, "Output sample ratio is %d", output_sample_ratio); + + int64_t delta = (first_packet_timestamp - reference_timestamp) + + config.latency * output_sample_ratio + + config.audio_backend_latency_offset * config.output_rate; + + if (delta >= 0) { + int64_t delta_fp_sec = + (delta << 32) / config.output_rate; // int64_t which is positive + first_packet_time_to_play = reference_timestamp_time + delta_fp_sec; } else { int64_t abs_delta = -delta; - uint64_t delta_fp_sec = (abs_delta << 32) / 44100; // int64_t which is positive - first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; + int64_t delta_fp_sec = + (abs_delta << 32) / config.output_rate; // int64_t which is positive + first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } if (local_time_now >= first_packet_time_to_play) { debug( 1, "First packet is late! It should have played before now. Flushing 0.1 seconds"); - player_flush(first_packet_timestamp + 4410); + player_flush(first_packet_timestamp + 4410 * output_sample_ratio); } } } 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)+config.latency+config.audio_backend_latency_offset; // uint32_t to int64_t is okay and int32t to int64t promotion is okay. - - if (delta>=0) { - uint64_t delta_fp_sec = (delta << 32) / 44100; // int64_t which is positive - first_packet_time_to_play=reference_timestamp_time+delta_fp_sec; + int64_t delta = (first_packet_timestamp - reference_timestamp) + + config.latency * output_sample_ratio + + config.audio_backend_latency_offset * config.output_rate; + + if (delta >= 0) { + int64_t delta_fp_sec = + (delta << 32) / config.output_rate; // int64_t which is positive + first_packet_time_to_play = reference_timestamp_time + delta_fp_sec; } else { int64_t abs_delta = -delta; - uint64_t delta_fp_sec = (abs_delta << 32) / 44100; // int64_t which is positive - first_packet_time_to_play=reference_timestamp_time-delta_fp_sec; + int64_t delta_fp_sec = + (abs_delta << 32) / config.output_rate; // int64_t which is positive + first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } - int64_t max_dac_delay = 4410; + int64_t max_dac_delay = config.output_rate / 10; int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC if (local_time_now >= first_packet_time_to_play) { // we've gone past the time... // debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp // of %llx and dac_delay of %d and %d packets; - // flush.",(((tn-first_packet_time_to_play)*44100)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, + // flush.",(((tn-first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, // ab_write)); if (config.output->flush) @@ -660,15 +990,15 @@ static abuf_t *buffer_get_frame(void) { } else { // first_packet_time_to_play is definitely later than local_time_now if ((config.output->delay) && (have_sent_prefiller_silence != 0)) { - int resp = config.output->delay(&dac_delay); + int resp = config.output->delay(&dac_delay); if (resp != 0) { - debug(1, "Error %d getting dac_delay in buffer_get_frame.",resp); + debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp); dac_delay = 0; - } + } } else dac_delay = 0; int64_t gross_frame_gap = - ((first_packet_time_to_play - local_time_now) * 44100) >> 32; + ((first_packet_time_to_play - local_time_now) * config.output_rate) >> 32; int64_t exact_frame_gap = gross_frame_gap - dac_delay; if (exact_frame_gap < 0) { // we've gone past the time... @@ -685,11 +1015,12 @@ static abuf_t *buffer_get_frame(void) { int64_t fs = filler_size; if (fs > (max_dac_delay - dac_delay)) fs = max_dac_delay - dac_delay; - if (fs<0) { - debug(2,"frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",max_dac_delay, dac_delay); - fs=0; + if (fs < 0) { + debug(2, "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld", + max_dac_delay, dac_delay); + fs = 0; } - if ((exact_frame_gap <= fs) || (exact_frame_gap <= frame_size * 2)) { + if ((exact_frame_gap <= fs) || (exact_frame_gap <= max_frames_per_packet * 2)) { fs = exact_frame_gap; // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, // with %d packets, ab_read is %04x, ab_write is @@ -698,15 +1029,18 @@ static abuf_t *buffer_get_frame(void) { ab_buffering = 0; } signed short *silence; - //if (fs==0) - // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and dac_delay of %lld.",gross_frame_gap,dac_delay); - // the fs (number of frames of silence to play) can be zero in the DAC doesn't start ouotputting frames for a while -- it could get loaded up but not start responding for many milliseconds. - if (fs!=0) { - silence = malloc(bytes_per_audio_frame*fs); - if (silence==NULL) - debug(1,"Failed to allocate %d byte silence buffer.",fs); + // if (fs==0) + // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and + // dac_delay of %lld.",gross_frame_gap,dac_delay); + // the fs (number of frames of silence to play) can be zero in the DAC doesn't start + // ouotputting frames for a while -- it could get loaded up but not start responding + // for many milliseconds. + if (fs != 0) { + silence = malloc(output_bytes_per_frame * fs); + if (silence == NULL) + debug(1, "Failed to allocate %d byte silence buffer.", fs); else { - memset(silence, 0, bytes_per_audio_frame*fs); + memset(silence, 0, output_bytes_per_frame * fs); // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d, // with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write)); config.output->play(silence, fs); @@ -720,11 +1054,14 @@ static abuf_t *buffer_get_frame(void) { if (ab_buffering == 0) { // not the time of the playing of the first frame 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); + get_reference_timestamp_stuff(&play_segment_reference_frame, &reference_timestamp_time, + &play_segment_reference_frame_remote_time); + play_segment_reference_frame *= output_sample_ratio; #ifdef CONFIG_METADATA - send_ssnc_metadata('prsm', NULL, 0, 0); // "resume", but don't wait if the queue is locked + send_ssnc_metadata('prsm', NULL, 0, + 0); // "resume", but don't wait if the queue is locked #endif - } + } } } } @@ -743,24 +1080,32 @@ static abuf_t *buffer_get_frame(void) { int do_wait = 0; // don't wait unless we can really prove we must if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { - do_wait = 1; // if the current frame exists and is ready, then wait unless it's time to let it go... - 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); // all types okay - if (reference_timestamp) { // if we have a reference time - uint32_t packet_timestamp = curframe->timestamp; // types okay - int64_t delta = (int64_t)packet_timestamp - (int64_t)reference_timestamp; // uint32_t to int64_t is okay. - int64_t offset = config.latency + config.audio_backend_latency_offset - - config.audio_backend_buffer_desired_length; // all arguments are int32_t, so expression promotion okay - int64_t net_offset = delta + offset; // okay + do_wait = + 1; // if the current frame exists and is ready, then wait unless it's time to let it go... + int64_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); // all types okay + reference_timestamp *= output_sample_ratio; + if (reference_timestamp) { // if we have a reference time + int64_t packet_timestamp = curframe->timestamp; // types okay + int64_t delta = packet_timestamp - reference_timestamp; + int64_t offset = + config.latency * output_sample_ratio + + config.audio_backend_latency_offset * config.output_rate - + config.audio_backend_buffer_desired_length * + config.output_rate; // all arguments are int32_t, so expression promotion okay + int64_t net_offset = delta + offset; // okay uint64_t time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { - uint64_t net_offset_fp_sec = (net_offset << 32) / 44100; // int64_t which is positive - time_to_play += net_offset_fp_sec; // using the latency requested... + uint64_t net_offset_fp_sec = + (net_offset << 32) / config.output_rate; // int64_t which is positive + time_to_play += net_offset_fp_sec; // using the latency requested... // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec); } else { int64_t abs_net_offset = -net_offset; - uint64_t net_offset_fp_sec = (abs_net_offset << 32) / 44100; // int64_t which is positive + uint64_t net_offset_fp_sec = + (abs_net_offset << 32) / config.output_rate; // int64_t which is positive time_to_play -= net_offset_fp_sec; // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec); } @@ -770,21 +1115,21 @@ static abuf_t *buffer_get_frame(void) { } } } - if (do_wait==0) - if ((ab_synced!=0) && (ab_read==ab_write)) { // the buffer is empty! - if (notified_buffer_empty==0) { - debug(1,"Buffers exhausted."); - notified_buffer_empty=1; + if (do_wait == 0) + if ((ab_synced != 0) && (ab_read == ab_write)) { // the buffer is empty! + if (notified_buffer_empty == 0) { + debug(1, "Buffers exhausted."); + notified_buffer_empty = 1; } - do_wait=1; + do_wait = 1; } wait = (ab_buffering || (do_wait != 0) || (!ab_synced)) && (!please_stop); if (wait) { uint64_t time_to_wait_for_wakeup_fp = - ((uint64_t)1 << 32) / 44100; // this is time period of one frame + ((uint64_t)1 << 32) / input_rate; // this is time period of one frame time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets - time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time + time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; @@ -802,7 +1147,6 @@ static abuf_t *buffer_get_frame(void) { #endif #ifdef COMPILE_FOR_OSX uint64_t sec = time_to_wait_for_wakeup_fp >> 32; - ; uint64_t nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; struct timespec time_to_wait; time_to_wait.tv_sec = sec; @@ -834,11 +1178,10 @@ static abuf_t *buffer_get_frame(void) { } } - if (!curframe->ready) { // debug(1, "Supplying a silent frame for frame %u", read); missing_packets++; - memset(curframe->data, 0, bytes_per_audio_frame*frame_size); + memset(curframe->data, 0, input_bytes_per_frame * max_frames_per_packet); curframe->timestamp = 0; } curframe->ready = 0; @@ -857,15 +1200,82 @@ static inline short shortmean(short a, short b) { return r; } +static inline int32_t mean_32(int32_t a, int32_t b) { + int64_t al = a; + int64_t bl = b; + int64_t mean = (al + bl) / 2; + int32_t r = (int32_t)mean; + if (r != mean) + debug(1, "Error calculating average of two int32_ts"); + return r; +} + +// this takes an array of signed 32-bit integers and (a) removes or inserts a frame as specified in +// stuff, +// (b) multiplies each sample by the fixedvolume (a 16-bit quantity) +// (c) dithers the result to the output size 32/24/16 bits +// (d) outputs the result in the approprate format +// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32 + // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 -static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff) { - if ((stuff > 1) || (stuff < -1) || (length <100)) { +static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, + char *outptr, int stuff, int dither) { + int tstuff = stuff; + char *l_outptr = outptr; + if ((stuff > 1) || (stuff < -1) || (length < 100)) { // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); - return length; + tstuff = 0; // if any of these conditions hold, don't stuff anything/ + } + + int i; + int stuffsamp = length; + if (tstuff) + // stuffsamp = rand() % (length - 1); + stuffsamp = + (rand() % (length - 2)) + 1; // ensure there's always a sample before and after the item + + pthread_mutex_lock(&vol_mutex); + for (i = 0; i < stuffsamp; i++) { // the whole frame, if no stuffing + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither); + }; + if (tstuff) { + if (tstuff == 1) { + // debug(3, "+++++++++"); + // interpolate one sample + process_sample(mean_32(inptr[-2], inptr[0]), &l_outptr, l_output_format, fix_volume, dither); + process_sample(mean_32(inptr[-1], inptr[1]), &l_outptr, l_output_format, fix_volume, dither); + } else if (stuff == -1) { + // debug(3, "---------"); + inptr++; + inptr++; + } + + // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the + // rest. + int remainder = length; + if (tstuff < 0) + remainder = remainder + tstuff; // don't run over the correct end of the output buffer + + for (i = stuffsamp; i < remainder; i++) { + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither); + } + } + pthread_mutex_unlock(&vol_mutex); + + return length + tstuff; +} + +static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff) { + int tstuff = stuff; + if ((stuff > 1) || (stuff < -1) || (length < 100)) { + // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); + tstuff = 0; // if any of these conditions hold, don't stuff anything/ } int i; int stuffsamp = length; - if (stuff) + if (tstuff) // stuffsamp = rand() % (length - 1); stuffsamp = (rand() % (length - 2)) + 1; // ensure there's always a sample before and after the item @@ -875,8 +1285,8 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff *outptr++ = dithered_vol(*inptr++); *outptr++ = dithered_vol(*inptr++); }; - if (stuff) { - if (stuff == 1) { + if (tstuff) { + if (tstuff == 1) { // debug(3, "+++++++++"); // interpolate one sample //*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1); @@ -888,11 +1298,12 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff inptr++; inptr++; } - - // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the rest. + + // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the + // rest. int remainder = length; - if (stuff<0) - remainder = remainder+stuff; // don't run over the correct end of the output buffer + if (tstuff < 0) + remainder = remainder + tstuff; // don't run over the correct end of the output buffer for (i = stuffsamp; i < remainder; i++) { *outptr++ = dithered_vol(*inptr++); @@ -901,22 +1312,115 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff } pthread_mutex_unlock(&vol_mutex); - return length + stuff; + return length + tstuff; } #ifdef HAVE_LIBSOXR +// this takes an array of signed 32-bit integers and +// (a) uses libsoxr to +// resample the array to have one more or one less frame, as specified in +// stuff, +// (b) multiplies each sample by the fixedvolume (a 16-bit quantity) +// (c) dithers the result to the output size 32/24/16 bits +// (d) outputs the result in the approprate format +// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32 + +static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length, + enum sps_format_t l_output_format, char *outptr, int stuff, + int dither) { + if (scratchBuffer == NULL) { + die("soxr scratchBuffer not initialised."); + } + int tstuff = stuff; + if ((stuff > 1) || (stuff < -1) || (length < 100)) { + // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); + tstuff = 0; // if any of these conditions hold, don't stuff anything/ + } + + if (tstuff) { + // debug(1,"Stuff %d.",stuff); + soxr_io_spec_t io_spec; + io_spec.itype = SOXR_INT32_I; + io_spec.otype = SOXR_INT32_I; + io_spec.scale = 1.0; // this seems to crash if not = 1.0 + io_spec.e = NULL; + io_spec.flags = 0; + + size_t odone; + + soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */ + inptr, length, NULL, /* Input. */ + scratchBuffer, length + tstuff, &odone, /* Output. */ + &io_spec, /* Input, output and transfer spec. */ + NULL, NULL); /* Default configuration.*/ + + if (error) + die("soxr error: %s\n", "error: %s\n", soxr_strerror(error)); + + if (odone > length + 1) + die("odone = %d!\n", odone); + + int i; + int32_t *ip, *op; + ip = inptr; + op = scratchBuffer; + + const int gpm = 5; + // keep the first (dpm) samples, to mitigate the Gibbs phenomenon + for (i = 0; i < gpm; i++) { + *op++ = *ip++; + *op++ = *ip++; + } + + // keep the last (dpm) samples, to mitigate the Gibbs phenomenon + op = scratchBuffer + (length + tstuff - gpm) * sizeof(int32_t); + ip = inptr + (length - gpm) * sizeof(int32_t); + for (i = 0; i < gpm; i++) { + *op++ = *ip++; + *op++ = *ip++; + } + + // now, do the volume, dither and formatting processing + ip = scratchBuffer; + char *l_outptr = outptr; + for (i = 0; i < length + tstuff; i++) { + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither); + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither); + //*op = dithered_vol(*op); + // op++; + //*op = dithered_vol(*op); + // op++; + }; + + } else { // the whole frame, if no stuffing + + // now, do the volume, dither and formatting processing + int32_t *ip = inptr; + char *l_outptr = outptr; + int i; + + for (i = 0; i < length; i++) { + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither); + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither); + //*op++ = dithered_vol(*ip++); + //*op++ = dithered_vol(*ip++); + }; + } + return length + tstuff; +} // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) { + int tstuff = stuff; if ((stuff > 1) || (stuff < -1) || (length < 100)) { - // debug(1, "Stuff argument to sox_stuff_buffer must be from -1 to +1 and length must be > 100."); - return length; + // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100."); + tstuff = 0; // if any of these conditions hold, don't stuff anything/ } int i; short *ip, *op; ip = inptr; op = outptr; - if (stuff) { + if (tstuff) { // debug(1,"Stuff %d.",stuff); soxr_io_spec_t io_spec; io_spec.itype = SOXR_INT16_I; @@ -927,9 +1431,9 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) size_t odone; - soxr_error_t error = soxr_oneshot(length, length + stuff, 2, /* Rates and # of chans. */ - inptr, length, NULL, /* Input. */ - outptr, length + stuff, &odone, /* Output. */ + soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */ + inptr, length, NULL, /* Input. */ + outptr, length + tstuff, &odone, /* Output. */ &io_spec, /* Input, output and transfer spec. */ NULL, NULL); /* Default configuration.*/ @@ -948,7 +1452,7 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) } // keep the last (dpm) samples, to mitigate the Gibbs phenomenon - op = outptr + (length + stuff - gpm) * sizeof(short); + op = outptr + (length + tstuff - gpm) * sizeof(short); ip = inptr + (length - gpm) * sizeof(short); for (i = 0; i < gpm; i++) { *op++ = *ip++; @@ -959,7 +1463,7 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) if (fix_volume != 65536.0) { // pthread_mutex_lock(&vol_mutex); op = outptr; - for (i = 0; i < length + stuff; i++) { + for (i = 0; i < length + tstuff; i++) { *op = dithered_vol(*op); op++; *op = dithered_vol(*op); @@ -977,7 +1481,7 @@ static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) }; // pthread_mutex_unlock(&vol_mutex); } - return length + stuff; + return length + tstuff; } #endif @@ -986,21 +1490,52 @@ typedef struct stats { // statistics for running averages } stats_t; static void *player_thread_func(void *arg) { - struct inter_threads_record itr; - itr.please_stop = 0; - // create and start the timing, control and audio receiver threads - pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; - pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr); + struct inter_threads_record itr; + itr.please_stop = 0; + timestamp_epoch = 0; // indicate that the next timestamp will be the first one. + maximum_timestamp_interval = input_rate * 60; // actually there shouldn't be more than about 13v + // seconds of a gap between successive rtptimes, at + // worst + + output_sample_ratio = config.output_rate / input_rate; + + debug(1, "Output sample ratio is %d.", output_sample_ratio); + + max_frame_size_change = 1 * output_sample_ratio; // we add or subtract one frame at the nominal + // rate, multiply it by the frame ratio. + + output_bytes_per_frame = 4; + switch (config.output_format) { + case SPS_FORMAT_S24_3LE: + case SPS_FORMAT_S24_3BE: + output_bytes_per_frame = 6; + break; + case SPS_FORMAT_S24: + output_bytes_per_frame = 8; + break; + case SPS_FORMAT_S32: + output_bytes_per_frame = 8; + break; + } + + debug(1, "Output frame bytes is %d.", output_bytes_per_frame); + + // create and start the timing, control and audio receiver threads + pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; + pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr); pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)&itr); pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)&itr); - session_corrections = 0; - play_segment_reference_frame = 0; // zero signals that we are not in a play segment - // check that there are enough buffers to accommodate the desired latency and the latency offset - - int maximum_latency = config.latency+config.audio_backend_latency_offset; - if ((maximum_latency+(352-1))/352 + 10 > BUFFER_FRAMES) - die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame packets may be accommodated.",maximum_latency,BUFFER_FRAMES); + session_corrections = 0; + play_segment_reference_frame = 0; // zero signals that we are not in a play segment + + // check that there are enough buffers to accommodate the desired latency and the latency offset + + int maximum_latency = config.latency + config.audio_backend_latency_offset * input_rate; + if ((maximum_latency + (352 - 1)) / 352 + 10 > BUFFER_FRAMES) + die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame " + "packets may be accommodated.", + maximum_latency, BUFFER_FRAMES); connection_state_to_output = get_requested_connection_state_to_output(); // this is about half a minute #define trend_interval 3758 @@ -1033,25 +1568,90 @@ static void *player_thread_func(void *arg) { char rnstate[256]; initstate(time(NULL), rnstate, 256); - signed short *inbuf, *outbuf, *silence; - outbuf = malloc(bytes_per_audio_frame*(frame_size+max_frame_size_change)); - if (outbuf==NULL) - debug(1,"Failed to allocate memory for an output buffer."); - silence = malloc(bytes_per_audio_frame*frame_size); - if (silence==NULL) - debug(1,"Failed to allocate memory for a silence buffer."); - memset(silence, 0, bytes_per_audio_frame*frame_size); + signed short *inbuf, *tbuf, *silence; + + int32_t *sbuf; + + char *outbuf; + + int inbuflength; + + int output_bit_depth = 16; // default; + + switch (config.output_format) { + case SPS_FORMAT_S8: + case SPS_FORMAT_U8: + output_bit_depth = 8; + break; + case SPS_FORMAT_S16: + output_bit_depth = 16; + break; + case SPS_FORMAT_S24: + case SPS_FORMAT_S24_3LE: + case SPS_FORMAT_S24_3BE: + output_bit_depth = 24; + break; + case SPS_FORMAT_S32: + output_bit_depth = 32; + break; + } + + debug(1, "Output bit depth is %d.", output_bit_depth); + + if (input_bit_depth > output_bit_depth) { + debug(1, "Dithering will be enabled because the input bit depth is greater than the output bit " + "depth"); + } + if (fix_volume != 0x10000) { + debug(1, "Dithering will be enabled the output volume is being altered in software"); + } + + // if we are changing any of the parameters of the input, like sample rate or sample depth, then + // we + // need an intermediate "transition" buffer + + if (1) { + // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { + // debug(1,"Define tbuf of length + // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); + tbuf = malloc(sizeof(int32_t) * 2 * + (max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + if (tbuf == NULL) + debug(1, "Failed to allocate memory for the transition buffer."); + sbuf = 0; + if (config.packet_stuffing == ST_soxr) { // needed for stuffing + sbuf = malloc(sizeof(int32_t) * 2 * + (max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + if (sbuf == NULL) + debug(1, "Failed to allocate memory for the transition buffer."); + } + } else { + tbuf = 0; + } + + // We might need an output buffer and a buffer of silence. + // The size of these dependents on the number of frames, the size of each frame and the maximum + // size change + outbuf = malloc(output_bytes_per_frame * + (max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + if (outbuf == NULL) + debug(1, "Failed to allocate memory for an output buffer."); + silence = malloc(output_bytes_per_frame * max_frames_per_packet * output_sample_ratio); + if (silence == NULL) + debug(1, "Failed to allocate memory for a silence buffer."); + memset(silence, 0, output_bytes_per_frame * max_frames_per_packet * output_sample_ratio); late_packet_message_sent = 0; first_packet_timestamp = 0; missing_packets = late_packets = too_late_packets = resend_requests = 0; flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to be used // as a null operand, so we'll use it like that too - int sync_error_out_of_bounds = 0; // number of times in a row that there's been a serious sync error + int sync_error_out_of_bounds = + 0; // number of times in a row that there's been a serious sync error if (config.statistics_requested) { if ((config.output->delay)) { - if (config.no_sync==0) { - inform("sync error in frames, " + if (config.no_sync == 0) { + inform("sync error in milliseconds, " "net correction in ppm, " "corrections in ppm, " "total packets, " @@ -1063,7 +1663,7 @@ static void *player_thread_func(void *arg) { "min buffer occupancy, " "max buffer occupancy"); } else { - inform("sync error in frames, " + inform("sync error in milliseconds, " "total packets, " "missing packets, " "late packets, " @@ -1074,7 +1674,8 @@ static void *player_thread_func(void *arg) { "max buffer occupancy"); } } else { - inform("total packets, " + inform("sync error in milliseconds, " + "total packets, " "missing packets, " "late packets, " "too late packets, " @@ -1088,22 +1689,112 @@ static void *player_thread_func(void *arg) { while (!please_stop) { abuf_t *inframe = buffer_get_frame(); if (inframe) { - if (inframe->data) { + inbuf = inframe->data; + inbuflength = inframe->length; + if (inbuf) { play_number++; // if it's a supplied silent frame, let us know... if (inframe->timestamp == 0) { // debug(1,"Player has a supplied silent frame."); last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); // manage the packet out of sequence minder - if (inframe->data==NULL) - debug(1,"NULL inframe->data to play -- skipping it."); + if (inbuf == NULL) + debug(1, "NULL inbuf to play -- skipping it."); else { - if (inframe->length==0) - debug(1,"empty frame to play -- skipping it."); + if (inbuflength == 0) + debug(1, "empty frame to play -- skipping it (1)."); else - config.output->play(inframe->data, inframe->length); + config.output->play(inbuf, inbuflength); } } else { + + int enable_dither = 0; + if ((fix_volume != 0x10000) || (input_bit_depth > output_bit_depth)) + enable_dither = 1; + + // here, let's transform the frame of data, if necessary + + if (tbuf != NULL) { // this will be null if no changes are needed + switch (input_bit_depth) { + case 16: { + int i, j; + int16_t ls, rs; + int16_t *inps = inbuf; + int16_t *outps = tbuf; + int32_t *outpl = (int32_t *)tbuf; + for (i = 0; i < inbuflength; i++) { + ls = *inps++; + rs = *inps++; + + // here, do the mode stuff -- mono / reverse stereo / leftonly / rightonly + + switch (config.playback_mode) { + case ST_mono: { + int both = ls + rs; + // Note -- this is assuming 16 bit signed. + if (both > INT16_MAX) { + both = INT16_MAX; + } else if (both < INT16_MIN) { + both = INT16_MIN; + } + int16_t sboth = (int16_t)both; + ls = sboth; + rs = sboth; + } break; + case ST_reverse_stereo: { + int16_t t = ls; + ls = rs; + rs = t; + } break; + case ST_left_only: + rs = ls; + break; + case ST_right_only: + ls = rs; + break; + } + + // here, replicate the samples if you're upsampling + + for (j = 0; j < output_sample_ratio; j++) { + // raise the 16-bit sample to 32 bits. + int32_t t = ls << 16; + *outpl++ = t; + int32_t u = rs << 16; + *outpl++ = u; + + /* + switch (output_bit_depth) { + case 16: + *outps++=ls; + *outps++=rs; + break; + case 24: { + int32_t t = ls<<8; + *outpl++=t; + int32_t u = rs<<8; + *outpl++=u; + } break; + case 32: { + int32_t t = ls<<16; + *outpl++=t; + int32_t u = rs<<16; + *outpl++=u; + } break; + } + */ + } + } + + } break; + default: + die("Shairport Sync only supports 16 bit input"); + } + + // inbuf = tbuf; + inbuflength *= output_sample_ratio; + } + // We have a frame of data. We need to see if we want to add or remove a frame from it to // keep in sync. // So we calculate the timing error for the first frame in the DAC. @@ -1113,13 +1804,14 @@ static void *player_thread_func(void *arg) { at_least_one_frame_seen = 1; - 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); // types okay - + int64_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); // types okay + reference_timestamp *= output_sample_ratio; int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t - nt = inframe->timestamp; // uint32_t to int64_t + nt = inframe->timestamp; // uint32_t to int64_t uint64_t local_time_now = get_absolute_time_in_fp(); // types okay // struct timespec tn; @@ -1130,12 +1822,18 @@ static void *player_thread_func(void *arg) { int64_t td = 0; int64_t td_in_frames = 0; if (local_time_now >= reference_timestamp_time) { - // debug(1,"td is %lld.",td); - td = local_time_now - reference_timestamp_time; // this is the positive value. Conversion is positive uint64_t to int64_t, thus okay - td_in_frames = (td * 44100) >> 32; + // debug(1,"td is %lld.",td); + td = local_time_now - reference_timestamp_time; // this is the positive value. + // Conversion is positive uint64_t to + // int64_t, thus okay + td_in_frames = (td * config.output_rate) >> 32; } else { - td = reference_timestamp_time - local_time_now; // this is the absolute value, which should be negated. Conversion is positive uint64_t to int64_t, thus okay. - td_in_frames = (td * 44100) >> 32; //use the absolute td value for the present. Types okay + td = reference_timestamp_time - local_time_now; // this is the absolute value, which + // should be negated. Conversion is + // positive uint64_t to int64_t, thus + // okay. + td_in_frames = (td * config.output_rate) >> + 32; // use the absolute td value for the present. Types okay td_in_frames = -td_in_frames; td = -td; // should be okay, as the range of values should be very small w.r.t 64 bits } @@ -1147,60 +1845,64 @@ static void *player_thread_func(void *arg) { // check sequencing if (last_seqno_read == -1) - last_seqno_read = inframe->sequence_number; //int32_t from seq_t, i.e. uint16_t, so okay. + last_seqno_read = + inframe->sequence_number; // int32_t from seq_t, i.e. uint16_t, so okay. else { - last_seqno_read = SUCCESSOR(last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. - if (inframe->sequence_number != last_seqno_read) { //seq_t, ei.e. uint16_t and int32_t, so okay - debug( - 1, - "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u and ab_write: %u.", - last_seqno_read, inframe->sequence_number,ab_read,ab_write); + last_seqno_read = + SUCCESSOR(last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. + if (inframe->sequence_number != + last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay + debug(1, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", + last_seqno_read, inframe->sequence_number, ab_read, ab_write); last_seqno_read = inframe->sequence_number; // reset warning... } } - buffer_occupancy = seq_diff(ab_read, ab_write); // int32_t from int32 + buffer_occupancy = seq_diff(ab_read, ab_write); // int32_t from int32 - if (buffer_occupancy < minimum_buffer_occupancy) - minimum_buffer_occupancy = buffer_occupancy; + if (buffer_occupancy < minimum_buffer_occupancy) + minimum_buffer_occupancy = buffer_occupancy; - if (buffer_occupancy > maximum_buffer_occupancy) - maximum_buffer_occupancy = buffer_occupancy; + if (buffer_occupancy > maximum_buffer_occupancy) + maximum_buffer_occupancy = buffer_occupancy; // here, we want to check (a) if we are meant to do synchronisation, // (b) if we have a delay procedure, (c) if we can get the delay. // If any of these are false, we don't do any synchronisation stuff - int resp = -1; // use this as a flag -- if negative, we can't rely on a real known delay + int resp = -1; // use this as a flag -- if negative, we can't rely on a real known delay current_delay = -1; // use this as a failure flag - - if (config.output->delay) { - long l_delay; - resp = config.output->delay(&l_delay); - current_delay = l_delay; - if (resp==0) { // no error - if (current_delay<0) { - debug(1,"Underrun of %d frames reported, but ignored.",current_delay); - current_delay=0; // could get a negative value if there was underrun, but ignore it. - } - if (current_delay < minimum_dac_queue_size) { - minimum_dac_queue_size = current_delay; // update for display later - } - } else { - debug(2, "Delay error %d when checking running latency.",resp); - } - } + + if (config.output->delay) { + long l_delay; + resp = config.output->delay(&l_delay); + current_delay = l_delay; + if (resp == 0) { // no error + if (current_delay < 0) { + debug(1, "Underrun of %d frames reported, but ignored.", current_delay); + current_delay = + 0; // could get a negative value if there was underrun, but ignore it. + } + if (current_delay < minimum_dac_queue_size) { + minimum_dac_queue_size = current_delay; // update for display later + } + } else { + debug(2, "Delay error %d when checking running latency.", resp); + } + } if (resp >= 0) { // this is the actual delay, including the latency we actually want, which will // fluctuate a good bit about a potentially rising or falling trend. int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t - // This is the timing error for the next audio frame in the DAC. - sync_error = delay - config.latency; // int64_t from int64_t - int32_t, so okay + sync_error = + delay - + config.latency * output_sample_ratio; // int64_t from int64_t - int32_t, so okay // if (llabs(sync_error)>352*512) // debug(1,"Very large sync error: %lld",sync_error); @@ -1208,10 +1910,10 @@ static void *player_thread_func(void *arg) { // before we finally commit to this frame, check its sequencing and timing // require a certain error before bothering to fix it... - if (sync_error > config.tolerance) { // int64_t > int, okay + if (sync_error > config.tolerance * config.output_rate) { // int64_t > int, okay amount_to_stuff = -1; } - if (sync_error < -config.tolerance) { + if (sync_error < -config.tolerance * config.output_rate) { amount_to_stuff = 1; } @@ -1222,91 +1924,98 @@ static void *player_thread_func(void *arg) { } // try to keep the corrections definitely below 1 in 1000 audio frames - - // calculate the time elapsed since the play session started. - - if (amount_to_stuff) { - if ((local_time_now) && (first_packet_time_to_play) && (local_time_now >= first_packet_time_to_play)) { - int64_t tp = (local_time_now - first_packet_time_to_play)>>32; // seconds int64_t from uint64_t which is always positive, so ok - - if (tp<5) + // calculate the time elapsed since the play session started. + + if (amount_to_stuff) { + if ((local_time_now) && (first_packet_time_to_play) && + (local_time_now >= first_packet_time_to_play)) { + + int64_t tp = (local_time_now - first_packet_time_to_play) >> + 32; // seconds int64_t from uint64_t which is always positive, so ok + + if (tp < 5) amount_to_stuff = 0; // wait at least five seconds - else if (tp<30) { - if ((random() % 1000) > 352) // keep it to about 1:1000 for the first thirty seconds + else if (tp < 30) { + if ((random() % 1000) > + 352) // keep it to about 1:1000 for the first thirty seconds amount_to_stuff = 0; } } } - - if (config.no_sync!=0) - amount_to_stuff = 0 ; // no stuffing if it's been disabled - - if ((amount_to_stuff == 0) && (fix_volume == 0x10000)) { - // if no stuffing needed and no volume adjustment, then - // don't send to stuff_buffer_* and don't copy to outbuf; just send directly to the - // output device... - if (inframe->data==NULL) - debug(1,"NULL inframe->data to play -- skipping it."); - else { - if (inframe->length==0) - debug(1,"empty frame to play -- skipping it (2)."); - else - config.output->play(inframe->data, inframe->length); - } - } else { + + if (config.no_sync != 0) + amount_to_stuff = 0; // no stuffing if it's been disabled + #ifdef HAVE_LIBSOXR - switch (config.packet_stuffing) { - case ST_basic: - // if (amount_to_stuff) debug(1,"Basic stuff..."); - play_samples = stuff_buffer_basic(inframe->data, inframe->length, outbuf, amount_to_stuff); - break; - case ST_soxr: - // if (amount_to_stuff) debug(1,"Soxr stuff..."); - play_samples = stuff_buffer_soxr(inframe->data, inframe->length, outbuf, amount_to_stuff); - break; - } + switch (config.packet_stuffing) { + case ST_basic: + // if (amount_to_stuff) debug(1,"Basic stuff..."); + if (tbuf) { + play_samples = + stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, + outbuf, amount_to_stuff, enable_dither); + } else + play_samples = + stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); + break; + case ST_soxr: + // if (amount_to_stuff) debug(1,"Soxr stuff..."); + if (tbuf) + play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, (int32_t *)sbuf, inbuflength, + config.output_format, outbuf, amount_to_stuff, + enable_dither); + else + play_samples = + stuff_buffer_soxr(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); + break; + } #else - // if (amount_to_stuff) debug(1,"Standard stuff..."); - play_samples = stuff_buffer_basic(inframe->data, inframe->length, outbuf, amount_to_stuff); + // if (amount_to_stuff) debug(1,"Standard stuff..."); + if (tbuf) + play_samples = + stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, outbuf, + amount_to_stuff, enable_dither); + else + play_samples = + stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff); #endif - /* - { - int co; - int is_silent=1; - short *p = outbuf; - for (co=0;coplay(outbuf, play_samples); + /* + { + int co; + int is_silent=1; + short *p = outbuf; + for (co=0;coplay((short *)outbuf, play_samples); // remove the (short*)! } // check for loss of sync // timestamp of zero means an inserted silent frame in place of a missing frame - + // not too sure if abs() is implemented for int64_t, so we'll do it manually int64_t abs_sync_error = sync_error; - if (abs_sync_error<0) + if (abs_sync_error < 0) abs_sync_error = -abs_sync_error; - - if ((config.no_sync==0) && (inframe->timestamp != 0) && (!please_stop) && (config.resyncthreshold != 0) && - (abs_sync_error > config.resyncthreshold)) { + + if ((config.no_sync == 0) && (inframe->timestamp != 0) && (!please_stop) && + (config.resyncthreshold > 0.0) && + (abs_sync_error > config.resyncthreshold * config.output_rate)) { sync_error_out_of_bounds++; // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld; // timestamp: %llx, time now @@ -1322,28 +2031,34 @@ static void *player_thread_func(void *arg) { sync_error_out_of_bounds = 0; } } else { - // if there is no delay procedure, or it's not working or not allowed, there can be no synchronising - + // if there is no delay procedure, or it's not working or not allowed, there can be no + // synchronising + if (fix_volume == 0x10000) - - if (inframe->data==NULL) - debug(1,"NULL inframe->data to play -- skipping it."); + + if (inbuf == NULL) + debug(1, "NULL inbuf to play -- skipping it."); else { - if (inframe->length==0) - debug(1,"empty frame to play -- skipping it (3)."); + if (inbuflength == 0) + debug(1, "empty frame to play -- skipping it (3)."); else - config.output->play(inframe->data, inframe->length); + config.output->play(inbuf, inbuflength); } else { - play_samples = stuff_buffer_basic(inframe->data, inframe->length, outbuf, 0); // no stuffing, but volume adjustment + if (tbuf) + play_samples = stuff_buffer_basic_32( + (int32_t *)tbuf, inbuflength, config.output_format, outbuf, 0, enable_dither); + else + play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, + 0); // no stuffing, but volume adjustment - if (outbuf==NULL) - debug(1,"NULL outbuf to play -- skipping it."); + if (outbuf == NULL) + debug(1, "NULL outbuf to play -- skipping it."); else { - if (inframe->length==0) - debug(1,"empty frame to play -- skipping it (4)."); + if (inbuf == 0) + debug(1, "empty frame to play -- skipping it (4)."); else - config.output->play(outbuf, play_samples); + config.output->play((short *)outbuf, play_samples); // remove the (short*)! } } } @@ -1356,10 +2071,12 @@ static void *player_thread_func(void *arg) { // new stats calculation. We want a running average of sync error, drift, adjustment, // number of additions+subtractions - - // this is a misleading hack -- the statistics should include some data on the number of valid samples and the number of times sync wasn't checked due to non availability of a delay figure. + + // this is a misleading hack -- the statistics should include some data on the number of + // valid samples and the number of times sync wasn't checked due to non availability of a + // delay figure. // for the present, stats are only updated when sync has been checked - if (sync_error!=-1) { + if (sync_error != -1) { if (number_of_statistics == trend_interval) { // here we remove the oldest statistical data and take it from the summaries as well tsum_of_sync_errors -= statistics[oldest_statistic].sync_error; @@ -1395,10 +2112,9 @@ static void *player_thread_func(void *arg) { tsum_of_corrections += amount_to_stuff; session_corrections += amount_to_stuff; - newest_statistic = (newest_statistic + 1) % trend_interval; number_of_statistics++; - } + } } if (play_number % print_interval == 0) { // we can now calculate running averages for sync error (frames), corrections (ppm), @@ -1411,67 +2127,56 @@ static void *player_thread_func(void *arg) { // if ((play_number/print_interval)%20==0) if (config.statistics_requested) { if (at_least_one_frame_seen) { - if ((config.output->delay)) { - if (config.no_sync==0) { - inform("%*.1f," /* Sync error inf frames */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, moving_average_sync_error, - 10, moving_average_correction * 1000000 / 352, - 10, moving_average_insertions_plus_deletions * 1000000 / 352, - 12, play_number, - 7, missing_packets, - 7, late_packets, - 7, too_late_packets, - 7, resend_requests, - 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, + if ((config.output->delay)) { + if (config.no_sync == 0) { + inform("%*.1f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * output_sample_ratio), 10, + moving_average_insertions_plus_deletions * 1000000 / + (352 * output_sample_ratio), + 12, play_number, 7, missing_packets, 7, late_packets, 7, too_late_packets, + 7, resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { - inform("%*.1f," /* Sync error inf frames */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*lli," /* min DAC queue size */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, moving_average_sync_error, - 12, play_number, - 7, missing_packets, - 7, late_packets, - 7, too_late_packets, - 7, resend_requests, - 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, - 5, maximum_buffer_occupancy); - } + inform("%*.1f," /* Sync error in milliseconds */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*lli," /* min DAC queue size */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + missing_packets, 7, late_packets, 7, too_late_packets, 7, resend_requests, + 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy); + } } else { - inform("%*.1f," /* Sync error inf frames */ - "%*d," /* total packets */ - "%*llu," /* missing packets */ - "%*llu," /* late packets */ - "%*llu," /* too late packets */ - "%*llu," /* resend requests */ - "%*d," /* min buffer occupancy */ - "%*d", /* max buffer occupancy */ - 10, moving_average_sync_error, - 12, play_number, - 7, missing_packets, - 7, late_packets, - 7, too_late_packets, - 7, resend_requests, - 5, minimum_buffer_occupancy, - 5, maximum_buffer_occupancy); + inform("%*.1f," /* Sync error in milliseconds */ + "%*d," /* total packets */ + "%*llu," /* missing packets */ + "%*llu," /* late packets */ + "%*llu," /* too late packets */ + "%*llu," /* resend requests */ + "%*d," /* min buffer occupancy */ + "%*d", /* max buffer occupancy */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + missing_packets, 7, late_packets, 7, too_late_packets, 7, resend_requests, 5, + minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { inform("No frames received in the last sampling interval."); @@ -1487,30 +2192,36 @@ static void *player_thread_func(void *arg) { } if (config.statistics_requested) { - int rawSeconds = (int) difftime( time( NULL ), playstart ); - int elapsedHours = rawSeconds / 3600; - int elapsedMin = (rawSeconds / 60) % 60; - int elapsedSec = rawSeconds % 60; - inform( "Playback Stopped. Total playing time %02d:%02d:%02d\n", elapsedHours, elapsedMin, elapsedSec ); + int rawSeconds = (int)difftime(time(NULL), playstart); + int elapsedHours = rawSeconds / 3600; + int elapsedMin = (rawSeconds / 60) % 60; + int elapsedSec = rawSeconds % 60; + inform("Playback Stopped. Total playing time %02d:%02d:%02d\n", elapsedHours, elapsedMin, + elapsedSec); } if (config.output->stop) - config.output->stop(); - usleep(100000); // allow this time to (?) allow the alsa subsystem to finish cleaning up after itself. 50 ms seems too short + config.output->stop(); + usleep(100000); // allow this time to (?) allow the alsa subsystem to finish cleaning up after + // itself. 50 ms seems too short free(outbuf); free(silence); - debug(1,"Shut down audio, control and timing threads"); + if (tbuf) + free(tbuf); + if (sbuf) + free(sbuf); + debug(1, "Shut down audio, control and timing threads"); itr.please_stop = 1; pthread_kill(rtp_audio_thread, SIGUSR1); pthread_kill(rtp_control_thread, SIGUSR1); pthread_kill(rtp_timing_thread, SIGUSR1); pthread_join(rtp_timing_thread, NULL); - debug(1,"timing thread joined"); + debug(1, "timing thread joined"); pthread_join(rtp_audio_thread, NULL); - debug(1,"audio thread joined"); + debug(1, "audio thread joined"); pthread_join(rtp_control_thread, NULL); - debug(1,"control thread joined"); - debug(1,"Player thread exit"); + debug(1, "control thread joined"); + debug(1, "Player thread exit"); return 0; } @@ -1522,72 +2233,102 @@ void player_volume(double airplay_volume) { // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30 // equal increments. Since the human ear's response is roughly logarithmic, we imagine these to // be power dB, i.e. from -30dB to 0dB. - + // We may have a hardware mixer, and if so, we will give it priority. // If a desired volume range is given, then we will try to accommodate it from // the top of the hardware mixer's range downwards. - - // If no desired volume range is given, we will use the native resolution of the hardware mixer, if any, - // or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0 dB, + + // If no desired volume range is given, we will use the native resolution of the hardware mixer, + // if any, + // or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0 + // dB, // corresponding to a multiplier of 1 to 65535. - - // Otherwise, we will accommodate the desired volume range in the combination of the software and hardware mixer + + // Otherwise, we will accommodate the desired volume range in the combination of the software and + // hardware mixer // Intuitively (!), it seems best to give the hardware mixer as big a role as possible, so // we will use its full range and then accommodate the rest of the attenuation in software. // A problem is that we don't know whether the lowest hardware volume actually mutes the output - // so we must assume that it does, and for this reason, the volume control goes at the "bottom" of the adjustment range - + // so we must assume that it does, and for this reason, the volume control goes at the "bottom" of + // the adjustment range + // 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. - - int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db, max_db; // hw_range_db is a flag; if 0 means no mixer - - int32_t sw_min_db = -9630; - int32_t sw_max_db = 0; - int32_t sw_range_db = sw_max_db - sw_min_db; - int32_t desired_range_db; // this is used as a flag; if 0 means no desired range - - if (config.volume_range_db) - desired_range_db = (int32_t)trunc(config.volume_range_db*100); - else - desired_range_db = 0; - + + int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db, + max_db; // hw_range_db is a flag; if 0 means no mixer + if (config.output->parameters) { // have a hardware mixer config.output->parameters(&audio_information); hw_max_db = audio_information.maximum_volume_dB; hw_min_db = audio_information.minimum_volume_dB; - hw_range_db = hw_max_db-hw_min_db; + hw_range_db = hw_max_db - hw_min_db; } else { // don't have a hardware mixer hw_max_db = hw_min_db = hw_range_db = 0; } - + + int32_t sw_min_db = -9630; + int32_t sw_max_db = 0; + int32_t sw_range_db = sw_max_db - sw_min_db; + int32_t desired_range_db = 0; // this is used as a flag; if 0 means no desired range + + if (config.volume_range_db) + desired_range_db = (int32_t)trunc(config.volume_range_db * 100); + + if (config.volume_max_db_set) { + if (hw_range_db) { + if (((config.volume_max_db * 100) < hw_max_db) && + ((config.volume_max_db * 100) > hw_min_db)) { + hw_max_db = (int)config.volume_max_db * 100; + hw_range_db = hw_max_db - hw_min_db; + } else { + inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB " + "to %d dB. It will be ignored.", + (int)(hw_max_db / 100), (int)(hw_min_db / 100)); + } + } else { + if (((config.volume_max_db * 100) < sw_max_db) && + ((config.volume_max_db * 100) > sw_min_db)) { + sw_max_db = (int)config.volume_max_db * 100; + sw_range_db = sw_max_db - sw_min_db; + } else { + inform("The volume_max_db setting is out of range of the software attenuation's limits of " + "0 dB to -96.3 dB. It will be ignored."); + } + } + } + if (desired_range_db) { // debug(1,"An attenuation range of %d is requested.",desired_range_db); // we have a desired volume range. if (hw_range_db) { - // we have a hardware mixer - if (hw_range_db>=desired_range_db) { + // we have a hardware mixer + if (hw_range_db >= desired_range_db) { // the hardware mixer can accommodate the desired range max_db = hw_max_db; min_db = max_db - desired_range_db; } else { - if ((hw_range_db+sw_range_db)=min_db)) { + debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db); + max_db = (int)(config.volume_max_db*100); + } else { + inform("The value of volume_max_db is invalid. It must be in the range %d to + %d.",max_db,min_db); + } + } + */ double hardware_attenuation, software_attenuation; - double scaled_attenuation = hw_min_db+sw_min_db; - + double scaled_attenuation = hw_min_db + sw_min_db; + // now, we can map the input to the desired output volume - if (airplay_volume==-144.0) { - // do a mute + if (airplay_volume == -144.0) { + // do a mute // needed even with hardware mute, as when sound is unmuted it might otherwise be very loud. - hardware_attenuation = hw_min_db; - software_attenuation = sw_min_db; - if (config.output->mute) - config.output->mute(1); // use real mute if it's there - + hardware_attenuation = hw_min_db; + software_attenuation = sw_min_db; + if (config.output->mute) + config.output->mute(1); // use real mute if it's there + } else { - if (config.output->mute) - config.output->mute(0); // unmute mute if it's there + if (config.output->mute) + config.output->mute(0); // unmute mute if it's there scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); if (hw_range_db) { // if there is a hardware mixer - if (scaled_attenuation<=hw_max_db) { + if (scaled_attenuation <= hw_max_db) { // the attenuation is so low that's it's in the hardware mixer's range // debug(1,"Attenuation all taken care of by the hardware mixer."); hardware_attenuation = scaled_attenuation; - software_attenuation = sw_max_db - (max_db-hw_max_db); // e.g. if the hw_max_db is +4 and the max is +40, this will be -36 (all by 100, of course) + software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and + // the max is +40, this will be -36 + // (all by 100, of course) } else { // debug(1,"Attenuation taken care of by hardware and software mixer."); hardware_attenuation = hw_max_db; // the hardware mixer is turned up full - software_attenuation = sw_max_db - (max_db-scaled_attenuation); + software_attenuation = sw_max_db - (max_db - scaled_attenuation); } } else { // if there is no hardware mixer, the scaled_volume is the software volume @@ -1638,14 +2392,16 @@ void player_volume(double airplay_volume) { software_attenuation = scaled_attenuation; } } - + if ((config.output->volume) && (hw_range_db)) { - config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value - //debug(1,"Hardware attenuation set to %f for airplay volume of %f.",hardware_attenuation,airplay_volume); + config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value + // debug(1,"Hardware attenuation set to %f for airplay volume of + // %f.",hardware_attenuation,airplay_volume); } double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); - // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of %f",software_attenuation,temp_fix_volume,airplay_volume); - + // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of + // %f",software_attenuation,temp_fix_volume,airplay_volume); + pthread_mutex_lock(&vol_mutex); fix_volume = temp_fix_volume; pthread_mutex_unlock(&vol_mutex); @@ -1654,17 +2410,15 @@ void player_volume(double airplay_volume) { char *dv = malloc(128); // will be freed in the metadata thread if (dv) { memset(dv, 0, 128); - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, - scaled_attenuation / 100.0, - min_db / 100.0, - max_db / 100.0); + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0, + min_db / 100.0, max_db / 100.0); send_ssnc_metadata('pvol', dv, strlen(dv), 1); } #endif } -void player_flush(uint32_t timestamp) { - debug(3,"Flush requested up to %u. It seems as if 0 is special.",timestamp); +void player_flush(int64_t timestamp) { + debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp); pthread_mutex_lock(&flush_mutex); flush_requested = 1; // if (timestamp!=0) @@ -1677,14 +2431,19 @@ void player_flush(uint32_t timestamp) { } int player_play(stream_cfg *stream, pthread_t *player_thread) { - //if (*player_thread!=NULL) - // die("Trying to create a second player thread for this RTSP session"); + // if (*player_thread!=NULL) + // die("Trying to create a second player thread for this RTSP session"); packet_count = 0; encrypted = stream->encrypted; if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); if (encrypted) { +#ifdef HAVE_LIBMBEDTLS + memset(&dctx, 0, sizeof(mbedtls_aes_context)); + mbedtls_aes_setkey_dec(&dctx, stream->aeskey, 128); +#endif + #ifdef HAVE_LIBPOLARSSL memset(&dctx, 0, sizeof(aes_context)); aes_setkey_dec(&dctx, stream->aeskey, 128); @@ -1695,7 +2454,7 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { #endif aesiv = stream->aesiv; } - init_decoder(stream->fmtp); + init_decoder(stream->fmtp); // this sets up incoming rate, bit depth, channels // must be after decoder init init_buffer(); please_stop = 0; @@ -1716,7 +2475,7 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { #endif if (rc) debug(1, "Error initialising condition variable."); - config.output->start(sampling_rate); + config.output->start(config.output_rate, config.output_format); size_t size = (PTHREAD_STACK_MIN + 256 * 1024); pthread_attr_t tattr; pthread_attr_init(&tattr); @@ -1729,20 +2488,20 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { } void player_stop(pthread_t *player_thread) { - //if (*thread==NULL) - // debug(1,"Trying to stop a non-existent player thread"); - // else { - please_stop = 1; - pthread_cond_signal(&flowcontrol); // tell it to give up - pthread_join(*player_thread, NULL); - #ifdef CONFIG_METADATA - send_ssnc_metadata('pend', NULL, 0, 1); - #endif - command_stop(); - free_buffer(); - free_decoder(); - int rc = pthread_cond_destroy(&flowcontrol); - if (rc) - debug(1, "Error destroying condition variable."); - // } + // if (*thread==NULL) + // debug(1,"Trying to stop a non-existent player thread"); + // else { + please_stop = 1; + pthread_cond_signal(&flowcontrol); // tell it to give up + pthread_join(*player_thread, NULL); +#ifdef CONFIG_METADATA + send_ssnc_metadata('pend', NULL, 0, 1); +#endif + command_stop(); + free_buffer(); + terminate_decoders(); + int rc = pthread_cond_destroy(&flowcontrol); + if (rc) + debug(1, "Error destroying condition variable."); + // } } diff --git a/player.h b/player.h index 9012b0cb..eba97e8e 100644 --- a/player.h +++ b/player.h @@ -18,8 +18,18 @@ int player_play(stream_cfg *cfg, pthread_t *thread); void player_stop(pthread_t *thread); void player_volume(double f); -void player_flush(uint32_t timestamp); +void player_flush(int64_t timestamp); -void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len); +void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len); + +int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic + // timestamp guaranteed to start between 2^32 2^33 + // frames and continue up to 2^64 frames +// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds. +// assumes, without checking, that successive timestamps in a series always span an interval of less +// than one minute. + +uint64_t monotonic_seqno(uint16_t seq_no); // add an epoch to the seq_no. Uses the accompanying + // timstamp to detemine the correct epoch #endif //_PLAYER_H diff --git a/rtp.c b/rtp.c index 8d5b46df..dcaf28d4 100644 --- a/rtp.c +++ b/rtp.c @@ -24,19 +24,19 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include #include "common.h" #include "player.h" @@ -59,19 +59,20 @@ typedef struct time_ping_record { static int running = 0; static char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client -static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it could be one of many, so we need to know it -static uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope -static short connection_ip_family; // AF_INET / AF_INET6 -static uint32_t client_active_remote; // used when you want to control the client... +static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it + // could be one of many, so we need to know it +static uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope +static short connection_ip_family; // AF_INET / AF_INET6 +static uint32_t client_active_remote; // used when you want to control the client... static SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client static SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client static int audio_socket; // our local [server] audio socket static int control_socket; // our local [server] control socket static int timing_socket; // local timing socket -//static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; +// static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread; -static uint32_t reference_timestamp; +static int64_t reference_timestamp; static uint64_t reference_timestamp_time; static uint64_t remote_reference_timestamp_time; @@ -104,29 +105,33 @@ void *rtp_audio_receiver(void *arg) { uint64_t time_of_previous_packet_fp = 0; float longest_packet_time_interval_us = 0.0; - - // mean and variance calculations from "online_variance" algorithm at https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm - + + // mean and variance calculations from "online_variance" algorithm at + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm + int32_t stat_n = 0; float stat_mean = 0.0; float stat_M2 = 0.0; - + ssize_t nread; - while (itr->please_stop==0) { + while (itr->please_stop == 0) { nread = recv(audio_socket, packet, sizeof(packet), 0); - + uint64_t local_time_now_fp = get_absolute_time_in_fp(); if (time_of_previous_packet_fp) { - float time_interval_us = (((local_time_now_fp - time_of_previous_packet_fp)*1000000)>>32)*1.0; + float time_interval_us = + (((local_time_now_fp - time_of_previous_packet_fp) * 1000000) >> 32) * 1.0; time_of_previous_packet_fp = local_time_now_fp; - if (time_interval_us>longest_packet_time_interval_us) - longest_packet_time_interval_us=time_interval_us; - stat_n+=1; + if (time_interval_us > longest_packet_time_interval_us) + longest_packet_time_interval_us = time_interval_us; + stat_n += 1; float stat_delta = time_interval_us - stat_mean; - stat_mean += stat_delta/stat_n; - stat_M2 += stat_delta*(time_interval_us - stat_mean); + stat_mean += stat_delta / stat_n; + stat_M2 += stat_delta * (time_interval_us - stat_mean); if (stat_n % 2500 == 0) { - debug(2,"Packet reception interval stats: mean, standard deviation and max for the last 2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",stat_mean, sqrtf(stat_M2 / (stat_n-1)),longest_packet_time_interval_us); + debug(2, "Packet reception interval stats: mean, standard deviation and max for the last " + "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.", + stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us); stat_n = 0; stat_mean = 0.0; stat_M2 = 0.0; @@ -155,11 +160,11 @@ void *rtp_audio_receiver(void *arg) { last_seqno = seqno; else { last_seqno = (last_seqno + 1) & 0xffff; - //if (seqno != last_seqno) + // if (seqno != last_seqno) // debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno); last_seqno = seqno; // reset warning... } - uint32_t timestamp = ntohl(*(unsigned long *)(pktp + 4)); + int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4))); // if (packet[1]&0x10) // debug(1,"Audio packet Extension bit set."); @@ -198,9 +203,9 @@ void *rtp_control_receiver(void *arg) { uint8_t packet[2048], *pktp; struct timespec tn; uint64_t remote_time_of_sync, local_time_now, remote_time_now; - uint32_t sync_rtp_timestamp, rtp_timestamp_less_latency; + int64_t sync_rtp_timestamp, rtp_timestamp_less_latency; ssize_t nread; - while (itr->please_stop==0) { + while (itr->please_stop == 0) { nread = recv(control_socket, packet, sizeof(packet), 0); local_time_now = get_absolute_time_in_fp(); // clock_gettime(CLOCK_MONOTONIC,&tn); @@ -229,17 +234,17 @@ void *rtp_control_receiver(void *arg) { // debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync); - rtp_timestamp_less_latency = ntohl(*((uint32_t *)&packet[4])); - sync_rtp_timestamp = ntohl(*((uint32_t *)&packet[16])); - + rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4]))); + sync_rtp_timestamp = monotonic_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) { + int64_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, @@ -265,11 +270,11 @@ void *rtp_control_receiver(void *arg) { } } else if (packet[1] == 0xd6) { // resent audio data in the control path -- whaale only? // debug(1, "Control Port -- Retransmitted Audio Data Packet received."); - pktp = packet+4; + pktp = packet + 4; plen -= 4; seq_t seqno = ntohs(*(unsigned short *)(pktp + 2)); - uint32_t timestamp = ntohl(*(unsigned long *)(pktp + 4)); + int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4))); pktp += 12; plen -= 12; @@ -293,7 +298,7 @@ void *rtp_control_receiver(void *arg) { void *rtp_timing_sender(void *arg) { debug(2, "Timing sender thread starting."); - int *stop = arg; // the parameter points to this request to stop thing + int *stop = arg; // the parameter points to this request to stop thing struct timing_request { char leader; char type; @@ -314,8 +319,8 @@ void *rtp_timing_sender(void *arg) { time_ping_count = 0; // we inherit the signal mask (SIGUSR1) - while (*stop==0) { - // debug(1,"Send a timing request"); + while (*stop == 0) { + // debug(1,"Send a timing request"); if (!running) die("rtp_timing_sender called without active stream!"); @@ -351,7 +356,7 @@ void *rtp_timing_receiver(void *arg) { debug(2, "Timing receiver -- Server RTP thread starting."); // we inherit the signal mask (SIGUSR1) - struct inter_threads_record *itr = arg; + struct inter_threads_record *itr = arg; uint8_t packet[2048], *pktp; ssize_t nread; @@ -365,11 +370,11 @@ void *rtp_timing_receiver(void *arg) { local_to_remote_time_jitters_count = 0; uint64_t first_remote_time = 0; uint64_t first_local_time = 0; - + uint64_t first_local_to_remote_time_difference = 0; uint64_t first_local_to_remote_time_difference_time; uint64_t l2rtd = 0; - while (itr->please_stop==0) { + while (itr->please_stop == 0) { nread = recv(timing_socket, packet, sizeof(packet), 0); arrival_time = get_absolute_time_in_fp(); // clock_gettime(CLOCK_MONOTONIC,&att); @@ -428,13 +433,14 @@ void *rtp_timing_receiver(void *arg) { // these are for diagnostics only -- not used time_pings[0].local_time = arrival_time; time_pings[0].remote_time = distant_transmit_time; - + time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time; time_pings[0].dispersion = return_time; if (time_ping_count < time_ping_history) time_ping_count++; - - uint64_t local_time_chosen = arrival_time;; + + uint64_t local_time_chosen = arrival_time; + ; uint64_t remote_time_chosen = distant_transmit_time; // now pick the timestamp with the lowest dispersion uint64_t l2rtd = time_pings[0].local_to_remote_difference; @@ -465,69 +471,81 @@ void *rtp_timing_receiver(void *arg) { // with dispersion of %lld us with delta of %lld us",rtus,ji); local_to_remote_time_difference = l2rtd; - if (first_local_to_remote_time_difference==0) { + if (first_local_to_remote_time_difference == 0) { first_local_to_remote_time_difference = local_to_remote_time_difference; first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); } - + int64_t clock_drift, clock_drift_in_usec; - if (first_local_time==0) { + if (first_local_time == 0) { first_local_time = local_time_chosen; first_remote_time = remote_time_chosen; clock_drift = 0; } else { uint64_t local_time_change = local_time_chosen - first_local_time; uint64_t remote_time_change = remote_time_chosen - first_remote_time; - if (remote_time_change >= local_time_change) clock_drift = remote_time_change - local_time_change; else clock_drift = -(local_time_change - remote_time_change); } - if (clock_drift>=0) - clock_drift_in_usec = (clock_drift * 1000000)>>32; + if (clock_drift >= 0) + clock_drift_in_usec = (clock_drift * 1000000) >> 32; else - clock_drift_in_usec = -(((-clock_drift) * 1000000)>>32); - - - - int64_t source_drift_usec; - if (play_segment_reference_frame!=0) { - 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); - uint64_t frame_difference = 0; - if (reference_timestamp>=play_segment_reference_frame) - frame_difference = (uint64_t)reference_timestamp-(uint64_t)play_segment_reference_frame; - else // rollover - frame_difference = (uint64_t)reference_timestamp+UINT64_C(0x100000000)-(uint64_t)play_segment_reference_frame; - uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference<<32)/44100); - uint64_t frame_time_difference_actual = remote_reference_timestamp_time-play_segment_reference_frame_remote_time; // this is all done by reference to the sources' system clock - // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, (frame_time_difference_actual*1000000)>>32); - if (frame_time_difference_calculated>=frame_time_difference_actual) // i.e. if the time it should have taken to send the packets is greater than the actual time difference measured on the source clock - // then the source DAC's clock is running fast relative to the source system clock - source_drift_usec = frame_time_difference_calculated-frame_time_difference_actual; - else - // otherwise the source DAC's clock is running slow relative to the source system clock - source_drift_usec = -(frame_time_difference_actual-frame_time_difference_calculated); - } else - source_drift_usec = 0; - source_drift_usec = (source_drift_usec*1000000)>>32; // turn it to microseconds - - //long current_delay = 0; - //if (config.output->delay) { - // config.output->delay(¤t_delay); - //} - // Useful for troubleshooting: - // clock_drift between source and local clock -- +ve means source is faster - // session_corrections -- the amount of correction done, in microseconds. +ve means frames added - // current_delay = delay in DAC buffer in frames - // source_drift_usec = how much faster (+ve) or slower the source DAC is running relative to the source clock - // buffer_occupancy = the number of buffers occupied. Crude, but should show no long term trend if source and device are in sync. - // return_time = the time from soliciting a timing packet to getting it back. It should be short ( < 5 ms) and pretty consistent. - // debug(1, "%lld\t%lld\t%ld\t%lld\t%u\t%llu", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32); - + clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32); + + int64_t source_drift_usec; + if (play_segment_reference_frame != 0) { + int64_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); + uint64_t frame_difference = 0; + if (reference_timestamp >= play_segment_reference_frame) + frame_difference = (uint64_t)reference_timestamp - (uint64_t)play_segment_reference_frame; + else // rollover + frame_difference = + (uint64_t)reference_timestamp + 0x100000000 - (uint64_t)play_segment_reference_frame; + uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference << 32) / 44100); + uint64_t frame_time_difference_actual = + remote_reference_timestamp_time - + play_segment_reference_frame_remote_time; // this is all done by reference to the + // sources' system clock + // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec + // actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, + // (frame_time_difference_actual*1000000)>>32); + if (frame_time_difference_calculated >= + frame_time_difference_actual) // i.e. if the time it should have taken to send the + // packets is greater than the actual time difference + // measured on the source clock + // then the source DAC's clock is running fast relative to the source system clock + source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual; + else + // otherwise the source DAC's clock is running slow relative to the source system clock + source_drift_usec = -(frame_time_difference_actual - frame_time_difference_calculated); + } else + source_drift_usec = 0; + source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds + + // long current_delay = 0; + // if (config.output->delay) { + // config.output->delay(¤t_delay); + //} + // Useful for troubleshooting: + // clock_drift between source and local clock -- +ve means source is faster + // session_corrections -- the amount of correction done, in microseconds. +ve means frames + // added + // current_delay = delay in DAC buffer in frames + // source_drift_usec = how much faster (+ve) or slower the source DAC is running relative + // to the source clock + // buffer_occupancy = the number of buffers occupied. Crude, but should show no long term + // trend if source and device are in sync. + // return_time = the time from soliciting a timing packet to getting it back. It should be + // short ( < 5 ms) and pretty consistent. + // debug(1, "%lld\t%lld\t%ld\t%lld\t%u\t%llu", + // clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32); + } else { debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); } @@ -545,44 +563,45 @@ void *rtp_timing_receiver(void *arg) { return NULL; } -static int bind_port(int ip_family,const char *self_ip_address,uint32_t scope_id,int *sock) { +static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id, int *sock) { // look for a port in the range, if any was specified. int desired_port = config.udp_port_base; int ret; - + int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP); - if (local_socket== -1) + if (local_socket == -1) die("Could not allocate a socket."); SOCKADDR myaddr; do { - memset(&myaddr,0,sizeof(myaddr)); - if (ip_family==AF_INET) { + memset(&myaddr, 0, sizeof(myaddr)); + if (ip_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr; sa->sin_family = AF_INET; sa->sin_port = ntohs(desired_port); - inet_pton(AF_INET,self_ip_address,&(sa->sin_addr)); - ret = bind(local_socket,(struct sockaddr*)sa, sizeof(struct sockaddr_in)); + inet_pton(AF_INET, self_ip_address, &(sa->sin_addr)); + ret = bind(local_socket, (struct sockaddr *)sa, sizeof(struct sockaddr_in)); } #ifdef AF_INET6 - if (ip_family==AF_INET6) { + if (ip_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&myaddr; sa6->sin6_family = AF_INET6; sa6->sin6_port = ntohs(desired_port); - inet_pton(AF_INET6,self_ip_address,&(sa6->sin6_addr)); - sa6->sin6_scope_id=scope_id; - ret = bind(local_socket,(struct sockaddr*)sa6, sizeof(struct sockaddr_in6)); + inet_pton(AF_INET6, self_ip_address, &(sa6->sin6_addr)); + sa6->sin6_scope_id = scope_id; + ret = bind(local_socket, (struct sockaddr *)sa6, sizeof(struct sockaddr_in6)); } -#endif - - } while ((ret<0) && (errno==EADDRINUSE) && (desired_port!=0) && (desired_port++ < config.udp_port_base+config.udp_port_range)); - +#endif + + } while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) && + (desired_port++ < config.udp_port_base + config.udp_port_range)); + // debug(1,"UDP port chosen: %d.",desired_port); - + if (ret < 0) { - close(local_socket); - die("error: could not bind a UDP port!"); + close(local_socket); + die("error: could not bind a UDP port!"); } - + int sport; SOCKADDR local; socklen_t local_len = sizeof(local); @@ -597,18 +616,18 @@ static int bind_port(int ip_family,const char *self_ip_address,uint32_t scope_id struct sockaddr_in *sa = (struct sockaddr_in *)&local; sport = ntohs(sa->sin_port); } - + *sock = local_socket; return sport; } -void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote, int *lsport, - int *lcport, int *ltport) { - +void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote, + int *lsport, int *lcport, int *ltport) { + // this gets the local and remote ip numbers (and ports used for the TCD stuff) // we use the local stuff to specify the address we are coming from and // we use the remote stuff to specify where we're goint to - + if (running) die("rtp_setup called with active stream!"); @@ -617,13 +636,13 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t client_active_remote = active_remote; // print out what we know about the client - void *client_addr,*self_addr; - int client_port,self_port; + void *client_addr, *self_addr; + int client_port, self_port; char client_port_str[64]; char self_addr_str[64]; - + connection_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client - + #ifdef AF_INET6 if (connection_ip_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote; @@ -644,13 +663,10 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t self_port = ntohs(sa4->sin_port); } - inet_ntop(connection_ip_family, client_addr, client_ip_string, - sizeof(client_ip_string)); - inet_ntop(connection_ip_family, self_addr, self_ip_string, - sizeof(self_ip_string)); - - debug(1, "Set up play connection from %s to self at %s.", client_ip_string,self_ip_string); + inet_ntop(connection_ip_family, client_addr, client_ip_string, sizeof(client_ip_string)); + inet_ntop(connection_ip_family, self_addr, self_ip_string, sizeof(self_ip_string)); + debug(1, "Set up play connection from %s to self at %s.", client_ip_string, self_ip_string); // set up a the record of the remote's control socket struct addrinfo hints; @@ -698,23 +714,24 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // now, we open three sockets -- one for the audio stream, one for the timing and one for the // control - *lsport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&audio_socket); - *lcport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&control_socket); - *ltport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&timing_socket); + *lsport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &audio_socket); + *lcport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &control_socket); + *ltport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &timing_socket); debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); reference_timestamp = 0; - //pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL); - //pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL); - //pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); + // pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL); + // pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL); + // pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); running = 1; request_sent = 0; } -void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time) { +void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, + uint64_t *remote_timestamp_time) { // types okay pthread_mutex_lock(&reference_time_mutex); *timestamp = reference_timestamp; @@ -732,25 +749,25 @@ void clear_reference_timestamp(void) { void rtp_shutdown(void) { if (!running) - debug(1,"rtp_shutdown called without active stream!"); + debug(1, "rtp_shutdown called without active stream!"); debug(2, "shutting down RTP thread"); clear_reference_timestamp(); -// debug(1,"Shut down audio, control and timing threads"); -// usleep(3000000); // hack -// pthread_kill(rtp_audio_thread, SIGUSR1); -// pthread_kill(rtp_control_thread, SIGUSR1); -// pthread_kill(rtp_timing_thread, SIGUSR1); -// pthread_join(rtp_audio_thread, &retval); -// pthread_join(rtp_control_thread, &retval); -// pthread_join(rtp_timing_thread, &retval); + // debug(1,"Shut down audio, control and timing threads"); + // usleep(3000000); // hack + // pthread_kill(rtp_audio_thread, SIGUSR1); + // pthread_kill(rtp_control_thread, SIGUSR1); + // pthread_kill(rtp_timing_thread, SIGUSR1); + // pthread_join(rtp_audio_thread, &retval); + // pthread_join(rtp_control_thread, &retval); + // pthread_join(rtp_timing_thread, &retval); running = 0; } void rtp_request_resend(seq_t first, uint32_t count) { if (running) { - //if (!request_sent) { - debug(3, "requesting resend of %d packets starting at %u.", count, first); + // if (!request_sent) { + debug(3, "requesting resend of %d packets starting at %u.", count, first); // request_sent = 1; //} @@ -771,8 +788,8 @@ void rtp_request_resend(seq_t first, uint32_t count) { perror("Error sendto-ing to audio socket"); } } else { - //if (!request_sent) { - debug(2, "rtp_request_resend called without active stream!"); + // if (!request_sent) { + debug(2, "rtp_request_resend called without active stream!"); // request_sent = 1; //} } diff --git a/rtp.h b/rtp.h index 446142d0..f4c8ab0b 100644 --- a/rtp.h +++ b/rtp.h @@ -5,21 +5,21 @@ #include "player.h" -typedef struct inter_threads_record { - uint32_t please_stop; -} inter_threads_record; +typedef struct inter_threads_record { uint32_t please_stop; } inter_threads_record; void *rtp_audio_receiver(void *arg); void *rtp_control_receiver(void *arg); void *rtp_timing_receiver(void *arg); -void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int controlport, int timingport, uint32_t active_remote, - int *local_server_port, int *local_control_port, int *local_timing_port); +void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int controlport, int timingport, + uint32_t active_remote, int *local_server_port, int *local_control_port, + int *local_timing_port); void rtp_shutdown(void); void rtp_request_resend(seq_t first, uint32_t count); void rtp_request_client_pause(void); // ask the client to pause -void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time); +void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, + uint64_t *remote_timestamp_time); void clear_reference_timestamp(void); uint64_t static local_to_remote_time_jitters; diff --git a/rtsp.c b/rtsp.c index 38b07453..59594019 100644 --- a/rtsp.c +++ b/rtsp.c @@ -26,23 +26,23 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include #include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include "config.h" @@ -50,14 +50,18 @@ #include #endif +#ifdef HAVE_LIBMBEDTLS +#include +#endif + #ifdef HAVE_LIBPOLARSSL #include #endif #include "common.h" +#include "mdns.h" #include "player.h" #include "rtp.h" -#include "mdns.h" #ifdef AF_INET6 #define INETx_ADDRSTRLEN INET6_ADDRSTRLEN @@ -82,7 +86,7 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; // only one thread is allowed to use the player at once. // it monitors the request variable (at least when interrupted) -//static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; +// static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; // static int please_shutdown = 0; // static pthread_t playing_thread = 0; @@ -90,14 +94,15 @@ typedef struct { int fd; int authorized; // set is a password is required and has been supplied stream_cfg stream; - SOCKADDR remote,local; + SOCKADDR remote, local; int stop; int running; pthread_t thread; pthread_t player_thread; } rtsp_conn_info; -static rtsp_conn_info *playing_conn = NULL; // the data structure representing the connection that has the player. +static rtsp_conn_info *playing_conn = + NULL; // the data structure representing the connection that has the player. static rtsp_conn_info **conns = NULL; void memory_barrier() { @@ -144,8 +149,7 @@ typedef struct { rtsp_message *carrier; } metadata_package; -void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, - uint32_t number_of_items) { +void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) { pthread_mutex_init(&the_queue->pc_queue_lock, NULL); pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL); pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL); @@ -157,8 +161,8 @@ void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, the_queue->eoq = 0; } -int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, - rtsp_message *carrier, int block); +int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, + int block); int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) { return send_metadata('ssnc', code, data, length, NULL, block); @@ -176,8 +180,7 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { if (rc) debug(1, "Error locking for pc_queue_add_item"); while (the_queue->count == the_queue->capacity) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, - &the_queue->pc_queue_lock); + rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed"); } @@ -214,8 +217,7 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { if (rc) debug(1, "Error locking for pc_queue_get_item"); while (the_queue->count == 0) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, - &the_queue->pc_queue_lock); + rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be added"); } @@ -263,11 +265,10 @@ static inline int rtsp_playing(void) { void rtsp_request_shutdown_stream(void) { debug(1, "Request to shut down all rtsp conversation threads"); - ask_other_rtsp_conversation_threads_to_stop( - 0); // i.e. ask all playing threads to stop + ask_other_rtsp_conversation_threads_to_stop(0); // i.e. ask all playing threads to stop } -//static void rtsp_take_player(void) { +// static void rtsp_take_player(void) { // if (rtsp_playing()) // return; @@ -314,8 +315,7 @@ void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread) { int i; debug(2, "asking playing threads to stop"); for (i = 0; i < nconns; i++) { - if (((except_this_thread == 0) || - (pthread_equal(conns[i]->thread, except_this_thread) == 0)) && + if (((except_this_thread == 0) || (pthread_equal(conns[i]->thread, except_this_thread) == 0)) && (conns[i]->running != 0)) { conns[i]->stop = 1; pthread_kill(conns[i]->thread, SIGUSR1); @@ -363,8 +363,7 @@ static void msg_retain(rtsp_message *msg) { static rtsp_message *msg_init(void) { rtsp_message *msg = malloc(sizeof(rtsp_message)); memset(msg, 0, sizeof(rtsp_message)); - msg->referenceCount = - 1; // from now on, any access to this must be protected with the lock + msg->referenceCount = 1; // from now on, any access to this must be protected with the lock return msg; } @@ -405,8 +404,7 @@ static void msg_free(rtsp_message *msg) { msg->referenceCount--; rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) - debug(1, "Error %d unlocking reference counter lock during msg_free()", - rc); + debug(1, "Error %d unlocking reference counter lock during msg_free()", rc); if (msg->referenceCount == 0) { int i; for (i = 0; i < msg->nheaders; i++) { @@ -479,8 +477,8 @@ fail: return 0; } -static enum rtsp_read_request_response -rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { +static enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, + rtsp_message **the_packet) { enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 512; char *buf = malloc(buflen + 1); @@ -499,8 +497,8 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { goto shutdown; } nread = read(conn->fd, buf + inbuf, buflen - inbuf); - - if (nread==0) { + + if (nread == 0) { // a blocking read that returns zero means eof -- implies connection closed debug(3, "RTSP connection closed."); reply = rtsp_read_request_response_shutdown_requested; @@ -542,8 +540,8 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { buflen = msg_size; } - uint64_t threshold_time = get_absolute_time_in_fp() + - ((uint64_t)5 << 32); // i.e. five seconds from now + uint64_t threshold_time = + get_absolute_time_in_fp() + ((uint64_t)5 << 32); // i.e. five seconds from now int warning_message_sent = 0; const size_t max_read_chunk = 50000; @@ -628,8 +626,7 @@ static void msg_write_response(int fd, rtsp_message *resp) { int ignore = write(fd, pkt, p - pkt + 2); } -static void handle_record(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // debug(1,"Handle Record"); resp->respcode = 200; // I think this is for telling the client what the absolute minimum latency @@ -663,16 +660,14 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, } } -static void handle_options(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_options(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " "PAUSE, FLUSH, TEARDOWN, " "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } -static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { if (!rtsp_playing()) debug(1, "This RTSP conversation thread doesn't think it's playing, but " "it's sending a response to teardown anyway"); @@ -681,8 +676,7 @@ static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, conn->stop = 1; } -static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { if (!rtsp_playing()) debug(1, "This RTSP conversation thread doesn't think it's playing, but " "it's sending a response to flush anyway"); @@ -705,8 +699,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, resp->respcode = 200; } -static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // debug(1,"Handle Setup"); int cport, tport; int lsport, lcport, ltport; @@ -768,10 +761,8 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, config.latency = config.iTunesLatency; } } else if (strstr(ua, "AirPlay") == ua) { - debug( - 2, - "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.", - config.AirPlayLatency); + debug(2, "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.", + config.AirPlayLatency); config.latency = config.AirPlayLatency; } else if (strstr(ua, "forked-daapd") == ua) { debug(2, "User-Agent is forked-daapd; selecting the forked-daapd latency " @@ -779,8 +770,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, config.ForkedDaapdLatency); config.latency = config.ForkedDaapdLatency; } else { - debug(2, "Unrecognised User-Agent. Using latency of %d frames.", - config.latency); + debug(2, "Unrecognised User-Agent. Using latency of %d frames.", config.latency); } } @@ -809,9 +799,8 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, p = strchr(p, '=') + 1; tport = atoi(p); -// rtsp_take_player(); - rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, - <port); + // rtsp_take_player(); + rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port); if (!lsport) goto error; char *q; @@ -853,13 +842,11 @@ error: resp->respcode = 451; // invalid arguments } -static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; } -static void handle_set_parameter_parameter(rtsp_conn_info *conn, - rtsp_message *req, +static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *cp = req->content; int cp_left = req->contentlength; @@ -888,7 +875,7 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, #ifdef CONFIG_METADATA if (!strncmp(cp, "progress: ", 10)) { char *progress = cp + 10; - //debug(2, "progress: \"%s\"\n", + // debug(2, "progress: \"%s\"\n", // progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); } else @@ -970,7 +957,6 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, // `clip` -- the payload is the IP number of the client, i.e. the sender of audio. // Can be an IPv4 or an IPv6 number. -// // including a simple base64 encoder to minimise malloc/free activity // From Stack Overflow, with thanks: @@ -981,12 +967,11 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, // add _so to end of name to avoid confusion with polarssl's implementation -static char encoding_table[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; +static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; static int mod_table[] = {0, 2, 1}; @@ -995,8 +980,8 @@ static int mod_table[] = {0, 2, 1}; // containing its maximum length // the actual length will be returned. -char *base64_encode_so(const unsigned char *data, size_t input_length, - char *encoded_data, size_t *output_length) { +char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data, + size_t *output_length) { size_t calculated_output_length = 4 * ((input_length + 2) / 3); if (calculated_output_length > *output_length) @@ -1093,8 +1078,7 @@ static void metadata_close(void) { fd = -1; } -void metadata_process(uint32_t type, uint32_t code, char *data, - uint32_t length) { +void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { // debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length); int ret; @@ -1108,7 +1092,8 @@ void metadata_process(uint32_t type, uint32_t code, char *data, memcpy(ptr, &v, 4); ptr += 4; memcpy(ptr, data, length); - sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr)); + sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, + sizeof(metadata_sockaddr)); } // readers may go away and come back @@ -1117,9 +1102,8 @@ void metadata_process(uint32_t type, uint32_t code, char *data, if (fd < 0) return; char thestring[1024]; - snprintf(thestring, 1024, - "%x%x%u", type, - code, length); + snprintf(thestring, 1024, "%x%x%u", type, code, + length); ret = non_blocking_write(fd, thestring, strlen(thestring)); if (ret < 0) { // debug(1,"metadata_process error %d exit 1",ret); @@ -1144,10 +1128,9 @@ void metadata_process(uint32_t type, uint32_t code, char *data, size_t towrite_count = remaining_count; if (towrite_count > 57) towrite_count = 57; - size_t outbuf_size = - 76; // size of output buffer on entry, length of result on exit - if (base64_encode_so((unsigned char *)remaining_data, towrite_count, - outbuf, &outbuf_size) == NULL) + size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit + if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) == + NULL) debug(1, "Error encoding base64 data."); // debug(1,"Remaining count: %d ret: %d, outbuf_size: // %d.",remaining_count,ret,outbuf_size); @@ -1191,16 +1174,15 @@ void *metadata_thread_function(void *ignore) { void metadata_init(void) { // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, - sizeof(metadata_package), metadata_queue_size); - int ret = - pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); + pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), + metadata_queue_size); + int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); if (ret) debug(1, "Failed to create metadata thread!"); } -int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, - rtsp_message *carrier, int block) { +int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, + int block) { // parameters: type, code, pointer to data or NULL, length of data or NULL, // the rtsp_message or @@ -1240,14 +1222,11 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, if ((rc == EBUSY) && (carrier)) msg_free(carrier); if (rc == EBUSY) - warn( - "Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", - type, code); + warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code); return rc; } -static void handle_set_parameter_metadata(rtsp_conn_info *conn, - rtsp_message *req, +static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *cp = req->content; int cl = req->contentlength; @@ -1257,14 +1236,12 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, uint32_t itag, vl; while (off < cl) { // pick up the metadata tag as an unsigned longint - memcpy(&itag, (uint32_t *)(cp + off), - sizeof(uint32_t)); /* can be misaligned, thus memcpy */ + memcpy(&itag, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */ itag = ntohl(itag); off += sizeof(uint32_t); // pick up the length of the data - memcpy(&vl, (uint32_t *)(cp + off), - sizeof(uint32_t)); /* can be misaligned, thus memcpy */ + memcpy(&vl, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */ vl = ntohl(vl); off += sizeof(uint32_t); @@ -1281,14 +1258,12 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn, #endif -static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +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) { +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."); @@ -1297,7 +1272,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, 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 @@ -1325,8 +1300,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, if (p == NULL) debug(1, "Missing RTP-Time info for metadata"); if (p) - send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, - 1); // metadata starting + send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, 1); // metadata starting else send_metadata('ssnc', 'mdst', NULL, 0, NULL, 0); // metadata starting, if rtptime is not available @@ -1334,8 +1308,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, handle_set_parameter_metadata(conn, req, resp); if (p) - send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, - 1); // metadata ending + send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, 1); // metadata ending else send_metadata('ssnc', 'mden', NULL, 0, NULL, 0); // metadata starting, if rtptime is not available @@ -1351,8 +1324,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, if (p == NULL) debug(1, "Missing RTP-Time info for picture item"); if (p) - send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, - 1); // picture starting + send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, 1); // picture starting else send_metadata('ssnc', 'pcst', NULL, 0, NULL, 0); // picture starting, if rtptime is not available @@ -1360,8 +1332,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1); if (p) - send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, - 1); // picture ending + send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, 1); // picture ending else send_metadata('ssnc', 'pcen', NULL, 0, NULL, 0); // picture ending, if rtptime is not available @@ -1372,11 +1343,9 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, #endif if (!strncmp(ct, "text/parameters", 15)) { // debug(2, "received parameters in SET_PARAMETER request."); - handle_set_parameter_parameter(conn, req, - resp); // this could be volume or progress + handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress } else { - debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", - ct); + debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", ct); } } else { debug(1, "missing Content-Type header in SET_PARAMETER request."); @@ -1385,8 +1354,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, resp->respcode = 200; } -static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, - rtsp_message *resp) { +static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { int have_the_player = 0; // interrupt session if permitted @@ -1403,15 +1371,15 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, die("Non existent the_playing_conn with play_lock enabled."); } usleep(1000000); // here, it is possible for other connections to come in and nab the player. - debug(1,"Try to get the player now"); - //pthread_mutex_lock(&play_lock); + debug(1, "Try to get the player now"); + // pthread_mutex_lock(&play_lock); if (pthread_mutex_trylock(&play_lock) == 0) have_the_player = 1; } } - - if (have_the_player) { - playing_conn = conn; // the present connection is now playing + + if (have_the_player) { + playing_conn = conn; // the present connection is now playing resp->respcode = 456; // 456 - Header Field Not Valid for Resource char *paesiv = NULL; char *prsaaeskey = NULL; @@ -1482,8 +1450,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, free(aeskey); } int i; - for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); - i++) + for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); char *hdr = msg_get_header(req, "X-Apple-Client-Name"); @@ -1651,6 +1618,22 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Final(digest_mu, &ctx); #endif +#ifdef HAVE_LIBMBEDTLS + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); + mbedtls_md5_finish(&tctx, digest_urp); + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); + mbedtls_md5_finish(&tctx, digest_mu); +#endif + #ifdef HAVE_LIBPOLARSSL md5_context tctx; md5_starts(&tctx); @@ -1658,8 +1641,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { md5_update(&tctx, (unsigned char *)":", 1); md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); md5_update(&tctx, (unsigned char *)":", 1); - md5_update(&tctx, (const unsigned char *)config.password, - strlen(config.password)); + md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); md5_finish(&tctx, digest_urp); md5_starts(&tctx); md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); @@ -1685,6 +1667,18 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Final(digest_total, &ctx); #endif +#ifdef HAVE_LIBMBEDTLS + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, buf, 32); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + for (i = 0; i < 16; i++) + sprintf((char *)buf + 2 * i, "%02x", digest_mu[i]); + mbedtls_md5_update(&tctx, buf, 32); + mbedtls_md5_finish(&tctx, digest_total); +#endif + #ifdef HAVE_LIBPOLARSSL md5_starts(&tctx); md5_update(&tctx, buf, 32); @@ -1731,8 +1725,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { do { reply = rtsp_read_request(conn, &req); if (reply == rtsp_read_request_response_ok) { - debug(3,"RTSP Packet received of type \"%s\":",req->method), - debug_print_msg_headers(3,req); + debug(3, "RTSP Packet received of type \"%s\":", req->method), + debug_print_msg_headers(3, req); resp = msg_init(); resp->respcode = 400; @@ -1740,26 +1734,25 @@ static void *rtsp_conversation_thread_func(void *pconn) { hdr = msg_get_header(req, "CSeq"); if (hdr) msg_add_header(resp, "CSeq", hdr); -// msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); + // msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); msg_add_header(resp, "Server", "AirTunes/105.1"); - - if ((conn->authorized==1) || (rtsp_auth(&auth_nonce, req, resp))==0) { - conn->authorized=1; // it must have been authorized or didn't need a password - struct method_handler *mh; - int method_selected = 0; - for (mh = method_handlers; mh->method; mh++) { - if (!strcmp(mh->method, req->method)) { - method_selected = 1; - mh->handler(conn, req, resp); - break; - } - } - if (method_selected == 0) - debug(1, "Unrecognised and unhandled rtsp request \"%s\".", - req->method); - } - debug(3,"RTSP Response:"); - debug_print_msg_headers(3,resp); + + if ((conn->authorized == 1) || (rtsp_auth(&auth_nonce, req, resp)) == 0) { + conn->authorized = 1; // it must have been authorized or didn't need a password + struct method_handler *mh; + int method_selected = 0; + for (mh = method_handlers; mh->method; mh++) { + if (!strcmp(mh->method, req->method)) { + method_selected = 1; + mh->handler(conn, req, resp); + break; + } + } + if (method_selected == 0) + debug(1, "Unrecognised and unhandled rtsp request \"%s\".", req->method); + } + debug(3, "RTSP Response:"); + debug_print_msg_headers(3, resp); msg_write_response(conn->fd, resp); msg_free(req); msg_free(resp); @@ -1781,11 +1774,11 @@ static void *rtsp_conversation_thread_func(void *pconn) { close(conn->fd); if (auth_nonce) free(auth_nonce); -// pthread_mutex_unlock(&playing_mutex); - // usleep(1000000); -// } // else { - //debug(1, "This RTSP conversation thread doesn't think it's playing for a " - // "close RTSP connection."); + // pthread_mutex_unlock(&playing_mutex); + // usleep(1000000); + // } // else { + // debug(1, "This RTSP conversation thread doesn't think it's playing for a " + // "close RTSP connection."); // } debug(2, "RTSP conversation thread terminated."); // please_shutdown = 0; @@ -1831,7 +1824,7 @@ void rtsp_listen_loop(void) { } for (p = info; p; p = p->ai_next) { - ret = 0; + ret = 0; int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); int yes = 1; @@ -1865,14 +1858,15 @@ void rtsp_listen_loop(void) { // report its availability. do not complain. if (ret) { - char *family; + char *family; #ifdef AF_INET6 - if (p->ai_family == AF_INET6) { - family = "IPv6"; - } else + if (p->ai_family == AF_INET6) { + family = "IPv6"; + } else #endif - family = "IPv4"; - debug(1, "Unable to listen on %s port %d. The error is: \"%s\".", family, config.port,strerror(errno)); + family = "IPv4"; + debug(1, "Unable to listen on %s port %d. The error is: \"%s\".", family, config.port, + strerror(errno)); continue; } @@ -1885,7 +1879,9 @@ void rtsp_listen_loop(void) { freeaddrinfo(info); if (!nsock) - die("Could not establish a service on port %d -- program terminating. Is another Shairport Sync running on this device?",config.port); + die("Could not establish a service on port %d -- program terminating. Is another instance of " + "Shairport Sync running on this device?", + config.port); int maxfd = -1; fd_set fds; @@ -1938,57 +1934,58 @@ void rtsp_listen_loop(void) { perror("failed to accept connection"); free(conn); } else { - SOCKADDR *local_info = (SOCKADDR*)&conn->local; + SOCKADDR *local_info = (SOCKADDR *)&conn->local; socklen_t size_of_reply = sizeof(*local_info); - memset(local_info,0,sizeof(SOCKADDR)); - if (getsockname(conn->fd, (struct sockaddr*)local_info, &size_of_reply)==0) { - + memset(local_info, 0, sizeof(SOCKADDR)); + if (getsockname(conn->fd, (struct sockaddr *)local_info, &size_of_reply) == 0) { + // IPv4: - if (local_info->SAFAMILY==AF_INET) { - char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string - char remote_ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string - struct sockaddr_in *sa = (struct sockaddr_in*)local_info; + if (local_info->SAFAMILY == AF_INET) { + char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string + char remote_ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string + struct sockaddr_in *sa = (struct sockaddr_in *)local_info; inet_ntop(AF_INET, &(sa->sin_addr), ip4, INET_ADDRSTRLEN); unsigned short int tport = ntohs(sa->sin_port); - sa = (struct sockaddr_in*)&conn->remote; + sa = (struct sockaddr_in *)&conn->remote; inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN); unsigned short int rport = ntohs(sa->sin_port); #ifdef CONFIG_METADATA - send_ssnc_metadata('clip', strdup(remote_ip4), strlen(remote_ip4) , 1); - send_ssnc_metadata('svip', strdup(ip4), strlen(ip4) , 1); + send_ssnc_metadata('clip', strdup(remote_ip4), strlen(remote_ip4), 1); + send_ssnc_metadata('svip', strdup(ip4), strlen(ip4), 1); #endif - - debug(1,"New RTSP connection from %s:%u to self at %s:%u.",remote_ip4,rport,ip4,tport); + debug(1, "New RTSP connection from %s:%u to self at %s:%u.", remote_ip4, rport, ip4, + tport); } #ifdef AF_INET6 - if (local_info->SAFAMILY==AF_INET6) { + if (local_info->SAFAMILY == AF_INET6) { // IPv6: - char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string + char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string char remote_ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string - struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)local_info; // pretend this is loaded with something + struct sockaddr_in6 *sa6 = + (struct sockaddr_in6 *)local_info; // pretend this is loaded with something inet_ntop(AF_INET6, &(sa6->sin6_addr), ip6, INET6_ADDRSTRLEN); u_int16_t tport = ntohs(sa6->sin6_port); - - sa6 = (struct sockaddr_in6*)&conn->remote; // pretend this is loaded with something + + sa6 = (struct sockaddr_in6 *)&conn->remote; // pretend this is loaded with something inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN); u_int16_t rport = ntohs(sa6->sin6_port); #ifdef CONFIG_METADATA - send_ssnc_metadata('clip', strdup(remote_ip6), strlen(remote_ip6), 1); - send_ssnc_metadata('svip', strdup(ip6), strlen(ip6), 1); + send_ssnc_metadata('clip', strdup(remote_ip6), strlen(remote_ip6), 1); + send_ssnc_metadata('svip', strdup(ip6), strlen(ip6), 1); #endif - debug(1,"New RTSP connection from [%s]:%u to self at [%s]:%u.",remote_ip6,rport,ip6,tport); + debug(1, "New RTSP connection from [%s]:%u to self at [%s]:%u.", remote_ip6, rport, ip6, + tport); } - #endif - +#endif + } else { - debug(1,"Error figuring out Shairport Sync's own IP number."); + debug(1, "Error figuring out Shairport Sync's own IP number."); } usleep(500000); pthread_t rtsp_conversation_thread; - ret = pthread_create(&rtsp_conversation_thread, NULL, - rtsp_conversation_thread_func, conn); + ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); if (ret) die("Failed to create RTSP receiver thread!"); diff --git a/rtsp.h b/rtsp.h index a2f6a952..5058c9a1 100644 --- a/rtsp.h +++ b/rtsp.h @@ -5,7 +5,6 @@ void rtsp_listen_loop(void); // void rtsp_shutdown_stream(void); void rtsp_request_shutdown_stream(void); - // initialise the metadata stuff void metadata_init(void); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 9bdbc790..cdbdc124 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -9,8 +9,8 @@ general = // You can use the following substitutions: // %h for the hostname, // %H for the Hostname (i.e. with first letter capitalised (ASCII only)), -// %v for the version number, e.g. 2.8.5 and -// %V for the full version string, e.g. 2.8.5-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc +// %v for the version number, e.g. 3.0 and +// %V for the full version string, e.g. 3.0-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc // Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H". // password = "secret"; // leave this commented out if you don't want to require a password // interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor. @@ -20,25 +20,31 @@ general = // udp_port_base = 6001; // start allocating UDP ports from this port number when needed // udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed). // statistics = "no"; // set to "yes" to print statistics in the log -// drift = 88; // allow this number of frames of drift away from exact synchronisation before attempting to correct it -// resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it +// drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it +// resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it // log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose. // 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. -// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. +// volume_range_db = 60 ; // use this advanced setting to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range. +// volume_max_db = 0.0 ; // use this advanced setting, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use. +// The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer). +// Leave it commented out to use mixer's maximum volume. // regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". -// playback_mode = "stereo"; // This can be "stereo" or "mono". Default is "stereo". +// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". +// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose +// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. +// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically. }; // How to deal with metadata, including artwork metadata = { -// enabled = "no"; // et to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe +// enabled = "no"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe // include_cover_art = "no"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes". // pipe_name = "/tmp/shairport-sync-metadata"; // pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up -// socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes" -// socket_port = 5555; // if socket_address is set, the port to send UDP packets to -// socket_msglength = 65000; // the maximum packet size for any UDP metadata. This will be clipped to be between 500 or 65000. The default is 500. +// socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes" +// socket_port = 5555; // if socket_address is set, the port to send UDP packets to +// socket_msglength = 65000; // the maximum packet size for any UDP metadata. This will be clipped to be between 500 or 65000. The default is 500. }; // Advanced parameters for controlling how a Shairport Sync runs @@ -59,10 +65,13 @@ alsa = // output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc. // mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. // mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. -// 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 = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. +// output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability. +// output_format = "S16"; // can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor. +// audio_backend_latency_offset_in_seconds = 0.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 -0.1. +// audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying. // disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". // period_size = ; // Use this optional advanced setting to set the alsa period size near to this value +// buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value // use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes" }; @@ -70,22 +79,22 @@ alsa = pipe = { // name = "/path/to/pipe"; // there is no default pipe name for the output -// 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. +// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length. }; // These are parameters for the "stdout" audio back end, a back end that directs raw CD-style audio output to stdout. No interpolation is done. 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. +// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length. }; // These are parameters for the "ao" audio back end. No interpolation is done. ao = { -// 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. +// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. +// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length. }; // Static latency settings are deprecated and the settings have been removed. diff --git a/shairport.c b/shairport.c index f725d607..c34a54a7 100644 --- a/shairport.c +++ b/shairport.c @@ -2,7 +2,7 @@ * Shairport, an Apple Airplay receiver * Copyright (c) James Laird 2013 * All rights reserved. - * Modifications (c) Mike Brady 2014--2016 + * Modifications (c) Mike Brady 2014--2017 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -25,23 +25,28 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include #include -#include -#include #include -#include -#include #include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include +#include #include "config.h" +#ifdef HAVE_LIBMBEDTLS +#include +#endif + #ifdef HAVE_LIBPOLARSSL #include #endif @@ -51,20 +56,20 @@ #endif #include "common.h" -#include "rtsp.h" -#include "rtp.h" #include "mdns.h" +#include "rtp.h" +#include "rtsp.h" +#include #include -#include #include #include -#include +#include static int shutting_down = 0; static char *appName = NULL; -char configuration_file_path[4096+1]; -char actual_configuration_file_path[4096+1]; +char configuration_file_path[4096 + 1]; +char actual_configuration_file_path[4096 + 1]; void shairport_shutdown() { if (shutting_down) @@ -80,7 +85,7 @@ static void sig_ignore(int foo, siginfo_t *bar, void *baz) {} static void sig_shutdown(int foo, siginfo_t *bar, void *baz) { debug(1, "shutdown requested..."); shairport_shutdown(); -// daemon_log(LOG_NOTICE, "exit..."); + // daemon_log(LOG_NOTICE, "exit..."); daemon_retval_send(255); daemon_pid_file_remove(); exit(0); @@ -105,52 +110,58 @@ static void sig_connect_audio_output(int foo, siginfo_t *bar, void *baz) { set_requested_connection_state_to_output(1); } -char* get_version_string() { - char* version_string = malloc(200); +char *get_version_string() { + char *version_string = malloc(200); if (version_string) { strcpy(version_string, PACKAGE_VERSION); - #ifdef HAVE_LIBPOLARSSL +#ifdef HAVE_LIBMBEDTLS + strcat(version_string, "-mbedTLS"); +#endif +#ifdef HAVE_LIBPOLARSSL strcat(version_string, "-PolarSSL"); - #endif - #ifdef HAVE_LIBSSL +#endif +#ifdef HAVE_LIBSSL strcat(version_string, "-OpenSSL"); - #endif - #ifdef CONFIG_TINYSVCMDNS +#endif +#ifdef CONFIG_TINYSVCMDNS strcat(version_string, "-tinysvcmdns"); - #endif - #ifdef CONFIG_AVAHI +#endif +#ifdef CONFIG_AVAHI strcat(version_string, "-Avahi"); - #endif - #ifdef CONFIG_DNS_SD +#endif +#ifdef CONFIG_DNS_SD strcat(version_string, "-dns_sd"); - #endif - #ifdef CONFIG_ALSA +#endif +#ifdef CONFIG_ALSA strcat(version_string, "-ALSA"); - #endif - #ifdef CONFIG_SNDIO +#endif +#ifdef CONFIG_SNDIO strcat(version_string, "-sndio"); - #endif - #ifdef CONFIG_AO +#endif +#ifdef CONFIG_AO strcat(version_string, "-ao"); - #endif - #ifdef CONFIG_PULSE +#endif +#ifdef CONFIG_PULSE strcat(version_string, "-pulse"); - #endif - #ifdef CONFIG_DUMMY +#endif +#ifdef CONFIG_SOUNDIO + strcat(version_string, "-soundio"); +#endif +#ifdef CONFIG_DUMMY strcat(version_string, "-dummy"); - #endif - #ifdef CONFIG_STDOUT +#endif +#ifdef CONFIG_STDOUT strcat(version_string, "-stdout"); - #endif - #ifdef CONFIG_PIPE +#endif +#ifdef CONFIG_PIPE strcat(version_string, "-pipe"); - #endif - #ifdef HAVE_LIBSOXR +#endif +#ifdef HAVE_LIBSOXR strcat(version_string, "-soxr"); - #endif - #ifdef CONFIG_METADATA +#endif +#ifdef CONFIG_METADATA strcat(version_string, "-metadata"); - #endif +#endif strcat(version_string, "-sysconfdir:"); strcat(version_string, SYSCONFDIR); } @@ -158,12 +169,12 @@ char* get_version_string() { } void print_version(void) { - char* version_string = get_version_string(); + char *version_string = get_version_string(); if (version_string) { printf("%s\n", version_string); free(version_string); } else { - debug(1,"Can't print version string!"); + debug(1, "Can't print version string!"); } } @@ -182,21 +193,26 @@ void usage(char *progname) { "/etc/shairport-sync.conf.\n"); printf("\n"); - printf("The following general options are for backward compatability. These and all new options have settings in the configuration file, by default /etc/shairport-sync.conf:\n"); + printf("The following general options are for backward compatability. These and all new options " + "have settings in the configuration file, by default /etc/shairport-sync.conf:\n"); printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n"); printf(" -p, --port=PORT set RTSP listening port.\n"); printf(" -a, --name=NAME set advertised name.\n"); - printf( - " -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an AirPlay device.\n"); + // printf(" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an " + // "AirPlay device.\n"); + // printf(" The default is to set it automatically.\n"); + // printf(" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes + // " + // "10 or later.\n"); + // printf(" The default is to set it automatically.\n"); + printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown " + "device.\n"); printf(" The default is to set it automatically.\n"); - printf( - " -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes 10 or later.\n"); + printf(" --forkedDaapdLatency=FRAMES [Deprecated] Set the latency for audio sent from " + "forked-daapd.\n"); printf(" The default is to set it automatically.\n"); - printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown device.\n"); - printf(" The default is to set it automatically.\n"); - printf(" --forkedDaapdLatency=FRAMES [Deprecated] Set the latency for audio sent from forked-daapd.\n"); - printf(" The default is to set it automatically.\n"); - printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, where \n"); + printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, " + "where \n"); printf(" \"basic\" (default) inserts or deletes audio frames from " "packet frames with low processor overhead, and \n"); printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- " @@ -209,18 +225,21 @@ void usage(char *progname) { "e.g. /usr/bin/logger.\n"); printf(" Executable scripts work, but must have #!/bin/sh (or " "whatever) in the headline.\n"); - printf(" -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n"); + printf( + " -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n"); printf(" -o, --output=BACKEND select audio output method.\n"); printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n"); printf(" if no mdns provider is specified,\n"); printf(" shairport tries them all until one works.\n"); - printf(" -r, --resync=THRESHOLD resync if error exceeds this number of frames. Set to 0 to " + printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. " + "Set to 0 to " "stop resyncing.\n"); printf(" -t, --timeout=SECONDS go back to idle mode from play mode after a break in " "communications of this many seconds (default 120). Set to 0 never to exit play mode.\n"); printf(" --statistics print some interesting statistics -- output to the logfile " "if running as a daemon.\n"); - printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE frames (default " + printf(" --tolerance=TOLERANCE [Deprecated] allow a synchronization error of TOLERANCE " + "frames (default " "88) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a " "password.\n"); @@ -237,11 +256,14 @@ void usage(char *progname) { } int parse_options(int argc, char **argv) { - // there are potential memory leaks here -- it's called a second time, previously allocated strings will dangle. + // there are potential memory leaks here -- it's called a second time, previously allocated + // strings will dangle. char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */ - char *stuffing = NULL; /* used for picking up the stuffing option */ - signed char c; /* used for argument parsing */ - int i = 0; /* used for tracking options */ + char *stuffing = NULL; /* used for picking up the stuffing option */ + signed char c; /* used for argument parsing */ + int i = 0; /* used for tracking options */ + int fResyncthreshold = (int)(config.resyncthreshold * 44100); + int fTolerance = (int)(config.tolerance * 44100); poptContext optCon; /* context for parsing command-line options */ struct poptOption optionsTable[] = { {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL}, @@ -262,20 +284,19 @@ int parse_options(int argc, char **argv) { {"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL}, {"AirPlayLatency", 'A', POPT_ARG_INT, &config.AirPlayLatency, 0, NULL}, {"iTunesLatency", 'i', POPT_ARG_INT, &config.iTunesLatency, 0, NULL}, - {"forkedDaapdLatency", 0, POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL}, + {"forkedDaapdLatency", 'f', POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL}, {"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL}, - {"resync", 'r', POPT_ARG_INT, &config.resyncthreshold, 0, NULL}, + {"resync", 'r', POPT_ARG_INT, &fResyncthreshold, 0, NULL}, {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL}, {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL}, - {"tolerance", 0, POPT_ARG_INT, &config.tolerance, 0, NULL}, + {"tolerance", 'z', POPT_ARG_INT, &fTolerance, 0, NULL}, #ifdef CONFIG_METADATA {"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL}, {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL}, #endif - POPT_AUTOHELP - {NULL, 0, 0, NULL, 0}}; + POPT_AUTOHELP{NULL, 0, 0, NULL, 0}}; -// we have to parse the command line arguments to look for a config file + // we have to parse the command line arguments to look for a config file int optind; optind = argc; int j; @@ -285,31 +306,61 @@ int parse_options(int argc, char **argv) { optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0); poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); - - /* Now do options processing just to get a debug level */ + + /* Now do options processing just to get a debug level */ debuglev = 0; while ((c = poptGetNextOpt(optCon)) >= 0) { switch (c) { case 'v': debuglev++; break; + case 'D': + inform("Warning: the option -D or --disconnectFromOutput is deprecated."); + break; + case 'R': + inform("Warning: the option -R or --reconnectToOutput is deprecated."); + break; + case 'A': + inform("Warning: the option -A or --AirPlayLatency is deprecated. This setting is now " + "automatically received from the AirPlay device."); + break; + case 'i': + inform("Warning: the option -i or --iTunesLatency is deprecated. This setting is now " + "automatically received from iTunes"); + break; + case 'f': + inform("Warning: the option --forkedDaapdLatency is deprecated. This setting is now " + "automatically received from forkedDaapd"); + break; + case 'r': + inform("Warning: the option -r or --resync is deprecated. Please use the " + "\"resync_threshold_in_seconds\" setting in the config file instead."); + break; + case 'z': + inform("Warning: the option --tolerance is deprecated. Please use the " + "\"drift_tolerance_in_seconds\" setting in the config file instead."); + break; } } if (c < -1) { die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); } + config.resyncthreshold = 1.0 * fResyncthreshold / 44100; + config.tolerance = 1.0 * fTolerance / 44100; + config_setting_t *setting; - const char *str=0; - int value=0; - - debug(1,"Looking for the configuration file \"%s\".",config.configfile); - + const char *str = 0; + int value = 0; + double dvalue = 0.0; + + debug(1, "Looking for the configuration file \"%s\".", config.configfile); + config_init(&config_file_stuff); - + char *config_file_real_path = realpath(config.configfile, NULL); - if (config_file_real_path==NULL) { - debug(2,"Can't resolve the configuration file \"%s\".",config.configfile); + if (config_file_real_path == NULL) { + debug(2, "Can't resolve the configuration file \"%s\".", config.configfile); } else { debug(2, "Looking for configuration file at full path \"%s\"", config_file_real_path); /* Read the file. If there is an error, report it and exit. */ @@ -357,7 +408,8 @@ int parse_options(int argc, char **argv) { config.udp_port_base = value; } - /* Get the udp port range setting. This is number of ports that will be tried for free ports , starting at the port base. Only three ports are needed. */ + /* Get the udp port range setting. This is number of ports that will be tried for free ports , + * starting at the port base. Only three ports are needed. */ if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) { if ((value < 0) || (value > 65535)) die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100", @@ -389,13 +441,27 @@ int parse_options(int argc, char **argv) { die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\""); } + /* The old drift tolerance setting. */ + if (config_lookup_int(config.cfg, "general.drift", &value)) { + inform("The drift setting is deprecated. Use " + "drift_tolerance_in_seconds instead"); + config.tolerance = 1.0 * value / 44100; + } + + /* The old resync setting. */ + if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) { + inform("The resync_threshold setting is deprecated. Use " + "resync_threshold_in_seconds instead"); + config.resyncthreshold = 1.0 * value / 44100; + } + /* Get the drift tolerance setting. */ - if (config_lookup_int(config.cfg, "general.drift", &value)) - config.tolerance = value; + if (config_lookup_float(config.cfg, "general.drift_tolerance_in_seconds", &dvalue)) + config.tolerance = dvalue; /* Get the resync setting. */ - if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) - config.resyncthreshold = value; + if (config_lookup_float(config.cfg, "general.resync_threshold_in_seconds", &dvalue)) + config.resyncthreshold = dvalue; /* Get the verbosity setting. */ if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) { @@ -417,31 +483,97 @@ int parse_options(int argc, char **argv) { die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\""); } + /* Get the optional volume_max_db setting. */ + if (config_lookup_float(config.cfg, "general.volume_max_db", &dvalue)) { + debug(1, "Max volume setting of %f dB", dvalue); + config.volume_max_db = dvalue; + config.volume_max_db_set = 1; + } + /* Get the playback_mode setting */ - if (config_lookup_string(config.cfg, "general.playback_mode", &str)) { + if (config_lookup_string(config.cfg, "general.playback_mode", &str)) { if (strcasecmp(str, "stereo") == 0) config.playback_mode = ST_stereo; else if (strcasecmp(str, "mono") == 0) config.playback_mode = ST_mono; + else if (strcasecmp(str, "reverse stereo") == 0) + config.playback_mode = ST_reverse_stereo; + else if (strcasecmp(str, "both left") == 0) + config.playback_mode = ST_left_only; + else if (strcasecmp(str, "both right") == 0) + config.playback_mode = ST_right_only; else - die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default) or \"mono\""); + die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", " + "\"reverse stereo\", \"both left\", \"both right\""); } - /* Get the regtype -- the service type and protocol, separated by a dot. Default is "_raop._tcp" */ + /* Get the interface to listen on, if specified Default is all interfaces */ + /* we keep the interface name and the index */ + + if (config_lookup_string(config.cfg, "general.interface", &str)) + config.interface = strdup(str); + + if (config_lookup_string(config.cfg, "general.interface", &str)) { + int specified_interface_found = 0; + + struct if_nameindex *if_ni, *i; + + if_ni = if_nameindex(); + if (if_ni == NULL) { + debug(1, "Can't get a list of interface names."); + } else { + for (i = if_ni; !(i->if_index == 0 && i->if_name == NULL); i++) { + // printf("%u: %s\n", i->if_index, i->if_name); + if (strcmp(i->if_name, str) == 0) { + config.interface_index = i->if_index; + specified_interface_found = 1; + } + } + } + + if_freenameindex(if_ni); + + if (specified_interface_found == 0) { + inform( + "The mdns service interface \"%s\" was not found, so the setting has been ignored.", + config.interface); + free(config.interface); + config.interface = NULL; + config.interface_index = 0; + } + } + + /* Get the regtype -- the service type and protocol, separated by a dot. Default is + * "_raop._tcp" */ if (config_lookup_string(config.cfg, "general.regtype", &str)) config.regtype = strdup(str); - - /* Get the volume range, in dB, that should be used If not set, it means you just use the range set by the mixer. */ + /* Get the volume range, in dB, that should be used If not set, it means you just use the + * range set by the mixer. */ if (config_lookup_int(config.cfg, "general.volume_range_db", &value)) { if ((value < 30) || (value > 150)) - die("Invalid volume range \"%sd\". It should be between 30 and 150 dB. Zero means use the mixer's native range", + die("Invalid volume range \"%sd\". It should be between 30 and 150 dB. Zero means use " + "the mixer's native range", value); else config.volume_range_db = value; } -/* Get the default latency. Deprecated! */ + /* Get the alac_decoder setting. */ + if (config_lookup_string(config.cfg, "general.alac_decoder", &str)) { + if (strcasecmp(str, "hammerton") == 0) + config.use_apple_decoder = 0; + else if (strcasecmp(str, "apple") == 0) { + if ((config.decoders_supported & 1 << decoder_apple_alac) != 0) + config.use_apple_decoder = 1; + else + inform("Support for the Apple ALAC decoder has not been compiled into this version of " + "Shairport Sync. The default decoder will be used."); + } else + die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\""); + } + + /* Get the default latency. Deprecated! */ if (config_lookup_int(config.cfg, "latencies.default", &value)) config.userSuppliedLatency = value; @@ -457,7 +589,7 @@ int parse_options(int argc, char **argv) { if (config_lookup_int(config.cfg, "latencies.forkedDaapd", &value)) config.ForkedDaapdLatency = value; - #ifdef CONFIG_METADATA +#ifdef CONFIG_METADATA /* Get the metadata setting. */ if (config_lookup_string(config.cfg, "metadata.enabled", &str)) { if (strcasecmp(str, "no") == 0) @@ -493,7 +625,7 @@ int parse_options(int argc, char **argv) { config.metadata_sockmsglength = value < 500 ? 500 : value > 65000 ? 65000 : value; } - #endif +#endif if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) { config.cmd_start = (char *)str; @@ -520,7 +652,8 @@ int parse_options(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.allow_session_interruption = 1; else - die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" " + die("Invalid session control allow_interruption option choice \"%s\". It should be " + "\"yes\" " "or \"no\""); } @@ -541,8 +674,9 @@ int parse_options(int argc, char **argv) { free(config_file_real_path); } -// now, do the command line options again, but this time do them fully -- it's a unix convention that command line -// arguments have precedence over configuration file settings. + // now, do the command line options again, but this time do them fully -- it's a unix convention + // that command line + // arguments have precedence over configuration file settings. optind = argc; for (j = 0; j < argc; j++) @@ -595,34 +729,34 @@ int parse_options(int argc, char **argv) { if (c < -1) { die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c)); } - + #ifdef CONFIG_METADATA if ((config.metadata_enabled == 1) && (config.metadata_pipename == NULL)) - config.metadata_pipename=strdup("/tmp/shairport-sync-metadata"); + config.metadata_pipename = strdup("/tmp/shairport-sync-metadata"); #endif -/* if the regtype hasn't been set, do it now */ - if (config.regtype==NULL) + /* if the regtype hasn't been set, do it now */ + if (config.regtype == NULL) config.regtype = strdup("_raop._tcp"); - - if (tdebuglev!=0) + + if (tdebuglev != 0) debuglev = tdebuglev; -/* if the Service Name wasn't specified, do it now */ + /* if the Service Name wasn't specified, do it now */ - if (raw_service_name==NULL) + if (raw_service_name == NULL) raw_service_name = strdup("%H"); - -// now, do the substitutions in the service name + + // now, do the substitutions in the service name char hostname[100]; gethostname(hostname, 100); - char *i1 = str_replace(raw_service_name,"%h",hostname); - if ((hostname[0]>='a') && (hostname[0]<='z')) - hostname[0] = hostname[0]-0x20; // convert a lowercase first letter into a capital letter - char *i2 = str_replace(i1,"%H",hostname); - char *i3 = str_replace(i2,"%v",PACKAGE_VERSION); + char *i1 = str_replace(raw_service_name, "%h", hostname); + if ((hostname[0] >= 'a') && (hostname[0] <= 'z')) + hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter + char *i2 = str_replace(i1, "%H", hostname); + char *i3 = str_replace(i2, "%v", PACKAGE_VERSION); char *vs = get_version_string(); - config.service_name = str_replace(i3,"%V",vs); + config.service_name = str_replace(i3, "%V", vs); free(i1); free(i2); free(i3); @@ -680,7 +814,6 @@ void shairport_startup_complete(void) { const char *pid_file_proc(void) { #ifdef HAVE_ASPRINTF static char *fn = NULL; - free(fn); asprintf(&fn, "%s/%s.pid", PIDDIR, daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); #else static char fn[8192]; @@ -695,6 +828,7 @@ const char *pid_file_proc(void) { void exit_function() { if (config.cfg) config_destroy(config.cfg); + // probably should be freeing malloc'ed memory here, including strdup-created strings... } int main(int argc, char **argv) { @@ -710,27 +844,27 @@ int main(int argc, char **argv) { free(basec); // set defaults - - + // get thje endianness union { - uint32_t u32; - uint8_t arr[4]; + uint32_t u32; + uint8_t arr[4]; } xn; - xn.arr[0] = 0x44; /* Lowest-address byte */ + xn.arr[0] = 0x44; /* Lowest-address byte */ xn.arr[1] = 0x33; xn.arr[2] = 0x22; - xn.arr[3] = 0x11; /* Highest-address byte */ - - if (xn.u32==0x11223344) + xn.arr[3] = 0x11; /* Highest-address byte */ + + if (xn.u32 == 0x11223344) endianness = SS_LITTLE_ENDIAN; - else if (xn.u32==0x33441122) + else if (xn.u32 == 0x33441122) endianness = SS_PDP_ENDIAN; - else if (xn.u32==0x44332211) + else if (xn.u32 == 0x44332211) endianness = SS_BIG_ENDIAN; - else die("Can not recognise the endianness of the processor."); - + else + die("Can not recognise the endianness of the processor."); + strcpy(configuration_file_path, SYSCONFDIR); strcat(configuration_file_path, "/"); strcat(configuration_file_path, appName); @@ -738,28 +872,48 @@ int main(int argc, char **argv) { config.configfile = configuration_file_path; config.statistics_requested = 0; // don't print stats in the log - 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 = -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 = -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 = -1; // -1 means not set. 99400 seems to be right - config.resyncthreshold = 441 * 5; // this number of frames is 50 ms + 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 = + -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 = + -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 = -1; // -1 means not set. 99400 seems to be right + config.resyncthreshold = 0.05; // 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. + config.tolerance = + 0.002; // this number of seconds of timing error before attempting to correct it. config.buffer_start_fill = 220; config.port = 5000; config.packet_stuffing = ST_basic; // simple interpolation or deletion - //char hostname[100]; - //gethostname(hostname, 100); - //config.service_name = malloc(20 + 100); - //snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname); - set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device + // char hostname[100]; + // gethostname(hostname, 100); + // config.service_name = malloc(20 + 100); + // snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname); + set_requested_connection_state_to_output( + 1); // we expect to be able to connect to the output device config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds. config.udp_port_base = 6001; config.udp_port_range = 100; + config.output_format = SPS_FORMAT_S16; // default + config.output_rate = 44100; // default + config.decoders_supported = + 1 << decoder_hammerton; // David Hammerton's decoder supported by default +#ifdef HAVE_APPLE_ALAC + config.decoders_supported += 1 << decoder_apple_alac; +#endif + + // initialise random number generator + + r64init(0); + + // initialise the randomw number array + + r64arrayinit(); /* Check if we are called with -V or --version parameter */ if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { @@ -854,7 +1008,7 @@ int main(int argc, char **argv) { // mDNS supports maximum of 63-character names (we append 13). if (strlen(config.service_name) > 50) { warn("Supplied name too long (max 50 characters)"); - config.service_name[50] = '\0'; //truncate it and carry on... + config.service_name[50] = '\0'; // truncate it and carry on... } /* here, daemonise with libdaemon */ @@ -924,69 +1078,84 @@ int main(int argc, char **argv) { config.output->init(argc - audio_arg, argv + audio_arg); // daemon_log(LOG_NOTICE, "startup"); - + switch (endianness) { - case SS_LITTLE_ENDIAN: - debug(2,"The processor is running little-endian."); - break; - case SS_BIG_ENDIAN: - debug(2,"The processor is running big-endian."); - break; - case SS_PDP_ENDIAN: - debug(2,"The processor is running pdp-endian."); - break; + case SS_LITTLE_ENDIAN: + debug(2, "The processor is running little-endian."); + break; + case SS_BIG_ENDIAN: + debug(2, "The processor is running big-endian."); + break; + case SS_PDP_ENDIAN: + debug(2, "The processor is running pdp-endian."); + break; } - + /* 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. + // 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; + // 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."); + 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; + + 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."); + 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; + + 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."); + 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."); + 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 version */ - - char* version_dbs = get_version_string(); + + char *version_dbs = get_version_string(); if (version_dbs) { - debug(1,"Version: \"%s\"",version_dbs); + debug(1, "Version: \"%s\"", version_dbs); free(version_dbs); } else { - debug(1,"Can't print the version information!"); + debug(1, "Can't print the version information!"); } /* Print out options */ @@ -1007,32 +1176,49 @@ int main(int argc, char **argv) { debug(2, "iTunesLatency is %d.", config.iTunesLatency); debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency); debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing); - debug(1, "resync time is %d.", config.resyncthreshold); + debug(1, "resync time is %f seconds.", config.resyncthreshold); debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption); debug(1, "busy timeout time is %d.", config.timeout); - debug(1, "drift tolerance is %d frames.", config.tolerance); + debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); - debug(1, "playback_mode is %d (0-stereo, 1-mono).", config.playback_mode); + if (config.volume_max_db_set) + debug(1, "volume_max_db is %d.", config.volume_max_db); + else + debug(1, "volume_max_db is not set"); + debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", + config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); - debug(1, "audio backend desired buffer length is %d.", + debug(1, "output_rate is %d.", config.output_rate); + debug(1, + "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S24_3LE, 6-S24_3BE, 7-S32).", + config.output_format); + debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); - debug(1, "audio backend latency offset is %d.", config.audio_backend_latency_offset); - debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", config.volume_range_db); + debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); + debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", + config.volume_range_db); debug(1, "zeroconf regtype is \"%s\".", config.regtype); - - char *realConfigPath = realpath(config.configfile,NULL); + debug(1, "decoders_supported field is %d.", config.decoders_supported); + debug(1, "use_apple_decoder is %d.", config.use_apple_decoder); + if (config.interface) + debug(1, "mdns service interface \"%s\" requested.", config.interface); + else + debug(1, "no special mdns service interface was requested."); + char *realConfigPath = realpath(config.configfile, NULL); if (realConfigPath) { - debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile,realConfigPath); + debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile, + realConfigPath); free(realConfigPath); } else { - debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile); - } + debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile); + } #ifdef CONFIG_METADATA debug(1, "metdata enabled is %d.", config.metadata_enabled); debug(1, "metadata pipename is \"%s\".", config.metadata_pipename); - debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr, config.metadata_sockport); + debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr, + config.metadata_sockport); debug(1, "metadata socket packet size is \"%d\".", config.metadata_sockmsglength); debug(1, "get-coverart is %d.", config.get_coverart); #endif @@ -1046,6 +1232,13 @@ int main(int argc, char **argv) { MD5_Final(ap_md5, &ctx); #endif +#ifdef HAVE_LIBMBEDTLS + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish(&tctx, ap_md5); +#endif + #ifdef HAVE_LIBPOLARSSL md5_context tctx; md5_starts(&tctx); @@ -1056,7 +1249,7 @@ int main(int argc, char **argv) { #ifdef CONFIG_METADATA metadata_init(); // create the metadata pipe if necessary #endif - daemon_log(LOG_INFO,"Successful Startup"); + daemon_log(LOG_INFO, "Successful Startup"); rtsp_listen_loop(); // should not reach this... diff --git a/tinysvcmdns.c b/tinysvcmdns.c index cadea483..bf261891 100644 --- a/tinysvcmdns.c +++ b/tinysvcmdns.c @@ -48,15 +48,15 @@ //******************************************************// // mdns.c // //******************************************************// -#include -#include -#include -#include #include +#include +#include +#include +#include #ifdef _WIN32 -#include #include +#include #else #include #endif @@ -1029,26 +1029,26 @@ size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len #include #define LOG_ERR 3 #else -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include #endif -#include -#include +#include #include +#include #include -#include +#include #include #include -#include +#include +#include +#include #include -#include -#include /* * Define a proper IP socket level if not already done.