From 9fe5d78d1f9c99db5d7457a6b00d6e44ba2c2c75 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 25 Feb 2017 15:18:34 +0000 Subject: [PATCH 001/239] Add back all accumulated changes to master branch at 3.0 release --- RELEASENOTES.md | 25 ++++++++++++++++++++++--- UPDATING.md | 35 +++++++++++++++++++---------------- man/shairport-sync.7 | 25 +++++++++++++++---------- man/shairport-sync.7.xml | 37 ++++++++++++++++++++++++------------- man/shairport-sync.html | 35 +++++++++++++++++++++++------------ rtp.c | 14 ++++++++++---- scripts/shairport-sync.conf | 20 ++++++++++---------- shairport-sync.spec | 10 +++++++++- 8 files changed, 132 insertions(+), 69 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3e1788fe..18996d0e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -99,6 +99,27 @@ Pesky Changes You Cannot Ignore **Bugs** * Documentation is not updated. +Version 2.8.6 – Stable Candidate +---- + +**Enhancements** +* This release contains a small change – it identifies itself as a ShairportSync device rather than an AirPort device. This should make it possible for Tuneblade, and possibly other players, to recognise it correctly. + +Version 2.8.5 – Stable Version +---- +This release includes bug fixes and minor enhancements and is recommended for all users. + +Note: if you're upgrading, there is a new `./configure` option: +==== +The build process now uses the directory path `sysconfdir` to determine where to place the configuration file `shairport-sync.conf`. +The default value for `sysconfdir` is `/usr/local/etc` which is used in the BSD family, whereas `/etc` is normally used in Linux. +To retain the present behaviour of Shairport Sync, *you must add an extra parameter to the `./configure... ` command.* The parameter you must add is `--sysconfdir=/etc`. (This has been added to the sample configuration command line in README.md.) + +The enhancements and bug fixes in 2.8.5 were made in versions 2.8.4.1 to 2.8.4.8 inclusive. Please read below for the full list. + +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** @@ -107,8 +128,6 @@ Version 2.8.4.8 – Development Version Version 2.8.4.7 – Development Version ---- -Pesky Changes You Cannot Ignore -==== * This update means the build process now uses the directory path `sysconfdir` to determine where to place the configuration file `shairport-sync.conf`. The default value for `sysconfdir` is `/usr/local/etc` which is used in the BSD family, whereas `/etc` is normally used in Linux. So, to retain the present behaviour of Shairport Sync, you must add an extra parameter to the `./configure... ` command. The parameter you must add is `--sysconfdir=/etc`. (This has been added to the sample configuration command line in README.md.) * Shairport Sync has been updated to use the value of `sysconfdir` to determine where to look for the configuration file. If `sysconfdir` has been left with its default value of `/usr/local/etc`, then Shairport Sync will look for `/usr/local/etc/shairport-sync.conf`. If, as recommended for Linux, `sysconfdir` has been set to `/etc`, then Shairport Sync will look, as before, for `/etc/shairport-sync.conf`. @@ -155,7 +174,7 @@ Version 2.8.4.1 – Development Version * Fixed two issues when including support for `pulseaudio`. * Corrected two small errors in sample parameters for the UDP metadata stream settings, thanks to [rkam](https://github.com/rkam). -Version 2.8.4 – Stable Version (Candidate Release) +Version 2.8.4 – Stable Version ---- This release includes important bug fixes and minor enhancements and is recommended for all users. No settings need to be changed. For advice on updating an installation you built yourself, please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page. diff --git a/UPDATING.md b/UPDATING.md index 1cf40049..481f3c63 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -10,43 +10,46 @@ But before you begin, you should update and upgrade any packages. Here is the sequence for Raspbian Jessie, which is based on Debian Jessie. The same commands work for Ubuntu, and maybe more. Here, a non-`root` user with `sudo` privileges is assumed. ``` -$sudo apt-get update -$sudo apt-get upgrade +$ sudo apt-get update +$ sudo apt-get upgrade ``` Next, stop playing music to your device. -Now, to update and install Shairport Sync, if you still have the directory in which you previously built Shairport Sync, navigate your way to it and execute the command: +Now, to update and install Shairport Sync, if you still have the directory in which you previously built Shairport Sync, it will contain the repository you originally downloaded. Navigate your way to it and 'pull' the changes from GitHub: + ``` -$git pull +$ git pull ``` -Otherwise, just pull Shairport Sync from `github` again and move into the new directory: +Otherwise – say if you deleted the repository – just pull Shairport Sync from GitHub again and move into the new directory: ``` -$git clone https://github.com/mikebrady/shairport-sync.git -$cd shairport-sync +$ git clone https://github.com/mikebrady/shairport-sync.git +$ cd shairport-sync ``` Now, while in the `shairport-sync` directory, perform the following commands (note that there is a choice you must make in there): ``` -$autoreconf -fi - +$ autoreconf -fi +``` +Please review the release notes to see if any configuration settings have been changed. For instance, in the transitions from version 2 to version 3, the `--with-ssl=polarssl` has been deprecated in favour of `--with-ssl=mbedtls`. +``` #The following is the standard configuration for a Linux that uses the systemd initialisation system: -$./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd +$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd --sysconfdir=/etc #OR #The following is the standard configuration for a Linux that uses the older System V initialisation system: -$./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemv +$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemv --sysconfdir=/etc -$make -$sudo make install +$ make +$ sudo make install ``` At this point you have downloaded, compiled and installed the updated Shairport Sync. However, the older version is still running. So, you need to do a little more: If you are on a `systemd`-based system such as Raspbian Jessie or recent versions of Ubuntu and Debian, execute the following commands: ``` -$sudo systemctl daemon-reload -$sudo systemctl restart shairport-sync +$ sudo systemctl daemon-reload +$ sudo systemctl restart shairport-sync ``` Otherwise execute the following command: ``` -$sudo service shairport-sync restart +$ sudo service shairport-sync restart ``` That's it. Your Shairport Sync should be upgraded now. diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 1f866811..c616ed68 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -23,7 +23,9 @@ shairport-sync can be compiled to stream audio, without synchronisation, to a pi Settings can be made using the configuration file (recommended for all new installations) or by using command-line options. .SH CONFIGURATION FILE SETTINGS -You should use the configuration file for setting up shairport-sync. This file is normally \fI/etc/shairport-sync.conf\f1. You may need to have root privileges to modify it. +You should use the configuration file for setting up shairport-sync. This file is usually \fIshairport-sync.conf\f1 and is generally located in the System Configuration Directory, which is normally the \fI/etc\f1 directory in Linux or the \fI/usr/local/etc\f1 directory in BSD unixes. You may need to have root privileges to modify it. + +(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command \fI$ shairport-sync -V\f1. One of the items in the output string is the value of the \fBsysconfdir\f1, i.e. the System Configuration Directory.) Settings are organised into groups, for example, there is a "general" group of standard settings, and there is an "alsa" group with settings that pertain to the ALSA back end. Here is an example of a typical configuration file: @@ -49,7 +51,7 @@ Settings are organised into groups, for example, there is a "general" group of s Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specified. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled. -A sample configuration file with all possible settings, but with all of them commented out, is installed at \fI/etc/shairport-sync.conf.sample\f1. +A sample configuration file with all possible settings, but with all of them commented out, is installed at \fIshairport-sync.conf.sample\f1, within the System Configuration Directory -- \fI/etc\f1 in Linux, \fI/usr/local/etc\f1 in BSD unixes. To retain backwards compatibility with previous versions of shairport-sync you can use still use command line options, but any new features, etc. will be available only via configuration file settings. @@ -96,10 +98,10 @@ Allow playback to drift up to \fIseconds\f1 out of exact synchronization before 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. "0" means no debug information, "3" means most debug information. The default is "0". +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. .TP \fBignore_volume_control=\f1\fI"choice"\f1\fB;\f1 -Set this \fIchoice\f1 to "yes" 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 "no". +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. @@ -113,10 +115,10 @@ Another potential use might be where the range specified by the mixer does not m As a third example, you can actually extend the range provided by a mixer. Many cheaper DACs have hardware mixers that offer a restricted attenuation range. If you specify a volume range greater than the range of the mixer, software attenuation and hardware attenuation will be combined to give the specified range. -If you omit this setting, the full "native" range of the mixer is used. +If you omit this setting, the native range of the mixer is used. .TP \fBregtype=\f1\fI"regTypeString"\f1\fB;\f1 -Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". +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 "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". @@ -131,12 +133,12 @@ This can be "hammerton" or "apple". This advanced setting allows you to choose t 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: .TP \fBoutput_device=\f1\fI"output_device"\f1\fB;\f1 -Use the output device called \fIoutput_device\f1. The default is the device called "default". +Use the output device called \fIoutput_device\f1. The default is the device called \fI"default"\f1. .TP .TP \fBmixer_control_name=\f1\fI"name"\f1\fB;\f1 Specify the \fIname\f1 of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. If you do not specify a mixer control name, shairport-sync will adjust the volume in software. \fBmixer_type=\f1\fI"mixer_type"\f1\fB;\f1 -This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware. +This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the \fBmixer_control_name\f1 setting -- if you specify a mixer name with the \fBmixer_control_name\f1 setting, it is assumed that the mixer is implemented in hardware. .TP \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. @@ -154,7 +156,7 @@ Use this setting to specify the frame rate to output to the ALSA device. Allowab 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 "yes" to disable synchronization. Default is "no". If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to audio buffer overflow or underflow. +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. .TP \fBperiod_size=\f1\fInumber\f1\fB;\f1 Use this optional advanced setting to set the alsa period size near to this value. @@ -162,6 +164,9 @@ Use this optional advanced setting to set the alsa period size near to this valu \fBbuffer_size=\f1\fInumber\f1\fB;\f1 Use this optional advanced setting to set the alsa buffer size near to this value. .TP +\fBuse_mmap_if_available=\f1\fI"yes"\f1\fB;\f1 +Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is \fI"yes"\f1. +.TP \fB"PIPE" SETTINGS\f1 These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, interleaved stereo. @@ -270,7 +275,7 @@ Execute \fIprogram\f1 when playback is about to begin. Specify the full path to If you want shairport-sync to wait until the command has completed before starting to play, select the \fB-w\f1 option as well. .TP \fB-c \f1\fIfilename\f1\fB | --configfile=\f1\fIfilename\f1 -Read configuration settings from \fIfilename\f1. The default is to read them from \fI/etc/shairport-sync.conf\f1. For information about configuration settings, see the "Configuration File Settings" section above. +Read configuration settings from \fIfilename\f1. The default is to read them from the \fIshairport-sync.conf\f1 in the System Configuration Directory -- \fI/etc\f1 in Linux, \fI/usr/local/etc\f1 in BSD unixes. For information about configuration settings, see the "Configuration File Settings" section above. .TP \fB-D | --disconnectFromOutput\f1 Disconnect the shairport-sync daemon from the output device and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option). diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 695655d1..db1ce722 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -80,9 +80,13 @@

You should use the configuration file for setting up shairport-sync. - This file is normally /etc/shairport-sync.conf. + This file is usually shairport-sync.conf and is generally located in the System Configuration Directory, which is normally the /etc directory in Linux or the /usr/local/etc directory in BSD unixes. You may need to have root privileges to modify it.

+

(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command $ shairport-sync -V. One of the items in the output string is the value of the sysconfdir, + i.e. the System Configuration Directory.)

+ +

Settings are organised into groups, for example, there is a "general" group of standard settings, and there is an "alsa" group with settings that pertain to the ALSA back end. Here is an example of a typical configuration file:

@@ -101,7 +105,7 @@

Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specified. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.

-

A sample configuration file with all possible settings, but with all of them commented out, is installed at /etc/shairport-sync.conf.sample.

+

A sample configuration file with all possible settings, but with all of them commented out, is installed at shairport-sync.conf.sample, within the System Configuration Directory -- /etc in Linux, /usr/local/etc in BSD unixes.

To retain backwards compatibility with previous versions of shairport-sync you can use still use command line options, but any new features, etc. will @@ -167,7 +171,8 @@

udp_port_range=range;

- Use this in conjunction with the prevous setting to specify the range 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. + Use this in conjunction with the prevous setting to specify the range 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.

mixer_type="mixer_type";

- This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting - -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware. + This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting + -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware. - + +

These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, interleaved stereo.

@@ -522,7 +532,8 @@ diff --git a/man/shairport-sync.html b/man/shairport-sync.html index 858863ab..5c1455f7 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -61,9 +61,13 @@

Configuration File Settings

You should use the configuration file for setting up shairport-sync. - This file is normally /etc/shairport-sync.conf. + This file is usually shairport-sync.conf and is generally located in the System Configuration Directory, which is normally the /etc directory in Linux or the /usr/local/etc directory in BSD unixes. You may need to have root privileges to modify it.

+

(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command $ shairport-sync -V. One of the items in the output string is the value of the sysconfdir, + i.e. the System Configuration Directory.)

+ +

Settings are organised into groups, for example, there is a "general" group of standard settings, and there is an "alsa" group with settings that pertain to the ALSA back end. Here is an example of a typical configuration file:

@@ -82,7 +86,7 @@

Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specified. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.

-

A sample configuration file with all possible settings, but with all of them commented out, is installed at /etc/shairport-sync.conf.sample.

+

A sample configuration file with all possible settings, but with all of them commented out, is installed at shairport-sync.conf.sample, within the System Configuration Directory -- /etc in Linux, /usr/local/etc in BSD unixes.

To retain backwards compatibility with previous versions of shairport-sync you can use still use command line options, but any new features, etc. will @@ -148,7 +152,8 @@

udp_port_range=range;

- Use this in conjunction with the prevous setting to specify the range 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. + Use this in conjunction with the prevous setting to specify the range 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.

drift_tolerance_in_seconds=seconds;

@@ -170,11 +175,12 @@

log_verbosity=0;

- Use this to specify how much debugging information should be output or logged. "0" means no debug information, "3" means most debug information. The default is "0". + Use this to specify how much debugging information should be output or logged. The value 0 means no debug information, 3 means most debug information. The default is 0.

ignore_volume_control="choice";

- Set this choice to "yes" 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 "no". + Set this choice to "yes" 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 "no". @@ -199,11 +205,11 @@ Many cheaper DACs have hardware mixers that offer a restricted attenuation range. If you specify a volume range greater than the range of the mixer, software attenuation and hardware attenuation will be combined to give the specified range.

-

If you omit this setting, the full "native" range of the mixer is used.

+

If you omit this setting, the native range of the mixer is used.

regtype="regTypeString";

- Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". + Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp". @@ -230,7 +236,7 @@

output_device="output_device";

- Use the output device called output_device. The default is the device called "default". + Use the output device called output_device. The default is the device called "default". @@ -240,8 +246,8 @@ If you do not specify a mixer control name, shairport-sync will adjust the volume in software.

mixer_type="mixer_type";

- This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting - -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware. + This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting + -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware.

mixer_device="mixer_device";

@@ -275,7 +281,7 @@

disable_synchronization="no";

- This is an advanced setting and is for debugging only. Set to "yes" to disable synchronization. Default is "no". + This is an advanced setting and is for debugging only. Set to "yes" to disable synchronization. Default is "no". If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to audio buffer overflow or underflow. @@ -289,6 +295,10 @@ 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". + +

"PIPE" SETTINGS

These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, interleaved stereo.

@@ -507,7 +517,8 @@

-c filename | --configfile=filename

- Read configuration settings from filename. The default is to read them from /etc/shairport-sync.conf. For information about configuration settings, see the "Configuration File Settings" section above. + Read configuration settings from filename. The default is to read them from the shairport-sync.conf in the System Configuration Directory -- /etc in Linux, /usr/local/etc in BSD unixes. + For information about configuration settings, see the "Configuration File Settings" section above.

diff --git a/rtp.c b/rtp.c index 7f8abee2..dcaf28d4 100644 --- a/rtp.c +++ b/rtp.c @@ -682,9 +682,12 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t die("Can't get address of client's control port"); #ifdef AF_INET6 - if (servinfo->ai_family == AF_INET6) + if (servinfo->ai_family == AF_INET6) { memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); - else + // ensure the scope id matches that of remote. this is needed for link-local addresses. + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&rtp_client_control_socket; + sa6->sin6_scope_id = self_scope_id; + } else #endif memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); freeaddrinfo(servinfo); @@ -698,9 +701,12 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0) die("Can't get address of client's timing port"); #ifdef AF_INET6 - if (servinfo->ai_family == AF_INET6) + if (servinfo->ai_family == AF_INET6) { memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); - else + // ensure the scope id matches that of remote. this is needed for link-local addresses. + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&rtp_client_timing_socket; + sa6->sin6_scope_id = self_scope_id; + } else #endif memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); freeaddrinfo(servinfo); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 53909bf5..d19e85f3 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -5,13 +5,13 @@ general = { // name = "%H"; // This means "Hostname" -- see below. This is the name the service will advertise to iTunes. -// The default is "Hostname" -- i.e. the machine's hostname with the first letter capitalised (ASCII only.) -// 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. 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". +// The default is "Hostname" -- i.e. the machine's hostname with the first letter capitalised (ASCII only.) +// 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. 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. // output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one. @@ -42,9 +42,9 @@ metadata = // 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 diff --git a/shairport-sync.spec b/shairport-sync.spec index b1d30575..a2b54c39 100644 --- a/shairport-sync.spec +++ b/shairport-sync.spec @@ -1,5 +1,5 @@ Name: shairport-sync -Version: 2.8.3 +Version: 3.0 Release: 1 Summary: AirTunes emulator. Shairport Sync adds multi-room capability with Audio Synchronisation. @@ -56,6 +56,14 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \ %doc AUTHORS LICENSES README.md %changelog +* Fri Feb 24 2017 Mike Brady 2.8.6 +- Many changes including 8- 16- 24- and 32-bit output +* Fri Oct 21 2016 Mike Brady 2.8.6 +- Advertise self as ShairportSync rather than AirPort device 2.8.6 +* Sun Sep 25 2016 Mike Brady 2.8.5 +- Bug fixes and small enhancements 2.8.5 +* Sat May 28 2016 Mike Brady 2.8.4 +- Bug fixes and a few small enhancements 2.8.4 * Fri Apr 15 2016 Mike Brady 2.8.2 - Stability improvements, bug fixes and a few special-purpose settings 2.8.2 * Wed Mar 02 2016 Mike Brady 2.8.1 From 96024568d786830a5440357672523137c1653185 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 25 Feb 2017 15:19:44 +0000 Subject: [PATCH 002/239] Add back all accumulated changes to master branch at 3.0 release to 3.1d0 --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 58ed7640..8ec31411 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0rc0], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.1d0], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a5082e44eead4e95ace3f0e9ef13b8bf3654bcb2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 25 Feb 2017 15:25:31 +0000 Subject: [PATCH 003/239] Fix it up to 3.1d0 --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 56dbc938..8ec31411 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.0], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.1d0], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 44480a33a720c01c3311b2050b59b6edd515f930 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 25 Feb 2017 15:30:45 +0000 Subject: [PATCH 004/239] Add reference to `soundio` --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 30599445..7b6babe0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ Shairport Sync ============= -**Welcome to Version 3 of Shairport Sync. This is a big update from version 2.X and is still a work in progress. 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.) @@ -105,6 +103,7 @@ $ autoreconf -i -f - `--with-alsa` for the ALSA audio back end. This is required. - `--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-soundio` include an optional backend module to enable raw audio to be output through the soundio system. - `--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`, `--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. From 64371bb85ccd6a100f5a8cf386bb4ca141df4765 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 25 Feb 2017 15:58:59 +0000 Subject: [PATCH 005/239] Remove obselete stuffing code. --- player.c | 212 +++++++++---------------------------------------------- 1 file changed, 32 insertions(+), 180 deletions(-) diff --git a/player.c b/player.c index 9b265279..f035989d 100644 --- a/player.c +++ b/player.c @@ -1213,7 +1213,7 @@ static inline int32_t mean_32(int32_t a, int32_t b) { // 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 +// (c) dithers the result to the output size 32/24/16/8 bits // (d) outputs the result in the approprate format // formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32 @@ -1267,61 +1267,13 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l 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 (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 - *outptr++ = dithered_vol(*inptr++); - *outptr++ = dithered_vol(*inptr++); - }; - if (tstuff) { - if (tstuff == 1) { - // debug(3, "+++++++++"); - // interpolate one sample - //*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1); - //*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1); - *outptr++ = dithered_vol(shortmean(inptr[-2], inptr[0])); - *outptr++ = dithered_vol(shortmean(inptr[-1], inptr[1])); - } 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++) { - *outptr++ = dithered_vol(*inptr++); - *outptr++ = dithered_vol(*inptr++); - } - } - pthread_mutex_unlock(&vol_mutex); - - 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 +// (c) dithers the result to the output size 32/24/16/8 bits // (d) outputs the result in the approprate format // formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32 @@ -1408,81 +1360,6 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng } 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 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 (tstuff) { - // debug(1,"Stuff %d.",stuff); - soxr_io_spec_t io_spec; - io_spec.itype = SOXR_INT16_I; - io_spec.otype = SOXR_INT16_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. */ - outptr, 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); - - 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 = outptr + (length + tstuff - gpm) * sizeof(short); - ip = inptr + (length - gpm) * sizeof(short); - for (i = 0; i < gpm; i++) { - *op++ = *ip++; - *op++ = *ip++; - } - - // finally, adjust the volume, if necessary - if (fix_volume != 65536.0) { - // pthread_mutex_lock(&vol_mutex); - op = outptr; - for (i = 0; i < length + tstuff; i++) { - *op = dithered_vol(*op); - op++; - *op = dithered_vol(*op); - op++; - }; - // pthread_mutex_unlock(&vol_mutex); - } - - } else { // the whole frame, if no stuffing - - // pthread_mutex_lock(&vol_mutex); - for (i = 0; i < length; i++) { - *op++ = dithered_vol(*ip++); - *op++ = dithered_vol(*ip++); - }; - // pthread_mutex_unlock(&vol_mutex); - } - return length + tstuff; -} #endif typedef struct stats { // statistics for running averages @@ -1603,30 +1480,24 @@ static void *player_thread_func(void *arg) { "depth"); } if (fix_volume != 0x10000) { - debug(1, "Dithering will be enabled the output volume is being altered in software"); + debug(1, "Dithering will be enabled because 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 + // 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 * + // 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) + die("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 (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; + if (sbuf == NULL) + die("Failed to allocate sbuf memory for the transition buffer."); } // We might need an output buffer and a buffer of silence. @@ -1635,10 +1506,10 @@ static void *player_thread_func(void *arg) { 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."); + die("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."); + die("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; @@ -1714,8 +1585,7 @@ static void *player_thread_func(void *arg) { // 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) { + switch (input_bit_depth) { case 16: { int i, j; int16_t ls, rs; @@ -1789,12 +1659,10 @@ static void *player_thread_func(void *arg) { } break; default: die("Shairport Sync only supports 16 bit input"); - } - - // inbuf = tbuf; - inbuflength *= output_sample_ratio; } + 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. @@ -1951,34 +1819,22 @@ static void *player_thread_func(void *arg) { 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); + play_samples = + stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, + outbuf, amount_to_stuff, enable_dither); 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); + play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, (int32_t *)sbuf, inbuflength, + config.output_format, outbuf, amount_to_stuff, + enable_dither); break; } #else // 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); + play_samples = + stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, outbuf, + amount_to_stuff, enable_dither); #endif /* @@ -2045,12 +1901,8 @@ static void *player_thread_func(void *arg) { config.output->play(inbuf, inbuflength); } else { - 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 + play_samples = stuff_buffer_basic_32( + (int32_t *)tbuf, inbuflength, config.output_format, outbuf, 0, enable_dither); if (outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); From 7b9cd28e9050e82ab2bce42405a628f230cb8b11 Mon Sep 17 00:00:00 2001 From: Yann Pomarede Date: Mon, 27 Feb 2017 22:43:23 +0100 Subject: [PATCH 006/239] 2 audio filters added, a volume-dependent-loudness and a convolution filter. --- .gitignore | 6 +- FFTConvolver/AudioFFT.cpp | 1018 +++++++++++++++++++++++++++++++++ FFTConvolver/AudioFFT.h | 176 ++++++ FFTConvolver/FFTConvolver.cpp | 202 +++++++ FFTConvolver/FFTConvolver.h | 100 ++++ FFTConvolver/Utilities.cpp | 113 ++++ FFTConvolver/Utilities.h | 351 ++++++++++++ FFTConvolver/convolver.cpp | 67 +++ FFTConvolver/convolver.h | 14 + Makefile.am | 6 +- common.h | 13 +- configure.ac | 11 +- loudness.c | 55 ++ loudness.h | 16 + player.c | 85 ++- scripts/shairport-sync.conf | 30 + shairport.c | 70 ++- 17 files changed, 2322 insertions(+), 11 deletions(-) create mode 100755 FFTConvolver/AudioFFT.cpp create mode 100755 FFTConvolver/AudioFFT.h create mode 100755 FFTConvolver/FFTConvolver.cpp create mode 100755 FFTConvolver/FFTConvolver.h create mode 100644 FFTConvolver/Utilities.cpp create mode 100644 FFTConvolver/Utilities.h create mode 100644 FFTConvolver/convolver.cpp create mode 100644 FFTConvolver/convolver.h create mode 100644 loudness.c create mode 100644 loudness.h diff --git a/.gitignore b/.gitignore index 9e055931..d98a86df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ /INSTALL /shairport-sync /shairport-sync.exe -/*.o +*.o /*~ *.xml~ /config.mk @@ -26,3 +26,7 @@ scripts/shairport-sync # Some eclipse project files .cproject .project + +# macOS stuff +.DS_Store +shairport-sync.xcodeproj diff --git a/FFTConvolver/AudioFFT.cpp b/FFTConvolver/AudioFFT.cpp new file mode 100755 index 00000000..32b3ddaa --- /dev/null +++ b/FFTConvolver/AudioFFT.cpp @@ -0,0 +1,1018 @@ +// ================================================================================== +// Copyright (c) 2016 HiFi-LoFi +// +// 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 restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ================================================================================== + +#include "AudioFFT.h" + +#include +#include +#include + + +#if defined(AUDIOFFT_APPLE_ACCELERATE) + #define AUDIOFFT_APPLE_ACCELERATE_USED + #include + #include +#elif defined (AUDIOFFT_FFTW3) + #define AUDIOFFT_FFTW3_USED + #include +#else + #if !defined(AUDIOFFT_OOURA) + #define AUDIOFFT_OOURA + #endif + #define AUDIOFFT_OOURA_USED + #include +#endif + + +namespace audiofft +{ + + namespace details + { + + static bool IsPowerOf2(size_t val) + { + return (val == 1 || (val & (val-1)) == 0); + } + + + template + void ConvertBuffer(TypeDest* dest, const TypeSrc* src, size_t len) + { + for (size_t i=0; i(src[i]); + } + } + + + template + void ScaleBuffer(TypeDest* dest, const TypeSrc* src, const TypeFactor factor, size_t len) + { + for (size_t i=0; i(static_cast(src[i]) * factor); + } + } + + + // ================================================================ + + +#ifdef AUDIOFFT_OOURA_USED + + /** + * @internal + * @class OouraFFT + * @brief FFT implementation based on the great radix-4 routines by Takuya Ooura + */ + class OouraFFT : public AudioFFTImpl + { + public: + OouraFFT() : + AudioFFTImpl(), + _size(0), + _ip(), + _w(), + _buffer() + { + } + + virtual void init(size_t size) override + { + if (_size != size) + { + _ip.resize(2 + static_cast(std::sqrt(static_cast(size)))); + _w.resize(size / 2); + _buffer.resize(size); + _size = size; + + const int size4 = static_cast(_size) / 4; + makewt(size4, _ip.data(), _w.data()); + makect(size4, _ip.data(), _w.data() + size4); + } + } + + virtual void fft(const float* data, float* re, float* im) override + { + // Convert into the format as required by the Ooura FFT + ConvertBuffer(&_buffer[0], data, _size); + + rdft(static_cast(_size), +1, _buffer.data(), _ip.data(), _w.data()); + + // Convert back to split-complex + { + double* b = &_buffer[0]; + double* bEnd = b + _size; + float *r = re; + float *i = im; + while (b != bEnd) + { + *(r++) = static_cast(*(b++)); + *(i++) = static_cast(-(*(b++))); + } + } + const size_t size2 = _size / 2; + re[size2] = -im[0]; + im[0] = 0.0; + im[size2] = 0.0; + } + + virtual void ifft(float* data, const float* re, const float* im) override + { + // Convert into the format as required by the Ooura FFT + { + double* b = &_buffer[0]; + double* bEnd = b + _size; + const float *r = re; + const float *i = im; + while (b != bEnd) + { + *(b++) = static_cast(*(r++)); + *(b++) = -static_cast(*(i++)); + } + _buffer[1] = re[_size / 2]; + } + + rdft(static_cast(_size), -1, _buffer.data(), _ip.data(), _w.data()); + + // Convert back to split-complex + ScaleBuffer(data, &_buffer[0], 2.0 / static_cast(_size), _size); + } + + private: + size_t _size; + std::vector _ip; + std::vector _w; + std::vector _buffer; + + void rdft(int n, int isgn, double *a, int *ip, double *w) + { + int nw = ip[0]; + int nc = ip[1]; + + if (isgn >= 0) + { + if (n > 4) + { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + rftfsub(n, a, nc, w + nw); + } + else if (n == 4) + { + cftfsub(n, a, w); + } + double xi = a[0] - a[1]; + a[0] += a[1]; + a[1] = xi; + } + else + { + a[1] = 0.5 * (a[0] - a[1]); + a[0] -= a[1]; + if (n > 4) + { + rftbsub(n, a, nc, w + nw); + bitrv2(n, ip + 2, a); + cftbsub(n, a, w); + } + else if (n == 4) + { + cftfsub(n, a, w); + } + } + } + + + /* -------- initializing routines -------- */ + + void makewt(int nw, int *ip, double *w) + { + int j, nwh; + double delta, x, y; + + ip[0] = nw; + ip[1] = 1; + if (nw > 2) { + nwh = nw >> 1; + delta = atan(1.0) / nwh; + w[0] = 1; + w[1] = 0; + w[nwh] = cos(delta * nwh); + w[nwh + 1] = w[nwh]; + if (nwh > 2) { + for (j = 2; j < nwh; j += 2) { + x = cos(delta * j); + y = sin(delta * j); + w[j] = x; + w[j + 1] = y; + w[nw - j] = y; + w[nw - j + 1] = x; + } + bitrv2(nw, ip + 2, w); + } + } + } + + + void makect(int nc, int *ip, double *c) + { + int j, nch; + double delta; + + ip[1] = nc; + if (nc > 1) { + nch = nc >> 1; + delta = atan(1.0) / nch; + c[0] = cos(delta * nch); + c[nch] = 0.5 * c[0]; + for (j = 1; j < nch; j++) { + c[j] = 0.5 * cos(delta * j); + c[nc - j] = 0.5 * sin(delta * j); + } + } + } + + + /* -------- child routines -------- */ + + + void bitrv2(int n, int *ip, double *a) + { + int j, j1, k, k1, l, m, m2; + double xr, xi, yr, yi; + + ip[0] = 0; + l = n; + m = 1; + while ((m << 3) < l) { + l >>= 1; + for (j = 0; j < m; j++) { + ip[m + j] = ip[j] + l; + } + m <<= 1; + } + m2 = 2 * m; + if ((m << 3) == l) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 -= m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + j1 = 2 * k + m2 + ip[k]; + k1 = j1 + m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } else { + for (k = 1; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } + } + } + + + void cftfsub(int n, double *a, double *w) + { + int j, j1, j2, j3, l; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + l = 2; + if (n > 8) { + cft1st(n, a, w); + l = 8; + while ((l << 2) < n) { + cftmdl(n, l, a, w); + l <<= 2; + } + } + if ((l << 2) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i - x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + } + } else { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = a[j + 1] - a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] += a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } + } + + + void cftbsub(int n, double *a, double *w) + { + int j, j1, j2, j3, l; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + l = 2; + if (n > 8) { + cft1st(n, a, w); + l = 8; + while ((l << 2) < n) { + cftmdl(n, l, a, w); + l <<= 2; + } + } + if ((l << 2) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = -a[j + 1] - a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = -a[j + 1] + a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i - x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i + x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i - x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i + x3r; + } + } else { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = -a[j + 1] + a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] = -a[j + 1] - a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } + } + + + void cft1st(int n, double *a, double *w) + { + int j, k1, k2; + double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[2]; + x0i = a[1] + a[3]; + x1r = a[0] - a[2]; + x1i = a[1] - a[3]; + x2r = a[4] + a[6]; + x2i = a[5] + a[7]; + x3r = a[4] - a[6]; + x3i = a[5] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[2] = x1r - x3i; + a[3] = x1i + x3r; + a[6] = x1r + x3i; + a[7] = x1i - x3r; + wk1r = w[2]; + x0r = a[8] + a[10]; + x0i = a[9] + a[11]; + x1r = a[8] - a[10]; + x1i = a[9] - a[11]; + x2r = a[12] + a[14]; + x2i = a[13] + a[15]; + x3r = a[12] - a[14]; + x3i = a[13] - a[15]; + a[8] = x0r + x2r; + a[9] = x0i + x2i; + a[12] = x2i - x0i; + a[13] = x0r - x2r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[10] = wk1r * (x0r - x0i); + a[11] = wk1r * (x0r + x0i); + x0r = x3i + x1r; + x0i = x3r - x1i; + a[14] = wk1r * (x0i - x0r); + a[15] = wk1r * (x0i + x0r); + k1 = 0; + for (j = 16; j < n; j += 16) { + k1 += 2; + k2 = 2 * k1; + wk2r = w[k1]; + wk2i = w[k1 + 1]; + wk1r = w[k2]; + wk1i = w[k2 + 1]; + wk3r = wk1r - 2 * wk2i * wk1i; + wk3i = 2 * wk2i * wk1r - wk1i; + x0r = a[j] + a[j + 2]; + x0i = a[j + 1] + a[j + 3]; + x1r = a[j] - a[j + 2]; + x1i = a[j + 1] - a[j + 3]; + x2r = a[j + 4] + a[j + 6]; + x2i = a[j + 5] + a[j + 7]; + x3r = a[j + 4] - a[j + 6]; + x3i = a[j + 5] - a[j + 7]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j + 4] = wk2r * x0r - wk2i * x0i; + a[j + 5] = wk2r * x0i + wk2i * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j + 2] = wk1r * x0r - wk1i * x0i; + a[j + 3] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j + 6] = wk3r * x0r - wk3i * x0i; + a[j + 7] = wk3r * x0i + wk3i * x0r; + wk1r = w[k2 + 2]; + wk1i = w[k2 + 3]; + wk3r = wk1r - 2 * wk2r * wk1i; + wk3i = 2 * wk2r * wk1r - wk1i; + x0r = a[j + 8] + a[j + 10]; + x0i = a[j + 9] + a[j + 11]; + x1r = a[j + 8] - a[j + 10]; + x1i = a[j + 9] - a[j + 11]; + x2r = a[j + 12] + a[j + 14]; + x2i = a[j + 13] + a[j + 15]; + x3r = a[j + 12] - a[j + 14]; + x3i = a[j + 13] - a[j + 15]; + a[j + 8] = x0r + x2r; + a[j + 9] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j + 12] = -wk2i * x0r - wk2r * x0i; + a[j + 13] = -wk2i * x0i + wk2r * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j + 10] = wk1r * x0r - wk1i * x0i; + a[j + 11] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j + 14] = wk3r * x0r - wk3i * x0i; + a[j + 15] = wk3r * x0i + wk3i * x0r; + } + } + + + void cftmdl(int n, int l, double *a, double *w) + { + int j, j1, j2, j3, k, k1, k2, m, m2; + double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + m = l << 2; + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i - x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + } + wk1r = w[2]; + for (j = m; j < l + m; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x2i - x0i; + a[j2 + 1] = x0r - x2r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * (x0r - x0i); + a[j1 + 1] = wk1r * (x0r + x0i); + x0r = x3i + x1r; + x0i = x3r - x1i; + a[j3] = wk1r * (x0i - x0r); + a[j3 + 1] = wk1r * (x0i + x0r); + } + k1 = 0; + m2 = 2 * m; + for (k = m2; k < n; k += m2) { + k1 += 2; + k2 = 2 * k1; + wk2r = w[k1]; + wk2i = w[k1 + 1]; + wk1r = w[k2]; + wk1i = w[k2 + 1]; + wk3r = wk1r - 2 * wk2i * wk1i; + wk3i = 2 * wk2i * wk1r - wk1i; + for (j = k; j < l + k; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j2] = wk2r * x0r - wk2i * x0i; + a[j2 + 1] = wk2r * x0i + wk2i * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * x0r - wk1i * x0i; + a[j1 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r - wk3i * x0i; + a[j3 + 1] = wk3r * x0i + wk3i * x0r; + } + wk1r = w[k2 + 2]; + wk1i = w[k2 + 3]; + wk3r = wk1r - 2 * wk2r * wk1i; + wk3i = 2 * wk2r * wk1r - wk1i; + for (j = k + m; j < l + (k + m); j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j2] = -wk2i * x0r - wk2r * x0i; + a[j2 + 1] = -wk2i * x0i + wk2r * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * x0r - wk1i * x0i; + a[j1 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r - wk3i * x0i; + a[j3 + 1] = wk3r * x0i + wk3i * x0r; + } + } + } + + + void rftfsub(int n, double *a, int nc, double *c) + { + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr - wki * xi; + yi = wkr * xi + wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } + } + + + void rftbsub(int n, double *a, int nc, double *c) + { + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + a[1] = -a[1]; + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr + wki * xi; + yi = wkr * xi - wki * xr; + a[j] -= yr; + a[j + 1] = yi - a[j + 1]; + a[k] += yr; + a[k + 1] = yi - a[k + 1]; + } + a[m + 1] = -a[m + 1]; + } + + OouraFFT(const OouraFFT&) = delete; + OouraFFT& operator=(const OouraFFT&) = delete; + }; + + std::unique_ptr MakeAudioFFTImpl() + { + return std::unique_ptr(new OouraFFT()); + } + + +#endif // AUDIOFFT_OOURA_USED + + + // ================================================================ + + +#ifdef AUDIOFFT_APPLE_ACCELERATE_USED + + + /** + * @internal + * @class AppleAccelerateFFT + * @brief FFT implementation using the Apple Accelerate framework internally + */ + class AppleAccelerateFFT : public AudioFFTImpl + { + public: + AppleAccelerateFFT() : + AudioFFTImpl(), + _size(0), + _powerOf2(0), + _fftSetup(0), + _re(), + _im() + { + } + + virtual ~AppleAccelerateFFT() + { + init(0); + } + + virtual void init(size_t size) override + { + if (_fftSetup) + { + vDSP_destroy_fftsetup(_fftSetup); + _size = 0; + _powerOf2 = 0; + _fftSetup = 0; + _re.clear(); + _im.clear(); + } + + if (size > 0) + { + _size = size; + _powerOf2 = 0; + while ((1 << _powerOf2) < _size) + { + ++_powerOf2; + } + _fftSetup = vDSP_create_fftsetup(_powerOf2, FFT_RADIX2); + _re.resize(_size / 2); + _im.resize(_size / 2); + } + } + + virtual void fft(const float* data, float* re, float* im) override + { + const size_t size2 = _size / 2; + DSPSplitComplex splitComplex; + splitComplex.realp = re; + splitComplex.imagp = im; + vDSP_ctoz(reinterpret_cast(data), 2, &splitComplex, 1, size2); + vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_FORWARD); + const float factor = 0.5f; + vDSP_vsmul(re, 1, &factor, re, 1, size2); + vDSP_vsmul(im, 1, &factor, im, 1, size2); + re[size2] = im[0]; + im[0] = 0.0f; + im[size2] = 0.0f; + } + + virtual void ifft(float* data, const float* re, const float* im) override + { + const size_t size2 = _size / 2; + ::memcpy(_re.data(), re, size2 * sizeof(float)); + ::memcpy(_im.data(), im, size2 * sizeof(float)); + _im[0] = re[size2]; + DSPSplitComplex splitComplex; + splitComplex.realp = _re.data(); + splitComplex.imagp = _im.data(); + vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_INVERSE); + vDSP_ztoc(&splitComplex, 1, reinterpret_cast(data), 2, size2); + const float factor = 1.0f / static_cast(_size); + vDSP_vsmul(data, 1, &factor, data, 1, _size); + } + + private: + size_t _size; + size_t _powerOf2; + FFTSetup _fftSetup; + std::vector _re; + std::vector _im; + + AppleAccelerateFFT(const AppleAccelerateFFT&) = delete; + AppleAccelerateFFT& operator=(const AppleAccelerateFFT&) = delete; + }; + + + std::unique_ptr MakeAudioFFTImpl() + { + return std::unique_ptr(new AppleAccelerateFFT()); + } + + +#endif // AUDIOFFT_APPLE_ACCELERATE_USED + + + // ================================================================ + + +#ifdef AUDIOFFT_FFTW3_USED + + + /** + * @internal + * @class FFTW3FFT + * @brief FFT implementation using FFTW3 internally (see fftw.org) + */ + class FFTW3FFT : public AudioFFTImpl + { + public: + FFTW3FFT() : + AudioFFTImpl(), + _size(0), + _complexSize(0), + _planForward(0), + _planBackward(0), + _data(0), + _re(0), + _im(0) + { + } + + virtual ~FFTW3FFT() + { + init(0); + } + + virtual void init(size_t size) override + { + if (_size != size) + { + if (_size > 0) + { + fftwf_destroy_plan(_planForward); + fftwf_destroy_plan(_planBackward); + _planForward = 0; + _planBackward = 0; + _size = 0; + _complexSize = 0; + + if (_data) + { + fftwf_free(_data); + _data = 0; + } + + if (_re) + { + fftwf_free(_re); + _re = 0; + } + + if (_im) + { + fftwf_free(_im); + _im = 0; + } + } + + if (size > 0) + { + _size = size; + _complexSize = AudioFFT::ComplexSize(_size); + const size_t complexSize = AudioFFT::ComplexSize(_size); + _data = reinterpret_cast(fftwf_malloc(_size * sizeof(float))); + _re = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float))); + _im = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float))); + + fftw_iodim dim; + dim.n = static_cast(size); + dim.is = 1; + dim.os = 1; + _planForward = fftwf_plan_guru_split_dft_r2c(1, &dim, 0, 0, _data, _re, _im, FFTW_MEASURE); + _planBackward = fftwf_plan_guru_split_dft_c2r(1, &dim, 0, 0, _re, _im, _data, FFTW_MEASURE); + } + } + } + + virtual void fft(const float* data, float* re, float* im) override + { + ::memcpy(_data, data, _size * sizeof(float)); + fftwf_execute_split_dft_r2c(_planForward, _data, _re, _im); + ::memcpy(re, _re, _complexSize * sizeof(float)); + ::memcpy(im, _im, _complexSize * sizeof(float)); + } + + void ifft(float* data, const float* re, const float* im) + { + ::memcpy(_re, re, _complexSize * sizeof(float)); + ::memcpy(_im, im, _complexSize * sizeof(float)); + fftwf_execute_split_dft_c2r(_planBackward, _re, _im, _data); + ScaleBuffer(data, _data, 1.0f / static_cast(_size), _size); + } + + private: + size_t _size; + size_t _complexSize; + fftwf_plan _planForward; + fftwf_plan _planBackward; + float* _data; + float* _re; + float* _im; + + FFTW3FFT(const FFTW3FFT&) = delete; + FFTW3FFT& operator=(const FFTW3FFT&) = delete; + }; + + + std::unique_ptr MakeAudioFFTImpl() + { + return std::unique_ptr(new FFTW3FFT()); + } + + +#endif // AUDIOFFT_FFTW3_USED + + } // End of namespace details + + + // ============================================================= + + + AudioFFT::AudioFFT() : + _impl(details::MakeAudioFFTImpl()) + { + } + + + void AudioFFT::init(size_t size) + { + assert(details::IsPowerOf2(size)); + _impl->init(size); + } + + + void AudioFFT::fft(const float* data, float* re, float* im) + { + _impl->fft(data, re, im); + } + + + void AudioFFT::ifft(float* data, const float* re, const float* im) + { + _impl->ifft(data, re, im); + } + + + size_t AudioFFT::ComplexSize(size_t size) + { + return (size / 2) + 1; + } + +} // End of namespace diff --git a/FFTConvolver/AudioFFT.h b/FFTConvolver/AudioFFT.h new file mode 100755 index 00000000..80cd2edc --- /dev/null +++ b/FFTConvolver/AudioFFT.h @@ -0,0 +1,176 @@ +// ================================================================================== +// Copyright (c) 2016 HiFi-LoFi +// +// 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 restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ================================================================================== + +#ifndef _AUDIOFFT_H +#define _AUDIOFFT_H + + +/** +* AudioFFT provides real-to-complex/complex-to-real FFT routines. +* +* Features: +* +* - Real-complex FFT and complex-real inverse FFT for power-of-2-sized real data. +* +* - Uniform interface to different FFT implementations (currently Ooura, FFTW3 and Apple Accelerate). +* +* - Complex data is handled in "split-complex" format, i.e. there are separate +* arrays for the real and imaginary parts which can be useful for SIMD optimizations +* (split-complex arrays have to be of length (size/2+1) representing bins from DC +* to Nyquist frequency). +* +* - Output is "ready to use" (all scaling etc. is already handled internally). +* +* - No allocations/deallocations after the initialization which makes it usable +* for real-time audio applications (that's what I wrote it for and using it). +* +* +* How to use it in your project: +* +* - Add the .h and .cpp file to your project - that's all. +* +* - To get extra speed, you can link FFTW3 to your project and define +* AUDIOFFT_FFTW3 (however, please check whether your project suits the +* according license). +* +* - To get the best speed on Apple platforms, you can link the Apple +* Accelerate framework to your project and define +* AUDIOFFT_APPLE_ACCELERATE (however, please check whether your +* project suits the according license). +* +* +* Remarks: +* +* - AudioFFT is not intended to be the fastest FFT, but to be a fast-enough +* FFT suitable for most audio applications. +* +* - AudioFFT uses the quite liberal MIT license. +* +* +* Example usage: +* @code +* #include "AudioFFT.h" +* +* void Example() +* { +* const size_t fftSize = 1024; // Needs to be power of 2! +* +* std::vector input(fftSize, 0.0f); +* std::vector re(audiofft::AudioFFT::ComplexSize(fftSize)); +* std::vector im(audiofft::AudioFFT::ComplexSize(fftSize)); +* std::vector output(fftSize); +* +* audiofft::AudioFFT fft; +* fft.init(1024); +* fft.fft(input.data(), re.data(), im.data()); +* fft.ifft(output.data(), re.data(), im.data()); +* } +* @endcode +*/ + + +#include +#include + + +namespace audiofft +{ + + namespace details + { + + class AudioFFTImpl + { + public: + AudioFFTImpl() = default; + virtual ~AudioFFTImpl() = default; + virtual void init(size_t size) = 0; + virtual void fft(const float* data, float* re, float* im) = 0; + virtual void ifft(float* data, const float* re, const float* im) = 0; + + private: + AudioFFTImpl(const AudioFFTImpl&) = delete; + AudioFFTImpl& operator=(const AudioFFTImpl&) = delete; + }; + } + + + // ====================================================== + + + /** + * @class AudioFFT + * @brief Performs 1D FFTs + */ + class AudioFFT + { + public: + /** + * @brief Constructor + */ + AudioFFT(); + + /** + * @brief Initializes the FFT object + * @param size Size of the real input (must be power 2) + */ + void init(size_t size); + + /** + * @brief Performs the forward FFT + * @param data The real input data (has to be of the length as specified in init()) + * @param re The real part of the complex output (has to be of length as returned by ComplexSize()) + * @param im The imaginary part of the complex output (has to be of length as returned by ComplexSize()) + */ + void fft(const float* data, float* re, float* im); + + /** + * @brief Performs the inverse FFT + * @param data The real output data (has to be of the length as specified in init()) + * @param re The real part of the complex input (has to be of length as returned by ComplexSize()) + * @param im The imaginary part of the complex input (has to be of length as returned by ComplexSize()) + */ + void ifft(float* data, const float* re, const float* im); + + /** + * @brief Calculates the necessary size of the real/imaginary complex arrays + * @param size The size of the real data + * @return The size of the real/imaginary complex arrays + */ + static size_t ComplexSize(size_t size); + + private: + std::unique_ptr _impl; + + AudioFFT(const AudioFFT&) = delete; + AudioFFT& operator=(const AudioFFT&) = delete; + }; + + + /** + * @deprecated + * @brief Let's keep an AudioFFTBase type around for now because it has been here already in the 1st version in order to avoid breaking existing code. + */ + typedef AudioFFT AudioFFTBase; + +} // End of namespace + +#endif // Header guard diff --git a/FFTConvolver/FFTConvolver.cpp b/FFTConvolver/FFTConvolver.cpp new file mode 100755 index 00000000..049dbed4 --- /dev/null +++ b/FFTConvolver/FFTConvolver.cpp @@ -0,0 +1,202 @@ +// ================================================================================== +// Copyright (c) 2012 HiFi-LoFi +// +// This is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ================================================================================== + +#include "FFTConvolver.h" + +#include +#include + +#if defined (FFTCONVOLVER_USE_SSE) + #include +#endif + + +namespace fftconvolver +{ + +FFTConvolver::FFTConvolver() : + _blockSize(0), + _segSize(0), + _segCount(0), + _fftComplexSize(0), + _segments(), + _segmentsIR(), + _fftBuffer(), + _fft(), + _preMultiplied(), + _conv(), + _overlap(), + _current(0), + _inputBuffer(), + _inputBufferFill(0) +{ +} + + +FFTConvolver::~FFTConvolver() +{ + reset(); +} + + +void FFTConvolver::reset() +{ + for (size_t i=0; i<_segCount; ++i) + { + delete _segments[i]; + delete _segmentsIR[i]; + } + + _blockSize = 0; + _segSize = 0; + _segCount = 0; + _fftComplexSize = 0; + _segments.clear(); + _segmentsIR.clear(); + _fftBuffer.clear(); + _fft.init(0); + _preMultiplied.clear(); + _conv.clear(); + _overlap.clear(); + _current = 0; + _inputBuffer.clear(); + _inputBufferFill = 0; +} + + +bool FFTConvolver::init(size_t blockSize, const Sample* ir, size_t irLen) +{ + reset(); + + if (blockSize == 0) + { + return false; + } + + // Ignore zeros at the end of the impulse response because they only waste computation time + while (irLen > 0 && ::fabs(ir[irLen-1]) < 0.000001f) + { + --irLen; + } + + if (irLen == 0) + { + return true; + } + + _blockSize = NextPowerOf2(blockSize); + _segSize = 2 * _blockSize; + _segCount = static_cast(::ceil(static_cast(irLen) / static_cast(_blockSize))); + _fftComplexSize = audiofft::AudioFFT::ComplexSize(_segSize); + + // FFT + _fft.init(_segSize); + _fftBuffer.resize(_segSize); + + // Prepare segments + for (size_t i=0; i<_segCount; ++i) + { + _segments.push_back(new SplitComplex(_fftComplexSize)); + } + + // Prepare IR + for (size_t i=0; i<_segCount; ++i) + { + SplitComplex* segment = new SplitComplex(_fftComplexSize); + const size_t remaining = irLen - (i * _blockSize); + const size_t sizeCopy = (remaining >= _blockSize) ? _blockSize : remaining; + CopyAndPad(_fftBuffer, &ir[i*_blockSize], sizeCopy); + _fft.fft(_fftBuffer.data(), segment->re(), segment->im()); + _segmentsIR.push_back(segment); + } + + // Prepare convolution buffers + _preMultiplied.resize(_fftComplexSize); + _conv.resize(_fftComplexSize); + _overlap.resize(_blockSize); + + // Prepare input buffer + _inputBuffer.resize(_blockSize); + _inputBufferFill = 0; + + // Reset current position + _current = 0; + + return true; +} + + +void FFTConvolver::process(const Sample* input, Sample* output, size_t len) +{ + if (_segCount == 0) + { + ::memset(output, 0, len * sizeof(Sample)); + return; + } + + size_t processed = 0; + while (processed < len) + { + const bool inputBufferWasEmpty = (_inputBufferFill == 0); + const size_t processing = std::min(len-processed, _blockSize-_inputBufferFill); + const size_t inputBufferPos = _inputBufferFill; + ::memcpy(_inputBuffer.data()+inputBufferPos, input+processed, processing * sizeof(Sample)); + + // Forward FFT + CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize); + _fft.fft(_fftBuffer.data(), _segments[_current]->re(), _segments[_current]->im()); + + // Complex multiplication + if (inputBufferWasEmpty) + { + _preMultiplied.setZero(); + for (size_t i=1; i<_segCount; ++i) + { + const size_t indexIr = i; + const size_t indexAudio = (_current + i) % _segCount; + ComplexMultiplyAccumulate(_preMultiplied, *_segmentsIR[indexIr], *_segments[indexAudio]); + } + } + _conv.copyFrom(_preMultiplied); + ComplexMultiplyAccumulate(_conv, *_segments[_current], *_segmentsIR[0]); + + // Backward FFT + _fft.ifft(_fftBuffer.data(), _conv.re(), _conv.im()); + + // Add overlap + Sum(output+processed, _fftBuffer.data()+inputBufferPos, _overlap.data()+inputBufferPos, processing); + + // Input buffer full => Next block + _inputBufferFill += processing; + if (_inputBufferFill == _blockSize) + { + // Input buffer is empty again now + _inputBuffer.setZero(); + _inputBufferFill = 0; + + // Save the overlap + ::memcpy(_overlap.data(), _fftBuffer.data()+_blockSize, _blockSize * sizeof(Sample)); + + // Update current segment + _current = (_current > 0) ? (_current - 1) : (_segCount - 1); + } + + processed += processing; + } +} + +} // End of namespace fftconvolver diff --git a/FFTConvolver/FFTConvolver.h b/FFTConvolver/FFTConvolver.h new file mode 100755 index 00000000..0c446088 --- /dev/null +++ b/FFTConvolver/FFTConvolver.h @@ -0,0 +1,100 @@ +// ================================================================================== +// Copyright (c) 2012 HiFi-LoFi +// +// This is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ================================================================================== + +#ifndef _FFTCONVOLVER_FFTCONVOLVER_H +#define _FFTCONVOLVER_FFTCONVOLVER_H + +#include "AudioFFT.h" +#include "Utilities.h" + +#include + + +namespace fftconvolver +{ + +/** +* @class FFTConvolver +* @brief Implementation of a partitioned FFT convolution algorithm with uniform block size +* +* Some notes on how to use it: +* +* - After initialization with an impulse response, subsequent data portions of +* arbitrary length can be convolved. The convolver internally can handle +* this by using appropriate buffering. +* +* - The convolver works without "latency" (except for the required +* processing time, of course), i.e. the output always is the convolved +* input for each processing call. +* +* - The convolver is suitable for real-time processing which means that no +* "unpredictable" operations like allocations, locking, API calls, etc. are +* performed during processing (all necessary allocations and preparations take +* place during initialization). +*/ +class FFTConvolver +{ +public: + FFTConvolver(); + virtual ~FFTConvolver(); + + /** + * @brief Initializes the convolver + * @param blockSize Block size internally used by the convolver (partition size) + * @param ir The impulse response + * @param irLen Length of the impulse response + * @return true: Success - false: Failed + */ + bool init(size_t blockSize, const Sample* ir, size_t irLen); + + /** + * @brief Convolves the the given input samples and immediately outputs the result + * @param input The input samples + * @param output The convolution result + * @param len Number of input/output samples + */ + void process(const Sample* input, Sample* output, size_t len); + + /** + * @brief Resets the convolver and discards the set impulse response + */ + void reset(); + +private: + size_t _blockSize; + size_t _segSize; + size_t _segCount; + size_t _fftComplexSize; + std::vector _segments; + std::vector _segmentsIR; + SampleBuffer _fftBuffer; + audiofft::AudioFFT _fft; + SplitComplex _preMultiplied; + SplitComplex _conv; + SampleBuffer _overlap; + size_t _current; + SampleBuffer _inputBuffer; + size_t _inputBufferFill; + + // Prevent uncontrolled usage + FFTConvolver(const FFTConvolver&); + FFTConvolver& operator=(const FFTConvolver&); +}; + +} // End of namespace fftconvolver + +#endif // Header guard diff --git a/FFTConvolver/Utilities.cpp b/FFTConvolver/Utilities.cpp new file mode 100644 index 00000000..a30ff148 --- /dev/null +++ b/FFTConvolver/Utilities.cpp @@ -0,0 +1,113 @@ +// ================================================================================== +// Copyright (c) 2012 HiFi-LoFi +// +// This is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ================================================================================== + +#include "Utilities.h" + + +namespace fftconvolver +{ + +bool SSEEnabled() +{ +#if defined(FFTCONVOLVER_USE_SSE) + return true; +#else + return false; +#endif +} + + +void Sum(Sample* FFTCONVOLVER_RESTRICT result, + const Sample* FFTCONVOLVER_RESTRICT a, + const Sample* FFTCONVOLVER_RESTRICT b, + size_t len) +{ + const size_t end4 = 4 * (len / 4); + for (size_t i=0; i. +// ================================================================================== + +#ifndef _FFTCONVOLVER_UTILITIES_H +#define _FFTCONVOLVER_UTILITIES_H + +#include +#include +#include +#include +#include + + +namespace fftconvolver +{ + +#if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) + #if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE) + #define FFTCONVOLVER_USE_SSE + #endif +#endif + + +#if defined (FFTCONVOLVER_USE_SSE) + #include +#endif + + +#if defined(__GNUC__) + #define FFTCONVOLVER_RESTRICT __restrict__ +#else + #define FFTCONVOLVER_RESTRICT +#endif + + +/** +* @brief Returns whether SSE optimization for the convolver is enabled +* @return true: Enabled - false: Disabled +*/ +bool SSEEnabled(); + + +/** +* @class Buffer +* @brief Simple buffer implementation (uses 16-byte alignment if SSE optimization is enabled) +*/ +template +class Buffer +{ +public: + explicit Buffer(size_t initialSize = 0) : + _data(0), + _size(0) + { + resize(initialSize); + } + + virtual ~Buffer() + { + clear(); + } + + void clear() + { + deallocate(_data); + _data = 0; + _size = 0; + } + + void resize(size_t size) + { + if (_size != size) + { + clear(); + + if (size > 0) + { + assert(!_data && _size == 0); + _data = allocate(size); + _size = size; + } + } + setZero(); + } + + size_t size() const + { + return _size; + } + + void setZero() + { + ::memset(_data, 0, _size * sizeof(T)); + } + + void copyFrom(const Buffer& other) + { + assert(_size == other._size); + if (this != &other) + { + ::memcpy(_data, other._data, _size * sizeof(T)); + } + } + + T& operator[](size_t index) + { + assert(_data && index < _size); + return _data[index]; + } + + const T& operator[](size_t index) const + { + assert(_data && index < _size); + return _data[index]; + } + + operator bool() const + { + return (_data != 0 && _size > 0); + } + + T* data() + { + return _data; + } + + const T* data() const + { + return _data; + } + + static void Swap(Buffer& a, Buffer& b) + { + std::swap(a._data, b._data); + std::swap(a._size, b._size); + } + +private: + T* allocate(size_t size) + { +#if defined(FFTCONVOLVER_USE_SSE) + return static_cast(_mm_malloc(size * sizeof(T), 16)); +#else + return new T[size]; +#endif + } + + void deallocate(T* ptr) + { +#if defined(FFTCONVOLVER_USE_SSE) + _mm_free(ptr); +#else + delete [] ptr; +#endif + } + + T* _data; + size_t _size; + + // Prevent uncontrolled usage + Buffer(const Buffer&); + Buffer& operator=(const Buffer&); +}; + + +/** +* @brief Type of one sample +*/ +typedef float Sample; + + +/** +* @brief Buffer for samples +*/ +typedef Buffer SampleBuffer; + + +/** +* @class SplitComplex +* @brief Buffer for split-complex representation of FFT results +* +* The split-complex representation stores the real and imaginary parts +* of FFT results in two different memory buffers which is useful e.g. for +* SIMD optimizations. +*/ +class SplitComplex +{ +public: + explicit SplitComplex(size_t initialSize = 0) : + _size(0), + _re(), + _im() + { + resize(initialSize); + } + + ~SplitComplex() + { + clear(); + } + + void clear() + { + _re.clear(); + _im.clear(); + _size = 0; + } + + void resize(size_t newSize) + { + _re.resize(newSize); + _im.resize(newSize); + _size = newSize; + } + + void setZero() + { + _re.setZero(); + _im.setZero(); + } + + void copyFrom(const SplitComplex& other) + { + _re.copyFrom(other._re); + _im.copyFrom(other._im); + } + + Sample* re() + { + return _re.data(); + } + + const Sample* re() const + { + return _re.data(); + } + + Sample* im() + { + return _im.data(); + } + + const Sample* im() const + { + return _im.data(); + } + + size_t size() const + { + return _size; + } + +private: + size_t _size; + SampleBuffer _re; + SampleBuffer _im; + + // Prevent uncontrolled usage + SplitComplex(const SplitComplex&); + SplitComplex& operator=(const SplitComplex&); +}; + + +/** +* @brief Returns the next power of 2 of a given number +* @param val The number +* @return The next power of 2 +*/ +template +T NextPowerOf2(const T& val) +{ + T nextPowerOf2 = 1; + while (nextPowerOf2 < val) + { + nextPowerOf2 *= 2; + } + return nextPowerOf2; +} + + +/** +* @brief Sums two given sample arrays +* @param result The result array +* @param a The 1st array +* @param b The 2nd array +* @param len The length of the arrays +*/ +void Sum(Sample* FFTCONVOLVER_RESTRICT result, + const Sample* FFTCONVOLVER_RESTRICT a, + const Sample* FFTCONVOLVER_RESTRICT b, + size_t len); + + +/** +* @brief Copies a source array into a destination buffer and pads the destination buffer with zeros +* @param dest The destination buffer +* @param src The source array +* @param srcSize The size of the source array +*/ +template +void CopyAndPad(Buffer& dest, const T* src, size_t srcSize) +{ + assert(dest.size() >= srcSize); + ::memcpy(dest.data(), src, srcSize * sizeof(T)); + ::memset(dest.data() + srcSize, 0, (dest.size()-srcSize) * sizeof(T)); +} + + +/** +* @brief Adds the complex product of two split-complex buffers to a result buffer +* @param result The result buffer +* @param a The 1st factor of the complex product +* @param b The 2nd factor of the complex product +*/ +void ComplexMultiplyAccumulate(SplitComplex& result, const SplitComplex& a, const SplitComplex& b); + + +/** +* @brief Adds the complex product of two split-complex arrays to a result array +* @param re The real part of the result buffer +* @param im The imaginary part of the result buffer +* @param reA The real part of the 1st factor of the complex product +* @param imA The imaginary part of the 1st factor of the complex product +* @param reB The real part of the 2nd factor of the complex product +* @param imB The imaginary part of the 2nd factor of the complex product +*/ +void ComplexMultiplyAccumulate(Sample* FFTCONVOLVER_RESTRICT re, + Sample* FFTCONVOLVER_RESTRICT im, + const Sample* FFTCONVOLVER_RESTRICT reA, + const Sample* FFTCONVOLVER_RESTRICT imA, + const Sample* FFTCONVOLVER_RESTRICT reB, + const Sample* FFTCONVOLVER_RESTRICT imB, + const size_t len); + +} // End of namespace fftconvolver + +#endif // Header guard diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp new file mode 100644 index 00000000..4589c8ed --- /dev/null +++ b/FFTConvolver/convolver.cpp @@ -0,0 +1,67 @@ + + +#include "convolver.h" +#include +#include "FFTConvolver.h" +#include "Utilities.h" +extern "C" { +#include "../common.h" +} + + +static fftconvolver::FFTConvolver convolver_l; +static fftconvolver::FFTConvolver convolver_r; + + +void convolver_init(const char* filename, int max_length) +{ + SF_INFO info; + assert(filename); + SNDFILE* file = sf_open(filename, SFM_READ, &info); + assert(file); + + if (info.samplerate != 44100) + die("Impulse file \"%s\" sample rate is %d Hz. Only 44100 Hz is supported", filename, info.samplerate); + + if (info.channels != 1 && info.channels != 2) + die("Impulse file \"%s\" contains %d channels. Only 1 or 2 is supported.", filename, info.channels); + + const size_t size = info.frames > max_length ? max_length : info.frames; + float buffer[size*info.channels]; + + size_t l = sf_readf_float(file, buffer, size); + assert(l == size); + + if (info.channels == 1) { + convolver_l.init(352, buffer, size); + convolver_r.init(352, buffer, size); + } else { + // deinterleave + float buffer_l[size]; + float buffer_r[size]; + + int i; + for (i=0; i>Including convolution support) + HAS_CONVOLUTION=1 + AM_INIT_AUTOMAKE([subdir-objects]) + AC_DEFINE([CONFIG_CONVOLUTION], 1, [Needed by the compiler.]) + AC_CHECK_LIB([sndfile], [sf_open], , AC_MSG_ERROR(Convolution support requires the sndfile library!))], ) +AM_CONDITIONAL([USE_CONVOLUTION], [test "x$HAS_CONVOLUTION" = "x1"]) + # Look for dns_sd flag AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [ AC_MSG_RESULT(>>Including dns_sd for mDNS support) @@ -295,8 +304,6 @@ AC_TYPE_UINT8_T AC_FUNC_ALLOCA AC_FUNC_ERROR_AT_LINE AC_FUNC_FORK -AC_FUNC_MALLOC -AC_FUNC_REALLOC AC_CHECK_FUNCS([atexit clock_gettime gethostname inet_ntoa memchr memmove memset mkfifo pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul]) AC_CONFIG_FILES([Makefile man/Makefile scripts/shairport-sync.service]) diff --git a/loudness.c b/loudness.c new file mode 100644 index 00000000..5db17a8c --- /dev/null +++ b/loudness.c @@ -0,0 +1,55 @@ +#include "loudness.h" +#include +#include "common.h" + +loudness_processor loudness_r; +loudness_processor loudness_l; + + +void _loudness_set_volume(loudness_processor *p, float volume) +{ + float gain = -(volume-config.loudness_reference_volume_db)*0.5; + if (gain < 0) + gain = 0; + + float Fc = 10.0; + float Q = 0.5; + + // Formula from http://www.earlevel.com/main/2011/01/02/biquad-formulas/ + float Fs = 44100.0; + + float K = tan(M_PI * Fc / Fs); + float V = pow(10.0, gain / 20.0); + + float norm = 1 / (1 + 1/Q * K + K * K); + p->a0 = (1 + V/Q * K + K * K) * norm; + p->a1 = 2 * (K * K - 1) * norm; + p->a2 = (1 - V/Q * K + K * K) * norm; + p->b1 = p->a1; + p->b2 = (1 - 1/Q * K + K * K) * norm; +} + +float loudness_process(loudness_processor *p, float i0) +{ + float o0 = p->a0*i0 + p->a1*p->i1 + p->a2*p->i2 - p->b1*p->o1 - p->b2*p->o2; + + p->o2 = p->o1; + p->o1 = o0; + + p->i2 = p->i1; + p->i1 = i0; + + return o0; +} + + +void loudness_set_volume(float volume) +{ + float gain = -(volume-config.loudness_reference_volume_db)*0.5; + if (gain < 0) + gain = 0; + + inform("Volume: %.1f dB - Loudness gain @10Hz: %.1f dB", volume, gain); + _loudness_set_volume(&loudness_l, volume); + _loudness_set_volume(&loudness_r, volume); +} diff --git a/loudness.h b/loudness.h new file mode 100644 index 00000000..c1a219a2 --- /dev/null +++ b/loudness.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +typedef struct { + float a0, a1, a2, b1, b2; + float i1, i2, o1, o2; +} loudness_processor; + + +extern loudness_processor loudness_r; +extern loudness_processor loudness_l; + +void loudness_set_volume(float volume); +float loudness_process(loudness_processor *p, float sample); + diff --git a/player.c b/player.c index f035989d..ebc98843 100644 --- a/player.c +++ b/player.c @@ -65,6 +65,10 @@ #include #endif +#ifdef CONFIG_CONVOLUTION +#include +#endif + #include "common.h" #include "player.h" #include "rtp.h" @@ -76,6 +80,8 @@ #include "apple_alac.h" #endif +#include "loudness.h" + // parameters from the source static unsigned char *aesiv; #ifdef HAVE_LIBSSL @@ -639,11 +645,16 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t int dither) { int64_t hyper_sample = sample; int result; - int64_t hyper_volume = (int64_t)volume << 16; - hyper_sample = hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to - // dither it down to its - // target resolution - + + if (config.loudness) { + hyper_sample <<= 32; // Do not apply volume as it has already been done with the Loudness DSP filter + } else { + int64_t hyper_volume = (int64_t)volume << 16; + hyper_sample = hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to + // dither it down to its + // target resolution + } + // next, do dither, if necessary if (dither) { @@ -1815,6 +1826,67 @@ static void *player_thread_func(void *arg) { if (config.no_sync != 0) amount_to_stuff = 0; // no stuffing if it's been disabled + + // Apply DSP here + if (config.loudness +#ifdef CONFIG_CONVOLUTION + || config.convolution +#endif + ) + { + int32_t *tbuf32 = (int32_t*)tbuf; + float fbuf_l[inbuflength]; + float fbuf_r[inbuflength]; + + // Deinterleave, and convert to float + int i; + for (i=0; i #include +#ifdef CONFIG_CONVOLUTION +#include +#endif + static int shutting_down = 0; static char *appName = NULL; char configuration_file_path[4096 + 1]; @@ -159,6 +163,9 @@ char *get_version_string() { #ifdef HAVE_LIBSOXR strcat(version_string, "-soxr"); #endif +#ifdef CONFIG_CONVOLUTION + strcat(version_string, "-convolution"); +#endif #ifdef CONFIG_METADATA strcat(version_string, "-metadata"); #endif @@ -661,7 +668,59 @@ int parse_options(int argc, char **argv) { config.timeout = value; config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 } - + +#ifdef CONFIG_CONVOLUTION + + if (config_lookup_string(config.cfg, "dsp.convolution", &str)) { + if (strcasecmp(str, "no") == 0) + config.convolution = 0; + else if (strcasecmp(str, "yes") == 0) + config.convolution = 1; + else + die("Invalid dsp.convolution. It should be \"yes\" or \"no\""); + + } + + if (config_lookup_float(config.cfg, "dsp.convolution_gain", &dvalue)) { + config.convolution_gain = dvalue; + if (dvalue > 10 || dvalue < -50) + die("Invalid value \"%f\" for dsp.convolution_gain. It should be between -50 and +10 dB", dvalue); + } + + config.convolution_max_length = 8192; + if (config_lookup_int(config.cfg, "dsp.convolution_max_length", &value)) { + config.convolution_max_length = value; + + if (value < 1 || value > 200000) + die("dsp.convolution_max_length must be within 1 and 200000"); + } + + if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) { + config.convolution_ir_file = str; + convolver_init(config.convolution_ir_file, config.convolution_max_length); + } + + if (config.convolution && config.convolution_ir_file == NULL) { + die("Convolution enabled but no convolution_ir_file provided"); + } +#endif + + if (config_lookup_string(config.cfg, "dsp.loudness", &str)) { + if (strcasecmp(str, "no") == 0) + config.loudness = 0; + else if (strcasecmp(str, "yes") == 0) + config.loudness = 1; + else + die("Invalid dsp.convolution. It should be \"yes\" or \"no\""); + } + + config.loudness_reference_volume_db = -20; + if (config_lookup_float(config.cfg, "dsp.loudness_reference_volume_db", &dvalue)) { + config.loudness_reference_volume_db = dvalue; + if (dvalue > 0 || dvalue < -100) + die("Invalid value \"%f\" for dsp.loudness_reference_volume_db. It should be between -100 and 0", dvalue); + } + } else { if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO) debug(1, "Error reading configuration file \"%s\": \"%s\".", @@ -1223,6 +1282,15 @@ int main(int argc, char **argv) { debug(1, "get-coverart is %d.", config.get_coverart); #endif +#ifdef CONFIG_CONVOLUTION + debug(1, "convolution is %d.", config.convolution); + debug(1, "convolution IR file is \"%s\"", config.convolution_ir_file); + debug(1, "convolution max length %d", config.convolution_max_length); + debug(1, "convolution gain is %f", config.convolution_gain); +#endif + debug(1, "loudness is %d.", config.loudness); + debug(1, "loudness reference level is %f", config.loudness_reference_volume_db); + uint8_t ap_md5[16]; #ifdef HAVE_LIBSSL From 70a9d371ca3c06503b8824f5fd9c1db3105d4b75 Mon Sep 17 00:00:00 2001 From: Yann Pomarede Date: Mon, 27 Feb 2017 23:15:58 +0100 Subject: [PATCH 007/239] prevent loudness if hardware mixer is used --- shairport.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shairport.c b/shairport.c index 34074b74..5837f240 100644 --- a/shairport.c +++ b/shairport.c @@ -721,6 +721,9 @@ int parse_options(int argc, char **argv) { die("Invalid value \"%f\" for dsp.loudness_reference_volume_db. It should be between -100 and 0", dvalue); } + if (config.loudness == 1 && config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) + die("Loudness activated but hardware volume is active. You must remove \"alsa.mixer_control_name\" to use the loudness filter."); + } else { if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO) debug(1, "Error reading configuration file \"%s\": \"%s\".", From f1c4b2dbed0e82ba67604ca2a1fb2079d01224ec Mon Sep 17 00:00:00 2001 From: Yann Pomarede Date: Tue, 28 Feb 2017 12:03:37 +0100 Subject: [PATCH 008/239] FFTConvolver needs C++11 and -O2 divides by 3 CPU usage on a Raspberry Pi. --- Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.am b/Makefile.am index a2f973d0..63b12b92 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,6 +57,7 @@ endif if USE_CONVOLUTION shairport_sync_SOURCES += FFTConvolver/AudioFFT.cpp FFTConvolver/FFTConvolver.cpp FFTConvolver/Utilities.cpp FFTConvolver/convolver.cpp +AM_CXXFLAGS = -std=c++11 -O2 endif if USE_DNS_SD From 7b414f4ef8101bffc809765e7c5244eee0e95657 Mon Sep 17 00:00:00 2001 From: Yann Pomarede Date: Tue, 28 Feb 2017 12:25:43 +0100 Subject: [PATCH 009/239] adding -O2 (x2.6 speed up on Raspberry Pi) --- Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 63b12b92..3dfae1d3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,8 +3,8 @@ 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 loudness.c -AM_CFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" -AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" +AM_CFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" -O2 +AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" -O2 if USE_APPLE_ALAC shairport_sync_SOURCES += apple_alac.cpp From 2ab2a84c1473e3e7af561423ca1b766126fc72fa Mon Sep 17 00:00:00 2001 From: Yann Pomarede Date: Tue, 28 Feb 2017 12:47:03 +0100 Subject: [PATCH 010/239] change function die, warn, inform and debug change funcions die, warn, inform and debug argument from `char*` to `const char*` to prevent a warning in C== --- common.c | 8 ++++---- common.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common.c b/common.c index 53d7715e..0776407d 100644 --- a/common.c +++ b/common.c @@ -91,7 +91,7 @@ int get_requested_connection_state_to_output() { return requested_connection_sta void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; } -void die(char *format, ...) { +void die(const char *format, ...) { char s[1024]; s[0] = 0; va_list args; @@ -103,7 +103,7 @@ void die(char *format, ...) { exit(1); } -void warn(char *format, ...) { +void warn(const char *format, ...) { char s[1024]; s[0] = 0; va_list args; @@ -113,7 +113,7 @@ void warn(char *format, ...) { daemon_log(LOG_WARNING, "%s", s); } -void debug(int level, char *format, ...) { +void debug(int level, const char *format, ...) { if (level > debuglev) return; char s[1024]; @@ -125,7 +125,7 @@ void debug(int level, char *format, ...) { daemon_log(LOG_DEBUG, "%s", s); } -void inform(char *format, ...) { +void inform(const char *format, ...) { char s[1024]; s[0] = 0; va_list args; diff --git a/common.h b/common.h index d3beeb0d..19f0cb94 100644 --- a/common.h +++ b/common.h @@ -172,10 +172,10 @@ uint64_t ranarray64u(); int64_t ranarray64i(); extern int debuglev; -void die(char *format, ...); -void warn(char *format, ...); -void inform(char *format, ...); -void debug(int level, char *format, ...); +void die(const char *format, ...); +void warn(const char *format, ...); +void inform(const char *format, ...); +void debug(int level, const char *format, ...); uint8_t *base64_dec(char *input, int *outlen); char *base64_enc(uint8_t *input, int length); From 2a25ac00a3a5993368ead98cf2523ea80b3ae3c0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 1 Mar 2017 08:54:54 +0000 Subject: [PATCH 011/239] Update RELEASENOTES.md --- RELEASENOTES.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 18996d0e..8c68575a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,14 @@ +Version 3.1d0 +==== +**New Features** +* Two audio filters added – a volume-dependent-loudness and a convolution filter. Many thanks to [yannpom](https://github.com/yannpom) for this. for more information right now, please refer to the conversation at [#484](https://github.com/mikebrady/shairport-sync/pull/484). + +**Note** +* Some housekeeping needs to be done on the location of configuration files and convolution impulse response files, and maybe compilation configuration settings, but they can wait for a little while. +* Documentation isn't updated + 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. From 7d672bcf561f37e4711b6fe8b6547e9636b0d20d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 7 Mar 2017 22:42:23 +0000 Subject: [PATCH 012/239] Untested, probably not working --- player.c | 97 +++++++++++++++++++++++++------------------------------- player.h | 49 +++++++++++++++++++++++++--- rtp.c | 16 +++++----- rtp.h | 2 -- rtsp.c | 15 ++------- 5 files changed, 98 insertions(+), 81 deletions(-) diff --git a/player.c b/player.c index 9b265279..9faa2031 100644 --- a/player.c +++ b/player.c @@ -76,11 +76,6 @@ #include "apple_alac.h" #endif -// parameters from the source -static unsigned char *aesiv; -#ifdef HAVE_LIBSSL -static AES_KEY aes; -#endif static int input_rate, input_bit_depth, input_num_channels, max_frames_per_packet; static uint32_t timestamp_epoch, last_timestamp, @@ -99,17 +94,8 @@ static int max_frame_size_change; // maximal resampling shift - conservative //#define OUTFRAME_BYTES(max_frames_per_packet) (4 * (max_frames_per_packet + 3)) -#ifdef HAVE_LIBMBEDTLS -static mbedtls_aes_context dctx; -#endif - -#ifdef HAVE_LIBPOLARSSL -static aes_context dctx; -#endif - // static pthread_t player_thread = NULL; static int please_stop; -static int encrypted; // Normally the audio is encrypted, but it may not be static int connection_state_to_output; // if true, then play incoming stuff; if false drop everything @@ -118,7 +104,6 @@ static alac_file *decoder_info; // debug variables static int late_packet_message_sent; -static uint64_t packet_count = 0; static int32_t last_seqno_read; static int decoder_in_use = 0; @@ -305,7 +290,7 @@ static inline int seq32_order(uint32_t a, uint32_t b) { return (C & 0x80000000) == 0; } -static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { +static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_conn_info* conn) { // parameters: where the decoded stuff goes, its length in samples, // the incoming packet, the length of the incoming packet in bytes // destlen should contain the allowed max number of samples on entry @@ -322,18 +307,18 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { int outsize = input_bytes_per_frame * (*destlen); // the size the output should be, in bytes int toutsize = outsize; - if (encrypted) { + if (conn->stream.encrypted) { unsigned char iv[16]; int aeslen = len & ~0xf; - memcpy(iv, aesiv, sizeof(iv)); + memcpy(iv, conn->stream.aesiv, sizeof(iv)); #ifdef HAVE_LIBMBEDTLS - mbedtls_aes_crypt_cbc(&dctx, MBEDTLS_AES_DECRYPT, aeslen, iv, buf, packet); + mbedtls_aes_crypt_cbc(&conn->dctx, MBEDTLS_AES_DECRYPT, aeslen, iv, buf, packet); #endif #ifdef HAVE_LIBPOLARSSL - aes_crypt_cbc(&dctx, AES_DECRYPT, aeslen, iv, buf, packet); + aes_crypt_cbc(&conn->dctx, AES_DECRYPT, aeslen, iv, buf, packet); #endif #ifdef HAVE_LIBSSL - AES_cbc_encrypt(buf, packet, aeslen, &aes, iv, AES_DECRYPT); + AES_cbc_encrypt(buf, packet, aeslen, &conn->aes, iv, AES_DECRYPT); #endif memcpy(packet + aeslen, buf + aeslen, len - aeslen); #ifdef HAVE_APPLE_ALAC @@ -377,7 +362,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) { if (outsize > toutsize) { debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " "truncated, but buffer overflow possible! Encrypted = %d.", - outsize, toutsize, encrypted); + outsize, toutsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } @@ -524,14 +509,14 @@ static void free_buffer(void) { free(audio_buffer[i].data); } -void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { +void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info *itr) { // all timestamps are done at the output rate int64_t ltimestamp = timestamp * output_sample_ratio; // ignore a request to flush that has been made before the first packet... - if (packet_count == 0) { + if (itr->packet_count == 0) { pthread_mutex_lock(&flush_mutex); flush_requested = 0; flush_rtp_timestamp = 0; @@ -539,7 +524,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { } pthread_mutex_lock(&ab_mutex); - packet_count++; + itr->packet_count++; time_of_last_audio_packet = get_absolute_time_in_fp(); if (connection_state_to_output) { // if we are supposed to be processing these packets @@ -603,7 +588,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) { if (abuf) { int datalen = max_frames_per_packet; - if (alac_decode(abuf->data, &datalen, data, len) == 0) { + if (alac_decode(abuf->data, &datalen, data, len, itr) == 0) { abuf->ready = 1; abuf->length = datalen; abuf->timestamp = ltimestamp; @@ -1490,8 +1475,28 @@ 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; + + rtsp_conn_info *conn = (rtsp_conn_info *)arg; + + conn->please_stop = 0; + conn->packet_count = 0; + + if (conn->stream.encrypted) { +#ifdef HAVE_LIBMBEDTLS + memset(&conn->dctx, 0, sizeof(mbedtls_aes_context)); + mbedtls_aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128); +#endif + +#ifdef HAVE_LIBPOLARSSL + memset(&conn->dctx, 0, sizeof(aes_context)); + aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128); +#endif + +#ifdef HAVE_LIBSSL + AES_set_decrypt_key(conn->stream.aeskey, 128, &conn->aes); +#endif + } + 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 @@ -1522,9 +1527,9 @@ static void *player_thread_func(void *arg) { // 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); + pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn); + pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); + pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); session_corrections = 0; play_segment_reference_frame = 0; // zero signals that we are not in a play segment @@ -2211,7 +2216,7 @@ static void *player_thread_func(void *arg) { if (sbuf) free(sbuf); debug(1, "Shut down audio, control and timing threads"); - itr.please_stop = 1; + conn->please_stop = 1; pthread_kill(rtp_audio_thread, SIGUSR1); pthread_kill(rtp_control_thread, SIGUSR1); pthread_kill(rtp_timing_thread, SIGUSR1); @@ -2430,31 +2435,15 @@ void player_flush(int64_t timestamp) { #endif } -int player_play(stream_cfg *stream, pthread_t *player_thread) { +int player_play(pthread_t *player_thread, rtsp_conn_info *conn) { + +// need to use conn in place of streram below. Need to put the stream as a parameter to he // 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); -#endif - -#ifdef HAVE_LIBSSL - AES_set_decrypt_key(stream->aeskey, 128, &aes); -#endif - aesiv = stream->aesiv; - } - init_decoder(stream->fmtp); // this sets up incoming rate, bit depth, channels + init_decoder((int32_t *)&conn->stream.fmtp); // this sets up incoming rate, bit depth, channels // must be after decoder init init_buffer(); please_stop = 0; @@ -2482,12 +2471,12 @@ int player_play(stream_cfg *stream, pthread_t *player_thread) { rc = pthread_attr_setstacksize(&tattr, size); if (rc) debug(1, "Error setting stack size for player_thread: %s", strerror(errno)); - pthread_create(player_thread, &tattr, player_thread_func, NULL); + pthread_create(player_thread, &tattr, player_thread_func, (void*)conn); pthread_attr_destroy(&tattr); return 0; } -void player_stop(pthread_t *player_thread) { +void player_stop(pthread_t *player_thread, rtsp_conn_info *conn) { // if (*thread==NULL) // debug(1,"Trying to stop a non-existent player thread"); // else { diff --git a/player.h b/player.h index eba97e8e..8ffd153b 100644 --- a/player.h +++ b/player.h @@ -1,6 +1,22 @@ #ifndef _PLAYER_H #define _PLAYER_H +#include "config.h" + +#ifdef HAVE_LIBMBEDTLS +#include +#include +#endif + +#ifdef HAVE_LIBPOLARSSL +#include +#include +#endif + +#ifdef HAVE_LIBSSL +#include +#endif + #include "audio.h" typedef struct { @@ -9,18 +25,43 @@ typedef struct { int32_t fmtp[12]; } stream_cfg; +typedef struct { + int fd; + int authorized; // set if a password is required and has been supplied + stream_cfg stream; + SOCKADDR remote, local; + int stop; + int running; + pthread_t thread; + pthread_t player_thread; + + uint32_t please_stop; + uint64_t packet_count; +#ifdef HAVE_LIBMBEDTLS + mbedtls_aes_context dctx; +#endif + +#ifdef HAVE_LIBPOLARSSL + aes_context dctx; +#endif + +#ifdef HAVE_LIBSSL + AES_KEY aes; +#endif +} rtsp_conn_info; + + typedef uint16_t seq_t; // wrapped number between two seq_t. int32_t seq_diff(seq_t a, seq_t b); -int player_play(stream_cfg *cfg, pthread_t *thread); -void player_stop(pthread_t *thread); +int player_play(pthread_t *thread, rtsp_conn_info* conn); +void player_stop(pthread_t *thread, rtsp_conn_info* conn); void player_volume(double f); void player_flush(int64_t timestamp); - -void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len); +void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info* conn); int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic // timestamp guaranteed to start between 2^32 2^33 diff --git a/rtp.c b/rtp.c index dcaf28d4..4f17e6e0 100644 --- a/rtp.c +++ b/rtp.c @@ -98,7 +98,7 @@ void *rtp_audio_receiver(void *arg) { debug(2, "Audio receiver -- Server RTP thread starting."); // we inherit the signal mask (SIGUSR1) - struct inter_threads_record *itr = arg; + rtsp_conn_info *conn = arg; int32_t last_seqno = -1; uint8_t packet[2048], *pktp; @@ -114,7 +114,7 @@ void *rtp_audio_receiver(void *arg) { float stat_M2 = 0.0; ssize_t nread; - while (itr->please_stop == 0) { + while (conn->please_stop == 0) { nread = recv(audio_socket, packet, sizeof(packet), 0); uint64_t local_time_now_fp = get_absolute_time_in_fp(); @@ -174,7 +174,7 @@ void *rtp_audio_receiver(void *arg) { // check if packet contains enough content to be reasonable if (plen >= 16) { - player_put_packet(seqno, timestamp, pktp, plen); + player_put_packet(seqno, timestamp, pktp, plen,conn); continue; } if (type == 0x56 && seqno == 0) { @@ -197,7 +197,7 @@ void *rtp_control_receiver(void *arg) { // we inherit the signal mask (SIGUSR1) debug(2, "Control receiver -- Server RTP thread starting."); - struct inter_threads_record *itr = arg; + rtsp_conn_info *conn = arg; reference_timestamp = 0; // nothing valid received yet uint8_t packet[2048], *pktp; @@ -205,7 +205,7 @@ void *rtp_control_receiver(void *arg) { uint64_t remote_time_of_sync, local_time_now, remote_time_now; int64_t sync_rtp_timestamp, rtp_timestamp_less_latency; ssize_t nread; - while (itr->please_stop == 0) { + while (conn->please_stop == 0) { nread = recv(control_socket, packet, sizeof(packet), 0); local_time_now = get_absolute_time_in_fp(); // clock_gettime(CLOCK_MONOTONIC,&tn); @@ -281,7 +281,7 @@ void *rtp_control_receiver(void *arg) { // check if packet contains enough content to be reasonable if (plen >= 16) { - player_put_packet(seqno, timestamp, pktp, plen); + player_put_packet(seqno, timestamp, pktp, plen, conn); continue; } else { debug(1, "Too-short retransmitted audio packet received in control port, ignored."); @@ -356,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; + rtsp_conn_info *conn = arg; uint8_t packet[2048], *pktp; ssize_t nread; @@ -374,7 +374,7 @@ void *rtp_timing_receiver(void *arg) { 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 (conn->please_stop == 0) { nread = recv(timing_socket, packet, sizeof(packet), 0); arrival_time = get_absolute_time_in_fp(); // clock_gettime(CLOCK_MONOTONIC,&att); diff --git a/rtp.h b/rtp.h index f4c8ab0b..4f6da117 100644 --- a/rtp.h +++ b/rtp.h @@ -5,8 +5,6 @@ #include "player.h" -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); diff --git a/rtsp.c b/rtsp.c index 59594019..164c8e8b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -90,17 +90,6 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; // static int please_shutdown = 0; // static pthread_t playing_thread = 0; -typedef struct { - int fd; - int authorized; // set is a password is required and has been supplied - stream_cfg stream; - 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 **conns = NULL; @@ -819,7 +808,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * strcat(hdr, q); // should unsplice the timing port entry } - player_play(&conn->stream, &conn->player_thread); // the thread better be 0 + player_play(&conn->player_thread,conn); // the thread better be 0 char *resphdr = alloca(200); *resphdr = 0; @@ -1764,7 +1753,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { debug(1, "Closing down RTSP conversation thread..."); if (rtsp_playing()) { - player_stop(&conn->player_thread); // might be less noisy doing this first + player_stop(&conn->player_thread,conn); // might be less noisy doing this first rtp_shutdown(); // usleep(400000); // let an angel pass... pthread_mutex_unlock(&play_lock); From cbcda67f64674d04de5f8edfa0edfed89c834ae0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Mar 2017 16:17:35 +0000 Subject: [PATCH 013/239] Fix a bug to ensure a silent frame is supplied to replace a missing frame. --- player.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/player.c b/player.c index 9faa2031..77092228 100644 --- a/player.c +++ b/player.c @@ -1166,8 +1166,7 @@ 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, input_bytes_per_frame * max_frames_per_packet); - curframe->timestamp = 0; + curframe->timestamp = 0; // indicate a silent frame should be substituted } curframe->ready = 0; ab_read = SUCCESSOR(ab_read); @@ -1698,19 +1697,11 @@ static void *player_thread_func(void *arg) { 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 (inbuf == NULL) - debug(1, "NULL inbuf to play -- skipping it."); - else { - if (inbuflength == 0) - debug(1, "empty frame to play -- skipping it (1)."); - else - config.output->play(inbuf, inbuflength); - } + config.output->play(silence, max_frames_per_packet * output_sample_ratio); } else { int enable_dither = 0; @@ -2067,6 +2058,7 @@ static void *player_thread_func(void *arg) { } } } + // mark the frame as finished inframe->timestamp = 0; From 498c1d6e1836c7fd054b60feba690aee653b84b5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Mar 2017 19:58:02 +0000 Subject: [PATCH 014/239] Move buffer inside player thread --- player.c | 64 +++++++++++++++++++++++++------------------------------- player.h | 18 +++++++++++++--- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/player.c b/player.c index 77092228..aa782931 100644 --- a/player.c +++ b/player.c @@ -113,20 +113,13 @@ static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; // default buffer size // needs to be a power of 2 because of the way BUFIDX(seqno) works -#define BUFFER_FRAMES 512 +//#define BUFFER_FRAMES 512 #define MAX_PACKET 2048 // DAC buffer occupancy stuff #define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 600 -typedef struct audio_buffer_entry { // decoded audio packets - int ready; - int64_t timestamp; - seq_t sequence_number; - signed short *data; - int length; // the length of the decoded data -} abuf_t; -static abuf_t audio_buffer[BUFFER_FRAMES]; +//static abuf_t audio_buffer[BUFFER_FRAMES]; #define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES) // mutex-protected variables @@ -205,11 +198,11 @@ int64_t monotonic_timestamp(uint32_t timestamp) { // add an epoch to the seq_no. Uses the accompanying timestamp to determine the correct epoch uint64_t monotonic_seqno(uint16_t seq_no) {} -static void ab_resync(void) { +static void ab_resync(rtsp_conn_info* conn) { int i; for (i = 0; i < BUFFER_FRAMES; i++) { - audio_buffer[i].ready = 0; - audio_buffer[i].sequence_number = 0; + conn->audio_buffer[i].ready = 0; + conn->audio_buffer[i].sequence_number = 0; } ab_synced = 0; last_seqno_read = -1; @@ -495,28 +488,28 @@ static void terminate_decoders(void) { #endif } -static void init_buffer(void) { +static void init_buffer(rtsp_conn_info* conn) { int i; for (i = 0; i < BUFFER_FRAMES; i++) - audio_buffer[i].data = + conn->audio_buffer[i].data = malloc(input_bytes_per_frame * (max_frames_per_packet + max_frame_size_change)); - ab_resync(); + ab_resync(conn); } -static void free_buffer(void) { +static void free_buffer(rtsp_conn_info* conn) { int i; for (i = 0; i < BUFFER_FRAMES; i++) - free(audio_buffer[i].data); + free(conn->audio_buffer[i].data); } -void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info *itr) { +void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info *conn) { // all timestamps are done at the output rate int64_t ltimestamp = timestamp * output_sample_ratio; // ignore a request to flush that has been made before the first packet... - if (itr->packet_count == 0) { + if (conn->packet_count == 0) { pthread_mutex_lock(&flush_mutex); flush_requested = 0; flush_rtp_timestamp = 0; @@ -524,7 +517,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r } pthread_mutex_lock(&ab_mutex); - itr->packet_count++; + conn->packet_count++; time_of_last_audio_packet = get_absolute_time_in_fp(); if (connection_state_to_output) { // if we are supposed to be processing these packets @@ -550,7 +543,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r ab_synced = 1; } if (ab_write == seqno) { // expected packet - abuf = audio_buffer + BUFIDX(seqno); + abuf = conn->audio_buffer + BUFIDX(seqno); ab_write = SUCCESSOR(seqno); } else if (seq_order(ab_write, seqno)) { // newer than expected // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8) @@ -561,19 +554,19 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r debug(1, "Unexpected gap size: %d.", gap); int i; for (i = 0; i < gap; i++) { - abuf = audio_buffer + BUFIDX(seq_sum(ab_write, i)); + abuf = conn->audio_buffer + BUFIDX(seq_sum(ab_write, i)); abuf->ready = 0; // to be sure, to be sure abuf->timestamp = 0; abuf->sequence_number = 0; } // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write); - abuf = audio_buffer + BUFIDX(seqno); + abuf = conn->audio_buffer + BUFIDX(seqno); // rtp_request_resend(ab_write, gap); // resend_requests++; ab_write = SUCCESSOR(seqno); } else if (seq_order(ab_read, seqno)) { // late but not yet played late_packets++; - abuf = audio_buffer + BUFIDX(seqno); + abuf = conn->audio_buffer + BUFIDX(seqno); } else { // too late. too_late_packets++; /* @@ -588,7 +581,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r if (abuf) { int datalen = max_frames_per_packet; - if (alac_decode(abuf->data, &datalen, data, len, itr) == 0) { + if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) { abuf->ready = 1; abuf->length = datalen; abuf->timestamp = ltimestamp; @@ -776,7 +769,7 @@ static inline short dithered_vol(short sample) { } // get the next frame, when available. return 0 if underrun/stream reset. -static abuf_t *buffer_get_frame(void) { +static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { int16_t buf_fill; uint64_t local_time_now; // struct timespec tn; @@ -823,7 +816,7 @@ static abuf_t *buffer_get_frame(void) { if (flush_requested == 1) { if (config.output->flush) config.output->flush(); - ab_resync(); + ab_resync(conn); first_packet_timestamp = 0; first_packet_time_to_play = 0; time_since_play_started = 0; @@ -834,7 +827,7 @@ static abuf_t *buffer_get_frame(void) { uint32_t flush_limit = 0; if (ab_synced) { do { - curframe = audio_buffer + BUFIDX(ab_read); + curframe = conn->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 @@ -869,7 +862,7 @@ static abuf_t *buffer_get_frame(void) { flush_limit = 0; } - curframe = audio_buffer + BUFIDX(ab_read); + curframe = conn->audio_buffer + BUFIDX(ab_read); if (curframe->ready) { notified_buffer_empty = 0; // at least one buffer now -- diagnostic only. @@ -968,7 +961,7 @@ static abuf_t *buffer_get_frame(void) { if (config.output->flush) config.output->flush(); - ab_resync(); + ab_resync(conn); first_packet_timestamp = 0; first_packet_time_to_play = 0; time_since_play_started = 0; @@ -993,7 +986,7 @@ static abuf_t *buffer_get_frame(void) { // ab_write)); if (config.output->flush) config.output->flush(); - ab_resync(); + ab_resync(conn); first_packet_timestamp = 0; first_packet_time_to_play = 0; } else { @@ -1154,7 +1147,7 @@ static abuf_t *buffer_get_frame(void) { if (!ab_buffering) { for (i = 8; i < (seq_diff(ab_read, ab_write) / 2); i = (i * 2)) { seq_t next = seq_sum(ab_read, i); - abuf = audio_buffer + BUFIDX(next); + abuf = conn->audio_buffer + BUFIDX(next); if (!abuf->ready) { rtp_request_resend(next, 1); // debug(1,"Resend %u.",next); @@ -1521,6 +1514,8 @@ static void *player_thread_func(void *arg) { output_bytes_per_frame = 8; break; } + + init_buffer(conn); debug(1, "Output frame bytes is %d.", output_bytes_per_frame); @@ -1691,7 +1686,7 @@ static void *player_thread_func(void *arg) { uint64_t tens_of_seconds = 0; while (!please_stop) { - abuf_t *inframe = buffer_get_frame(); + abuf_t *inframe = buffer_get_frame(conn); if (inframe) { inbuf = inframe->data; inbuflength = inframe->length; @@ -2218,6 +2213,7 @@ static void *player_thread_func(void *arg) { debug(1, "audio thread joined"); pthread_join(rtp_control_thread, NULL); debug(1, "control thread joined"); + free_buffer(conn); debug(1, "Player thread exit"); return 0; } @@ -2437,7 +2433,6 @@ int player_play(pthread_t *player_thread, rtsp_conn_info *conn) { BUFFER_FRAMES); init_decoder((int32_t *)&conn->stream.fmtp); // this sets up incoming rate, bit depth, channels // must be after decoder init - init_buffer(); please_stop = 0; command_start(); #ifdef CONFIG_METADATA @@ -2479,7 +2474,6 @@ void player_stop(pthread_t *player_thread, rtsp_conn_info *conn) { send_ssnc_metadata('pend', NULL, 0, 1); #endif command_stop(); - free_buffer(); terminate_decoders(); int rc = pthread_cond_destroy(&flowcontrol); if (rc) diff --git a/player.h b/player.h index 8ffd153b..9564d0bd 100644 --- a/player.h +++ b/player.h @@ -19,6 +19,20 @@ #include "audio.h" +typedef uint16_t seq_t; + +typedef struct audio_buffer_entry { // decoded audio packets + int ready; + int64_t timestamp; + seq_t sequence_number; + signed short *data; + int length; // the length of the decoded data +} abuf_t; + +// default buffer size +// needs to be a power of 2 because of the way BUFIDX(seqno) works +#define BUFFER_FRAMES 512 + typedef struct { int encrypted; uint8_t aesiv[16], aeskey[16]; @@ -35,6 +49,7 @@ typedef struct { pthread_t thread; pthread_t player_thread; + abuf_t audio_buffer[BUFFER_FRAMES]; uint32_t please_stop; uint64_t packet_count; #ifdef HAVE_LIBMBEDTLS @@ -50,9 +65,6 @@ typedef struct { #endif } rtsp_conn_info; - -typedef uint16_t seq_t; - // wrapped number between two seq_t. int32_t seq_diff(seq_t a, seq_t b); From ea072ec2490ab1565004ca3bbc29e1abd5573151 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 8 Mar 2017 20:13:10 +0000 Subject: [PATCH 015/239] Move init and terminate decoder calls inside the player thread -- still using globals though --- player.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index aa782931..bec24faa 100644 --- a/player.c +++ b/player.c @@ -1515,6 +1515,8 @@ static void *player_thread_func(void *arg) { break; } + init_decoder((int32_t *)&conn->stream.fmtp); // this sets up incoming rate, bit depth, channels + // must be after decoder init init_buffer(conn); debug(1, "Output frame bytes is %d.", output_bytes_per_frame); @@ -2214,6 +2216,7 @@ static void *player_thread_func(void *arg) { pthread_join(rtp_control_thread, NULL); debug(1, "control thread joined"); free_buffer(conn); + terminate_decoders(); debug(1, "Player thread exit"); return 0; } @@ -2431,8 +2434,6 @@ int player_play(pthread_t *player_thread, rtsp_conn_info *conn) { if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); - init_decoder((int32_t *)&conn->stream.fmtp); // this sets up incoming rate, bit depth, channels - // must be after decoder init please_stop = 0; command_start(); #ifdef CONFIG_METADATA @@ -2474,7 +2475,6 @@ void player_stop(pthread_t *player_thread, rtsp_conn_info *conn) { send_ssnc_metadata('pend', NULL, 0, 1); #endif command_stop(); - terminate_decoders(); int rc = pthread_cond_destroy(&flowcontrol); if (rc) debug(1, "Error destroying condition variable."); From d90fd0df500d571f37bc9d4b3f14b13ef8896669 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 9 Mar 2017 14:08:06 +0000 Subject: [PATCH 016/239] Change the order of initialisation back to waht it was before de-globalising the init_decoder, etc. --- player.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/player.c b/player.c index bec24faa..3b96f954 100644 --- a/player.c +++ b/player.c @@ -1473,6 +1473,12 @@ static void *player_thread_func(void *arg) { conn->please_stop = 0; conn->packet_count = 0; + init_decoder((int32_t *)&conn->stream.fmtp); // this sets up incoming rate, bit depth, channels + // must be after decoder init + init_buffer(conn); + + debug(1, "Output frame bytes is %d.", output_bytes_per_frame); + if (conn->stream.encrypted) { #ifdef HAVE_LIBMBEDTLS memset(&conn->dctx, 0, sizeof(mbedtls_aes_context)); @@ -1515,12 +1521,6 @@ static void *player_thread_func(void *arg) { break; } - init_decoder((int32_t *)&conn->stream.fmtp); // this sets up incoming rate, bit depth, channels - // must be after decoder init - init_buffer(conn); - - 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 *)conn); From cae9337dc49331275adc98be9761db0933ec0ed4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 9 Mar 2017 14:26:28 +0000 Subject: [PATCH 017/239] De-globalise max_frames_per_packet,input_num_channels,input_bit_depth,input_rate --- player.c | 54 ++++++++++++++++++++++++++---------------------------- player.h | 1 + 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/player.c b/player.c index 3b96f954..455ad466 100644 --- a/player.c +++ b/player.c @@ -76,8 +76,6 @@ #include "apple_alac.h" #endif -static int input_rate, input_bit_depth, input_num_channels, max_frames_per_packet; - static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 // or 1. @@ -367,7 +365,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co return reply; } -static int init_decoder(int32_t fmtp[12]) { +static int init_decoder(int32_t fmtp[12],rtsp_conn_info* conn) { // This is a guess, but the format of the fmtp looks identical to the format of an // ALACSpecificCOnfig @@ -448,22 +446,22 @@ static int init_decoder(int32_t fmtp[12]) { alac_file *alac; - max_frames_per_packet = fmtp[1]; // number of audio frames per packet. + conn->max_frames_per_packet = fmtp[1]; // number of audio frames per packet. - input_rate = fmtp[11]; - input_num_channels = fmtp[7]; - input_bit_depth = fmtp[3]; + conn->input_rate = fmtp[11]; + conn->input_num_channels = fmtp[7]; + conn->input_bit_depth = fmtp[3]; - input_bytes_per_frame = input_num_channels * ((input_bit_depth + 7) / 8); + input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - alac = alac_create(input_bit_depth, input_num_channels); + alac = alac_create(conn->input_bit_depth, conn->input_num_channels); if (!alac) return 1; decoder_info = alac; - alac->setinfo_max_samples_per_frame = max_frames_per_packet; + alac->setinfo_max_samples_per_frame = conn->max_frames_per_packet; alac->setinfo_7a = fmtp[2]; - alac->setinfo_sample_size = input_bit_depth; + alac->setinfo_sample_size = conn->input_bit_depth; alac->setinfo_rice_historymult = fmtp[4]; alac->setinfo_rice_initialhistory = fmtp[5]; alac->setinfo_rice_kmodifier = fmtp[6]; @@ -492,7 +490,7 @@ static void init_buffer(rtsp_conn_info* conn) { int i; for (i = 0; i < BUFFER_FRAMES; i++) conn->audio_buffer[i].data = - malloc(input_bytes_per_frame * (max_frames_per_packet + max_frame_size_change)); + malloc(input_bytes_per_frame * (conn->max_frames_per_packet + max_frame_size_change)); ab_resync(conn); } @@ -580,7 +578,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r // pthread_mutex_unlock(&ab_mutex); if (abuf) { - int datalen = max_frames_per_packet; + int datalen = conn->max_frames_per_packet; if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) { abuf->ready = 1; abuf->length = datalen; @@ -998,7 +996,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { max_dac_delay, dac_delay); fs = 0; } - if ((exact_frame_gap <= fs) || (exact_frame_gap <= max_frames_per_packet * 2)) { + if ((exact_frame_gap <= fs) || (exact_frame_gap <= conn->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 @@ -1105,7 +1103,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { if (wait) { uint64_t time_to_wait_for_wakeup_fp = - ((uint64_t)1 << 32) / input_rate; // this is time period of one frame + ((uint64_t)1 << 32) / conn->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 @@ -1473,7 +1471,7 @@ static void *player_thread_func(void *arg) { conn->please_stop = 0; conn->packet_count = 0; - init_decoder((int32_t *)&conn->stream.fmtp); // this sets up incoming rate, bit depth, channels + init_decoder((int32_t *)&conn->stream.fmtp,conn); // this sets up incoming rate, bit depth, channels // must be after decoder init init_buffer(conn); @@ -1496,11 +1494,11 @@ static void *player_thread_func(void *arg) { } 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 + maximum_timestamp_interval = conn->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; + output_sample_ratio = config.output_rate / conn->input_rate; debug(1, "Output sample ratio is %d.", output_sample_ratio); @@ -1532,7 +1530,7 @@ static void *player_thread_func(void *arg) { // 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; + int maximum_latency = config.latency + config.audio_backend_latency_offset * conn->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.", @@ -1599,7 +1597,7 @@ static void *player_thread_func(void *arg) { debug(1, "Output bit depth is %d.", output_bit_depth); - if (input_bit_depth > output_bit_depth) { + if (conn->input_bit_depth > output_bit_depth) { debug(1, "Dithering will be enabled because the input bit depth is greater than the output bit " "depth"); } @@ -1616,13 +1614,13 @@ static void *player_thread_func(void *arg) { // 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)); + (conn->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)); + (conn->max_frames_per_packet * output_sample_ratio + max_frame_size_change)); if (sbuf == NULL) debug(1, "Failed to allocate memory for the transition buffer."); } @@ -1634,13 +1632,13 @@ static void *player_thread_func(void *arg) { // 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)); + (conn->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); + silence = malloc(output_bytes_per_frame * conn->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); + memset(silence, 0, output_bytes_per_frame * conn->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; @@ -1698,17 +1696,17 @@ static void *player_thread_func(void *arg) { // debug(1,"Player has a supplied silent frame."); last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); // manage the packet out of sequence minder - config.output->play(silence, max_frames_per_packet * output_sample_ratio); + config.output->play(silence, conn->max_frames_per_packet * output_sample_ratio); } else { int enable_dither = 0; - if ((fix_volume != 0x10000) || (input_bit_depth > output_bit_depth)) + if ((fix_volume != 0x10000) || (conn->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) { + switch (conn->input_bit_depth) { case 16: { int i, j; int16_t ls, rs; diff --git a/player.h b/player.h index 9564d0bd..81922f35 100644 --- a/player.h +++ b/player.h @@ -50,6 +50,7 @@ typedef struct { pthread_t player_thread; abuf_t audio_buffer[BUFFER_FRAMES]; + int max_frames_per_packet,input_num_channels,input_bit_depth,input_rate; uint32_t please_stop; uint64_t packet_count; #ifdef HAVE_LIBMBEDTLS From 600cebacd4ae2d8fdffa8c5499067dff517c6901 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 9 Mar 2017 14:43:41 +0000 Subject: [PATCH 018/239] De-globalise previous_random_number --- player.c | 218 ++++++++++--------------------------------------------- player.h | 1 + 2 files changed, 39 insertions(+), 180 deletions(-) diff --git a/player.c b/player.c index 455ad466..7ef8b0d0 100644 --- a/player.c +++ b/player.c @@ -84,7 +84,6 @@ static int input_bytes_per_frame = 4; static int output_bytes_per_frame; static int output_sample_ratio; static int output_rate; -static int64_t previous_random_number = 0; // The maximum frame size change there can be is +/- 1; static int max_frame_size_change; @@ -612,7 +611,7 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { } static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume, - int dither) { + int dither,rtsp_conn_info *conn) { int64_t hyper_sample = sample; int result; int64_t hyper_volume = (int64_t)volume << 16; @@ -659,8 +658,8 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t // 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; + int64_t tpdf = (r & dither_mask) - (conn->previous_random_number & dither_mask); + conn->previous_random_number = r; // add dither, allowing for clipping if (tpdf >= 0) { if (INT64_MAX - tpdf >= hyper_sample) @@ -1194,7 +1193,7 @@ static inline int32_t mean_32(int32_t a, int32_t b) { // stuff: 1 means add 1; 0 means do nothing; -1 means remove 1 static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format, - char *outptr, int stuff, int dither) { + char *outptr, int stuff, int dither,rtsp_conn_info *conn) { int tstuff = stuff; char *l_outptr = outptr; if ((stuff > 1) || (stuff < -1) || (length < 100)) { @@ -1211,15 +1210,15 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l 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); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither,conn); }; 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); + process_sample(mean_32(inptr[-2], inptr[0]), &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(mean_32(inptr[-1], inptr[1]), &l_outptr, l_output_format, fix_volume, dither,conn); } else if (stuff == -1) { // debug(3, "---------"); inptr++; @@ -1233,8 +1232,8 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l 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); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither,conn); } } pthread_mutex_unlock(&vol_mutex); @@ -1242,53 +1241,6 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l 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 (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 - *outptr++ = dithered_vol(*inptr++); - *outptr++ = dithered_vol(*inptr++); - }; - if (tstuff) { - if (tstuff == 1) { - // debug(3, "+++++++++"); - // interpolate one sample - //*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1); - //*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1); - *outptr++ = dithered_vol(shortmean(inptr[-2], inptr[0])); - *outptr++ = dithered_vol(shortmean(inptr[-1], inptr[1])); - } 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++) { - *outptr++ = dithered_vol(*inptr++); - *outptr++ = dithered_vol(*inptr++); - } - } - pthread_mutex_unlock(&vol_mutex); - - return length + tstuff; -} #ifdef HAVE_LIBSOXR // this takes an array of signed 32-bit integers and @@ -1302,7 +1254,7 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff 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) { + int dither,rtsp_conn_info *conn) { if (scratchBuffer == NULL) { die("soxr scratchBuffer not initialised."); } @@ -1359,8 +1311,8 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng 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); + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); //*op = dithered_vol(*op); // op++; //*op = dithered_vol(*op); @@ -1375,89 +1327,14 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng 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); + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); //*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 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 (tstuff) { - // debug(1,"Stuff %d.",stuff); - soxr_io_spec_t io_spec; - io_spec.itype = SOXR_INT16_I; - io_spec.otype = SOXR_INT16_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. */ - outptr, 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); - - 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 = outptr + (length + tstuff - gpm) * sizeof(short); - ip = inptr + (length - gpm) * sizeof(short); - for (i = 0; i < gpm; i++) { - *op++ = *ip++; - *op++ = *ip++; - } - - // finally, adjust the volume, if necessary - if (fix_volume != 65536.0) { - // pthread_mutex_lock(&vol_mutex); - op = outptr; - for (i = 0; i < length + tstuff; i++) { - *op = dithered_vol(*op); - op++; - *op = dithered_vol(*op); - op++; - }; - // pthread_mutex_unlock(&vol_mutex); - } - - } else { // the whole frame, if no stuffing - - // pthread_mutex_lock(&vol_mutex); - for (i = 0; i < length; i++) { - *op++ = dithered_vol(*ip++); - *op++ = dithered_vol(*ip++); - }; - // pthread_mutex_unlock(&vol_mutex); - } - return length + tstuff; -} #endif typedef struct stats { // statistics for running averages @@ -1470,6 +1347,8 @@ static void *player_thread_func(void *arg) { conn->please_stop = 0; conn->packet_count = 0; + conn->previous_random_number = 0; + init_decoder((int32_t *)&conn->stream.fmtp,conn); // this sets up incoming rate, bit depth, channels // must be after decoder init @@ -1605,28 +1484,22 @@ static void *player_thread_func(void *arg) { 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 * - (conn->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 * - (conn->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 need an intermediate "transition" buffer + + // 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 * + (conn->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 * + (conn->max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + if (sbuf == NULL) + debug(1, "Failed to allocate memory for the transition buffer."); + } // 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 @@ -1942,34 +1815,22 @@ static void *player_thread_func(void *arg) { 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); + outbuf, amount_to_stuff, enable_dither, conn); 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); + enable_dither,conn); break; } #else // 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); + amount_to_stuff, enable_dither,conn); #endif /* @@ -2036,12 +1897,9 @@ static void *player_thread_func(void *arg) { config.output->play(inbuf, inbuflength); } else { - 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 + (int32_t *)tbuf, inbuflength, config.output_format, outbuf, 0, enable_dither,conn); if (outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); diff --git a/player.h b/player.h index 81922f35..b257ac30 100644 --- a/player.h +++ b/player.h @@ -51,6 +51,7 @@ typedef struct { abuf_t audio_buffer[BUFFER_FRAMES]; int max_frames_per_packet,input_num_channels,input_bit_depth,input_rate; + int64_t previous_random_number; uint32_t please_stop; uint64_t packet_count; #ifdef HAVE_LIBMBEDTLS From 05305e15816c15f3474e5387dcf70e4b035ababc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 9 Mar 2017 16:10:39 +0000 Subject: [PATCH 019/239] De-globalise input_bytes_per_frame,output_bytes_per_frame,output_sample_ratio,max_frame_size_change,decoder_info,shutdown_requested,connection_state_to_output,player_thread_please_stop --- player.c | 182 ++++++++++++++++++------------------------------------- player.h | 8 +++ rtp.c | 2 +- 3 files changed, 68 insertions(+), 124 deletions(-) diff --git a/player.c b/player.c index 7ef8b0d0..6a92585d 100644 --- a/player.c +++ b/player.c @@ -80,27 +80,13 @@ static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 // or 1. -static int input_bytes_per_frame = 4; -static int output_bytes_per_frame; -static int output_sample_ratio; -static int output_rate; - -// The maximum frame size change there can be is +/- 1; -static int max_frame_size_change; // #define FRAME_BYTES(max_frames_per_packet) (4 * max_frames_per_packet) // maximal resampling shift - conservative //#define OUTFRAME_BYTES(max_frames_per_packet) (4 * (max_frames_per_packet + 3)) // static pthread_t player_thread = NULL; -static int please_stop; - -static int - connection_state_to_output; // if true, then play incoming stuff; if false drop everything - -static alac_file *decoder_info; // debug variables -static int late_packet_message_sent; static int32_t last_seqno_read; static int decoder_in_use = 0; @@ -126,7 +112,6 @@ static int64_t first_packet_timestamp = 0; static int flush_requested = 0; static int64_t flush_rtp_timestamp; static uint64_t time_of_last_audio_packet; -static int shutdown_requested; // mutexes and condition variables static pthread_mutex_t ab_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -294,7 +279,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co unsigned char packetp[MAX_PACKET]; assert(len <= MAX_PACKET); int reply = 0; // everything okay - int outsize = input_bytes_per_frame * (*destlen); // the size the output should be, in bytes + int outsize = conn->input_bytes_per_frame * (*destlen); // the size the output should be, in bytes int toutsize = outsize; if (conn->stream.encrypted) { @@ -326,7 +311,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co debug(1, "Hammerton Decoder used on encrypted audio."); decoder_in_use = 1 << decoder_hammerton; } - alac_decode_frame(decoder_info, packet, (unsigned char *)dest, &outsize); + alac_decode_frame(conn->decoder_info, packet, (unsigned char *)dest, &outsize); } } else { // not encrypted @@ -345,7 +330,7 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co debug(1, "Hammerton Decoder used on unencrypted audio."); decoder_in_use = 1 << decoder_hammerton; } - alac_decode_frame(decoder_info, buf, dest, &outsize); + alac_decode_frame(conn->decoder_info, buf, dest, &outsize); } } @@ -356,11 +341,11 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co reply = -1; // output packet is the wrong size } - *destlen = outsize / input_bytes_per_frame; - if ((outsize % input_bytes_per_frame) != 0) + *destlen = outsize / conn->input_bytes_per_frame; + if ((outsize % conn->input_bytes_per_frame) != 0) debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " "and the audio frame size (%d).", - *destlen, outsize, input_bytes_per_frame); + *destlen, outsize, conn->input_bytes_per_frame); return reply; } @@ -451,12 +436,12 @@ static int init_decoder(int32_t fmtp[12],rtsp_conn_info* conn) { conn->input_num_channels = fmtp[7]; conn->input_bit_depth = fmtp[3]; - input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); + conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); alac = alac_create(conn->input_bit_depth, conn->input_num_channels); if (!alac) return 1; - decoder_info = alac; + conn->decoder_info = alac; alac->setinfo_max_samples_per_frame = conn->max_frames_per_packet; alac->setinfo_7a = fmtp[2]; @@ -478,8 +463,8 @@ static int init_decoder(int32_t fmtp[12],rtsp_conn_info* conn) { return 0; } -static void terminate_decoders(void) { - alac_free(decoder_info); +static void terminate_decoders(rtsp_conn_info* conn) { + alac_free(conn->decoder_info); #ifdef HAVE_APPLE_ALAC apple_alac_terminate(); #endif @@ -489,7 +474,7 @@ static void init_buffer(rtsp_conn_info* conn) { int i; for (i = 0; i < BUFFER_FRAMES; i++) conn->audio_buffer[i].data = - malloc(input_bytes_per_frame * (conn->max_frames_per_packet + max_frame_size_change)); + malloc(conn->input_bytes_per_frame * (conn->max_frames_per_packet + conn->max_frame_size_change)); ab_resync(conn); } @@ -503,7 +488,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r // all timestamps are done at the output rate - int64_t ltimestamp = timestamp * output_sample_ratio; + int64_t ltimestamp = timestamp * conn->output_sample_ratio; // ignore a request to flush that has been made before the first packet... if (conn->packet_count == 0) { @@ -516,7 +501,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r pthread_mutex_lock(&ab_mutex); conn->packet_count++; time_of_last_audio_packet = get_absolute_time_in_fp(); - if (connection_state_to_output) { // if we are supposed to be processing these packets + if (conn->connection_state_to_output) { // if we are supposed to be processing these packets // if (flush_rtp_timestamp != 0) // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); @@ -566,13 +551,6 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r abuf = conn->audio_buffer + BUFIDX(seqno); } else { // too late. too_late_packets++; - /* - if (!late_packet_message_sent) { - debug(1, "too-late packet received: %u; ab_read: %u; ab_write: %u.", seqno, ab_read, - ab_write); - late_packet_message_sent=1; - } - */ } // pthread_mutex_unlock(&ab_mutex); @@ -730,41 +708,6 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t *outp += result; } -static inline short dithered_vol(short sample) { - long out; - - 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; - } else { - if (LONG_MIN - tpdf <= out) - out += tpdf; - else - out = LONG_MIN; - } - } - return out >> 16; -} - // get the next frame, when available. return 0 if underrun/stream reset. static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { int16_t buf_fill; @@ -787,22 +730,22 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // config.timeout of zero means don't check..., but iTunes may be confused by a long gap // followed by a resumption... - if ((time_of_last_audio_packet != 0) && (shutdown_requested == 0) && + if ((time_of_last_audio_packet != 0) && (conn->shutdown_requested == 0) && (config.dont_check_timeout == 0)) { uint64_t ct = config.timeout; // go from int to 64-bit int if ((local_time_now > time_of_last_audio_packet) && (local_time_now - time_of_last_audio_packet >= ct << 32)) { debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\""); rtsp_request_shutdown_stream(); - shutdown_requested = 1; + conn->shutdown_requested = 1; } } int rco = get_requested_connection_state_to_output(); - if (connection_state_to_output != rco) { - connection_state_to_output = rco; + if (conn->connection_state_to_output != rco) { + conn->connection_state_to_output = rco; // change happening - if (connection_state_to_output == 0) { // going off + if (conn->connection_state_to_output == 0) { // going off pthread_mutex_lock(&flush_mutex); flush_requested = 1; pthread_mutex_unlock(&flush_mutex); @@ -870,7 +813,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { 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; + reference_timestamp *= conn->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)); @@ -903,10 +846,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e. // -4410 frames. - debug(1, "Output sample ratio is %d", output_sample_ratio); + debug(1, "Output sample ratio is %d", conn->output_sample_ratio); int64_t delta = (first_packet_timestamp - reference_timestamp) + - config.latency * output_sample_ratio + + config.latency * conn->output_sample_ratio + config.audio_backend_latency_offset * config.output_rate; if (delta >= 0) { @@ -924,7 +867,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { debug( 1, "First packet is late! It should have played before now. Flushing 0.1 seconds"); - player_flush(first_packet_timestamp + 4410 * output_sample_ratio); + player_flush(first_packet_timestamp + 4410 * conn->output_sample_ratio); } } } @@ -932,7 +875,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { if (first_packet_time_to_play != 0) { // recalculate first_packet_time_to_play -- the latency might change int64_t delta = (first_packet_timestamp - reference_timestamp) + - config.latency * output_sample_ratio + + config.latency * conn->output_sample_ratio + config.audio_backend_latency_offset * config.output_rate; if (delta >= 0) { @@ -1011,11 +954,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // 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); + silence = malloc(conn->output_bytes_per_frame * fs); if (silence == NULL) debug(1, "Failed to allocate %d byte silence buffer.", fs); else { - memset(silence, 0, output_bytes_per_frame * fs); + memset(silence, 0, conn->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); @@ -1031,7 +974,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { 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); - play_segment_reference_frame *= output_sample_ratio; + play_segment_reference_frame *= conn->output_sample_ratio; #ifdef CONFIG_METADATA send_ssnc_metadata('prsm', NULL, 0, 0); // "resume", but don't wait if the queue is locked @@ -1061,12 +1004,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { 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; + reference_timestamp *= conn->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.latency * conn->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 @@ -1098,7 +1041,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { } do_wait = 1; } - wait = (ab_buffering || (do_wait != 0) || (!ab_synced)) && (!please_stop); + wait = (ab_buffering || (do_wait != 0) || (!ab_synced)) && (!conn->player_thread_please_stop); if (wait) { uint64_t time_to_wait_for_wakeup_fp = @@ -1131,7 +1074,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { } } while (wait); - if (please_stop) { + if (conn->player_thread_please_stop) { pthread_mutex_unlock(&ab_mutex); return 0; } @@ -1313,10 +1256,6 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng for (i = 0; i < length + tstuff; i++) { process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); - //*op = dithered_vol(*op); - // op++; - //*op = dithered_vol(*op); - // op++; }; } else { // the whole frame, if no stuffing @@ -1329,8 +1268,6 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng for (i = 0; i < length; i++) { process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); - //*op++ = dithered_vol(*ip++); - //*op++ = dithered_vol(*ip++); }; } return length + tstuff; @@ -1348,13 +1285,14 @@ static void *player_thread_func(void *arg) { conn->please_stop = 0; conn->packet_count = 0; conn->previous_random_number = 0; - + conn->input_bytes_per_frame = 4; + conn->player_thread_please_stop = 0; init_decoder((int32_t *)&conn->stream.fmtp,conn); // this sets up incoming rate, bit depth, channels // must be after decoder init init_buffer(conn); - debug(1, "Output frame bytes is %d.", output_bytes_per_frame); + debug(1, "Output frame bytes is %d.", conn->output_bytes_per_frame); if (conn->stream.encrypted) { #ifdef HAVE_LIBMBEDTLS @@ -1377,24 +1315,24 @@ static void *player_thread_func(void *arg) { // seconds of a gap between successive rtptimes, at // worst - output_sample_ratio = config.output_rate / conn->input_rate; + conn->output_sample_ratio = config.output_rate / conn->input_rate; - debug(1, "Output sample ratio is %d.", output_sample_ratio); + debug(1, "Output sample ratio is %d.", conn->output_sample_ratio); - max_frame_size_change = 1 * output_sample_ratio; // we add or subtract one frame at the nominal + conn->max_frame_size_change = 1 * conn->output_sample_ratio; // we add or subtract one frame at the nominal // rate, multiply it by the frame ratio. - output_bytes_per_frame = 4; + conn->output_bytes_per_frame = 4; switch (config.output_format) { case SPS_FORMAT_S24_3LE: case SPS_FORMAT_S24_3BE: - output_bytes_per_frame = 6; + conn->output_bytes_per_frame = 6; break; case SPS_FORMAT_S24: - output_bytes_per_frame = 8; + conn->output_bytes_per_frame = 8; break; case SPS_FORMAT_S32: - output_bytes_per_frame = 8; + conn->output_bytes_per_frame = 8; break; } @@ -1414,7 +1352,7 @@ static void *player_thread_func(void *arg) { 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(); + conn->connection_state_to_output = get_requested_connection_state_to_output(); // this is about half a minute #define trend_interval 3758 stats_t statistics[trend_interval]; @@ -1435,7 +1373,7 @@ static void *player_thread_func(void *arg) { int64_t current_delay; int play_number = 0; time_of_last_audio_packet = 0; - shutdown_requested = 0; + conn->shutdown_requested = 0; number_of_statistics = oldest_statistic = newest_statistic = 0; tsum_of_sync_errors = tsum_of_corrections = tsum_of_insertions_and_deletions = tsum_of_drifts = 0; @@ -1490,13 +1428,13 @@ static void *player_thread_func(void *arg) { // 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 * - (conn->max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->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 * - (conn->max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (sbuf == NULL) debug(1, "Failed to allocate memory for the transition buffer."); } @@ -1504,15 +1442,14 @@ static void *player_thread_func(void *arg) { // 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 * - (conn->max_frames_per_packet * output_sample_ratio + max_frame_size_change)); + outbuf = malloc(conn->output_bytes_per_frame * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (outbuf == NULL) debug(1, "Failed to allocate memory for an output buffer."); - silence = malloc(output_bytes_per_frame * conn->max_frames_per_packet * output_sample_ratio); + silence = malloc(conn->output_bytes_per_frame * conn->max_frames_per_packet * conn->output_sample_ratio); if (silence == NULL) debug(1, "Failed to allocate memory for a silence buffer."); - memset(silence, 0, output_bytes_per_frame * conn->max_frames_per_packet * output_sample_ratio); - late_packet_message_sent = 0; + memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * conn->output_sample_ratio); 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 @@ -1558,7 +1495,7 @@ static void *player_thread_func(void *arg) { } uint64_t tens_of_seconds = 0; - while (!please_stop) { + while (!conn->player_thread_please_stop) { abuf_t *inframe = buffer_get_frame(conn); if (inframe) { inbuf = inframe->data; @@ -1569,7 +1506,7 @@ static void *player_thread_func(void *arg) { // debug(1,"Player has a supplied silent frame."); last_seqno_read = (SUCCESSOR(last_seqno_read) & 0xffff); // manage the packet out of sequence minder - config.output->play(silence, conn->max_frames_per_packet * output_sample_ratio); + config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); } else { int enable_dither = 0; @@ -1620,7 +1557,7 @@ static void *player_thread_func(void *arg) { // here, replicate the samples if you're upsampling - for (j = 0; j < output_sample_ratio; j++) { + for (j = 0; j < conn->output_sample_ratio; j++) { // raise the 16-bit sample to 32 bits. int32_t t = ls << 16; *outpl++ = t; @@ -1656,7 +1593,7 @@ static void *player_thread_func(void *arg) { } // inbuf = tbuf; - inbuflength *= output_sample_ratio; + inbuflength *= conn->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 @@ -1672,7 +1609,7 @@ static void *player_thread_func(void *arg) { 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; + reference_timestamp *= conn->output_sample_ratio; int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t nt = inframe->timestamp; // uint32_t to int64_t @@ -1766,7 +1703,7 @@ static void *player_thread_func(void *arg) { // This is the timing error for the next audio frame in the DAC. sync_error = delay - - config.latency * output_sample_ratio; // int64_t from int64_t - int32_t, so okay + config.latency * conn->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); @@ -1865,7 +1802,7 @@ static void *player_thread_func(void *arg) { if (abs_sync_error < 0) abs_sync_error = -abs_sync_error; - if ((config.no_sync == 0) && (inframe->timestamp != 0) && (!please_stop) && + if ((config.no_sync == 0) && (inframe->timestamp != 0) && (!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) && (abs_sync_error > config.resyncthreshold * config.output_rate)) { sync_error_out_of_bounds++; @@ -1992,9 +1929,9 @@ static void *player_thread_func(void *arg) { "%*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_correction * 1000000 / (352 * conn->output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / - (352 * output_sample_ratio), + (352 * conn->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); @@ -2072,7 +2009,7 @@ static void *player_thread_func(void *arg) { pthread_join(rtp_control_thread, NULL); debug(1, "control thread joined"); free_buffer(conn); - terminate_decoders(); + terminate_decoders(conn); debug(1, "Player thread exit"); return 0; } @@ -2290,7 +2227,6 @@ int player_play(pthread_t *player_thread, rtsp_conn_info *conn) { if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); - please_stop = 0; command_start(); #ifdef CONFIG_METADATA send_ssnc_metadata('pbeg', NULL, 0, 1); @@ -2324,7 +2260,7 @@ void player_stop(pthread_t *player_thread, rtsp_conn_info *conn) { // if (*thread==NULL) // debug(1,"Trying to stop a non-existent player thread"); // else { - please_stop = 1; + conn->player_thread_please_stop = 1; pthread_cond_signal(&flowcontrol); // tell it to give up pthread_join(*player_thread, NULL); #ifdef CONFIG_METADATA diff --git a/player.h b/player.h index b257ac30..fee8d5a0 100644 --- a/player.h +++ b/player.h @@ -18,6 +18,7 @@ #endif #include "audio.h" +#include "alac.h" typedef uint16_t seq_t; @@ -51,9 +52,16 @@ typedef struct { abuf_t audio_buffer[BUFFER_FRAMES]; int max_frames_per_packet,input_num_channels,input_bit_depth,input_rate; + int input_bytes_per_frame,output_bytes_per_frame,output_sample_ratio; + int max_frame_size_change; int64_t previous_random_number; + alac_file *decoder_info; uint32_t please_stop; uint64_t packet_count; + int shutdown_requested; + int connection_state_to_output; + int player_thread_please_stop; + #ifdef HAVE_LIBMBEDTLS mbedtls_aes_context dctx; #endif diff --git a/rtp.c b/rtp.c index 4f17e6e0..04f72b2b 100644 --- a/rtp.c +++ b/rtp.c @@ -129,7 +129,7 @@ void *rtp_audio_receiver(void *arg) { 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 " + debug(3, "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; From c87414237ff02b917e703ca13028e1334643fef9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 9 Mar 2017 16:28:26 +0000 Subject: [PATCH 020/239] de-globalise missing_packets, late_packets, too_late_packets, resend_requests, decoder_in_use, last_seqno_read --- player.c | 43 ++++++++++++++++++++----------------------- player.h | 5 +++++ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/player.c b/player.c index 6a92585d..1d33d6d9 100644 --- a/player.c +++ b/player.c @@ -88,7 +88,6 @@ static uint32_t timestamp_epoch, last_timestamp, // debug variables static int32_t last_seqno_read; -static int decoder_in_use = 0; // interthread variables static int fix_volume = 0x10000; @@ -120,11 +119,6 @@ static pthread_cond_t flowcontrol; static int64_t first_packet_time_to_play, time_since_play_started; // nanoseconds -static audio_parameters audio_information; - -// stats -static uint64_t missing_packets, late_packets, too_late_packets, resend_requests; - // make timestamps and seqnos definitely monotonic // add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 @@ -298,18 +292,18 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co memcpy(packet + aeslen, buf + aeslen, len - aeslen); #ifdef HAVE_APPLE_ALAC if (config.use_apple_decoder) { - if (decoder_in_use != 1 << decoder_apple_alac) { + if (conn->decoder_in_use != 1 << decoder_apple_alac) { debug(1, "Apple ALAC Decoder used on encrypted audio."); - decoder_in_use = 1 << decoder_apple_alac; + conn->decoder_in_use = 1 << decoder_apple_alac; } apple_alac_decode_frame(packet, len, (unsigned char *)dest, &outsize); outsize = outsize * 4; // bring the size to bytes } else #endif { - if (decoder_in_use != 1 << decoder_hammerton) { + if (conn->decoder_in_use != 1 << decoder_hammerton) { debug(1, "Hammerton Decoder used on encrypted audio."); - decoder_in_use = 1 << decoder_hammerton; + conn->decoder_in_use = 1 << decoder_hammerton; } alac_decode_frame(conn->decoder_info, packet, (unsigned char *)dest, &outsize); } @@ -317,18 +311,18 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co // not encrypted #ifdef HAVE_APPLE_ALAC if (config.use_apple_decoder) { - if (decoder_in_use != 1 << decoder_apple_alac) { + if (conn->decoder_in_use != 1 << decoder_apple_alac) { debug(1, "Apple ALAC Decoder used on unencrypted audio."); - decoder_in_use = 1 << decoder_apple_alac; + conn->decoder_in_use = 1 << decoder_apple_alac; } apple_alac_decode_frame(buf, len, (unsigned char *)dest, &outsize); outsize = outsize * 4; // bring the size to bytes } else #endif { - if (decoder_in_use != 1 << decoder_hammerton) { + if (conn->decoder_in_use != 1 << decoder_hammerton) { debug(1, "Hammerton Decoder used on unencrypted audio."); - decoder_in_use = 1 << decoder_hammerton; + conn->decoder_in_use = 1 << decoder_hammerton; } alac_decode_frame(conn->decoder_info, buf, dest, &outsize); } @@ -547,10 +541,10 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r // resend_requests++; ab_write = SUCCESSOR(seqno); } else if (seq_order(ab_read, seqno)) { // late but not yet played - late_packets++; + conn->late_packets++; abuf = conn->audio_buffer + BUFIDX(seqno); } else { // too late. - too_late_packets++; + conn->too_late_packets++; } // pthread_mutex_unlock(&ab_mutex); @@ -1091,14 +1085,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { if (!abuf->ready) { rtp_request_resend(next, 1); // debug(1,"Resend %u.",next); - resend_requests++; + conn->resend_requests++; } } } if (!curframe->ready) { // debug(1, "Supplying a silent frame for frame %u", read); - missing_packets++; + conn->missing_packets++; curframe->timestamp = 0; // indicate a silent frame should be substituted } curframe->ready = 0; @@ -1287,6 +1281,8 @@ static void *player_thread_func(void *arg) { conn->previous_random_number = 0; conn->input_bytes_per_frame = 4; conn->player_thread_please_stop = 0; + conn->decoder_in_use = 0; + init_decoder((int32_t *)&conn->stream.fmtp,conn); // this sets up incoming rate, bit depth, channels // must be after decoder init @@ -1451,7 +1447,7 @@ static void *player_thread_func(void *arg) { debug(1, "Failed to allocate memory for a silence buffer."); memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * conn->output_sample_ratio); first_packet_timestamp = 0; - missing_packets = late_packets = too_late_packets = resend_requests = 0; + conn->missing_packets = conn->late_packets = conn->too_late_packets = conn->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 = @@ -1932,8 +1928,8 @@ static void *player_thread_func(void *arg) { moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / (352 * conn->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, + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, + 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { inform("%*.1f," /* Sync error in milliseconds */ @@ -1947,7 +1943,7 @@ static void *player_thread_func(void *arg) { "%*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, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } @@ -1962,7 +1958,7 @@ static void *player_thread_func(void *arg) { "%*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, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { @@ -2051,6 +2047,7 @@ void player_volume(double airplay_volume) { max_db; // hw_range_db is a flag; if 0 means no mixer if (config.output->parameters) { + audio_parameters audio_information; // have a hardware mixer config.output->parameters(&audio_information); hw_max_db = audio_information.maximum_volume_dB; diff --git a/player.h b/player.h index fee8d5a0..b310bc12 100644 --- a/player.h +++ b/player.h @@ -61,6 +61,11 @@ typedef struct { int shutdown_requested; int connection_state_to_output; int player_thread_please_stop; + // stats + uint64_t missing_packets, late_packets, too_late_packets, resend_requests; + int decoder_in_use; + // debug variables + int32_t last_seqno_read; #ifdef HAVE_LIBMBEDTLS mbedtls_aes_context dctx; From f06e638d24343eaec6c7ee977647afe89508260b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 9 Mar 2017 17:22:48 +0000 Subject: [PATCH 021/239] de-globalise flowcontrol ab_mutex,flush_mutex, first_packet_time_to_play, time_since_play_started --- player.c | 143 +++++++++++++++++++++++++++---------------------------- player.h | 8 +++- rtsp.c | 6 +-- 3 files changed, 79 insertions(+), 78 deletions(-) diff --git a/player.c b/player.c index 1d33d6d9..4ddfe578 100644 --- a/player.c +++ b/player.c @@ -79,16 +79,7 @@ static uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 // or 1. - -// #define FRAME_BYTES(max_frames_per_packet) (4 * max_frames_per_packet) -// maximal resampling shift - conservative -//#define OUTFRAME_BYTES(max_frames_per_packet) (4 * (max_frames_per_packet + 3)) - -// static pthread_t player_thread = NULL; - -// debug variables -static int32_t last_seqno_read; - + // interthread variables static int fix_volume = 0x10000; static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -112,13 +103,6 @@ static int flush_requested = 0; static int64_t flush_rtp_timestamp; static uint64_t time_of_last_audio_packet; -// mutexes and condition variables -static pthread_mutex_t ab_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t flush_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t flowcontrol; - -static int64_t first_packet_time_to_play, time_since_play_started; // nanoseconds - // make timestamps and seqnos definitely monotonic // add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 @@ -181,7 +165,7 @@ static void ab_resync(rtsp_conn_info* conn) { conn->audio_buffer[i].sequence_number = 0; } ab_synced = 0; - last_seqno_read = -1; + conn->last_seqno_read = -1; ab_buffering = 1; } @@ -486,13 +470,13 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r // ignore a request to flush that has been made before the first packet... if (conn->packet_count == 0) { - pthread_mutex_lock(&flush_mutex); + pthread_mutex_lock(&conn->flush_mutex); flush_requested = 0; flush_rtp_timestamp = 0; - pthread_mutex_unlock(&flush_mutex); + pthread_mutex_unlock(&conn->flush_mutex); } - pthread_mutex_lock(&ab_mutex); + pthread_mutex_lock(&conn->ab_mutex); conn->packet_count++; time_of_last_audio_packet = get_absolute_time_in_fp(); if (conn->connection_state_to_output) { // if we are supposed to be processing these packets @@ -565,11 +549,11 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r // pthread_mutex_lock(&ab_mutex); } - int rc = pthread_cond_signal(&flowcontrol); + int rc = pthread_cond_signal(&conn->flowcontrol); if (rc) debug(1, "Error signalling flowcontrol."); } - pthread_mutex_unlock(&ab_mutex); + pthread_mutex_unlock(&conn->ab_mutex); } int32_t rand_in_range(int32_t exclusive_range_limit) { @@ -712,7 +696,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { abuf_t *curframe; int notified_buffer_empty = 0; // diagnostic only - pthread_mutex_lock(&ab_mutex); + pthread_mutex_lock(&conn->ab_mutex); int wait; long dac_delay = 0; // long because alsa returns a long do { @@ -740,23 +724,23 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { conn->connection_state_to_output = rco; // change happening if (conn->connection_state_to_output == 0) { // going off - pthread_mutex_lock(&flush_mutex); + pthread_mutex_lock(&conn->flush_mutex); flush_requested = 1; - pthread_mutex_unlock(&flush_mutex); + pthread_mutex_unlock(&conn->flush_mutex); } } - pthread_mutex_lock(&flush_mutex); + pthread_mutex_lock(&conn->flush_mutex); if (flush_requested == 1) { if (config.output->flush) config.output->flush(); ab_resync(conn); first_packet_timestamp = 0; - first_packet_time_to_play = 0; - time_since_play_started = 0; + conn->first_packet_time_to_play = 0; + conn->time_since_play_started = 0; flush_requested = 0; } - pthread_mutex_unlock(&flush_mutex); + pthread_mutex_unlock(&conn->flush_mutex); uint32_t flush_limit = 0; if (ab_synced) { @@ -849,25 +833,25 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { 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; + conn->first_packet_time_to_play = reference_timestamp_time + delta_fp_sec; } else { int64_t abs_delta = -delta; 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; + conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } - if (local_time_now >= first_packet_time_to_play) { + if (local_time_now >= conn->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 * conn->output_sample_ratio); + player_flush(first_packet_timestamp + 4410 * conn->output_sample_ratio,conn); } } } - if (first_packet_time_to_play != 0) { - // recalculate first_packet_time_to_play -- the latency might change + if (conn->first_packet_time_to_play != 0) { + // recalculate conn->first_packet_time_to_play -- the latency might change int64_t delta = (first_packet_timestamp - reference_timestamp) + config.latency * conn->output_sample_ratio + config.audio_backend_latency_offset * config.output_rate; @@ -875,32 +859,32 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { 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; + conn->first_packet_time_to_play = reference_timestamp_time + delta_fp_sec; } else { int64_t abs_delta = -delta; 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; + conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } 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) { + if (local_time_now >= conn->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)*config.output_rate)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, + // flush.",(((tn-conn->first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read, // ab_write)); if (config.output->flush) config.output->flush(); ab_resync(conn); first_packet_timestamp = 0; - first_packet_time_to_play = 0; - time_since_play_started = 0; + conn->first_packet_time_to_play = 0; + conn->time_since_play_started = 0; } else { - // first_packet_time_to_play is definitely later than local_time_now + // conn->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); if (resp != 0) { @@ -910,19 +894,19 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { } else dac_delay = 0; int64_t gross_frame_gap = - ((first_packet_time_to_play - local_time_now) * config.output_rate) >> 32; + ((conn->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... // debug(1,"Run a bit past the exact start time by %lld frames, with time now of // %llx, fpttp of %llx and dac_delay of %d and %d packets; - // flush.",-exact_frame_gap,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read, + // flush.",-exact_frame_gap,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read, // ab_write)); if (config.output->flush) config.output->flush(); ab_resync(conn); first_packet_timestamp = 0; - first_packet_time_to_play = 0; + conn->first_packet_time_to_play = 0; } else { int64_t fs = filler_size; if (fs > (max_dac_delay - dac_delay)) @@ -1052,7 +1036,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { time_of_wakeup.tv_sec = sec; time_of_wakeup.tv_nsec = nsec; - pthread_cond_timedwait(&flowcontrol, &ab_mutex, &time_of_wakeup); + pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); // int rc = pthread_cond_timedwait(&flowcontrol,&ab_mutex,&time_of_wakeup); // if (rc!=0) // debug(1,"pthread_cond_timedwait returned error code %d.",rc); @@ -1063,13 +1047,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { struct timespec time_to_wait; time_to_wait.tv_sec = sec; time_to_wait.tv_nsec = nsec; - pthread_cond_timedwait_relative_np(&flowcontrol, &ab_mutex, &time_to_wait); + pthread_cond_timedwait_relative_np(&conn->flowcontrol, &conn->ab_mutex, &time_to_wait); #endif } } while (wait); if (conn->player_thread_please_stop) { - pthread_mutex_unlock(&ab_mutex); + pthread_mutex_unlock(&conn->ab_mutex); return 0; } @@ -1097,7 +1081,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { } curframe->ready = 0; ab_read = SUCCESSOR(ab_read); - pthread_mutex_unlock(&ab_mutex); + pthread_mutex_unlock(&conn->ab_mutex); return curframe; } @@ -1500,8 +1484,8 @@ static void *player_thread_func(void *arg) { play_number++; 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 + conn->last_seqno_read = + (SUCCESSOR(conn->last_seqno_read) & 0xffff); // manage the packet out of sequence minder config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); } else { @@ -1641,18 +1625,18 @@ static void *player_thread_func(void *arg) { int amount_to_stuff = 0; // check sequencing - if (last_seqno_read == -1) - last_seqno_read = + if (conn->last_seqno_read == -1) + conn->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. + conn->last_seqno_read = + SUCCESSOR(conn->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 + conn->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... + conn->last_seqno_read, inframe->sequence_number, ab_read, ab_write); + conn->last_seqno_read = inframe->sequence_number; // reset warning... } } @@ -1725,10 +1709,10 @@ static void *player_thread_func(void *arg) { // 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)) { + if ((local_time_now) && (conn->first_packet_time_to_play) && + (local_time_now >= conn->first_packet_time_to_play)) { - int64_t tp = (local_time_now - first_packet_time_to_play) >> + int64_t tp = (local_time_now - conn->first_packet_time_to_play) >> 32; // seconds int64_t from uint64_t which is always positive, so ok if (tp < 5) @@ -1810,7 +1794,7 @@ static void *player_thread_func(void *arg) { "resyncing. Error: %lld.", sync_error_out_of_bounds, sync_error); sync_error_out_of_bounds = 0; - player_flush(nt); + player_flush(nt,conn); } } else { sync_error_out_of_bounds = 0; @@ -2011,7 +1995,7 @@ static void *player_thread_func(void *arg) { } // takes the volume as specified by the airplay protocol -void player_volume(double airplay_volume) { +void player_volume(double airplay_volume, rtsp_conn_info* conn) { // The volume ranges -144.0 (mute) or -30 -- 0. See // http://git.zx2c4.com/Airtunes2/about/#setting-volume @@ -2203,13 +2187,13 @@ void player_volume(double airplay_volume) { #endif } -void player_flush(int64_t timestamp) { +void player_flush(int64_t timestamp, rtsp_conn_info* conn) { debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp); - pthread_mutex_lock(&flush_mutex); + pthread_mutex_lock(&conn->flush_mutex); flush_requested = 1; // if (timestamp!=0) flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this - pthread_mutex_unlock(&flush_mutex); + pthread_mutex_unlock(&conn->flush_mutex); play_segment_reference_frame = 0; #ifdef CONFIG_METADATA send_ssnc_metadata('pfls', NULL, 0, 1); @@ -2229,18 +2213,25 @@ int player_play(pthread_t *player_thread, rtsp_conn_info *conn) { send_ssnc_metadata('pbeg', NULL, 0, 1); #endif + + int rc = pthread_mutex_init(&conn->ab_mutex, NULL); + if (rc) + debug(1, "Error initialising ab_mutex."); + rc = pthread_mutex_init(&conn->flush_mutex, NULL); + if (rc) + debug(1, "Error initialising flush_mutex."); // set the flowcontrol condition variable to wait on a monotonic clock #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN pthread_condattr_t attr; pthread_condattr_init(&attr); pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. - int rc = pthread_cond_init(&flowcontrol, &attr); + rc = pthread_cond_init(&conn->flowcontrol, &attr); #endif #ifdef COMPILE_FOR_OSX - int rc = pthread_cond_init(&flowcontrol, NULL); + rc = pthread_cond_init(&conn->flowcontrol, NULL); #endif if (rc) - debug(1, "Error initialising condition variable."); + debug(1, "Error initialising flowcontrol condition variable."); config.output->start(config.output_rate, config.output_format); size_t size = (PTHREAD_STACK_MIN + 256 * 1024); pthread_attr_t tattr; @@ -2258,14 +2249,20 @@ void player_stop(pthread_t *player_thread, rtsp_conn_info *conn) { // debug(1,"Trying to stop a non-existent player thread"); // else { conn->player_thread_please_stop = 1; - pthread_cond_signal(&flowcontrol); // tell it to give up + pthread_cond_signal(&conn->flowcontrol); // tell it to give up pthread_join(*player_thread, NULL); #ifdef CONFIG_METADATA send_ssnc_metadata('pend', NULL, 0, 1); #endif command_stop(); - int rc = pthread_cond_destroy(&flowcontrol); + int rc = pthread_cond_destroy(&conn->flowcontrol); if (rc) - debug(1, "Error destroying condition variable."); + debug(1, "Error destroying flowcontrol condition variable."); + rc = pthread_mutex_destroy(&conn->flush_mutex); + if (rc) + debug(1, "Error destroying flush_mutex variable."); + rc = pthread_mutex_destroy(&conn->ab_mutex); + if (rc) + debug(1, "Error destroying ab_mutex variable."); // } } diff --git a/player.h b/player.h index b310bc12..f5dfb732 100644 --- a/player.h +++ b/player.h @@ -61,11 +61,15 @@ typedef struct { int shutdown_requested; int connection_state_to_output; int player_thread_please_stop; + int64_t first_packet_time_to_play, time_since_play_started; // nanoseconds // stats uint64_t missing_packets, late_packets, too_late_packets, resend_requests; int decoder_in_use; // debug variables int32_t last_seqno_read; +// mutexes and condition variables + pthread_cond_t flowcontrol; + pthread_mutex_t ab_mutex,flush_mutex; #ifdef HAVE_LIBMBEDTLS mbedtls_aes_context dctx; @@ -86,8 +90,8 @@ int32_t seq_diff(seq_t a, seq_t b); int player_play(pthread_t *thread, rtsp_conn_info* conn); void player_stop(pthread_t *thread, rtsp_conn_info* conn); -void player_volume(double f); -void player_flush(int64_t timestamp); +void player_volume(double f, rtsp_conn_info* conn); +void player_flush(int64_t timestamp, rtsp_conn_info* conn); void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info* conn); int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic diff --git a/rtsp.c b/rtsp.c index 164c8e8b..ac531800 100644 --- a/rtsp.c +++ b/rtsp.c @@ -643,7 +643,7 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 rtptime--; // debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime); - player_flush(rtptime); + player_flush(rtptime,conn); } } } @@ -684,7 +684,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * } } // debug(1,"RTSP Flush Requested: %u.",rtptime); - player_flush(rtptime); + player_flush(rtptime,conn); resp->respcode = 200; } @@ -848,7 +848,7 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r float volume = atof(cp + 8); if (config.ignore_volume_control == 0) { debug(2, "volume: %f\n", volume); - player_volume(volume); + player_volume(volume,conn); } #ifdef CONFIG_METADATA else { // if ignore volume is on... From 3ada8c944c1ff359eeb00a2e2452dd2e6a0c8afa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 9 Mar 2017 17:51:07 +0000 Subject: [PATCH 022/239] Make initialising and destroying mutexes and condition variables internal to the player thread. --- player.c | 70 ++++++++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/player.c b/player.c index 4ddfe578..65324406 100644 --- a/player.c +++ b/player.c @@ -155,9 +155,6 @@ int64_t monotonic_timestamp(uint32_t timestamp) { return return_value; } -// add an epoch to the seq_no. Uses the accompanying timestamp to determine the correct epoch -uint64_t monotonic_seqno(uint16_t seq_no) {} - static void ab_resync(rtsp_conn_info* conn) { int i; for (i = 0; i < BUFFER_FRAMES; i++) { @@ -1260,13 +1257,33 @@ static void *player_thread_func(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; + conn->please_stop = 0; conn->packet_count = 0; conn->previous_random_number = 0; conn->input_bytes_per_frame = 4; conn->player_thread_please_stop = 0; conn->decoder_in_use = 0; - + + int rc = pthread_mutex_init(&conn->ab_mutex, NULL); + if (rc) + debug(1, "Error initialising ab_mutex."); + rc = pthread_mutex_init(&conn->flush_mutex, NULL); + if (rc) + debug(1, "Error initialising flush_mutex."); +// set the flowcontrol condition variable to wait on a monotonic clock +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN + pthread_condattr_t attr; + pthread_condattr_init(&attr); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. + rc = pthread_cond_init(&conn->flowcontrol, &attr); +#endif +#ifdef COMPILE_FOR_OSX + rc = pthread_cond_init(&conn->flowcontrol, NULL); +#endif + if (rc) + debug(1, "Error initialising flowcontrol condition variable."); + config.output->start(config.output_rate, config.output_format); init_decoder((int32_t *)&conn->stream.fmtp,conn); // this sets up incoming rate, bit depth, channels // must be after decoder init @@ -1990,6 +2007,16 @@ static void *player_thread_func(void *arg) { debug(1, "control thread joined"); free_buffer(conn); terminate_decoders(conn); + // remove flow control and mutexes + rc = pthread_cond_destroy(&conn->flowcontrol); + if (rc) + debug(1, "Error destroying flowcontrol condition variable."); + rc = pthread_mutex_destroy(&conn->flush_mutex); + if (rc) + debug(1, "Error destroying flush_mutex variable."); + rc = pthread_mutex_destroy(&conn->ab_mutex); + if (rc) + debug(1, "Error destroying ab_mutex variable."); debug(1, "Player thread exit"); return 0; } @@ -2212,31 +2239,10 @@ int player_play(pthread_t *player_thread, rtsp_conn_info *conn) { #ifdef CONFIG_METADATA send_ssnc_metadata('pbeg', NULL, 0, 1); #endif - - - int rc = pthread_mutex_init(&conn->ab_mutex, NULL); - if (rc) - debug(1, "Error initialising ab_mutex."); - rc = pthread_mutex_init(&conn->flush_mutex, NULL); - if (rc) - debug(1, "Error initialising flush_mutex."); -// set the flowcontrol condition variable to wait on a monotonic clock -#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN - pthread_condattr_t attr; - pthread_condattr_init(&attr); - pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. - rc = pthread_cond_init(&conn->flowcontrol, &attr); -#endif -#ifdef COMPILE_FOR_OSX - rc = pthread_cond_init(&conn->flowcontrol, NULL); -#endif - if (rc) - debug(1, "Error initialising flowcontrol condition variable."); - config.output->start(config.output_rate, config.output_format); - size_t size = (PTHREAD_STACK_MIN + 256 * 1024); + size_t size = (PTHREAD_STACK_MIN + 256 * 1024); pthread_attr_t tattr; pthread_attr_init(&tattr); - rc = pthread_attr_setstacksize(&tattr, size); + int rc = pthread_attr_setstacksize(&tattr, size); if (rc) debug(1, "Error setting stack size for player_thread: %s", strerror(errno)); pthread_create(player_thread, &tattr, player_thread_func, (void*)conn); @@ -2255,14 +2261,4 @@ void player_stop(pthread_t *player_thread, rtsp_conn_info *conn) { send_ssnc_metadata('pend', NULL, 0, 1); #endif command_stop(); - int rc = pthread_cond_destroy(&conn->flowcontrol); - if (rc) - debug(1, "Error destroying flowcontrol condition variable."); - rc = pthread_mutex_destroy(&conn->flush_mutex); - if (rc) - debug(1, "Error destroying flush_mutex variable."); - rc = pthread_mutex_destroy(&conn->ab_mutex); - if (rc) - debug(1, "Error destroying ab_mutex variable."); - // } } From 755e95904ca118e2e86070a982b44a60a20b429f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Mar 2017 17:44:50 +0000 Subject: [PATCH 023/239] De globalise everything except ab_read and ab_write and the volume control stuff --- player.c | 139 +++++++++++++++++++++++++++---------------------------- player.h | 9 +++- rtp.c | 8 ++-- 3 files changed, 80 insertions(+), 76 deletions(-) diff --git a/player.c b/player.c index 65324406..24e394c2 100644 --- a/player.c +++ b/player.c @@ -76,10 +76,7 @@ #include "apple_alac.h" #endif -static uint32_t timestamp_epoch, last_timestamp, - maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 - // or 1. - + // interthread variables static int fix_volume = 0x10000; static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -97,11 +94,6 @@ static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; // mutex-protected variables static seq_t ab_read, ab_write; -static int ab_buffering = 1, ab_synced = 0; -static int64_t first_packet_timestamp = 0; -static int flush_requested = 0; -static int64_t flush_rtp_timestamp; -static uint64_t time_of_last_audio_packet; // make timestamps and seqnos definitely monotonic @@ -112,42 +104,42 @@ static uint64_t time_of_last_audio_packet; // over 50,000 years. // also, it won't reach zero until then, if ever, so we can safely say that a null monotonic // timestamp can mean something special -int64_t monotonic_timestamp(uint32_t timestamp) { +int64_t monotonic_timestamp(uint32_t timestamp,rtsp_conn_info* conn) { int64_t previous_value; int64_t return_value; - if (timestamp_epoch == 0) { - if (timestamp > maximum_timestamp_interval) - timestamp_epoch = 1; + if (conn->timestamp_epoch == 0) { + if (timestamp > conn->maximum_timestamp_interval) + conn->timestamp_epoch = 1; else - timestamp_epoch = 2; - previous_value = timestamp_epoch; + conn->timestamp_epoch = 2; + previous_value = conn->timestamp_epoch; previous_value <<= 32; previous_value += timestamp; } else { - previous_value = timestamp_epoch; + previous_value = conn->timestamp_epoch; previous_value <<= 32; - previous_value += last_timestamp; - if (timestamp < last_timestamp) { + previous_value += conn->last_timestamp; + if (timestamp < conn->last_timestamp) { // the incoming timestamp is less than the last one. // if the difference is more than a minute, assume it's really from the next epoch - if ((last_timestamp - timestamp) > maximum_timestamp_interval) - timestamp_epoch++; + if ((conn->last_timestamp - timestamp) > conn->maximum_timestamp_interval) + conn->timestamp_epoch++; } else { // the incoming timestamp is greater than the last one. // if the difference is more than a minute, assume it's really from the previous epoch - if ((timestamp - last_timestamp) > maximum_timestamp_interval) - timestamp_epoch--; + if ((timestamp - conn->last_timestamp) > conn->maximum_timestamp_interval) + conn->timestamp_epoch--; } } - last_timestamp = timestamp; - return_value = timestamp_epoch; + conn->last_timestamp = timestamp; + return_value = conn->timestamp_epoch; return_value <<= 32; return_value += timestamp; if (previous_value > return_value) { - if ((previous_value - return_value) > maximum_timestamp_interval) + if ((previous_value - return_value) > conn->maximum_timestamp_interval) debug(1, "interval between successive rtptimes greater than allowed!"); } else { - if ((return_value - previous_value) > maximum_timestamp_interval) + if ((return_value - previous_value) > conn->maximum_timestamp_interval) debug(1, "interval between successive rtptimes greater than allowed!"); } if (return_value < 0) @@ -161,9 +153,9 @@ static void ab_resync(rtsp_conn_info* conn) { conn->audio_buffer[i].ready = 0; conn->audio_buffer[i].sequence_number = 0; } - ab_synced = 0; + conn->ab_synced = 0; conn->last_seqno_read = -1; - ab_buffering = 1; + conn->ab_buffering = 1; } // the sequence number is a 16-bit unsigned number which wraps pretty often @@ -468,36 +460,36 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r // ignore a request to flush that has been made before the first packet... if (conn->packet_count == 0) { pthread_mutex_lock(&conn->flush_mutex); - flush_requested = 0; - flush_rtp_timestamp = 0; + conn->flush_requested = 0; + conn->flush_rtp_timestamp = 0; pthread_mutex_unlock(&conn->flush_mutex); } pthread_mutex_lock(&conn->ab_mutex); conn->packet_count++; - time_of_last_audio_packet = get_absolute_time_in_fp(); + conn->time_of_last_audio_packet = get_absolute_time_in_fp(); if (conn->connection_state_to_output) { // if we are supposed to be processing these packets // if (flush_rtp_timestamp != 0) // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp); - if ((flush_rtp_timestamp != 0) && (ltimestamp <= flush_rtp_timestamp)) { + if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) { debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to " "timestamp: %lld.", - seqno, ltimestamp, flush_rtp_timestamp); + seqno, ltimestamp, conn->flush_rtp_timestamp); } else { - if ((flush_rtp_timestamp != 0x0) && - (ltimestamp > flush_rtp_timestamp)) // if we have gone past the flush boundary time - flush_rtp_timestamp = 0x0; + if ((conn->flush_rtp_timestamp != 0x0) && + (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time + conn->flush_rtp_timestamp = 0x0; abuf_t *abuf = 0; - if (!ab_synced) { + if (!conn->ab_synced) { debug(2, "syncing to seqno %u.", seqno); ab_write = seqno; ab_read = seqno; - ab_synced = 1; + conn->ab_synced = 1; } if (ab_write == seqno) { // expected packet abuf = conn->audio_buffer + BUFIDX(seqno); @@ -705,11 +697,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // config.timeout of zero means don't check..., but iTunes may be confused by a long gap // followed by a resumption... - if ((time_of_last_audio_packet != 0) && (conn->shutdown_requested == 0) && + if ((conn->time_of_last_audio_packet != 0) && (conn->shutdown_requested == 0) && (config.dont_check_timeout == 0)) { uint64_t ct = config.timeout; // go from int to 64-bit int - if ((local_time_now > time_of_last_audio_packet) && - (local_time_now - time_of_last_audio_packet >= ct << 32)) { + if ((local_time_now > conn->time_of_last_audio_packet) && + (local_time_now - conn->time_of_last_audio_packet >= ct << 32)) { debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\""); rtsp_request_shutdown_stream(); conn->shutdown_requested = 1; @@ -722,25 +714,25 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // change happening if (conn->connection_state_to_output == 0) { // going off pthread_mutex_lock(&conn->flush_mutex); - flush_requested = 1; + conn->flush_requested = 1; pthread_mutex_unlock(&conn->flush_mutex); } } pthread_mutex_lock(&conn->flush_mutex); - if (flush_requested == 1) { + if (conn->flush_requested == 1) { if (config.output->flush) config.output->flush(); ab_resync(conn); - first_packet_timestamp = 0; + conn->first_packet_timestamp = 0; conn->first_packet_time_to_play = 0; conn->time_since_play_started = 0; - flush_requested = 0; + conn->flush_requested = 0; } pthread_mutex_unlock(&conn->flush_mutex); uint32_t flush_limit = 0; - if (ab_synced) { + if (conn->ab_synced) { do { curframe = conn->audio_buffer + BUFIDX(ab_read); if ((ab_read != ab_write) && (curframe->ready)) { // it could be synced and empty, under @@ -760,17 +752,17 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { } } - if ((flush_rtp_timestamp != 0) && (curframe->timestamp <= flush_rtp_timestamp)) { + if ((conn->flush_rtp_timestamp != 0) && (curframe->timestamp <= conn->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 (curframe->timestamp > flush_rtp_timestamp) - flush_rtp_timestamp = 0; + if (curframe->timestamp > conn->flush_rtp_timestamp) + conn->flush_rtp_timestamp = 0; } - } while ((flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); + } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0)); if (flush_limit == 8820) { debug(1, "Flush hit the 8820 frame limit!"); @@ -781,7 +773,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { if (curframe->ready) { 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 + if (conn->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 sent some silent frames to the // DAC int64_t reference_timestamp; @@ -789,12 +781,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time); reference_timestamp *= conn->output_sample_ratio; - if (first_packet_timestamp == 0) { // if this is the very first packet + if (conn->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)); if (reference_timestamp) { // if we have a reference time // debug(1,"First frame seen with timestamp..."); - first_packet_timestamp = curframe->timestamp; // we will keep buffering until we are + conn->first_packet_timestamp = curframe->timestamp; // we will keep buffering until we are // supposed to start playing this have_sent_prefiller_silence = 0; @@ -823,7 +815,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { debug(1, "Output sample ratio is %d", conn->output_sample_ratio); - int64_t delta = (first_packet_timestamp - reference_timestamp) + + int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + config.latency * conn->output_sample_ratio + config.audio_backend_latency_offset * config.output_rate; @@ -842,14 +834,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { debug( 1, "First packet is late! It should have played before now. Flushing 0.1 seconds"); - player_flush(first_packet_timestamp + 4410 * conn->output_sample_ratio,conn); + player_flush(conn->first_packet_timestamp + 4410 * conn->output_sample_ratio,conn); } } } if (conn->first_packet_time_to_play != 0) { // recalculate conn->first_packet_time_to_play -- the latency might change - int64_t delta = (first_packet_timestamp - reference_timestamp) + + int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + config.latency * conn->output_sample_ratio + config.audio_backend_latency_offset * config.output_rate; @@ -877,7 +869,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { if (config.output->flush) config.output->flush(); ab_resync(conn); - first_packet_timestamp = 0; + conn->first_packet_timestamp = 0; conn->first_packet_time_to_play = 0; conn->time_since_play_started = 0; } else { @@ -902,7 +894,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { if (config.output->flush) config.output->flush(); ab_resync(conn); - first_packet_timestamp = 0; + conn->first_packet_timestamp = 0; conn->first_packet_time_to_play = 0; } else { int64_t fs = filler_size; @@ -919,7 +911,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // with %d packets, ab_read is %04x, ab_write is // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, // ab_write),ab_read,ab_write); - ab_buffering = 0; + conn->ab_buffering = 0; } signed short *silence; // if (fs==0) @@ -944,7 +936,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { } } } - if (ab_buffering == 0) { + if (conn->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, @@ -972,7 +964,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // Note: the last three items are expressed in frames and must be converted to time. int do_wait = 0; // don't wait unless we can really prove we must - if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) { + if ((conn->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... int64_t reference_timestamp; @@ -1009,14 +1001,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { } } if (do_wait == 0) - if ((ab_synced != 0) && (ab_read == ab_write)) { // the buffer is empty! + if ((conn->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; } - wait = (ab_buffering || (do_wait != 0) || (!ab_synced)) && (!conn->player_thread_please_stop); + wait = (conn->ab_buffering || (do_wait != 0) || (!conn->ab_synced)) && (!conn->player_thread_please_stop); if (wait) { uint64_t time_to_wait_for_wakeup_fp = @@ -1059,7 +1051,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // check if t+8, t+16, t+32, t+64, t+128, ... (buffer_start_fill / 2) // packets have arrived... last-chance resend - if (!ab_buffering) { + if (!conn->ab_buffering) { for (i = 8; i < (seq_diff(ab_read, ab_write) / 2); i = (i * 2)) { seq_t next = seq_sum(ab_read, i); abuf = conn->audio_buffer + BUFIDX(next); @@ -1264,6 +1256,11 @@ static void *player_thread_func(void *arg) { conn->input_bytes_per_frame = 4; conn->player_thread_please_stop = 0; conn->decoder_in_use = 0; + conn->ab_buffering = 1; + conn->ab_synced = 0; + conn->first_packet_timestamp = 0; + conn->flush_requested = 0; + int rc = pthread_mutex_init(&conn->ab_mutex, NULL); if (rc) @@ -1307,8 +1304,8 @@ static void *player_thread_func(void *arg) { #endif } - timestamp_epoch = 0; // indicate that the next timestamp will be the first one. - maximum_timestamp_interval = conn->input_rate * 60; // actually there shouldn't be more than about 13v + conn->timestamp_epoch = 0; // indicate that the next timestamp will be the first one. + conn->maximum_timestamp_interval = conn->input_rate * 60; // actually there shouldn't be more than about 13v // seconds of a gap between successive rtptimes, at // worst @@ -1369,7 +1366,7 @@ static void *player_thread_func(void *arg) { int play_samples; int64_t current_delay; int play_number = 0; - time_of_last_audio_packet = 0; + conn->time_of_last_audio_packet = 0; conn->shutdown_requested = 0; number_of_statistics = oldest_statistic = newest_statistic = 0; tsum_of_sync_errors = tsum_of_corrections = tsum_of_insertions_and_deletions = tsum_of_drifts = 0; @@ -1447,9 +1444,9 @@ static void *player_thread_func(void *arg) { if (silence == NULL) debug(1, "Failed to allocate memory for a silence buffer."); memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * conn->output_sample_ratio); - first_packet_timestamp = 0; + conn->first_packet_timestamp = 0; conn->missing_packets = conn->late_packets = conn->too_late_packets = conn->resend_requests = 0; - flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to be used + conn->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 @@ -2217,9 +2214,9 @@ void player_volume(double airplay_volume, rtsp_conn_info* conn) { void player_flush(int64_t timestamp, rtsp_conn_info* conn) { debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp); pthread_mutex_lock(&conn->flush_mutex); - flush_requested = 1; + conn->flush_requested = 1; // if (timestamp!=0) - flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this + conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this pthread_mutex_unlock(&conn->flush_mutex); play_segment_reference_frame = 0; #ifdef CONFIG_METADATA diff --git a/player.h b/player.h index f5dfb732..0f760362 100644 --- a/player.h +++ b/player.h @@ -70,6 +70,13 @@ typedef struct { // mutexes and condition variables pthread_cond_t flowcontrol; pthread_mutex_t ab_mutex,flush_mutex; + uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 + // or 1. + int ab_buffering,ab_synced; + int64_t first_packet_timestamp; + int flush_requested; + int64_t flush_rtp_timestamp; + uint64_t time_of_last_audio_packet; #ifdef HAVE_LIBMBEDTLS mbedtls_aes_context dctx; @@ -94,7 +101,7 @@ void player_volume(double f, rtsp_conn_info* conn); void player_flush(int64_t timestamp, rtsp_conn_info* conn); void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info* conn); -int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic +int64_t monotonic_timestamp(uint32_t timestamp,rtsp_conn_info* conn); // 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. diff --git a/rtp.c b/rtp.c index 04f72b2b..26ea8193 100644 --- a/rtp.c +++ b/rtp.c @@ -164,7 +164,7 @@ void *rtp_audio_receiver(void *arg) { // debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno); last_seqno = seqno; // reset warning... } - int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4))); + int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)),conn); // if (packet[1]&0x10) // debug(1,"Audio packet Extension bit set."); @@ -234,8 +234,8 @@ void *rtp_control_receiver(void *arg) { // debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync); - rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4]))); - sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16]))); + rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4])),conn); + sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])),conn); if (config.use_negotiated_latencies) { int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + 11025; @@ -274,7 +274,7 @@ void *rtp_control_receiver(void *arg) { plen -= 4; seq_t seqno = ntohs(*(unsigned short *)(pktp + 2)); - int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4))); + int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)),conn); pktp += 12; plen -= 12; From b559139862db8f1042bc16d67bc0ddb047312e3d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Mar 2017 19:20:49 +0000 Subject: [PATCH 024/239] De-globalise fix_volume and the vol_mutex --- player.c | 48 +++++++++++++++++++++++++----------------------- player.h | 2 ++ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/player.c b/player.c index 24e394c2..da664d27 100644 --- a/player.c +++ b/player.c @@ -76,11 +76,6 @@ #include "apple_alac.h" #endif - -// interthread variables -static int fix_volume = 0x10000; -static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; - // default buffer size // needs to be a power of 2 because of the way BUFIDX(seqno) works //#define BUFFER_FRAMES 512 @@ -1118,17 +1113,17 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l stuffsamp = (rand() % (length - 2)) + 1; // ensure there's always a sample before and after the item - pthread_mutex_lock(&vol_mutex); + pthread_mutex_lock(&conn->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,conn); - process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); + process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); }; 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,conn); - process_sample(mean_32(inptr[-1], inptr[1]), &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(mean_32(inptr[-2], inptr[0]), &l_outptr, l_output_format, conn->fix_volume, dither,conn); + process_sample(mean_32(inptr[-1], inptr[1]), &l_outptr, l_output_format, conn->fix_volume, dither,conn); } else if (stuff == -1) { // debug(3, "---------"); inptr++; @@ -1142,11 +1137,11 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l 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,conn); - process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); + process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); } } - pthread_mutex_unlock(&vol_mutex); + pthread_mutex_unlock(&conn->vol_mutex); return length + tstuff; } @@ -1221,8 +1216,8 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng ip = scratchBuffer; char *l_outptr = outptr; for (i = 0; i < length + tstuff; i++) { - process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); - process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); + process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); }; } else { // the whole frame, if no stuffing @@ -1233,8 +1228,8 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng int i; for (i = 0; i < length; i++) { - process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); - process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither,conn); + process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); + process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); }; } return length + tstuff; @@ -1260,6 +1255,7 @@ static void *player_thread_func(void *arg) { conn->ab_synced = 0; conn->first_packet_timestamp = 0; conn->flush_requested = 0; + conn->fix_volume = 0x10000; int rc = pthread_mutex_init(&conn->ab_mutex, NULL); @@ -1268,6 +1264,9 @@ static void *player_thread_func(void *arg) { rc = pthread_mutex_init(&conn->flush_mutex, NULL); if (rc) debug(1, "Error initialising flush_mutex."); + rc = pthread_mutex_init(&conn->vol_mutex, NULL); + if (rc) + debug(1, "Error initialising vol_mutex."); // set the flowcontrol condition variable to wait on a monotonic clock #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN pthread_condattr_t attr; @@ -1412,7 +1411,7 @@ static void *player_thread_func(void *arg) { debug(1, "Dithering will be enabled because the input bit depth is greater than the output bit " "depth"); } - if (fix_volume != 0x10000) { + if (conn->fix_volume != 0x10000) { debug(1, "Dithering will be enabled the output volume is being altered in software"); } @@ -1504,7 +1503,7 @@ static void *player_thread_func(void *arg) { } else { int enable_dither = 0; - if ((fix_volume != 0x10000) || (conn->input_bit_depth > output_bit_depth)) + if ((conn->fix_volume != 0x10000) || (conn->input_bit_depth > output_bit_depth)) enable_dither = 1; // here, let's transform the frame of data, if necessary @@ -1817,7 +1816,7 @@ static void *player_thread_func(void *arg) { // if there is no delay procedure, or it's not working or not allowed, there can be no // synchronising - if (fix_volume == 0x10000) + if (conn->fix_volume == 0x10000) if (inbuf == NULL) debug(1, "NULL inbuf to play -- skipping it."); @@ -2014,6 +2013,9 @@ static void *player_thread_func(void *arg) { rc = pthread_mutex_destroy(&conn->ab_mutex); if (rc) debug(1, "Error destroying ab_mutex variable."); + rc = pthread_mutex_destroy(&conn->vol_mutex); + if (rc) + debug(1, "Error destroying vol_mutex variable."); debug(1, "Player thread exit"); return 0; } @@ -2196,9 +2198,9 @@ void player_volume(double airplay_volume, rtsp_conn_info* conn) { // 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); + pthread_mutex_lock(&conn->vol_mutex); + conn->fix_volume = temp_fix_volume; + pthread_mutex_unlock(&conn->vol_mutex); #ifdef CONFIG_METADATA char *dv = malloc(128); // will be freed in the metadata thread diff --git a/player.h b/player.h index 0f760362..c8b39877 100644 --- a/player.h +++ b/player.h @@ -70,6 +70,8 @@ typedef struct { // mutexes and condition variables pthread_cond_t flowcontrol; pthread_mutex_t ab_mutex,flush_mutex; + pthread_mutex_t vol_mutex; + int fix_volume; uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 // or 1. int ab_buffering,ab_synced; From 314b5291a6496e6deb7b1c8428c523dd81405b1a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Mar 2017 19:41:51 +0000 Subject: [PATCH 025/239] Make the seq_diff... etc calls explicitly depend on a base value --- player.c | 24 ++++++++++++------------ player.h | 3 --- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/player.c b/player.c index da664d27..1df3798d 100644 --- a/player.c +++ b/player.c @@ -173,9 +173,9 @@ static inline seq_t PREDECESSOR(seq_t x) { } // anything with ORDINATE in it must be proctected by the ab_mutex -static inline int32_t ORDINATE(seq_t x) { +static inline int32_t ORDINATE(seq_t x, seq_t base) { int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay - int32_t q = ab_read; // int32_t from seq_t, i.e. uint16_t, so okay + int32_t q = base; // int32_t from seq_t, i.e. uint16_t, so okay int32_t t = (p + 0x10000 - q) & 0xffff; // we definitely will get a positive number in t at this point, but it might be a // positive alias of a negative number, i.e. x might actually be "before" ab_read @@ -189,15 +189,15 @@ static inline int32_t ORDINATE(seq_t x) { } // wrapped number between two seq_t. -int32_t seq_diff(seq_t a, seq_t b) { - int32_t diff = ORDINATE(b) - ORDINATE(a); +int32_t seq_diff(seq_t a, seq_t b,seq_t base) { + int32_t diff = ORDINATE(b,base) - ORDINATE(a,base); return diff; } // the sequence numbers will wrap pretty often. // this returns true if the second arg is after the first -static inline int seq_order(seq_t a, seq_t b) { - int32_t d = ORDINATE(b) - ORDINATE(a); +static inline int seq_order(seq_t a, seq_t b, seq_t base) { + int32_t d = ORDINATE(b,base) - ORDINATE(a,base); return d > 0; } @@ -489,11 +489,11 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r if (ab_write == seqno) { // expected packet abuf = conn->audio_buffer + BUFIDX(seqno); ab_write = SUCCESSOR(seqno); - } else if (seq_order(ab_write, seqno)) { // newer than expected + } else if (seq_order(ab_write, seqno, ab_read)) { // newer than expected // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8) // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and seqno: // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno); - int32_t gap = seq_diff(ab_write, seqno); + int32_t gap = seq_diff(ab_write, seqno, ab_read); if (gap <= 0) debug(1, "Unexpected gap size: %d.", gap); int i; @@ -508,7 +508,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r // rtp_request_resend(ab_write, gap); // resend_requests++; ab_write = SUCCESSOR(seqno); - } else if (seq_order(ab_read, seqno)) { // late but not yet played + } else if (seq_order(ab_read, seqno, ab_read)) { // late but not yet played conn->late_packets++; abuf = conn->audio_buffer + BUFIDX(seqno); } else { // too late. @@ -738,7 +738,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // some kind of sync problem has occurred. if (BUFIDX(curframe->sequence_number) == BUFIDX(ab_read)) { // it looks like some kind of aliasing has happened - if (seq_order(ab_read, curframe->sequence_number)) { + if (seq_order(ab_read, curframe->sequence_number,ab_read)) { ab_read = curframe->sequence_number; debug(1, "Aliasing of buffer index -- reset."); } @@ -1047,7 +1047,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // packets have arrived... last-chance resend if (!conn->ab_buffering) { - for (i = 8; i < (seq_diff(ab_read, ab_write) / 2); i = (i * 2)) { + for (i = 8; i < (seq_diff(ab_read, ab_write, ab_read) / 2); i = (i * 2)) { seq_t next = seq_sum(ab_read, i); abuf = conn->audio_buffer + BUFIDX(next); if (!abuf->ready) { @@ -1653,7 +1653,7 @@ static void *player_thread_func(void *arg) { } } - buffer_occupancy = seq_diff(ab_read, ab_write); // int32_t from int32 + buffer_occupancy = seq_diff(ab_read, ab_write, ab_read); // int32_t from int32 if (buffer_occupancy < minimum_buffer_occupancy) minimum_buffer_occupancy = buffer_occupancy; diff --git a/player.h b/player.h index c8b39877..a40ef6bd 100644 --- a/player.h +++ b/player.h @@ -93,9 +93,6 @@ typedef struct { #endif } rtsp_conn_info; -// wrapped number between two seq_t. -int32_t seq_diff(seq_t a, seq_t b); - int player_play(pthread_t *thread, rtsp_conn_info* conn); void player_stop(pthread_t *thread, rtsp_conn_info* conn); From 5c48bfe0e9da89fc0dacf25c3cd7b7c328aaf49e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Mar 2017 19:57:02 +0000 Subject: [PATCH 026/239] De-globalise ab_read and ab_write. player thread now fully reentrant. --- player.c | 51 ++++++++++++++++++++++++--------------------------- player.h | 1 + 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/player.c b/player.c index 1df3798d..f6187c84 100644 --- a/player.c +++ b/player.c @@ -87,9 +87,6 @@ //static abuf_t audio_buffer[BUFFER_FRAMES]; #define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES) -// mutex-protected variables -static seq_t ab_read, ab_write; - // make timestamps and seqnos definitely monotonic // add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33 @@ -482,23 +479,23 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r if (!conn->ab_synced) { debug(2, "syncing to seqno %u.", seqno); - ab_write = seqno; - ab_read = seqno; + conn->ab_write = seqno; + conn->ab_read = seqno; conn->ab_synced = 1; } - if (ab_write == seqno) { // expected packet + if (conn->ab_write == seqno) { // expected packet abuf = conn->audio_buffer + BUFIDX(seqno); - ab_write = SUCCESSOR(seqno); - } else if (seq_order(ab_write, seqno, ab_read)) { // newer than expected + conn->ab_write = SUCCESSOR(seqno); + } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8) // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and seqno: // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno); - int32_t gap = seq_diff(ab_write, seqno, ab_read); + int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read); if (gap <= 0) debug(1, "Unexpected gap size: %d.", gap); int i; for (i = 0; i < gap; i++) { - abuf = conn->audio_buffer + BUFIDX(seq_sum(ab_write, i)); + abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i)); abuf->ready = 0; // to be sure, to be sure abuf->timestamp = 0; abuf->sequence_number = 0; @@ -507,8 +504,8 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, r abuf = conn->audio_buffer + BUFIDX(seqno); // rtp_request_resend(ab_write, gap); // resend_requests++; - ab_write = SUCCESSOR(seqno); - } else if (seq_order(ab_read, seqno, ab_read)) { // late but not yet played + conn->ab_write = SUCCESSOR(seqno); + } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played conn->late_packets++; abuf = conn->audio_buffer + BUFIDX(seqno); } else { // too late. @@ -729,17 +726,17 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { uint32_t flush_limit = 0; if (conn->ab_synced) { do { - curframe = conn->audio_buffer + BUFIDX(ab_read); - if ((ab_read != ab_write) && (curframe->ready)) { // it could be synced and empty, under + curframe = conn->audio_buffer + BUFIDX(conn->ab_read); + if ((conn->ab_read != conn->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) { + if (curframe->sequence_number != conn->ab_read) { // some kind of sync problem has occurred. - if (BUFIDX(curframe->sequence_number) == BUFIDX(ab_read)) { + if (BUFIDX(curframe->sequence_number) == BUFIDX(conn->ab_read)) { // it looks like some kind of aliasing has happened - if (seq_order(ab_read, curframe->sequence_number,ab_read)) { - ab_read = curframe->sequence_number; + if (seq_order(conn->ab_read, curframe->sequence_number,conn->ab_read)) { + conn->ab_read = curframe->sequence_number; debug(1, "Aliasing of buffer index -- reset."); } } else { @@ -752,7 +749,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { curframe->timestamp); curframe->ready = 0; flush_limit++; - ab_read = SUCCESSOR(ab_read); + conn->ab_read = SUCCESSOR(conn->ab_read); } if (curframe->timestamp > conn->flush_rtp_timestamp) conn->flush_rtp_timestamp = 0; @@ -764,7 +761,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { flush_limit = 0; } - curframe = conn->audio_buffer + BUFIDX(ab_read); + curframe = conn->audio_buffer + BUFIDX(conn->ab_read); if (curframe->ready) { notified_buffer_empty = 0; // at least one buffer now -- diagnostic only. @@ -996,7 +993,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { } } if (do_wait == 0) - if ((conn->ab_synced != 0) && (ab_read == ab_write)) { // the buffer is empty! + if ((conn->ab_synced != 0) && (conn->ab_read == conn->ab_write)) { // the buffer is empty! if (notified_buffer_empty == 0) { debug(1, "Buffers exhausted."); notified_buffer_empty = 1; @@ -1041,14 +1038,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { return 0; } - seq_t read = ab_read; + seq_t read = conn->ab_read; // check if t+8, t+16, t+32, t+64, t+128, ... (buffer_start_fill / 2) // packets have arrived... last-chance resend if (!conn->ab_buffering) { - for (i = 8; i < (seq_diff(ab_read, ab_write, ab_read) / 2); i = (i * 2)) { - seq_t next = seq_sum(ab_read, i); + for (i = 8; i < (seq_diff(conn->ab_read, conn->ab_write, conn->ab_read) / 2); i = (i * 2)) { + seq_t next = seq_sum(conn->ab_read, i); abuf = conn->audio_buffer + BUFIDX(next); if (!abuf->ready) { rtp_request_resend(next, 1); @@ -1064,7 +1061,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { curframe->timestamp = 0; // indicate a silent frame should be substituted } curframe->ready = 0; - ab_read = SUCCESSOR(ab_read); + conn->ab_read = SUCCESSOR(conn->ab_read); pthread_mutex_unlock(&conn->ab_mutex); return curframe; } @@ -1648,12 +1645,12 @@ static void *player_thread_func(void *arg) { conn->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.", - conn->last_seqno_read, inframe->sequence_number, ab_read, ab_write); + conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write); conn->last_seqno_read = inframe->sequence_number; // reset warning... } } - buffer_occupancy = seq_diff(ab_read, ab_write, ab_read); // int32_t from int32 + buffer_occupancy = seq_diff(conn->ab_read, conn->ab_write, conn->ab_read); // int32_t from int32 if (buffer_occupancy < minimum_buffer_occupancy) minimum_buffer_occupancy = buffer_occupancy; diff --git a/player.h b/player.h index a40ef6bd..41e8373a 100644 --- a/player.h +++ b/player.h @@ -79,6 +79,7 @@ typedef struct { int flush_requested; int64_t flush_rtp_timestamp; uint64_t time_of_last_audio_packet; + seq_t ab_read, ab_write; #ifdef HAVE_LIBMBEDTLS mbedtls_aes_context dctx; From b4f772c3b1adcbd98cfb7e9d0bf421eda94362e4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 10 Mar 2017 21:00:18 +0000 Subject: [PATCH 027/239] Change the identified basck to ShairportSync -- lost in the move from 2.8.6 to 3.0 --- mdns.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mdns.h b/mdns.h index c4fe4bc6..7aa9dbb2 100644 --- a/mdns.h +++ b/mdns.h @@ -24,7 +24,7 @@ typedef struct { // "sr=44100", "vn=3", "txtvers=1", config.password ? "pw=true" : "pw=false" #define MDNS_RECORD_WITH_METADATA \ - "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ + "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", \ "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false" @@ -35,7 +35,7 @@ typedef struct { // "txtvers=1", config.password ? "pw=true" : "pw=false" #define MDNS_RECORD_WITHOUT_METADATA \ - "sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \ + "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \ "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", \ config.password ? "pw=true" : "pw=false" From 0fa606b4fec12497c165c0bd86bcb2119e37c25f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 11 Mar 2017 16:10:00 +0000 Subject: [PATCH 028/239] Update RELEASENOTES.md --- RELEASENOTES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8c68575a..154091d6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,14 @@ +Version 3.1d1 +==== +This is a stability update. Some users have reported occasional crashes particularly when a play session ends and another begins immediately. The thread that plays a session was not reentrant, and this _may_ have been causing those stability problems. The player thread is now fully re-entrant, so those stability problems should be gone. The changes made were quite extensive, so more bugs may have been reintroduced. Your stability reports would be welcome. + +Please note that there are still problems with SoundCloud. Soundcloud seems to have problems with AirPlay in general -- the problems are not specific to Shairport Sync. + +**Bug Fixes** +* Fixed a bug that was causing Shairport Sync to possibly make a very loud and alarming noise whenever an audio frame was missing. +* In 2.8.6, a change was made to the way Shairport Sync identified itself, so that it could be recognised by TuneBlade as an open source application and treated preferentially. That change was inadventently lost in the transition from 2.8.6 to 3.0. Now it's restored. + + Version 3.1d0 ==== **New Features** From 0ad34287c381f3a5c13b6e128288ab47c32a3ae2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 13 Mar 2017 19:10:08 +0000 Subject: [PATCH 029/239] Stop the "ao", "pulse" and "sndio" backends from complaining about the new output rate and format information. --- audio_ao.c | 4 ---- audio_pulse.c | 4 ---- audio_sndio.c | 4 ---- 3 files changed, 12 deletions(-) diff --git a/audio_ao.c b/audio_ao.c index 87f5f749..299a8a20 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -170,10 +170,6 @@ static void deinit(void) { } 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_pulse.c b/audio_pulse.c index 083f863b..282263cb 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -111,10 +111,6 @@ static void deinit(void) { } 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 357e405b..e8f14807 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -54,10 +54,6 @@ static int init(int argc, char **argv) { 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); } From 87eb53cf82b83720473600329ea006e8d32b9c88 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 14 Mar 2017 09:13:22 +0000 Subject: [PATCH 030/239] Fix small bug in interpolation error message. --- shairport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shairport.c b/shairport.c index 5837f240..adbbede5 100644 --- a/shairport.c +++ b/shairport.c @@ -435,7 +435,7 @@ int parse_options(int argc, char **argv) { else if (strcasecmp(str, "soxr") == 0) config.packet_stuffing = ST_soxr; else - die("Invalid interpolation option choice \"%s\". It should be \"basic\" or \"soxr\""); + die("Invalid interpolation option choice. It should be \"basic\" or \"soxr\""); } /* Get the statistics setting. */ From eaeea548341f2c9d31aeb7a2e36bcf5194624d98 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 14 Mar 2017 09:44:14 +0000 Subject: [PATCH 031/239] Ignore .dirstamp files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d98a86df..53812396 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ depcomp install-sh missing stamp-h1 +.dirstamp .deps man/Makefile man/Makefile.in From 2b6a9420a92d434189df7d00fe6ad2fbc191a7f5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 14 Mar 2017 09:45:06 +0000 Subject: [PATCH 032/239] Add a memory barrier after initialising the rtsp_conn record. Stop a potential race condition. --- rtsp.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rtsp.c b/rtsp.c index ac531800..ce4dab75 100644 --- a/rtsp.c +++ b/rtsp.c @@ -285,9 +285,10 @@ static void cleanup_threads(void) { // debug(2, "culling threads."); for (i = 0; i < nconns;) { if (conns[i]->running == 0) { + debug(1, "found a non-running thread"); pthread_join(conns[i]->thread, &retval); free(conns[i]); - debug(3, "one thread joined..."); + debug(1, "one thread joined..."); nconns--; if (nconns) conns[i] = conns[nconns]; @@ -1914,6 +1915,8 @@ void rtsp_listen_loop(void) { continue; rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info)); + if (conn==0) + die("Couldn't allocate memory for an rtsp_conn_info record."); memset(conn, 0, sizeof(rtsp_conn_info)); socklen_t slen = sizeof(conn->remote); @@ -1974,14 +1977,15 @@ void rtsp_listen_loop(void) { usleep(500000); pthread_t rtsp_conversation_thread; + conn->stop = 0; + conn->authorized = 0; + conn->running = 1; ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); if (ret) die("Failed to create RTSP receiver thread!"); conn->thread = rtsp_conversation_thread; - conn->stop = 0; - conn->authorized = 0; - conn->running = 1; + memory_barrier(); track_thread(conn); } } From 178f78cd8b517697bdfbe364a78ba99543b37907 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 14 Mar 2017 09:49:59 +0000 Subject: [PATCH 033/239] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 18f5322f..24e837b3 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.1d0], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.1s1], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 10f3bc1eab19f1facbf69396b1280df2f91dc8e7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 14 Mar 2017 10:38:48 +0000 Subject: [PATCH 034/239] Clean up creation of conversation thread -- no functional difference. --- rtsp.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/rtsp.c b/rtsp.c index ce4dab75..a02cc8d5 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1974,18 +1974,15 @@ void rtsp_listen_loop(void) { } else { debug(1, "Error figuring out Shairport Sync's own IP number."); } - - usleep(500000); - pthread_t rtsp_conversation_thread; - conn->stop = 0; - conn->authorized = 0; + usleep(500000); +// pthread_t rtsp_conversation_thread; +// conn->thread = rtsp_conversation_thread; +// conn->stop = 0; // record's memory has been zeroed +// conn->authorized = 0; // record's memory has been zeroed conn->running = 1; - ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); + ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func, conn); // also acts as a memory barrier if (ret) die("Failed to create RTSP receiver thread!"); - - conn->thread = rtsp_conversation_thread; - memory_barrier(); track_thread(conn); } } From 0d22130081df0db789fadb161fe903239ac280ab Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Mar 2017 14:49:02 +0000 Subject: [PATCH 035/239] Add specific metadat message when a FLUSH message is received. Also add diabnostic for fmtp parameters. --- rtsp.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index a02cc8d5..7f8039bb 100644 --- a/rtsp.c +++ b/rtsp.c @@ -685,6 +685,12 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * } } // debug(1,"RTSP Flush Requested: %u.",rtptime); + #ifdef CONFIG_METADATA + if (p) + send_metadata('ssnc', 'flsr', p, strlen(p), req, 1); + else + send_metadata('ssnc', 'flsr', NULL, 0, NULL,0); + #endif player_flush(rtptime,conn); resp->respcode = 200; } @@ -1442,6 +1448,9 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag int i; for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); + // here we should check the sanity ot the fmtp values + for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) + debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]); char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { @@ -1974,7 +1983,7 @@ void rtsp_listen_loop(void) { } else { debug(1, "Error figuring out Shairport Sync's own IP number."); } - usleep(500000); +// usleep(500000); // pthread_t rtsp_conversation_thread; // conn->thread = rtsp_conversation_thread; // conn->stop = 0; // record's memory has been zeroed From 43fdb12aca4da0f628cf10e9a8dfc208107a9bba Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Mar 2017 21:19:31 +0000 Subject: [PATCH 036/239] Fix audio backend latency offset calculations --- player.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/player.c b/player.c index 8dba9bc8..e052a816 100644 --- a/player.c +++ b/player.c @@ -820,7 +820,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + config.latency * conn->output_sample_ratio + - config.audio_backend_latency_offset * config.output_rate; + (int64_t)(config.audio_backend_latency_offset * config.output_rate); if (delta >= 0) { int64_t delta_fp_sec = @@ -846,7 +846,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // recalculate conn->first_packet_time_to_play -- the latency might change int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + config.latency * conn->output_sample_ratio + - config.audio_backend_latency_offset * config.output_rate; + (int64_t)(config.audio_backend_latency_offset * config.output_rate); if (delta >= 0) { int64_t delta_fp_sec = @@ -980,7 +980,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { int64_t delta = packet_timestamp - reference_timestamp; int64_t offset = config.latency * conn->output_sample_ratio + - config.audio_backend_latency_offset * config.output_rate - + (int64_t)(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 @@ -1347,7 +1347,7 @@ static void *player_thread_func(void *arg) { // 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 * conn->input_rate; + int maximum_latency = config.latency + (int64_t)(config.audio_backend_latency_offset * config.output_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.", @@ -1699,7 +1699,7 @@ static void *player_thread_func(void *arg) { // This is the timing error for the next audio frame in the DAC. sync_error = delay - - config.latency * conn->output_sample_ratio; // int64_t from int64_t - int32_t, so okay + (config.latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate)); // int64_t from int64_t - int32_t, so okay // if (llabs(sync_error)>352*512) // debug(1,"Very large sync error: %lld",sync_error); From 755c46a27c42f26b1ee3f7e8a278d69a13482c71 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Mar 2017 21:19:50 +0000 Subject: [PATCH 037/239] Fix audio backend latency offset calculations --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 24e837b3..0773b287 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.1s1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.1s2], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 3679fa5b905ff3847f8093f2cbc59d1ea32e1bc4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Mar 2017 21:23:17 +0000 Subject: [PATCH 038/239] Update RELEASENOTES.md --- RELEASENOTES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 154091d6..9e0e77d3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,10 @@ +Version 3.1s1 and 3.1s2 +==== +**Bug fix** +3.1s2 fixes issues with audio backend latency offset calculations. + +Other minor fixes will be detailed later. + Version 3.1d1 ==== This is a stability update. Some users have reported occasional crashes particularly when a play session ends and another begins immediately. The thread that plays a session was not reentrant, and this _may_ have been causing those stability problems. The player thread is now fully re-entrant, so those stability problems should be gone. The changes made were quite extensive, so more bugs may have been reintroduced. Your stability reports would be welcome. From 253cc86b75598eb684109c3de0b19d9e5178bb33 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 15 Mar 2017 21:23:35 +0000 Subject: [PATCH 039/239] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9e0e77d3..05782d22 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,7 @@ Version 3.1s1 and 3.1s2 ==== **Bug fix** -3.1s2 fixes issues with audio backend latency offset calculations. +* 3.1s2 fixes issues with audio backend latency offset calculations. Other minor fixes will be detailed later. From e122fde2fb53bd381b52e7a6377868bc2c910017 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 08:47:26 +0000 Subject: [PATCH 040/239] Add some debug messages around requests to stop and wathc out for double announcements --- rtsp.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/rtsp.c b/rtsp.c index 7f8039bb..db617a39 100644 --- a/rtsp.c +++ b/rtsp.c @@ -303,7 +303,7 @@ static void cleanup_threads(void) { void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread) { int i; - debug(2, "asking playing threads to stop"); + debug(1, "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)) && (conns[i]->running != 0)) { @@ -663,6 +663,7 @@ static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag "it's sending a response to teardown anyway"); resp->respcode = 200; msg_add_header(resp, "Connection", "close"); + debug(2,"TEARDOWN asking connection to stop"); conn->stop = 1; } @@ -1360,17 +1361,24 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (config.allow_session_interruption == 1) { // some other thread has the player ... ask it to relinquish the thread if (playing_conn) { - playing_conn->stop = 1; - memory_barrier(); - pthread_kill(playing_conn->thread, SIGUSR1); + debug(1,"Playing connection asked to stop"); + if (playing_conn==conn) { + debug(1,"ANNOuNCE asking to stop itself."); + } else { + playing_conn->stop = 1; + memory_barrier(); + pthread_kill(playing_conn->thread, SIGUSR1); + usleep(1000000); // here, it is possible for other connections to come in and nab the player. + } } else { 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); if (pthread_mutex_trylock(&play_lock) == 0) have_the_player = 1; + else + debug(1,"ANNOUNCE failed to get the player"); } } From 728d3d4956f2a803a42a38807ef72a410cbf1aa6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 12:33:56 +0000 Subject: [PATCH 041/239] add conn to many procedure calls --- player.c | 16 ++++++++-------- rtp.c | 28 ++++++++++++++-------------- rtp.h | 12 ++++++------ rtsp.c | 4 ++-- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/player.c b/player.c index e052a816..d1d9a90d 100644 --- a/player.c +++ b/player.c @@ -782,7 +782,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { 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); + &remote_reference_timestamp_time, conn); reference_timestamp *= conn->output_sample_ratio; if (conn->first_packet_timestamp == 0) { // if this is the very first packet // debug(1,"First frame seen, time %u, with %d @@ -943,7 +943,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { // 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); + &play_segment_reference_frame_remote_time, conn); play_segment_reference_frame *= conn->output_sample_ratio; #ifdef CONFIG_METADATA send_ssnc_metadata('prsm', NULL, 0, @@ -973,7 +973,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { 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 + &remote_reference_timestamp_time, conn); // all types okay reference_timestamp *= conn->output_sample_ratio; if (reference_timestamp) { // if we have a reference time int64_t packet_timestamp = curframe->timestamp; // types okay @@ -1059,7 +1059,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { seq_t next = seq_sum(conn->ab_read, i); abuf = conn->audio_buffer + BUFIDX(next); if (!abuf->ready) { - rtp_request_resend(next, 1); + rtp_request_resend(next, 1, conn); // debug(1,"Resend %u.",next); conn->resend_requests++; } @@ -1338,9 +1338,9 @@ static void *player_thread_func(void *arg) { // 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 *)conn); - pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn); - pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn); + pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void*) conn); + pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void*) conn); + pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void*) conn); session_corrections = 0; play_segment_reference_frame = 0; // zero signals that we are not in a play segment @@ -1604,7 +1604,7 @@ static void *player_thread_func(void *arg) { 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 + &remote_reference_timestamp_time, conn); // types okay reference_timestamp *= conn->output_sample_ratio; int64_t rt, nt; rt = reference_timestamp; // uint32_t to int64_t diff --git a/rtp.c b/rtp.c index 26ea8193..c15d2281 100644 --- a/rtp.c +++ b/rtp.c @@ -94,11 +94,11 @@ static pthread_mutex_t reference_time_mutex = PTHREAD_MUTEX_INITIALIZER; uint64_t static local_to_remote_time_difference; // used to switch between local and remote clocks -void *rtp_audio_receiver(void *arg) { +void *rtp_audio_receiver(void* arg) { debug(2, "Audio receiver -- Server RTP thread starting."); // we inherit the signal mask (SIGUSR1) - rtsp_conn_info *conn = arg; + rtsp_conn_info *conn = (rtsp_conn_info*)arg; int32_t last_seqno = -1; uint8_t packet[2048], *pktp; @@ -193,11 +193,12 @@ void *rtp_audio_receiver(void *arg) { return NULL; } -void *rtp_control_receiver(void *arg) { +void *rtp_control_receiver(void* arg) { // we inherit the signal mask (SIGUSR1) debug(2, "Control receiver -- Server RTP thread starting."); - rtsp_conn_info *conn = arg; + + rtsp_conn_info *conn = (rtsp_conn_info*)arg; reference_timestamp = 0; // nothing valid received yet uint8_t packet[2048], *pktp; @@ -355,8 +356,7 @@ void *rtp_timing_sender(void *arg) { void *rtp_timing_receiver(void *arg) { debug(2, "Timing receiver -- Server RTP thread starting."); // we inherit the signal mask (SIGUSR1) - - rtsp_conn_info *conn = arg; + rtsp_conn_info *conn = (rtsp_conn_info*)arg; uint8_t packet[2048], *pktp; ssize_t nread; @@ -500,7 +500,7 @@ void *rtp_timing_receiver(void *arg) { 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); + &remote_reference_timestamp_time, conn); uint64_t frame_difference = 0; if (reference_timestamp >= play_segment_reference_frame) frame_difference = (uint64_t)reference_timestamp - (uint64_t)play_segment_reference_frame; @@ -622,7 +622,7 @@ static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_ } void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote, - int *lsport, int *lcport, int *ltport) { + int *lsport, int *lcport, int *ltport, rtsp_conn_info *conn) { // 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 @@ -731,7 +731,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t } void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, - uint64_t *remote_timestamp_time) { + uint64_t *remote_timestamp_time, rtsp_conn_info *conn) { // types okay pthread_mutex_lock(&reference_time_mutex); *timestamp = reference_timestamp; @@ -740,19 +740,19 @@ void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, pthread_mutex_unlock(&reference_time_mutex); } -void clear_reference_timestamp(void) { +void clear_reference_timestamp(rtsp_conn_info *conn) { pthread_mutex_lock(&reference_time_mutex); reference_timestamp = 0; reference_timestamp_time = 0; pthread_mutex_unlock(&reference_time_mutex); } -void rtp_shutdown(void) { +void rtp_shutdown(rtsp_conn_info *conn) { if (!running) debug(1, "rtp_shutdown called without active stream!"); debug(2, "shutting down RTP thread"); - clear_reference_timestamp(); + clear_reference_timestamp(conn); // debug(1,"Shut down audio, control and timing threads"); // usleep(3000000); // hack // pthread_kill(rtp_audio_thread, SIGUSR1); @@ -764,7 +764,7 @@ void rtp_shutdown(void) { running = 0; } -void rtp_request_resend(seq_t first, uint32_t count) { +void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { if (running) { // if (!request_sent) { debug(3, "requesting resend of %d packets starting at %u.", count, first); @@ -795,7 +795,7 @@ void rtp_request_resend(seq_t first, uint32_t count) { } } -void rtp_request_client_pause() { +void rtp_request_client_pause(rtsp_conn_info *conn) { if (running) { if (client_active_remote == 0) { debug(1, "Can't request a client pause: no valid active remote."); diff --git a/rtp.h b/rtp.h index 4f6da117..b02a9409 100644 --- a/rtp.h +++ b/rtp.h @@ -11,14 +11,14 @@ 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_shutdown(void); -void rtp_request_resend(seq_t first, uint32_t count); -void rtp_request_client_pause(void); // ask the client to pause + int *local_timing_port, rtsp_conn_info* conn); +void rtp_shutdown(rtsp_conn_info* conn); +void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info* conn); +void rtp_request_client_pause(rtsp_conn_info* conn); // ask the client to pause void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, - uint64_t *remote_timestamp_time); -void clear_reference_timestamp(void); + uint64_t *remote_timestamp_time, rtsp_conn_info* conn); +void clear_reference_timestamp(rtsp_conn_info* conn); uint64_t static local_to_remote_time_jitters; uint64_t static local_to_remote_time_jitters_count; diff --git a/rtsp.c b/rtsp.c index db617a39..22369669 100644 --- a/rtsp.c +++ b/rtsp.c @@ -797,7 +797,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * tport = atoi(p); // rtsp_take_player(); - rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port); + rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port, conn); if (!lsport) goto error; char *q; @@ -1772,7 +1772,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { debug(1, "Closing down RTSP conversation thread..."); if (rtsp_playing()) { player_stop(&conn->player_thread,conn); // might be less noisy doing this first - rtp_shutdown(); + rtp_shutdown(conn); // usleep(400000); // let an angel pass... pthread_mutex_unlock(&play_lock); } From e4d5570eee1dd9df4ee43b18d4e070ae9bd3f7d8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 13:19:38 +0000 Subject: [PATCH 042/239] Add rtp init and terminate, mirror all globals in the conn data structure --- player.c | 4 ++-- player.h | 47 +++++++++++++++++++++++++++++++++++++++++++++++ rtp.c | 51 +++++++++++++++++++++++++++------------------------ rtp.h | 3 +++ rtsp.c | 3 +++ 5 files changed, 82 insertions(+), 26 deletions(-) diff --git a/player.c b/player.c index d1d9a90d..e0bceea9 100644 --- a/player.c +++ b/player.c @@ -1292,8 +1292,6 @@ static void *player_thread_func(void *arg) { // must be after decoder init init_buffer(conn); - debug(1, "Output frame bytes is %d.", conn->output_bytes_per_frame); - if (conn->stream.encrypted) { #ifdef HAVE_LIBMBEDTLS memset(&conn->dctx, 0, sizeof(mbedtls_aes_context)); @@ -1336,6 +1334,8 @@ static void *player_thread_func(void *arg) { break; } + debug(1, "Output frame bytes is %d.", conn->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*) conn); diff --git a/player.h b/player.h index 41e8373a..3e02a96d 100644 --- a/player.h +++ b/player.h @@ -1,6 +1,8 @@ #ifndef _PLAYER_H #define _PLAYER_H +#include + #include "config.h" #ifdef HAVE_LIBMBEDTLS @@ -20,6 +22,16 @@ #include "audio.h" #include "alac.h" +#define time_ping_history 8 +#define time_ping_fudge_factor 100000 + +typedef struct time_ping_record { + uint64_t local_to_remote_difference; + uint64_t dispersion; + uint64_t local_time; + uint64_t remote_time; +} time_ping_record; + typedef uint16_t seq_t; typedef struct audio_buffer_entry { // decoded audio packets @@ -92,6 +104,41 @@ typedef struct { #ifdef HAVE_LIBSSL AES_KEY aes; #endif + +// RTP stuff +// only one RTP session can be active at a time. +int rtp_running; + +char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client +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 +uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope +short connection_ip_family; // AF_INET / AF_INET6 +uint32_t client_active_remote; // used when you want to control the client... + +SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client +SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client +int audio_socket; // our local [server] audio socket +int control_socket; // our local [server] control socket +int timing_socket; // local timing socket + +int64_t reference_timestamp; +uint64_t reference_timestamp_time; +uint64_t remote_reference_timestamp_time; + +// debug variables +int request_sent; + +uint8_t time_ping_count; +struct time_ping_record time_pings[time_ping_history]; + +uint64_t departure_time; // dangerous -- this assumes that there will never be two timing + // request in flight at the same time + +pthread_mutex_t reference_time_mutex; + +uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks + } rtsp_conn_info; int player_play(pthread_t *thread, rtsp_conn_info* conn); diff --git a/rtp.c b/rtp.c index c15d2281..b93d648f 100644 --- a/rtp.c +++ b/rtp.c @@ -42,21 +42,8 @@ #include "player.h" #include "rtp.h" -/* -// this does not compile properly with OpenWrt Barrier Breaker... -#if defined(__linux__) -#include -#endif -*/ -typedef struct time_ping_record { - uint64_t local_to_remote_difference; - uint64_t dispersion; - uint64_t local_time; - uint64_t remote_time; -} time_ping_record; - // only one RTP session can be active at a time. -static int running = 0; +static int rtp_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 @@ -79,9 +66,6 @@ static uint64_t remote_reference_timestamp_time; // debug variables static int request_sent; -#define time_ping_history 8 -#define time_ping_fudge_factor 100000 - static uint8_t time_ping_count; struct time_ping_record time_pings[time_ping_history]; @@ -94,6 +78,25 @@ static pthread_mutex_t reference_time_mutex = PTHREAD_MUTEX_INITIALIZER; uint64_t static local_to_remote_time_difference; // used to switch between local and remote clocks +void rtp_initialise(rtsp_conn_info* conn) { + + rtp_running = 0; +// initialise the timer mutex + int rc = pthread_mutex_init(&conn->reference_time_mutex, NULL); + if (rc) + debug(1, "Error initialising reference_time_mutex."); + +} + +void rtp_terminate(rtsp_conn_info* conn) { + +// destroy the timer mutex + int rc = pthread_mutex_destroy(&conn->reference_time_mutex); + if (rc) + debug(1, "Error destroying reference_time_mutex variable."); +} + + void *rtp_audio_receiver(void* arg) { debug(2, "Audio receiver -- Server RTP thread starting."); @@ -323,7 +326,7 @@ void *rtp_timing_sender(void *arg) { while (*stop == 0) { // debug(1,"Send a timing request"); - if (!running) + if (!rtp_running) die("rtp_timing_sender called without active stream!"); // debug(1, "Requesting ntp timestamp exchange."); @@ -628,7 +631,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // 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) + if (rtp_running) die("rtp_setup called with active stream!"); debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport); @@ -726,7 +729,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL); // pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); - running = 1; + rtp_running = 1; request_sent = 0; } @@ -748,7 +751,7 @@ void clear_reference_timestamp(rtsp_conn_info *conn) { } void rtp_shutdown(rtsp_conn_info *conn) { - if (!running) + if (!rtp_running) debug(1, "rtp_shutdown called without active stream!"); debug(2, "shutting down RTP thread"); @@ -761,11 +764,11 @@ void rtp_shutdown(rtsp_conn_info *conn) { // pthread_join(rtp_audio_thread, &retval); // pthread_join(rtp_control_thread, &retval); // pthread_join(rtp_timing_thread, &retval); - running = 0; + rtp_running = 0; } void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { - if (running) { + if (rtp_running) { // if (!request_sent) { debug(3, "requesting resend of %d packets starting at %u.", count, first); // request_sent = 1; @@ -796,7 +799,7 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { } void rtp_request_client_pause(rtsp_conn_info *conn) { - if (running) { + if (rtp_running) { if (client_active_remote == 0) { debug(1, "Can't request a client pause: no valid active remote."); } else { diff --git a/rtp.h b/rtp.h index b02a9409..a78548ab 100644 --- a/rtp.h +++ b/rtp.h @@ -5,6 +5,9 @@ #include "player.h" +void rtp_initialise(rtsp_conn_info* conn); +void rtp_terminate(rtsp_conn_info* conn); + void *rtp_audio_receiver(void *arg); void *rtp_control_receiver(void *arg); void *rtp_timing_receiver(void *arg); diff --git a/rtsp.c b/rtsp.c index 22369669..7120fd3e 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1723,6 +1723,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { pthread_sigmask(SIG_UNBLOCK, &set, NULL); rtsp_conn_info *conn = pconn; + + rtp_initialise(conn); rtsp_message *req, *resp; char *hdr, *auth_nonce = NULL; @@ -1787,6 +1789,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { // debug(1, "This RTSP conversation thread doesn't think it's playing for a " // "close RTSP connection."); // } + rtp_terminate(conn); debug(2, "RTSP conversation thread terminated."); // please_shutdown = 0; return NULL; From f3d85bc71a960166c79c7dc28c4346287013282f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 15:01:08 +0000 Subject: [PATCH 043/239] Deglobalise rtp_running, make rtp timing sender use the conn data strucutre --- player.h | 2 ++ rtp.c | 29 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/player.h b/player.h index 3e02a96d..8a3c9a32 100644 --- a/player.h +++ b/player.h @@ -139,6 +139,8 @@ pthread_mutex_t reference_time_mutex; uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks +int timing_sender_stop; // for asking the timing-sending thread to stop + } rtsp_conn_info; int player_play(pthread_t *thread, rtsp_conn_info* conn); diff --git a/rtp.c b/rtp.c index b93d648f..f3dcf0a7 100644 --- a/rtp.c +++ b/rtp.c @@ -43,7 +43,7 @@ #include "rtp.h" // only one RTP session can be active at a time. -static int rtp_running = 0; +//static int rtp_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 @@ -80,7 +80,7 @@ uint64_t static local_to_remote_time_difference; // used to switch between local void rtp_initialise(rtsp_conn_info* conn) { - rtp_running = 0; + conn->rtp_running = 0; // initialise the timer mutex int rc = pthread_mutex_init(&conn->reference_time_mutex, NULL); if (rc) @@ -302,7 +302,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 + rtsp_conn_info *conn = (rtsp_conn_info*)arg; struct timing_request { char leader; char type; @@ -323,10 +323,10 @@ void *rtp_timing_sender(void *arg) { time_ping_count = 0; // we inherit the signal mask (SIGUSR1) - while (*stop == 0) { + while (conn->timing_sender_stop == 0) { // debug(1,"Send a timing request"); - if (!rtp_running) + if (!conn->rtp_running) die("rtp_timing_sender called without active stream!"); // debug(1, "Requesting ntp timestamp exchange."); @@ -363,9 +363,9 @@ void *rtp_timing_receiver(void *arg) { uint8_t packet[2048], *pktp; ssize_t nread; - int request_stop = 0; + conn->timing_sender_stop = 0; pthread_t timer_requester; - pthread_create(&timer_requester, NULL, &rtp_timing_sender, (void *)&request_stop); + pthread_create(&timer_requester, NULL, &rtp_timing_sender, arg); // struct timespec att; uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time, transit_time, processing_time; @@ -555,9 +555,10 @@ void *rtp_timing_receiver(void *arg) { } debug(1, "Timing thread interrupted. terminating."); - request_stop = 1; + conn->timing_sender_stop = 1; void *retval; pthread_kill(timer_requester, SIGUSR1); + debug(1, "Wait for timer requester to exit."); pthread_join(timer_requester, &retval); debug(1, "Closed and terminated timer requester thread."); debug(1, "Timing RTP thread terminated."); @@ -631,7 +632,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // 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 (rtp_running) + if (conn->rtp_running) die("rtp_setup called with active stream!"); debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport); @@ -729,7 +730,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL); // pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); - rtp_running = 1; + conn->rtp_running = 1; request_sent = 0; } @@ -751,7 +752,7 @@ void clear_reference_timestamp(rtsp_conn_info *conn) { } void rtp_shutdown(rtsp_conn_info *conn) { - if (!rtp_running) + if (!conn->rtp_running) debug(1, "rtp_shutdown called without active stream!"); debug(2, "shutting down RTP thread"); @@ -764,11 +765,11 @@ void rtp_shutdown(rtsp_conn_info *conn) { // pthread_join(rtp_audio_thread, &retval); // pthread_join(rtp_control_thread, &retval); // pthread_join(rtp_timing_thread, &retval); - rtp_running = 0; + conn->rtp_running = 0; } void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { - if (rtp_running) { + if (conn->rtp_running) { // if (!request_sent) { debug(3, "requesting resend of %d packets starting at %u.", count, first); // request_sent = 1; @@ -799,7 +800,7 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { } void rtp_request_client_pause(rtsp_conn_info *conn) { - if (rtp_running) { + if (conn->rtp_running) { if (client_active_remote == 0) { debug(1, "Can't request a client pause: no valid active remote."); } else { From 4fdc9d70eb7aa9352fbbecf89d4df2f2f4817b5b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 15:05:55 +0000 Subject: [PATCH 044/239] deglobalise client_ip_string and self_ip_string --- rtp.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rtp.c b/rtp.c index f3dcf0a7..3e50a8fa 100644 --- a/rtp.c +++ b/rtp.c @@ -45,8 +45,8 @@ // only one RTP session can be active at a time. //static int rtp_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 +//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 @@ -667,10 +667,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)); + inet_ntop(connection_ip_family, client_addr, conn->client_ip_string, sizeof(conn->client_ip_string)); + inet_ntop(connection_ip_family, self_addr, conn->self_ip_string, sizeof(conn->self_ip_string)); - debug(1, "Set up play connection from %s to self at %s.", client_ip_string, self_ip_string); + debug(1, "Set up play connection from %s to self at %s.", conn->client_ip_string, conn->self_ip_string); // set up a the record of the remote's control socket struct addrinfo hints; @@ -682,7 +682,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t hints.ai_socktype = SOCK_DGRAM; char portstr[20]; snprintf(portstr, 20, "%d", cport); - if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0) + if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0) die("Can't get address of client's control port"); #ifdef AF_INET6 @@ -702,7 +702,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t hints.ai_family = connection_ip_family; hints.ai_socktype = SOCK_DGRAM; snprintf(portstr, 20, "%d", tport); - if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0) + if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0) die("Can't get address of client's timing port"); #ifdef AF_INET6 if (servinfo->ai_family == AF_INET6) { @@ -718,9 +718,9 @@ 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, conn->self_ip_string, self_scope_id, &audio_socket); + *lcport = bind_port(connection_ip_family, conn->self_ip_string, self_scope_id, &control_socket); + *ltport = bind_port(connection_ip_family, conn->self_ip_string, self_scope_id, &timing_socket); debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); @@ -818,7 +818,7 @@ void rtp_request_client_pause(rtsp_conn_info *conn) { hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - getaddrinfo(client_ip_string, "3689", &hints, &res); + getaddrinfo(conn->client_ip_string, "3689", &hints, &res); // make a socket: @@ -838,7 +838,7 @@ void rtp_request_client_pause(rtsp_conn_info *conn) { sprintf(message, "GET /ctrl-int/1/pause HTTP/1.1\r\nHost: %s:3689\r\nActive-Remote: %u\r\n\r\n", - client_ip_string, client_active_remote); + conn->client_ip_string, client_active_remote); // debug(1,"Sending this message: \"%s\".",message); // Send some data From 9efeefdda70376bf4dd5eda48a3e2169bd42a594 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 15:09:03 +0000 Subject: [PATCH 045/239] deglobalise self_scope_id for ipv6 --- rtp.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rtp.c b/rtp.c index 3e50a8fa..e5bb0411 100644 --- a/rtp.c +++ b/rtp.c @@ -48,7 +48,7 @@ //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 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... @@ -655,7 +655,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t sa6 = (struct sockaddr_in6 *)local; self_addr = &(sa6->sin6_addr); self_port = ntohs(sa6->sin6_port); - self_scope_id = sa6->sin6_scope_id; + conn->self_scope_id = sa6->sin6_scope_id; } #endif if (connection_ip_family == AF_INET) { @@ -690,7 +690,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); // ensure the scope id matches that of remote. this is needed for link-local addresses. struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&rtp_client_control_socket; - sa6->sin6_scope_id = self_scope_id; + sa6->sin6_scope_id = conn->self_scope_id; } else #endif memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); @@ -709,7 +709,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); // ensure the scope id matches that of remote. this is needed for link-local addresses. struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&rtp_client_timing_socket; - sa6->sin6_scope_id = self_scope_id; + sa6->sin6_scope_id = conn->self_scope_id; } else #endif memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); @@ -718,9 +718,9 @@ 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, conn->self_ip_string, self_scope_id, &audio_socket); - *lcport = bind_port(connection_ip_family, conn->self_ip_string, self_scope_id, &control_socket); - *ltport = bind_port(connection_ip_family, conn->self_ip_string, self_scope_id, &timing_socket); + *lsport = bind_port(connection_ip_family, conn->self_ip_string, conn->self_scope_id, &audio_socket); + *lcport = bind_port(connection_ip_family, conn->self_ip_string, conn->self_scope_id, &control_socket); + *ltport = bind_port(connection_ip_family, conn->self_ip_string, conn->self_scope_id, &timing_socket); debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); From 04cb1d09984d10820d98d4f64d38fa2cd934e402 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 15:29:16 +0000 Subject: [PATCH 046/239] deglobalise sockets and reference timestamp --- rtp.c | 102 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/rtp.c b/rtp.c index e5bb0411..ce69091e 100644 --- a/rtp.c +++ b/rtp.c @@ -49,19 +49,19 @@ //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 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 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 int64_t reference_timestamp; -static uint64_t reference_timestamp_time; -static uint64_t remote_reference_timestamp_time; +//static int64_t reference_timestamp; +//static uint64_t reference_timestamp_time; +//static uint64_t remote_reference_timestamp_time; // debug variables static int request_sent; @@ -118,7 +118,7 @@ void *rtp_audio_receiver(void* arg) { ssize_t nread; while (conn->please_stop == 0) { - nread = recv(audio_socket, packet, sizeof(packet), 0); + nread = recv(conn->audio_socket, packet, sizeof(packet), 0); uint64_t local_time_now_fp = get_absolute_time_in_fp(); if (time_of_previous_packet_fp) { @@ -191,7 +191,7 @@ void *rtp_audio_receiver(void* arg) { } debug(1, "Audio receiver -- Server RTP thread interrupted. terminating."); - close(audio_socket); + close(conn->audio_socket); return NULL; } @@ -203,14 +203,14 @@ void *rtp_control_receiver(void* arg) { rtsp_conn_info *conn = (rtsp_conn_info*)arg; - reference_timestamp = 0; // nothing valid received yet + conn->reference_timestamp = 0; // nothing valid received yet uint8_t packet[2048], *pktp; struct timespec tn; uint64_t remote_time_of_sync, local_time_now, remote_time_now; int64_t sync_rtp_timestamp, rtp_timestamp_less_latency; ssize_t nread; while (conn->please_stop == 0) { - nread = recv(control_socket, packet, sizeof(packet), 0); + nread = recv(conn->control_socket, packet, sizeof(packet), 0); local_time_now = get_absolute_time_in_fp(); // clock_gettime(CLOCK_MONOTONIC,&tn); // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000; @@ -257,9 +257,9 @@ void *rtp_control_receiver(void* arg) { // after the one whose RTP is given. Weird. } pthread_mutex_lock(&reference_time_mutex); - remote_reference_timestamp_time = remote_time_of_sync; - reference_timestamp_time = remote_time_of_sync - local_to_remote_time_difference; - reference_timestamp = sync_rtp_timestamp; + conn->remote_reference_timestamp_time = remote_time_of_sync; + conn->reference_timestamp_time = remote_time_of_sync - local_to_remote_time_difference; + conn->reference_timestamp = sync_rtp_timestamp; pthread_mutex_unlock(&reference_time_mutex); // debug(1,"New Reference timestamp and timestamp time..."); // get estimated remote time now @@ -295,7 +295,7 @@ void *rtp_control_receiver(void* arg) { } debug(1, "Control RTP thread interrupted. terminating."); - close(control_socket); + close(conn->control_socket); return NULL; } @@ -338,11 +338,11 @@ void *rtp_timing_sender(void *arg) { departure_time = get_absolute_time_in_fp(); socklen_t msgsize = sizeof(struct sockaddr_in); #ifdef AF_INET6 - if (rtp_client_timing_socket.SAFAMILY == AF_INET6) { + if (conn->rtp_client_timing_socket.SAFAMILY == AF_INET6) { msgsize = sizeof(struct sockaddr_in6); } #endif - if (sendto(timing_socket, &req, sizeof(req), 0, (struct sockaddr *)&rtp_client_timing_socket, + if (sendto(conn->timing_socket, &req, sizeof(req), 0, (struct sockaddr *)&conn->rtp_client_timing_socket, msgsize) == -1) { perror("Error sendto-ing to timing socket"); } @@ -378,7 +378,7 @@ void *rtp_timing_receiver(void *arg) { uint64_t first_local_to_remote_time_difference_time; uint64_t l2rtd = 0; while (conn->please_stop == 0) { - nread = recv(timing_socket, packet, sizeof(packet), 0); + nread = recv(conn->timing_socket, packet, sizeof(packet), 0); arrival_time = get_absolute_time_in_fp(); // clock_gettime(CLOCK_MONOTONIC,&att); @@ -562,7 +562,7 @@ void *rtp_timing_receiver(void *arg) { pthread_join(timer_requester, &retval); debug(1, "Closed and terminated timer requester thread."); debug(1, "Timing RTP thread terminated."); - close(timing_socket); + close(conn->timing_socket); return NULL; } @@ -637,7 +637,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport); - client_active_remote = active_remote; + conn->client_active_remote = active_remote; // print out what we know about the client void *client_addr, *self_addr; @@ -645,10 +645,10 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t 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 + conn->connection_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client #ifdef AF_INET6 - if (connection_ip_family == AF_INET6) { + if (conn->connection_ip_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote; client_addr = &(sa6->sin6_addr); client_port = ntohs(sa6->sin6_port); @@ -658,7 +658,7 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t conn->self_scope_id = sa6->sin6_scope_id; } #endif - if (connection_ip_family == AF_INET) { + if (conn->connection_ip_family == AF_INET) { struct sockaddr_in *sa4 = (struct sockaddr_in *)remote; client_addr = &(sa4->sin_addr); client_port = ntohs(sa4->sin_port); @@ -667,8 +667,8 @@ 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, conn->client_ip_string, sizeof(conn->client_ip_string)); - inet_ntop(connection_ip_family, self_addr, conn->self_ip_string, sizeof(conn->self_ip_string)); + inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string, sizeof(conn->client_ip_string)); + inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string, sizeof(conn->self_ip_string)); debug(1, "Set up play connection from %s to self at %s.", conn->client_ip_string, conn->self_ip_string); @@ -676,9 +676,9 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t struct addrinfo hints; struct addrinfo *servinfo; - memset(&rtp_client_control_socket, 0, sizeof(rtp_client_control_socket)); + memset(&conn->rtp_client_control_socket, 0, sizeof(conn->rtp_client_control_socket)); memset(&hints, 0, sizeof hints); - hints.ai_family = connection_ip_family; + hints.ai_family = conn->connection_ip_family; hints.ai_socktype = SOCK_DGRAM; char portstr[20]; snprintf(portstr, 20, "%d", cport); @@ -687,45 +687,45 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t #ifdef AF_INET6 if (servinfo->ai_family == AF_INET6) { - memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); + memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); // ensure the scope id matches that of remote. this is needed for link-local addresses. - struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&rtp_client_control_socket; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_control_socket; sa6->sin6_scope_id = conn->self_scope_id; } else #endif - memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); + memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); freeaddrinfo(servinfo); // set up a the record of the remote's timing socket - memset(&rtp_client_timing_socket, 0, sizeof(rtp_client_timing_socket)); + memset(&conn->rtp_client_timing_socket, 0, sizeof(conn->rtp_client_timing_socket)); memset(&hints, 0, sizeof hints); - hints.ai_family = connection_ip_family; + hints.ai_family = conn->connection_ip_family; hints.ai_socktype = SOCK_DGRAM; snprintf(portstr, 20, "%d", tport); if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0) die("Can't get address of client's timing port"); #ifdef AF_INET6 if (servinfo->ai_family == AF_INET6) { - memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); + memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6)); // ensure the scope id matches that of remote. this is needed for link-local addresses. - struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&rtp_client_timing_socket; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_timing_socket; sa6->sin6_scope_id = conn->self_scope_id; } else #endif - memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); + memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in)); freeaddrinfo(servinfo); // 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, conn->self_ip_string, conn->self_scope_id, &audio_socket); - *lcport = bind_port(connection_ip_family, conn->self_ip_string, conn->self_scope_id, &control_socket); - *ltport = bind_port(connection_ip_family, conn->self_ip_string, conn->self_scope_id, &timing_socket); + *lsport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->audio_socket); + *lcport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->control_socket); + *ltport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->timing_socket); debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); - reference_timestamp = 0; + conn->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); @@ -738,16 +738,16 @@ void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time, rtsp_conn_info *conn) { // types okay pthread_mutex_lock(&reference_time_mutex); - *timestamp = reference_timestamp; - *timestamp_time = reference_timestamp_time; - *remote_timestamp_time = remote_reference_timestamp_time; + *timestamp = conn->reference_timestamp; + *timestamp_time = conn->reference_timestamp_time; + *remote_timestamp_time = conn->remote_reference_timestamp_time; pthread_mutex_unlock(&reference_time_mutex); } void clear_reference_timestamp(rtsp_conn_info *conn) { pthread_mutex_lock(&reference_time_mutex); - reference_timestamp = 0; - reference_timestamp_time = 0; + conn->reference_timestamp = 0; + conn->reference_timestamp_time = 0; pthread_mutex_unlock(&reference_time_mutex); } @@ -783,11 +783,11 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { *(unsigned short *)(req + 6) = htons(count); // count socklen_t msgsize = sizeof(struct sockaddr_in); #ifdef AF_INET6 - if (rtp_client_control_socket.SAFAMILY == AF_INET6) { + if (conn->rtp_client_control_socket.SAFAMILY == AF_INET6) { msgsize = sizeof(struct sockaddr_in6); } #endif - if (sendto(audio_socket, req, sizeof(req), 0, (struct sockaddr *)&rtp_client_control_socket, + if (sendto(conn->audio_socket, req, sizeof(req), 0, (struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) { perror("Error sendto-ing to audio socket"); } @@ -801,7 +801,7 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { void rtp_request_client_pause(rtsp_conn_info *conn) { if (conn->rtp_running) { - if (client_active_remote == 0) { + if (conn->client_active_remote == 0) { debug(1, "Can't request a client pause: no valid active remote."); } else { // debug(1,"Send a client pause request to %s:3689 with active remote @@ -838,7 +838,7 @@ void rtp_request_client_pause(rtsp_conn_info *conn) { sprintf(message, "GET /ctrl-int/1/pause HTTP/1.1\r\nHost: %s:3689\r\nActive-Remote: %u\r\n\r\n", - conn->client_ip_string, client_active_remote); + conn->client_ip_string, conn->client_active_remote); // debug(1,"Sending this message: \"%s\".",message); // Send some data From 672c84c72807ac54ddd0aa154a0b83d417a24cb2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 15:47:07 +0000 Subject: [PATCH 047/239] Make rtp stuff thread-safe -- remove all globals --- rtp.c | 108 ++++++++++++++++++++-------------------------------------- 1 file changed, 36 insertions(+), 72 deletions(-) diff --git a/rtp.c b/rtp.c index ce69091e..15b4892c 100644 --- a/rtp.c +++ b/rtp.c @@ -42,42 +42,6 @@ #include "player.h" #include "rtp.h" -// only one RTP session can be active at a time. -//static int rtp_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 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 int64_t reference_timestamp; -//static uint64_t reference_timestamp_time; -//static uint64_t remote_reference_timestamp_time; - -// debug variables -static int request_sent; - -static uint8_t time_ping_count; -struct time_ping_record time_pings[time_ping_history]; - -// static struct timespec dtt; // dangerous -- this assumes that there will never be two timing -// request in flight at the same time -static uint64_t departure_time; // dangerous -- this assumes that there will never be two timing - // request in flight at the same time - -static pthread_mutex_t reference_time_mutex = PTHREAD_MUTEX_INITIALIZER; - -uint64_t static local_to_remote_time_difference; // used to switch between local and remote clocks - void rtp_initialise(rtsp_conn_info* conn) { conn->rtp_running = 0; @@ -231,7 +195,7 @@ void *rtp_control_receiver(void* arg) { *obfp=0; debug(1,"Sync Packet Received: \"%s\"",obf); */ - if (local_to_remote_time_difference) { // need a time packet to be interchanged first... + if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged first... remote_time_of_sync = (uint64_t)ntohl(*((uint32_t *)&packet[8])) << 32; remote_time_of_sync += ntohl(*((uint32_t *)&packet[12])); @@ -256,11 +220,11 @@ void *rtp_control_receiver(void* arg) { // it's as if the first sync after a flush or resume is the timing of the next packet // after the one whose RTP is given. Weird. } - pthread_mutex_lock(&reference_time_mutex); + pthread_mutex_lock(&conn->reference_time_mutex); conn->remote_reference_timestamp_time = remote_time_of_sync; - conn->reference_timestamp_time = remote_time_of_sync - local_to_remote_time_difference; + conn->reference_timestamp_time = remote_time_of_sync - conn->local_to_remote_time_difference; conn->reference_timestamp = sync_rtp_timestamp; - pthread_mutex_unlock(&reference_time_mutex); + pthread_mutex_unlock(&conn->reference_time_mutex); // debug(1,"New Reference timestamp and timestamp time..."); // get estimated remote time now // remote_time_now = local_time_now + local_to_remote_time_difference; @@ -320,7 +284,7 @@ void *rtp_timing_sender(void *arg) { req.filler = 0; req.seqno = htons(7); - time_ping_count = 0; + conn->time_ping_count = 0; // we inherit the signal mask (SIGUSR1) while (conn->timing_sender_stop == 0) { @@ -335,7 +299,7 @@ void *rtp_timing_sender(void *arg) { req.origin = req.receive = req.transmit = 0; // clock_gettime(CLOCK_MONOTONIC,&dtt); - departure_time = get_absolute_time_in_fp(); + conn->departure_time = get_absolute_time_in_fp(); socklen_t msgsize = sizeof(struct sockaddr_in); #ifdef AF_INET6 if (conn->rtp_client_timing_socket.SAFAMILY == AF_INET6) { @@ -403,7 +367,7 @@ void *rtp_timing_receiver(void *arg) { // arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000; // departure_time = ((uint64_t)dtt.tv_sec<<32)+((uint64_t)dtt.tv_nsec<<32)/1000000000; - return_time = arrival_time - departure_time; + return_time = arrival_time - conn->departure_time; // uint64_t rtus = (return_time*1000000)>>32; debug(1,"Time ping turnaround time: %lld // us.",rtus); @@ -429,43 +393,43 @@ void *rtp_timing_receiver(void *arg) { unsigned int cc; for (cc = time_ping_history - 1; cc > 0; cc--) { - time_pings[cc] = time_pings[cc - 1]; - time_pings[cc].dispersion = (time_pings[cc].dispersion * 133) / + conn->time_pings[cc] = conn->time_pings[cc - 1]; + conn->time_pings[cc].dispersion = (conn->time_pings[cc].dispersion * 133) / 100; // make the dispersions 'age' by this rational factor } // these are for diagnostics only -- not used - time_pings[0].local_time = arrival_time; - time_pings[0].remote_time = distant_transmit_time; + conn->time_pings[0].local_time = arrival_time; + conn->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++; + conn->time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time; + conn->time_pings[0].dispersion = return_time; + if (conn->time_ping_count < time_ping_history) + conn->time_ping_count++; 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; - uint64_t tld = time_pings[0].dispersion; - for (cc = 1; cc < time_ping_count; cc++) - if (time_pings[cc].dispersion < tld) { - l2rtd = time_pings[cc].local_to_remote_difference; - tld = time_pings[cc].dispersion; - local_time_chosen = time_pings[cc].local_time; - remote_time_chosen = time_pings[cc].remote_time; + uint64_t l2rtd = conn->time_pings[0].local_to_remote_difference; + uint64_t tld = conn->time_pings[0].dispersion; + for (cc = 1; cc < conn->time_ping_count; cc++) + if (conn->time_pings[cc].dispersion < tld) { + l2rtd = conn->time_pings[cc].local_to_remote_difference; + tld = conn->time_pings[cc].dispersion; + local_time_chosen = conn->time_pings[cc].local_time; + remote_time_chosen = conn->time_pings[cc].remote_time; } int64_t ji; - if (time_ping_count > 1) { - if (l2rtd > local_to_remote_time_difference) { + if (conn->time_ping_count > 1) { + if (l2rtd > conn->local_to_remote_time_difference) { local_to_remote_time_jitters = - local_to_remote_time_jitters + l2rtd - local_to_remote_time_difference; - ji = l2rtd - local_to_remote_time_difference; + local_to_remote_time_jitters + l2rtd - conn->local_to_remote_time_difference; + ji = l2rtd - conn->local_to_remote_time_difference; } else { local_to_remote_time_jitters = - local_to_remote_time_jitters + local_to_remote_time_difference - l2rtd; - ji = -(local_to_remote_time_difference - l2rtd); + local_to_remote_time_jitters + conn->local_to_remote_time_difference - l2rtd; + ji = -(conn->local_to_remote_time_difference - l2rtd); } local_to_remote_time_jitters_count += 1; } @@ -473,9 +437,9 @@ void *rtp_timing_receiver(void *arg) { // int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time difference // with dispersion of %lld us with delta of %lld us",rtus,ji); - local_to_remote_time_difference = l2rtd; + conn->local_to_remote_time_difference = l2rtd; 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 = conn->local_to_remote_time_difference; first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); } @@ -731,24 +695,24 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t // pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL); conn->rtp_running = 1; - request_sent = 0; + conn->request_sent = 0; } void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time, rtsp_conn_info *conn) { // types okay - pthread_mutex_lock(&reference_time_mutex); + pthread_mutex_lock(&conn->reference_time_mutex); *timestamp = conn->reference_timestamp; *timestamp_time = conn->reference_timestamp_time; *remote_timestamp_time = conn->remote_reference_timestamp_time; - pthread_mutex_unlock(&reference_time_mutex); + pthread_mutex_unlock(&conn->reference_time_mutex); } void clear_reference_timestamp(rtsp_conn_info *conn) { - pthread_mutex_lock(&reference_time_mutex); + pthread_mutex_lock(&conn->reference_time_mutex); conn->reference_timestamp = 0; conn->reference_timestamp_time = 0; - pthread_mutex_unlock(&reference_time_mutex); + pthread_mutex_unlock(&conn->reference_time_mutex); } void rtp_shutdown(rtsp_conn_info *conn) { From f8b0e2ed29ceff1a8da1e806c1e144c03446276b Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 15:47:20 +0000 Subject: [PATCH 048/239] Make rtp stuff thread-safe -- remove all globals --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0773b287..a71a445c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.1s2], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.1s3], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 6ac6a37fd800ca9e5ed7e3784917c891dd943e4e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 16:12:24 +0000 Subject: [PATCH 049/239] Update RELEASENOTES.md --- RELEASENOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 05782d22..f2e153eb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,7 @@ +Version 3.1s3 +==== +More extensive changes have been made to improve the stability by making some operations reentrant. The instabilities occur sometimes when interrupting a play session by another. + Version 3.1s1 and 3.1s2 ==== **Bug fix** From 78ebecdf3044d91078409e08d82ec8dffb3eafe5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 16 Mar 2017 16:12:40 +0000 Subject: [PATCH 050/239] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f2e153eb..6dba547a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ Version 3.1s3 ==== -More extensive changes have been made to improve the stability by making some operations reentrant. The instabilities occur sometimes when interrupting a play session by another. +More extensive changes have been made to improve stability by making some operations reentrant. The instabilities occur sometimes when interrupting a play session by another. Version 3.1s1 and 3.1s2 ==== From 69c042585a38286f45064d5b52ccad86798679d9 Mon Sep 17 00:00:00 2001 From: Denys Berkovskyy Date: Tue, 21 Mar 2017 11:16:05 +0000 Subject: [PATCH 051/239] Fix various typos. --- README.md | 8 ++++---- RELEASENOTES.md | 42 ++++++++++++++++++++-------------------- TROUBLESHOOTING.md | 6 +++--- common.h | 2 +- man/shairport-sync.7.xml | 6 +++--- mdns_avahi.c | 2 +- shairport.c | 2 +- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 7b6babe0..577683ba 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi. -Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux and Fedora inside VMWare Fusion on a Mac, but synchronisation in inaccurate — possibly because the soundcard is being emulated. +Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux and Fedora inside VMWare Fusion on a Mac, but synchronisation in inaccurate — possibly because the sound card is being emulated. Shairport Sync will output to `alsa` cards, to standard output and to pipes using appropriate backends. You can try compiling additional backends in as you wish, but it definitely will not work properly with them. Maybe someday... @@ -160,7 +160,7 @@ Enter: to build the application. **Install into to a `systemd` system** -To complete the installation, you need to define a `shairport-sync` group and user. This is a security measure – the user and group are relatively unprivileged, and the user does not have login priviliges. The user must be a member of the `audio` group to be able to access the audio hardware. The following commands define the group and user correctly if they do not already exist (note the use of `sudo` – omit this if you already have superuser privileges: +To complete the installation, you need to define a `shairport-sync` group and user. This is a security measure – the user and group are relatively unprivileged, and the user does not have login privileges. The user must be a member of the `audio` group to be able to access the audio hardware. The following commands define the group and user correctly if they do not already exist (note the use of `sudo` – omit this if you already have superuser privileges: ``` $getent group shairport-sync &>/dev/null || sudo groupadd -r shairport-sync >/dev/null @@ -242,7 +242,7 @@ Shairport Sync can run programs just before it starts to play an audio stream an Please note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the standard `#!/bin/...` first line. (This behaviour may be different from other Shairports.) -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. +Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, 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` 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. @@ -333,7 +333,7 @@ alsa = { }; ``` -For an NSLU2, which has no internal soundcard, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus: +For an NSLU2, which has no internal sound card, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus: On an NSLU2, to drive a first generation Griffin iMic: ``` diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6dba547a..8c446100 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -17,7 +17,7 @@ Please note that there are still problems with SoundCloud. Soundcloud seems to h **Bug Fixes** * Fixed a bug that was causing Shairport Sync to possibly make a very loud and alarming noise whenever an audio frame was missing. -* In 2.8.6, a change was made to the way Shairport Sync identified itself, so that it could be recognised by TuneBlade as an open source application and treated preferentially. That change was inadventently lost in the transition from 2.8.6 to 3.0. Now it's restored. +* In 2.8.6, a change was made to the way Shairport Sync identified itself, so that it could be recognised by TuneBlade as an open source application and treated preferentially. That change was inadvertently lost in the transition from 2.8.6 to 3.0. Now it's restored. Version 3.1d0 @@ -98,14 +98,14 @@ 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. +* Fix a small and generally silent error in configure.ac so that it only looks for the systemd directory 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. +* Reduces processor load back to V2.X levels by using a pre-calculated array of pseudorandom numbers to do dithering. Doesn't seem to make any audible difference. Version 3.0d18 – Development Version ---- @@ -122,7 +122,7 @@ Note: all Version 3 changes are summarized above. 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. +* Sox-based interpolation at higher sample rates may overload your CPU -- you might have to choose between higher sample rates and sox-based interpolation. **Bugs** * Documentation is not updated. @@ -241,7 +241,7 @@ The following is a summary of the bug fixes and enhancements since version 2.8.3 * Metadata can now be provided via UDP -- thanks to [faceless2](https://github.com/faceless2). * Statistics output is more machine readable -- thanks to [Jörg Krause](https://github.com/joerg-krause) -* The `shairport-sync.spec` file has been updated for compatability with building Debian packages using `checkinstall` -- thanks to [vru1](https://github.com/vru1). +* The `shairport-sync.spec` file has been updated for compatibility with building Debian packages using `checkinstall` -- thanks to [vru1](https://github.com/vru1). Version 2.8.3.11 – Development Version ---- @@ -412,7 +412,7 @@ Version 2.7.9 -- Development Version Version 2.7.8 -- Development Version ---- **Bug Fix** -* Fixed an issue whereby Shairport Sync did not reset the hardware mixer volume level before resuming playing. The issue was caused by not releasing and later reaquiring the mixer when pausing and resuming. Thanks to [Tim Curtis](https://github.com/moodeaudio) for reporting the issue. +* Fixed an issue whereby Shairport Sync did not reset the hardware mixer volume level before resuming playing. The issue was caused by not releasing and later reacquiring the mixer when pausing and resuming. Thanks to [Tim Curtis](https://github.com/moodeaudio) for reporting the issue. Version 2.7.7 -- Development Version ---- @@ -447,7 +447,7 @@ Version 2.7.5 -- Development Version Version 2.7.4 -- Development Version ---- **Enhancements** -* Use the correct method for finding the `systemd` unit path, as recomended by debain maintainers and +* Use the correct method for finding the `systemd` unit path, as recommended by debian maintainers and http://www.freedesktop.org/software/systemd/man/daemon.html#Installing%20Systemd%20Service%20Files. Thanks to [dantheperson](https://github.com/dantheperson). * Rather than hardwire the path `/usr/local/bin` as the path to the shairport-sync executable, the value of `$PREFIX` is now used during configuration. Thanks to [Nick Steel](https://github.com/kingosticks). * Add some extra diagnostic messages if the hardware buffer in the DAC is smaller than desired. @@ -461,7 +461,7 @@ http://www.freedesktop.org/software/systemd/man/daemon.html#Installing%20Systemd Version 2.7.3 -- Development Version ---- **Bug Fix** -* The dither code was broken in Shairport Sync and also less than ideal anyway. Fixed and improved. Dither is added whenever you use the software volume control at less than full volume. See http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf for a very influential paper by Lipshitz, Wannamaker and Vanderkooy, 1992. The dither code in Shairport Sync was inherited from Shairport and does not conform to the recommendations in the paper -- specifically the implementation would give one bit of dither where the paper recommends two bits peak-to-peak. The other thing is that the inherited dither code was actually broken in Shairport Sync. So, the new dither code gives a two bit peak-to-peak dither based on a Triangular Probability Distribution Function (TPDF). It sounds like a very low-level white noise, unmodulated by the audio material. It would be nice if it was even lower, but it's better than listening to the artifacts present when dithering is disabled. +* The dither code was broken in Shairport Sync and also less than ideal anyway. Fixed and improved. Dither is added whenever you use the software volume control at less than full volume. See http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf for a very influential paper by Lipshitz, Wannamaker and Vanderkooy, 1992. The dither code in Shairport Sync was inherited from Shairport and does not conform to the recommendations in the paper -- specifically the implementation would give one bit of dither where the paper recommends two bits peak-to-peak. The other thing is that the inherited dither code was actually broken in Shairport Sync. So, the new dither code gives a two bit peak-to-peak dither based on a Triangular Probability Distribution Function (TPDF). It sounds like a very low-level white noise, unmodulated by the audio material. It would be nice if it was even lower, but it's better than listening to the artefacts present when dithering is disabled. Version 2.7.2 -- Development Version ---- @@ -479,7 +479,7 @@ Version 2.7.1 -- Development Version Version 2.7 -- Development Version ---- **New Features** -* Extend the volume range for some DACs. Background: some of the cheaper DACS have a very small volume range (that is, the ratio of the highest to the lowest volume, expressed in decibels, is very small). In some really cheap DACs it's only around 30 dB. That means that the difference betweeen the lowest and highest volume settings isn't large enough. With the new feature, if you set the `general` `volume_range_db` to more than the hardware mixer's range, Shairport Sync will combine the hardware mixer's range with a software attenuator to give the desired range. For example, suppose you want a volume range of 70 dB and the hardware mixer offers only 30 dB, then Shairport Sync will make up the other 40 dB with a software attenuator. One drawback is that, when the volume is being changed, there may be a slight delay (0.15 seconds by default) as the audio, whose volume may have been adjusted in software, propagates through the system. Another slight possible drawback is a slightly heavier load on the processor. +* Extend the volume range for some DACs. Background: some of the cheaper DACS have a very small volume range (that is, the ratio of the highest to the lowest volume, expressed in decibels, is very small). In some really cheap DACs it's only around 30 dB. That means that the difference between the lowest and highest volume settings isn't large enough. With the new feature, if you set the `general` `volume_range_db` to more than the hardware mixer's range, Shairport Sync will combine the hardware mixer's range with a software attenuator to give the desired range. For example, suppose you want a volume range of 70 dB and the hardware mixer offers only 30 dB, then Shairport Sync will make up the other 40 dB with a software attenuator. One drawback is that, when the volume is being changed, there may be a slight delay (0.15 seconds by default) as the audio, whose volume may have been adjusted in software, propagates through the system. Another slight possible drawback is a slightly heavier load on the processor. * Check for underflow a little better when buffer aliasing occurs on very bad connections... * Add extra debug messages to the alsa back end to diagnose strange DACs. * Add configuration file for the `libao` back end -- to change the buffer size and the latency offset, same as for stdout. @@ -501,7 +501,7 @@ This is basically version 2.4.2 with two small fixes. It's been bumped to 2.6 be Version 2.4.2 ---- -This release has important enhancements, bug fixes and documentation updates. It also appears to bring compatiblity with Synology NAS devices. +This release has important enhancements, bug fixes and documentation updates. It also appears to bring compatibility with Synology NAS devices. **New Features** @@ -520,7 +520,7 @@ Using source-specified latencies is now automatic unless non-standard static lat * Volume ratios expressed in decibels are now consistently denominated in voltage decibels rather than power decibels. The rationale is that the levels refer to voltage levels, and power is proportional to the square of voltage. Thus a ratio of levels of 65535 to 1 is 96.3 dB rather than the 48.15 dB used before. * The latency figure returned to the source as part of the response to an rtsp request packet is 11,025, which may (?) be meant to indicate the minimum latency the device is capable of. -* An experimental handler for a GET_PARAMETER rtsp request has been added. It does nothing except log the occurence. +* An experimental handler for a GET_PARAMETER rtsp request has been added. It does nothing except log the occurrence. * The RTSP request dispatcher now logs an event whenever an unrecognised rtsp has been made. @@ -532,8 +532,8 @@ This release has three small bug fixes and some small documentation updates. Changes from the previous stable version -- 2.4 -- are summarised here: * The USE_CUSTOM_LOCAL_STATE_DIR macro was still being used when it should have been USE_CUSTOM_PID_DIR. This could affect users using a custom location for the PID directory. - * A compiler error has been fixed that occured if metadata was enabled and tinysvcmdns was included. - * A crash has been fixed that occured if metadata was enabled and a metadata pipename was not specified. + * A compiler error has been fixed that occurred if metadata was enabled and tinysvcmdns was included. + * A crash has been fixed that occurred if metadata was enabled and a metadata pipename was not specified. (Thanks to the contributors who reported bugs.) **Small Changes** @@ -553,7 +553,7 @@ Changes from the previous stable version -- 2.2.5 -- are summarised here: * Metadata is now supported -- it can be delivered to a unix pipe for processing by a helper application. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. * Raw PCM audio can be delivered to standard output ("stdout") or to a unix pipe. The internal architecture has changed considerably to support this. * Support for compilation on OpenWrt back to Attitude Adjustment. - * Can play unencrypted audio streams -- complatible with, e.g. Whaale. + * Can play unencrypted audio streams -- compatible with, e.g. Whaale. * Uses the libconfig library. * Runs on a wider range of platforms, including Arch Linux and Fedora. * Bug fixes. @@ -580,7 +580,7 @@ Version 2.3.12 **Enhancements** -* Larger range of interpolation. Shairport Sync was previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 frames. This contraint has been relaxed, and it is now able to make corrections of up to 1 in 352 frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows: +* Larger range of interpolation. Shairport Sync was previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 frames. This constraint has been relaxed, and it is now able to make corrections of up to 1 in 352 frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows: * No corrections happen for the first five seconds. * Corrections of up to about 1 in 1000 for the next 25 seconds. * Corrections of up to 1 in 352 thereafter. @@ -599,7 +599,7 @@ Bug fix * The "pipe" backend used output code that would block if the pipe didn't have a reader. This has been replaced by non-blocking code. Here are some implications: * When the pipe is created, Shairport Sync will not block if a reader isn't present. * If the pipe doesn't have a reader when Shairport Sync wants to output to it, the output will be discarded. - * If a reader disappears while writing is occuring, the write will time out after five seconds. + * If a reader disappears while writing is occurring, the write will time out after five seconds. * Shairport Sync will only close the pipe on termination. Version 2.3.9 @@ -673,10 +673,10 @@ These releases were faulty and have been deleted. Version 2.3.1 ----- -Some big changes "under the hood" have been made, leading to limited support for unsynchronised output to `stdout` or to a named pipe and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided. +Some big changes "under the hood" have been made, leading to limited support for unsynchronised output to `stdout` or to a named pipe and continuation of de facto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided. In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`. -This gives more flexibility in adding features gives better compatability across different versions of Linux. +This gives more flexibility in adding features gives better compatibility across different versions of Linux. Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file. Note that, for the present, settings in the configuration will have priority over command line options for Shairport Sync itself, in contravention of the normal unix convention. Audio back end command line options, i.e. those after the `--`, have priority over configuration file settings for the audio backends. @@ -700,7 +700,7 @@ If you are using metadata, please note that the option has changed somewhat. The * Support for the `pipe` back end has been enhanced to provide raw 16-bit 44.1kHz stereo PCM output to a named pipe. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`. * Support for the `dummy` audio backend device continues. To activate, set `output_backend = "dummy"` in in the general section of the configuration. To include support for this back end, use the configuration option `--with-dummy`. * Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that no stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow. - * New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. The `audio_backend_buffer_desired_length` default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting, so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. If you're using an alsa back end and are using a hardware mixers, this isn't a problem. The `audio_backend_latency_offset` allows you emit frames to the audio back end some time before or after the synchronised time. This would be useful, for example, if you are outputting to a device that takes 20 ms to process audio; yoou would specify a `audio_backend_latency_offset = -882`, where 882 is the number of frames in 20 ms, to compensate for the device delay. + * New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. The `audio_backend_buffer_desired_length` default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting, so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. If you're using an alsa back end and are using a hardware mixers, this isn't a problem. The `audio_backend_latency_offset` allows you emit frames to the audio back end some time before or after the synchronised time. This would be useful, for example, if you are outputting to a device that takes 20 ms to process audio; you would specify a `audio_backend_latency_offset = -882`, where 882 is the number of frames in 20 ms, to compensate for the device delay. Version 2.3 ----- @@ -754,7 +754,7 @@ Version 2.2 ----- * Enhancements: * New password option: `--password=SECRET` - * New tolerance option: `--tolerance=FRAMES`. Use this option to specify the largest synchronisation error to allow before making corrections. The default is 88 frames, i.e. 2 milliseconds. The default tolerance is fine for streaming over wired ethernet; however, if some of the stream's path is via WiFi, or if the source is a third-party product, it may lead to much overcorrection -- i.e. the difference between "corrections" and "net correction" in the `--statistics` option. Increasing the tolerence may reduce the amount of overcorrection. + * New tolerance option: `--tolerance=FRAMES`. Use this option to specify the largest synchronisation error to allow before making corrections. The default is 88 frames, i.e. 2 milliseconds. The default tolerance is fine for streaming over wired ethernet; however, if some of the stream's path is via WiFi, or if the source is a third-party product, it may lead to much overcorrection -- i.e. the difference between "corrections" and "net correction" in the `--statistics` option. Increasing the tolerance may reduce the amount of overcorrection. Version 2.1.15 ----- @@ -809,7 +809,7 @@ With this feature, you can allow Shairport Sync always to advertise and provide * Annoying things you should know about if you're updating from a previous version: * Options `--with-openssl`, `--with-polarssl` have been replaced with a new option `--with-ssl= @@ -238,7 +238,7 @@ diff --git a/mdns_avahi.c b/mdns_avahi.c index 31c02bea..f8e75d17 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -179,7 +179,7 @@ static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, break; case AVAHI_ENTRY_GROUP_UNCOMMITED: - debug(2, "avahi: service '%s' group is not yet commited.", name); + debug(2, "avahi: service '%s' group is not yet committed.", name); break; case AVAHI_ENTRY_GROUP_REGISTERING: diff --git a/shairport.c b/shairport.c index adbbede5..55baf671 100644 --- a/shairport.c +++ b/shairport.c @@ -200,7 +200,7 @@ 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 " + printf("The following general options are for backward compatibility. 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"); From 9ebf2476108c5e94f3f4f17fc600b8e072b4fbb8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Apr 2017 15:20:30 +0100 Subject: [PATCH 052/239] Don't die if you can't change the volume control -- just report it --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index e3458cab..fd3ba087 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -913,7 +913,7 @@ static void volume(double vol) { debug(1, "Can't set playback volume accurately to %f dB.", vol); if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0) if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 1) != 0) - die("Failed to set playback dB volume"); + debug(1,"Could not set playback dB volume on the mixer."); } } } From b4025ae48925b335c3c8fa8c6f3c39fabcd1e41c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Apr 2017 15:22:21 +0100 Subject: [PATCH 053/239] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index a71a445c..63e8347b 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.1s3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.1s4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a5bfab12be54863d2550a672d9d508c124f60811 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 1 Apr 2017 15:24:00 +0100 Subject: [PATCH 054/239] Update RELEASENOTES.md --- RELEASENOTES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8c446100..9ee87457 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,8 @@ +Version 3.1s4 +==== +**Bug fix** +* If Shairport Sync tried to change a mixer level and failed, it would terminate. Now is just logs a debug report. + Version 3.1s3 ==== More extensive changes have been made to improve stability by making some operations reentrant. The instabilities occur sometimes when interrupting a play session by another. From b8700b3f1b1c3d35f135e3b8af5cfa36cfcfff48 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 18:31:47 +0100 Subject: [PATCH 055/239] Fully release the hardware device on flush. Simplify flushing. Also improve some debug messages. --- audio_alsa.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index fd3ba087..f843d0d9 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -520,7 +520,7 @@ int open_alsa_device(void) { } ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); if (ret < 0) { - die("audio_alsa: Sample format %d not available for device \"%s\": %s", sf, alsa_out_dev, + die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, alsa_out_dev, snd_strerror(ret)); } @@ -805,8 +805,17 @@ static void play(short buf[], int samples) { if (hardware_mixer) open_mixer(); pthread_mutex_unlock(&alsa_mutex); + // the mutex must be unlocked for the following call to proceed if ((hardware_mixer) && (ret == 0) && (audio_alsa.volume)) audio_alsa.volume(set_volume); + + pthread_mutex_lock(&alsa_mutex); + int derr; + if (hardware_mixer && alsa_mix_handle) { + // debug(1,"unmute"); + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); + } + pthread_mutex_unlock(&alsa_mutex); } if (ret == 0) { pthread_mutex_lock(&alsa_mutex); @@ -843,27 +852,48 @@ static void flush(void) { pthread_mutex_lock(&alsa_mutex); int derr; if (hardware_mixer && alsa_mix_handle) { + // debug(1,"Mute"); + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); snd_mixer_close(alsa_mix_handle); alsa_mix_handle = NULL; } if (alsa_handle) { // debug(1,"Dropping frames for flush..."); + /* if ((derr = snd_pcm_drop(alsa_handle))) debug(1, "Error dropping frames: \"%s\".", snd_strerror(derr)); // debug(1,"Dropped frames ok. State is %d.",snd_pcm_state(alsa_handle)); if ((derr = snd_pcm_prepare(alsa_handle))) debug(1, "Error preparing after flush: \"%s\".", snd_strerror(derr)); // debug(1,"Frames successfully dropped."); + */ + /* if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED) debug(1,"Flush returns to SND_PCM_STATE_PREPARED state."); if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING) debug(1,"Flush returns to SND_PCM_STATE_RUNNING state."); */ + /* 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)); + */ + + + + // this is derived from http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2latency_8c-example.html#a45 + + if ((derr=snd_pcm_nonblock(alsa_handle, 0))) + debug(1, "Error %d (\"%s\") unblocking output device.", derr, snd_strerror(derr)); + if ((derr=snd_pcm_drain(alsa_handle))) + debug(1, "Error %d (\"%s\") draining output device.", derr, snd_strerror(derr)); + if ((derr=snd_pcm_nonblock(alsa_handle, 1))) + debug(1, "Error %d (\"%s\") reblocking output device.", derr, snd_strerror(derr)); + if ((derr=snd_pcm_hw_free(alsa_handle))) + debug(1, "Error %d (\"%s\") freeing output device hardware.", derr, snd_strerror(derr)); + // flush also closes the device snd_pcm_close(alsa_handle); alsa_handle = NULL; @@ -888,7 +918,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(3, "Setting volume db to %f.", vol); set_volume = vol; if (hardware_mixer && alsa_mix_handle) { if (has_softvol) { From ec0bc81bafe04d03e55b3f12735f071011e8f9dd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 18:33:05 +0100 Subject: [PATCH 056/239] Improve error reporting if pipe can't be opened or written to. --- audio_pipe.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/audio_pipe.c b/audio_pipe.c index 22cac672..9b7d75c7 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -42,21 +42,36 @@ static int fd = -1; char *pipename = NULL; +int warned = 0; 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); + if ((fd<-1) && (warned==0)) { + warn("Error %d opening the pipe named \"%s\".",errno,pipename); + warned = 1; + } } static void play(short buf[], int samples) { // if the file is not open, try to open it. + char errorstring[1024]; if (fd == -1) { fd = open(pipename, O_WRONLY | O_NONBLOCK); } // if it's got a reader, write to it. - if (fd != -1) { - int ignore = non_blocking_write(fd, buf, samples * 4); - } + if (fd > 0) { + int rc = non_blocking_write(fd, buf, samples * 4); + if ((rc<0) && (warned==0)) { + strerror_r(errno,(char*)errorstring,1024); + warn("Error %d writing to the pipe named \"%s\": \"%s\".",errno,pipename,errorstring); + warned = 1; + } + } else if ((fd == -1) && (warned==0)) { + strerror_r(errno,(char*)errorstring,1024); + warn("Error %d opening the pipe named \"%s\": \"%s\".",errno,pipename,errorstring); + warned = 1; + } } static void stop(void) { From 8ab4f3050ef1b70ed377bba9dec6625f833cdf71 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 18:34:25 +0100 Subject: [PATCH 057/239] Improve error reporting if stdout can't be written to. --- audio_stdout.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/audio_stdout.c b/audio_stdout.c index 2e79f76b..ee4a81b3 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -40,7 +40,16 @@ static int fd = -1; 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) { + char errorstring[1024]; + int warned = 0; + int rc = write(fd, buf, samples * 4); + if ((rc<0) && (warned==0)) { + strerror_r(errno,(char*)errorstring,1024); + warn("Error %d writing to stdout: \"%s\".",errno,errorstring); + warned = 1; + } +} static void stop(void) { // don't close stdout From 9a8a0f0764c2867ac155d7fb64cc26e04cf9c8f6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 18:37:44 +0100 Subject: [PATCH 058/239] Make this threadsafe and modify the clock drift diagnostics --- rtp.c | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/rtp.c b/rtp.c index 15b4892c..921da780 100644 --- a/rtp.c +++ b/rtp.c @@ -391,10 +391,10 @@ void *rtp_timing_receiver(void *arg) { uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2; - unsigned int cc; + unsigned int cc,chosen; for (cc = time_ping_history - 1; cc > 0; cc--) { conn->time_pings[cc] = conn->time_pings[cc - 1]; - conn->time_pings[cc].dispersion = (conn->time_pings[cc].dispersion * 133) / + conn->time_pings[cc].dispersion = (conn->time_pings[cc].dispersion * 110) / 100; // make the dispersions 'age' by this rational factor } // these are for diagnostics only -- not used @@ -412,9 +412,11 @@ void *rtp_timing_receiver(void *arg) { // now pick the timestamp with the lowest dispersion uint64_t l2rtd = conn->time_pings[0].local_to_remote_difference; uint64_t tld = conn->time_pings[0].dispersion; + chosen = 0; for (cc = 1; cc < conn->time_ping_count; cc++) if (conn->time_pings[cc].dispersion < tld) { l2rtd = conn->time_pings[cc].local_to_remote_difference; + chosen = cc; tld = conn->time_pings[cc].dispersion; local_time_chosen = conn->time_pings[cc].local_time; remote_time_chosen = conn->time_pings[cc].remote_time; @@ -444,6 +446,7 @@ void *rtp_timing_receiver(void *arg) { } int64_t clock_drift, clock_drift_in_usec; + double clock_drift_ppm = 0.0; if (first_local_time == 0) { first_local_time = local_time_chosen; first_remote_time = remote_time_chosen; @@ -456,28 +459,30 @@ void *rtp_timing_receiver(void *arg) { 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; - else - 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); + clock_drift_ppm = (1.0*clock_drift_in_usec)/(local_time_change>>32); + } + int64_t source_drift_usec; - if (play_segment_reference_frame != 0) { + if (conn->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, conn); uint64_t frame_difference = 0; - if (reference_timestamp >= play_segment_reference_frame) - frame_difference = (uint64_t)reference_timestamp - (uint64_t)play_segment_reference_frame; + if (reference_timestamp >= conn->play_segment_reference_frame) + frame_difference = (uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame; else // rollover frame_difference = - (uint64_t)reference_timestamp + 0x100000000 - (uint64_t)play_segment_reference_frame; + (uint64_t)reference_timestamp + 0x100000000 - (uint64_t)conn->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 + conn->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, @@ -500,18 +505,14 @@ void *rtp_timing_receiver(void *arg) { // 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); + //debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec %10.1lld\treturn_time_in_usec %10.1llu", + //clock_drift_ppm, + //chosen, + //(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); From 6009d43abf061f0fdb3e8245d75bbdcc13de27dc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 18:39:09 +0100 Subject: [PATCH 059/239] Move the code to cehck for ignoring volume control to player.c --- rtsp.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/rtsp.c b/rtsp.c index 7120fd3e..5cb65328 100644 --- a/rtsp.c +++ b/rtsp.c @@ -671,7 +671,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * if (!rtsp_playing()) debug(1, "This RTSP conversation thread doesn't think it's playing, but " "it's sending a response to flush anyway"); - char *p; + char *p = NULL; uint32_t rtptime = 0; char *hdr = msg_get_header(req, "RTP-Info"); @@ -854,20 +854,8 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r if (!strncmp(cp, "volume: ", 8)) { float volume = atof(cp + 8); - if (config.ignore_volume_control == 0) { - debug(2, "volume: %f\n", volume); - player_volume(volume,conn); - } -#ifdef CONFIG_METADATA - else { // if ignore volume is on... - char *dv = malloc(128); // will be freed in the metadata thread - if (dv) { - memset(dv, 0, 128); - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", volume, 0.0, 0.0, 0.0); - send_ssnc_metadata('pvol', dv, strlen(dv), 1); - } - } -#endif + debug(3, "volume: %f\n", volume); + player_volume(volume,conn); } else #ifdef CONFIG_METADATA if (!strncmp(cp, "progress: ", 10)) { @@ -1293,7 +1281,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m // not all items have RTP-time stuff in them, which is okay if (!strncmp(ct, "application/x-dmap-tagged", 25)) { - debug(2, "received metadata tags in SET_PARAMETER request."); + debug(3, "received metadata tags in SET_PARAMETER request."); if (p == NULL) debug(1, "Missing RTP-Time info for metadata"); if (p) From 7860d692276a37d94aafb46defdf46a6be08e5a5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 18:44:11 +0100 Subject: [PATCH 060/239] Move some globals into the thread parameter block. Add setting for logOutputLevel --- common.h | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/common.h b/common.h index b615c16a..89f2901a 100644 --- a/common.h +++ b/common.h @@ -39,9 +39,9 @@ enum endian_type { } endian_type; enum stuffing_type { - ST_basic = 0, - ST_soxr, -} type; + ST_basic = 0, // straight deletion or insertion of a frame in a 352-frame packet + ST_soxr, // use libsoxr to make a 352 frame packet one frame longer or shorter + } type; enum playback_mode_type { ST_stereo = 0, @@ -56,7 +56,7 @@ enum decoders_supported_type { decoder_apple_alac, } decoders_supported_type; -// the following enum must _exactly match the snd_pcm_format_t definition in the alsa pcm.h file. +// the following enum is for the formats recognised -- currently only S16LE is recognised for input, so these are output only for the present enum sps_format_t { SPS_FORMAT_UNKNOWN = 0, @@ -97,7 +97,7 @@ typedef struct { 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. - int dont_check_timeout; // this is used to maintain backward compatibility with the old -t option + int dont_check_timeout; // this is used to maintain backward compatability with the old -t option // behaviour; only set by -t 0, cleared by everything else char *output_name; audio_output *output; @@ -110,6 +110,7 @@ typedef struct { int64_t AirPlayLatency; // supplied with --AirPlayLatency option int64_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option int daemonise; + int logOutputLevel; // log output level int statistics_requested, use_negotiated_latencies; enum playback_mode_type playback_mode; char *cmd_start, *cmd_stop; @@ -200,11 +201,6 @@ uint32_t uatoi(const char *nptr); shairport_cfg config; config_t config_file_stuff; -int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative -int64_t session_corrections; -int64_t play_segment_reference_frame; -uint64_t play_segment_reference_frame_remote_time; - void command_start(void); void command_stop(void); From 5855264eff7c8267bbbc489a8a1160b70446c855 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 18:45:28 +0100 Subject: [PATCH 061/239] Improve error reporting in non-blocking write code --- common.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common.c b/common.c index 0776407d..0a60a1af 100644 --- a/common.c +++ b/common.c @@ -647,9 +647,10 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) { // debug(1, "non-blocking write error waiting for pipe to become ready for writing..."); } else if (rc == 0) { // warn("non-blocking write timeout waiting for pipe to become ready for writing"); - rc = -2; + rc = -1; + errno = -ETIME; } else { // rc > 0, implying it might be ready - size_t bytes_written = write(fd, ibuf, bytes_remaining); + ssize_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; From 2d8fbb75727ed19dde1ee67f997bf3d7f36f320d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 19:00:02 +0100 Subject: [PATCH 062/239] Add new command line option --logOutputLevel --- shairport.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shairport.c b/shairport.c index 55baf671..3e34d60c 100644 --- a/shairport.c +++ b/shairport.c @@ -250,7 +250,8 @@ void usage(char *progname) { "88) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a " "password.\n"); -#ifdef CONFIG_METADATA + printf(" --logOutputLevel log the output level setting -- useful for setting maximum volume.\n"); + #ifdef CONFIG_METADATA printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. " "--metadata-pipename=/tmp/shairport-sync-metadata.\n"); printf(" The default is /tmp/shairport-sync-metadata.\n"); @@ -280,6 +281,7 @@ int parse_options(int argc, char **argv) { {"daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL}, {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL}, {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, + {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL}, {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL}, {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL}, {"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL}, @@ -492,7 +494,7 @@ int parse_options(int argc, char **argv) { /* 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); + // debug(1, "Max volume setting of %f dB", dvalue); config.volume_max_db = dvalue; config.volume_max_db_set = 1; } From bbf7857f88e4af56fb971586bdf4237cdf9feceb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 20:16:13 +0100 Subject: [PATCH 063/239] Fix issue where sound from the last play was still there with the next play. Make timing stuff threadsafe --- player.h | 72 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/player.h b/player.h index 8a3c9a32..0e58b3ba 100644 --- a/player.h +++ b/player.h @@ -23,7 +23,6 @@ #include "alac.h" #define time_ping_history 8 -#define time_ping_fudge_factor 100000 typedef struct time_ping_record { uint64_t local_to_remote_difference; @@ -105,42 +104,58 @@ typedef struct { AES_KEY aes; #endif -// RTP stuff -// only one RTP session can be active at a time. -int rtp_running; + int amountStuffed; -char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client -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 -uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope -short connection_ip_family; // AF_INET / AF_INET6 -uint32_t client_active_remote; // used when you want to control the client... + int32_t framesProcessedInThisEpoch; + int32_t framesGeneratedInThisEpoch; + int32_t correctionsRequestedInThisEpoch; + int64_t syncErrorsInThisEpoch; -SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client -SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client -int audio_socket; // our local [server] audio socket -int control_socket; // our local [server] control socket -int timing_socket; // local timing socket + // RTP stuff + // only one RTP session can be active at a time. + int rtp_running; -int64_t reference_timestamp; -uint64_t reference_timestamp_time; -uint64_t remote_reference_timestamp_time; + char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client + 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 + uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope + short connection_ip_family; // AF_INET / AF_INET6 + uint32_t client_active_remote; // used when you want to control the client... -// debug variables -int request_sent; + SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client + SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client + int audio_socket; // our local [server] audio socket + int control_socket; // our local [server] control socket + int timing_socket; // local timing socket -uint8_t time_ping_count; -struct time_ping_record time_pings[time_ping_history]; + int64_t reference_timestamp; + uint64_t reference_timestamp_time; + uint64_t remote_reference_timestamp_time; -uint64_t departure_time; // dangerous -- this assumes that there will never be two timing - // request in flight at the same time + // debug variables + int request_sent; -pthread_mutex_t reference_time_mutex; + uint8_t time_ping_count; + struct time_ping_record time_pings[time_ping_history]; -uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks + uint64_t departure_time; // dangerous -- this assumes that there will never be two timing + // request in flight at the same time -int timing_sender_stop; // for asking the timing-sending thread to stop + pthread_mutex_t reference_time_mutex; + uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks + + int timing_sender_stop; // for asking the timing-sending thread to stop + int last_stuff_request; + + int64_t play_segment_reference_frame; + uint64_t play_segment_reference_frame_remote_time; + + int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative + int64_t session_corrections; + + int play_number_after_flush; + } rtsp_conn_info; int player_play(pthread_t *thread, rtsp_conn_info* conn); @@ -157,7 +172,4 @@ int64_t monotonic_timestamp(uint32_t timestamp,rtsp_conn_info* conn); // add an // 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 From c991054055a0b2f89147d54b717f2112ceb6b631 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 20:18:03 +0100 Subject: [PATCH 064/239] Fix issue where sound from the last play was still there with the next play. Make timing stuff threadsaf, handle volume_max_db correctly when ignore_volume_control is true --- player.c | 192 +++++++++++++++++++++++++++---------------------------- 1 file changed, 95 insertions(+), 97 deletions(-) diff --git a/player.c b/player.c index e0bceea9..6012d01a 100644 --- a/player.c +++ b/player.c @@ -929,8 +929,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { debug(1, "Failed to allocate %d byte silence buffer.", fs); else { memset(silence, 0, conn->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)); + // debug(1,"Frames to start: %llu, DAC delay %ld, buffer: %d packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, conn->ab_write, conn->ab_read)); config.output->play(silence, fs); free(silence); have_sent_prefiller_silence = 1; @@ -942,9 +941,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) { if (conn->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, conn); - play_segment_reference_frame *= conn->output_sample_ratio; + get_reference_timestamp_stuff(&conn->play_segment_reference_frame, &reference_timestamp_time, + &conn->play_segment_reference_frame_remote_time, conn); + conn->play_segment_reference_frame *= conn->output_sample_ratio; #ifdef CONFIG_METADATA send_ssnc_metadata('prsm', NULL, 0, 0); // "resume", but don't wait if the queue is locked @@ -1150,7 +1149,7 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l } } pthread_mutex_unlock(&conn->vol_mutex); - + conn->amountStuffed=tstuff; return length + tstuff; } @@ -1239,6 +1238,7 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither,conn); }; } + conn->amountStuffed=tstuff; return length + tstuff; } #endif @@ -1317,8 +1317,9 @@ static void *player_thread_func(void *arg) { debug(1, "Output sample ratio is %d.", conn->output_sample_ratio); - conn->max_frame_size_change = 1 * conn->output_sample_ratio; // we add or subtract one frame at the nominal + conn->max_frame_size_change = 500 * conn->output_sample_ratio; // we add or subtract one frame at the nominal // rate, multiply it by the frame ratio. + // but, on some occasions, more than one frame could be added conn->output_bytes_per_frame = 4; switch (config.output_format) { @@ -1342,19 +1343,21 @@ static void *player_thread_func(void *arg) { pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void*) conn); pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void*) conn); - session_corrections = 0; - play_segment_reference_frame = 0; // zero signals that we are not in a play segment + conn->session_corrections = 0; + conn->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 + (int64_t)(config.audio_backend_latency_offset * config.output_rate); + int maximum_latency = config.latency + (int)(config.audio_backend_latency_offset * config.output_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); conn->connection_state_to_output = get_requested_connection_state_to_output(); // this is about half a minute -#define trend_interval 3758 +//#define trend_interval 3758 +#define trend_interval 1003 + stats_t statistics[trend_interval]; int number_of_statistics, oldest_statistic, newest_statistic; int at_least_one_frame_seen = 0; @@ -1364,14 +1367,16 @@ static void *player_thread_func(void *arg) { int64_t minimum_dac_queue_size = INT64_MAX; int32_t minimum_buffer_occupancy = INT32_MAX; int32_t maximum_buffer_occupancy = INT32_MIN; - + time_t playstart = time(NULL); - buffer_occupancy = 0; + conn->buffer_occupancy = 0; int play_samples; int64_t current_delay; int play_number = 0; + conn->play_number_after_flush = 0; +// int last_timestamp = 0; // for debugging only conn->time_of_last_audio_packet = 0; conn->shutdown_requested = 0; number_of_statistics = oldest_statistic = newest_statistic = 0; @@ -1455,6 +1460,11 @@ static void *player_thread_func(void *arg) { // 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 + + conn->framesProcessedInThisEpoch = 0; + conn->framesGeneratedInThisEpoch = 0; + conn->correctionsRequestedInThisEpoch = 0; + if (config.statistics_requested) { if ((config.output->delay)) { @@ -1501,13 +1511,22 @@ static void *player_thread_func(void *arg) { inbuflength = inframe->length; if (inbuf) { play_number++; + conn->play_number_after_flush++; if (inframe->timestamp == 0) { // debug(1,"Player has a supplied silent frame."); conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) & 0xffff); // manage the packet out of sequence minder config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); + } else if (conn->play_number_after_flush < 10) { + /* + int64_t difference = 0; + if (last_timestamp) + difference = inframe->timestamp - last_timestamp; + last_timestamp = inframe->timestamp; + debug(1, "Play number %d, monotonic timestamp %llx, difference %lld.",conn->play_number_after_flush,inframe->timestamp,difference); + */ + config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); } else { - int enable_dither = 0; if ((conn->fix_volume != 0x10000) || (conn->input_bit_depth > output_bit_depth)) enable_dither = 1; @@ -1561,27 +1580,6 @@ static void *player_thread_func(void *arg) { *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; - } - */ } } @@ -1656,13 +1654,13 @@ static void *player_thread_func(void *arg) { } } - buffer_occupancy = seq_diff(conn->ab_read, conn->ab_write, conn->ab_read); // int32_t from int32 + conn->buffer_occupancy = seq_diff(conn->ab_read, conn->ab_write, conn->ab_read); // int32_t from int32 - if (buffer_occupancy < minimum_buffer_occupancy) - minimum_buffer_occupancy = buffer_occupancy; + if (conn->buffer_occupancy < minimum_buffer_occupancy) + minimum_buffer_occupancy = conn->buffer_occupancy; - if (buffer_occupancy > maximum_buffer_occupancy) - maximum_buffer_occupancy = buffer_occupancy; + if (conn->buffer_occupancy > maximum_buffer_occupancy) + maximum_buffer_occupancy = conn->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. @@ -1697,6 +1695,11 @@ static void *player_thread_func(void *arg) { 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. + + // if positive, it means that the packet will be late -- the delay is longer than requested + // if negative, the packet will be early -- the delay is less than expected. + + sync_error = delay - (config.latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate)); // int64_t from int64_t - int32_t, so okay @@ -1731,20 +1734,22 @@ static void *player_thread_func(void *arg) { int64_t tp = (local_time_now - conn->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 amount_to_stuff = 0; } + */ } } if (config.no_sync != 0) amount_to_stuff = 0; // no stuffing if it's been disabled - // Apply DSP here if (config.loudness #ifdef CONFIG_CONVOLUTION @@ -1805,7 +1810,6 @@ static void *player_thread_func(void *arg) { } -#ifdef HAVE_LIBSOXR switch (config.packet_stuffing) { case ST_basic: // if (amount_to_stuff) debug(1,"Basic stuff..."); @@ -1813,19 +1817,15 @@ static void *player_thread_func(void *arg) { stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, outbuf, amount_to_stuff, enable_dither, conn); break; +#ifdef HAVE_LIBSOXR case ST_soxr: // if (amount_to_stuff) debug(1,"Soxr stuff..."); play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, (int32_t *)sbuf, inbuflength, config.output_format, outbuf, amount_to_stuff, enable_dither,conn); break; - } -#else - // if (amount_to_stuff) debug(1,"Standard stuff..."); - play_samples = - stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, outbuf, - amount_to_stuff, enable_dither,conn); #endif + } /* { @@ -1879,31 +1879,12 @@ static void *player_thread_func(void *arg) { } else { // if there is no delay procedure, or it's not working or not allowed, there can be no // synchronising - - if (conn->fix_volume == 0x10000) - - if (inbuf == NULL) - debug(1, "NULL inbuf to play -- skipping it."); - else { - if (inbuflength == 0) - debug(1, "empty frame to play -- skipping it (3)."); - else - config.output->play(inbuf, inbuflength); - } - else { - - play_samples = stuff_buffer_basic_32( - (int32_t *)tbuf, inbuflength, config.output_format, outbuf, 0, enable_dither,conn); - - if (outbuf == NULL) - debug(1, "NULL outbuf to play -- skipping it."); - else { - if (inbuf == 0) - debug(1, "empty frame to play -- skipping it (4)."); - else - config.output->play((short *)outbuf, play_samples); // remove the (short*)! - } - } + play_samples = stuff_buffer_basic_32( + (int32_t *)tbuf, inbuflength, config.output_format, outbuf, 0, enable_dither,conn); + if (outbuf == NULL) + debug(1, "NULL outbuf to play -- skipping it."); + else + config.output->play((short *)outbuf, play_samples); // remove the (short*)! } @@ -1935,7 +1916,7 @@ static void *player_thread_func(void *arg) { } statistics[newest_statistic].sync_error = sync_error; - statistics[newest_statistic].correction = amount_to_stuff; + statistics[newest_statistic].correction = conn->amountStuffed; if (number_of_statistics == 0) statistics[newest_statistic].drift = 0; @@ -1944,17 +1925,17 @@ static void *player_thread_func(void *arg) { sync_error - previous_sync_error - previous_correction; previous_sync_error = sync_error; - previous_correction = amount_to_stuff; + previous_correction = conn->amountStuffed; tsum_of_sync_errors += sync_error; tsum_of_drifts += statistics[newest_statistic].drift; - if (amount_to_stuff > 0) { - tsum_of_insertions_and_deletions += amount_to_stuff; + if (conn->amountStuffed > 0) { + tsum_of_insertions_and_deletions += conn->amountStuffed; } else { - tsum_of_insertions_and_deletions -= amount_to_stuff; + tsum_of_insertions_and_deletions -= conn->amountStuffed; } - tsum_of_corrections += amount_to_stuff; - session_corrections += amount_to_stuff; + tsum_of_corrections += conn->amountStuffed; + conn->session_corrections += conn->amountStuffed; newest_statistic = (newest_statistic + 1) % trend_interval; number_of_statistics++; @@ -1973,7 +1954,7 @@ static void *player_thread_func(void *arg) { if (at_least_one_frame_seen) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("%*.1f," /* Sync error in milliseconds */ + inform(" %*.1f," /* Sync error in milliseconds */ "%*.1f," /* net correction in ppm */ "%*.1f," /* corrections in ppm */ "%*d," /* total packets */ @@ -1984,7 +1965,7 @@ static void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, + 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ 1000 * moving_average_sync_error / config.output_rate, 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, moving_average_insertions_plus_deletions * 1000000 / @@ -1993,7 +1974,7 @@ static void *player_thread_func(void *arg) { 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } else { - inform("%*.1f," /* Sync error in milliseconds */ + inform(" %*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2002,14 +1983,14 @@ static void *player_thread_func(void *arg) { "%*lli," /* min DAC queue size */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, + 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { - inform("%*.1f," /* Sync error in milliseconds */ + inform(" %*.1f," /* Sync error in milliseconds */ "%*d," /* total packets */ "%*llu," /* missing packets */ "%*llu," /* late packets */ @@ -2017,7 +1998,7 @@ static void *player_thread_func(void *arg) { "%*llu," /* resend requests */ "%*d," /* min buffer occupancy */ "%*d", /* max buffer occupancy */ - 10, + 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); @@ -2048,12 +2029,6 @@ static void *player_thread_func(void *arg) { 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); - if (tbuf) - free(tbuf); - if (sbuf) - free(sbuf); debug(1, "Shut down audio, control and timing threads"); conn->please_stop = 1; pthread_kill(rtp_audio_thread, SIGUSR1); @@ -2081,6 +2056,14 @@ static void *player_thread_func(void *arg) { if (rc) debug(1, "Error destroying vol_mutex variable."); debug(1, "Player thread exit"); + if (outbuf) + free(outbuf); + if (silence) + free(silence); + if (tbuf) + free(tbuf); + if (sbuf) + free(sbuf); return 0; } @@ -2135,11 +2118,14 @@ void player_volume(double airplay_volume, rtsp_conn_info* conn) { 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 + int32_t desired_range_db = 0; // this is also 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); + // This is wrong, I think -- it doesn't work properly if the volume range is composite and you + // want to set a maximum value which should affect the hardware mixer. + if (config.volume_max_db_set) { if (hw_range_db) { if (((config.volume_max_db * 100) < hw_max_db) && @@ -2173,6 +2159,7 @@ void player_volume(double airplay_volume, rtsp_conn_info* conn) { max_db = hw_max_db; min_db = max_db - desired_range_db; } else { + // we have a hardware mixer and a desired range greater than the mixer's range. if ((hw_range_db + sw_range_db) < desired_range_db) { inform("The volume attenuation range %f is greater than can be accommodated by the " "hardware and software -- set to %f.", @@ -2220,7 +2207,7 @@ void player_volume(double airplay_volume, rtsp_conn_info* conn) { 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) { + if ((airplay_volume == -144.0) && (config.ignore_volume_control == 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; @@ -2231,7 +2218,10 @@ void player_volume(double airplay_volume, rtsp_conn_info* conn) { } else { if (config.output->mute) config.output->mute(0); // unmute mute if it's there - scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); + if (config.ignore_volume_control == 1) + scaled_attenuation = max_db; + else + 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) { @@ -2268,12 +2258,19 @@ void player_volume(double airplay_volume, rtsp_conn_info* conn) { if (config.loudness) loudness_set_volume(software_attenuation/100); + + if (config.logOutputLevel) { + inform("Output Level set to: %.2f dB.",scaled_attenuation / 100.0); + } #ifdef CONFIG_METADATA 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, + if (config.ignore_volume_control == 1) + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0); + else + 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); } @@ -2287,7 +2284,8 @@ void player_flush(int64_t timestamp, rtsp_conn_info* conn) { // if (timestamp!=0) conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this pthread_mutex_unlock(&conn->flush_mutex); - play_segment_reference_frame = 0; + conn->play_segment_reference_frame = 0; + conn->play_number_after_flush = 0; #ifdef CONFIG_METADATA send_ssnc_metadata('pfls', NULL, 0, 1); #endif From 953e33bdcc4e89606579335f41fea45b6dbdff55 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 7 Apr 2017 20:31:12 +0100 Subject: [PATCH 065/239] Add information about --logOutputLevel --- man/shairport-sync.7 | 5 ++++- man/shairport-sync.7.xml | 16 +++++++++++++--- man/shairport-sync.html | 10 ++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index c616ed68..1b460331 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 [--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 +\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 [--logOutputLevel=]\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 @@ -301,6 +301,9 @@ Print brief help message and exit. \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 +\fB--logOutputLevel\f1 +Use this to log the volume level when the volume is changed. It may be useful if you are trying to determine a suitable value for the maximum volume level. Not available as a configuration file setting. +.TP \fB-L | --latency=\f1\fIlatency\f1 Use this to set the \fIdefault latency\f1, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the \fIdefault latency\f1 is 88,200 frames, where there are 44,100 frames to the second. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index ef7bbebc..89cfcfbc 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -42,6 +42,7 @@ [-c configurationfile] [-E command] [--get-cover-art] + [--logOutputLevel=] [-L latency] [-m backend] [--meta-dir=directory] @@ -204,7 +205,7 @@ @@ -238,7 +239,7 @@ @@ -597,6 +598,15 @@

+ + + + + -

These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system. @@ -256,159 +279,113 @@

output_device="output_device";

- Use the output device called output_device. The default is the device called "default". +

Use the output device called output_device. The default is the device called "default".

mixer_type="mixer_type";

- This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting - -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware. +

This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting + -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware.

- - - + + + + +

These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.

+ + + + + + + + +

These settings are for the new PulseAudio backend.

+ +

These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, interleaved stereo.

-

Use the name 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 aplay. The audio_backend_latency_offset_in_seconds affects precisely - when the first audio packet is sent - and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.

-

These are the settings available within the pipe group:

- - - -

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 aplay. The audio_backend_latency_offset_in_seconds affects precisely when the first audio packet is sent - and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.

-

These are the settings available within the stdout group:

- - - - +

There are no settings for the STDOUT backend.

-

These settings are for the AO backend, used for the libao audio library.

-

There are two settings affecting timing. The audio_backend_latency_offset_in_seconds affects precisely when the first audio packet is sent - and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.

-

These are the settings available within the ao group:

+

There are no configuration file settings for the AO backend.

- - - -

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, @@ -421,33 +398,33 @@

enabled="choice";

- Set the choice to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it, - along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no". +

Set the choice to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it, + along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".

@@ -456,29 +433,29 @@
@@ -554,9 +531,9 @@

Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at - /var/run/shairport-sync.pid, which is used by the + /var/run/shairport-sync/shairport-sync.pid, which is used by the -k, -D and -R options to locate - the daemon at a later time. + the daemon at a later time. See also the -j option.

@@ -590,6 +567,14 @@

+ + -

mixer_type="mixer_type";

-

This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting - -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware.

@@ -335,12 +331,12 @@

These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.

@@ -483,7 +479,7 @@ diff --git a/man/shairport-sync.html b/man/shairport-sync.html index 5f3534a1..9a7055af 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -13,7 +13,7 @@ [-c configurationfile] [-E command] [--get-cover-art] - [--logOutputLevel=] + [--logOutputLevel] [-L latency] [-m backend] [--meta-dir=directory] @@ -69,7 +69,7 @@ i.e. the System Configuration Directory.)

-

Settings are organised into groups, for example, there is a "general" group of +

Within the configuraton file, settings are organised into groups, for example, there is a "general" group of standard settings, and there is an "alsa" group with settings that pertain to the ALSA back end. Here is an example of a typical configuration file:

@@ -77,6 +77,7 @@

name = "Mike's Boombox";

interpolation = "soxr";

password = "secret";

+

output_backend = "alsa";

};

alsa = {

@@ -263,16 +264,11 @@

Use the output device called output_device. The default is the device called "default".

-

mixer_control_name="name";

Specify the name of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. If you do not specify a mixer control name, shairport-sync will adjust the volume in software.

-

mixer_type="mixer_type";

-

This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting - -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware.

-

mixer_device="mixer_device";

By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.

@@ -307,7 +303,7 @@

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".

+

Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes".

@@ -316,12 +312,12 @@

These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.

device="snd/0";

-

Use this optional setting to specify the name of the output device, e.g. "snd/0".

+

Use this optional setting to specify the name of the output device, e.g. "snd/0". The default is to use the SNDIO system's default.

rate=44100;

Use this optional setting to specify the output rate in frames per second. Valid rates are 44100, 88200, 176400 or 352800. - The output device must have the capability to accept data at the specified rate. Default is 44100.

+ The output device must have the capability to accept data at the specified rate. The default is 44100.

format="S16";

@@ -347,7 +343,7 @@

These settings are for the new PulseAudio backend.

application_name="Shairport Sync";

-

Use this to set the name to appear in the Sounds "Applications" tab when Shairport Sync is active Default is "Shairport Sync".

+

Use this to set the name to appear in the Sounds "Applications" tab when Shairport Sync is active. The default is the name "Shairport Sync".

"PIPE" SETTINGS

@@ -468,7 +464,7 @@

-a service name | --name=service name

- Use this service name to identify this player in iTunes, etc. + Use this service name to identify this player in iTunes, etc.

The following substitutions are allowed: %h for the computer's hostname, @@ -476,7 +472,7 @@ %v for the shairport-sync version number, e.g. "3.0.1" and %V 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.

-

+ @@ -611,8 +607,7 @@

-o outputbackend | --output=outputbackend

Force the use of the specified output backend to play the audio. - The default is to try the first one. (This is not used at - present.) + The default is to try the first one.

From e513e5330c736692de8c06c3e465a4114d064d0f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 31 Jul 2017 11:57:57 +0200 Subject: [PATCH 226/239] Tidy up source using clang-format --- audio.c | 15 ++--- audio_alsa.c | 154 +++++++++++++++++++++++++----------------------- audio_ao.c | 7 +-- audio_pa.c | 82 ++++++++++++-------------- audio_pipe.c | 23 ++++---- audio_pulse.c | 3 +- audio_sndio.c | 40 ++++++------- audio_soundio.c | 8 +-- audio_stdout.c | 12 ++-- common.c | 58 +++++++++--------- common.h | 26 ++++---- loudness.c | 43 ++++++-------- loudness.h | 2 - mdns.h | 4 +- player.c | 75 +++++++++++++---------- player.h | 77 ++++++++++++------------ rtp.c | 100 ++++++++++++++++--------------- rtp.h | 16 ++--- rtsp.c | 73 ++++++++++++----------- shairport.c | 103 +++++++++++++++++--------------- 20 files changed, 472 insertions(+), 449 deletions(-) diff --git a/audio.c b/audio.c index a1fcf614..8fbea259 100644 --- a/audio.c +++ b/audio.c @@ -24,8 +24,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "common.h" #include "audio.h" +#include "common.h" #include "config.h" #include #include @@ -117,7 +117,8 @@ void audio_ls_outputs(void) { } void parse_general_audio_options(void) { - /* this must be called after the output device has been initialised, so that the default values are set before any options are chosen */ + /* this must be called after the output device has been initialised, so that the default values + * are set before any options are chosen */ int value; double dvalue; if (config.cfg != NULL) { @@ -130,7 +131,7 @@ void parse_general_audio_options(void) { die("Invalid audio_backend_buffer_desired_length value: \"%d\". It " "should be between 0 and " "66150, default is %d", - value,(int)(config.audio_backend_buffer_desired_length*44100)); + value, (int)(config.audio_backend_buffer_desired_length * 44100)); } else { inform("The setting general.audio_backend_buffer_desired_length is deprecated. " "Use general.audio_backend_buffer_desired_length_in_seconds instead."); @@ -145,7 +146,7 @@ void parse_general_audio_options(void) { die("Invalid audio_backend_buffer_desired_length_in_seconds value: \"%f\". It " "should be between 0 and " "1.5, default is %.3f seconds", - dvalue,config.audio_backend_buffer_desired_length); + dvalue, config.audio_backend_buffer_desired_length); } else { config.audio_backend_buffer_desired_length = dvalue; } @@ -167,7 +168,8 @@ void parse_general_audio_options(void) { } /* Get the latency offset in seconds. */ - if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds", &dvalue)) { + if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds", + &dvalue)) { if ((dvalue < -1.0) || (dvalue > 1.5)) { die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It " "should be between -1.0 and +1.5, default is 0 seconds", @@ -178,8 +180,7 @@ void parse_general_audio_options(void) { } /* Get the desired length of the silent lead-in. */ - if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", - &dvalue)) { + if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", &dvalue)) { if ((dvalue < 0.0) || (dvalue > 4)) { die("Invalid audio_backend_silent_lead_in_time \"%f\". It " "must be between 0.0 and 4.0 seconds. Omit setting to use the default value", diff --git a/audio_alsa.c b/audio_alsa.c index cd91e046..f880d1f7 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -53,18 +53,19 @@ 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 = NULL, // a function will be provided if it can, and is allowed to, do hardware mute - .volume = NULL, // a function will be provided if it can do hardware 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 = NULL, // a function will be provided if it can, and is allowed to, do hardware mute + .volume = NULL, // a function will be provided if it can do hardware volume + .parameters = ¶meters}; static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -87,8 +88,8 @@ static char *alsa_mix_ctrl = "Master"; static int alsa_mix_index = 0; static int hardware_mixer = 0; static int has_softvol = 0; -static int volume_set_request = 0; // set when an external request is made to set the volume. -int mute_request_pending = 0; // set when an external request is made to mute or unmute. +static int volume_set_request = 0; // set when an external request is made to set the volume. +int mute_request_pending = 0; // set when an external request is made to mute or unmute. int overriding_mute_state_requested = 0; // 1 = mute; 0 = unmute requested static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, @@ -109,9 +110,7 @@ static void help(void) { " *) default option\n"); } -void set_alsa_out_dev(char *dev) { - alsa_out_dev = dev; -} +void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; } int open_mixer() { if (hardware_mixer) { @@ -138,18 +137,20 @@ int open_mixer() { die("Failed to find mixer element"); return 1; } else { - return 0; + return 0; } } void close_mixer() { - if (alsa_mix_handle) { - snd_mixer_close(alsa_mix_handle); - alsa_mix_handle=NULL; - } + if (alsa_mix_handle) { + snd_mixer_close(alsa_mix_handle); + alsa_mix_handle = NULL; + } } -#define RELEASE_ALSA_MUTEX_AND_DIE(X) pthread_mutex_unlock(&alsa_mutex);die(X); +#define RELEASE_ALSA_MUTEX_AND_DIE(X) \ + pthread_mutex_unlock(&alsa_mutex); \ + die(X); static int init(int argc, char **argv) { pthread_mutex_lock(&alsa_mutex); @@ -164,11 +165,11 @@ static int init(int argc, char **argv) { config.alsa_use_playback_switch_for_mute = 1; config.audio_backend_latency_offset = 0; - config.audio_backend_buffer_desired_length = 0.15; + config.audio_backend_buffer_desired_length = 0.15; // get settings from settings file first, allow them to be overridden by // command line options - + // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); @@ -209,7 +210,7 @@ static int init(int argc, char **argv) { die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\""); } } - + /* Get the mute_using_playback_switch setting. */ if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) { if (strcasecmp(str, "no") == 0) @@ -218,10 +219,11 @@ static int init(int argc, char **argv) { config.alsa_use_playback_switch_for_mute = 1; else { pthread_mutex_unlock(&alsa_mutex); - die("Invalid mute_use_playback_switch option choice \"%s\". It should be \"yes\" or \"no\""); + die("Invalid mute_use_playback_switch 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) @@ -413,15 +415,16 @@ static int init(int argc, char **argv) { */ } } - if ((config.alsa_use_playback_switch_for_mute==1) && (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) { + if ((config.alsa_use_playback_switch_for_mute == 1) && + (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 // debug(1, "Has mixer and mute ability we will use."); } else { - // debug(1, "Has mixer but not using hardware mute."); - } + // debug(1, "Has mixer but not using hardware mute."); + } close_mixer(); } else { - // debug(1, "Has no mixer and thus no hardware mute."); + // debug(1, "Has no mixer and thus no hardware mute."); } alsa_mix_handle = NULL; @@ -435,7 +438,7 @@ static void deinit(void) { } int open_alsa_device(void) { -//the alsa mutex is already acquired when this is called + // the alsa mutex is already acquired when this is called const snd_pcm_uframes_t minimal_buffer_headroom = 352 * 2; // we accept this much headroom in the hardware buffer, but we'll @@ -467,7 +470,8 @@ int open_alsa_device(void) { ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); if (ret < 0) { - pthread_mutex_unlock(&alsa_mutex);; + pthread_mutex_unlock(&alsa_mutex); + ; die("audio_alsa: Broken configuration for device \"%s\": no configurations " "available", alsa_out_dev); @@ -527,8 +531,8 @@ int open_alsa_device(void) { ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); if (ret < 0) { pthread_mutex_unlock(&alsa_mutex); - die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, alsa_out_dev, - snd_strerror(ret)); + die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, + alsa_out_dev, snd_strerror(ret)); } ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); @@ -816,10 +820,10 @@ static void play(short buf[], int samples) { pthread_mutex_lock(&alsa_mutex); ret = open_alsa_device(); if (ret == 0) { - if (audio_alsa.volume) - do_volume(set_volume); - if (audio_alsa.mute) - do_mute(0); + if (audio_alsa.volume) + do_volume(set_volume); + if (audio_alsa.mute) + do_mute(0); } pthread_mutex_unlock(&alsa_mutex); } @@ -831,7 +835,7 @@ static void play(short buf[], int samples) { if ((err = snd_pcm_prepare(alsa_handle))) { ignore = snd_pcm_recover(alsa_handle, err, 1); debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); - } + } } if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { @@ -874,7 +878,7 @@ static void flush(void) { debug(1, "Error preparing after flush: \"%s\".", snd_strerror(derr)); // debug(1,"Frames successfully dropped."); */ - + /* if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED) debug(1,"Flush returns to SND_PCM_STATE_PREPARED state."); @@ -886,21 +890,20 @@ static void flush(void) { (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING))) debug(1, "Flush returning unexpected state -- %d.", snd_pcm_state(alsa_handle)); */ - - - - // this is derived from http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2latency_8c-example.html#a45 - - if ((derr=snd_pcm_nonblock(alsa_handle, 0))) + + // this is derived from + // http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2latency_8c-example.html#a45 + + if ((derr = snd_pcm_nonblock(alsa_handle, 0))) debug(1, "Error %d (\"%s\") unblocking output device.", derr, snd_strerror(derr)); - if ((derr=snd_pcm_drain(alsa_handle))) + if ((derr = snd_pcm_drain(alsa_handle))) debug(1, "Error %d (\"%s\") draining output device.", derr, snd_strerror(derr)); - if ((derr=snd_pcm_nonblock(alsa_handle, 1))) + if ((derr = snd_pcm_nonblock(alsa_handle, 1))) debug(1, "Error %d (\"%s\") reblocking output device.", derr, snd_strerror(derr)); - if ((derr=snd_pcm_hw_free(alsa_handle))) + if ((derr = snd_pcm_hw_free(alsa_handle))) debug(1, "Error %d (\"%s\") freeing output device hardware.", derr, snd_strerror(derr)); - + // flush also closes the device snd_pcm_close(alsa_handle); alsa_handle = NULL; @@ -949,7 +952,7 @@ void do_volume(double vol) { // caller is assumed to have the alsa_mutex when us debug(1, "Can't set playback volume accurately to %f dB.", vol); if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0) if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 1) != 0) - debug(1,"Could not set playback dB volume on the mixer."); + debug(1, "Could not set playback dB volume on the mixer."); } } volume_set_request = 0; // any external request that has been made is now satisfied @@ -994,31 +997,32 @@ static void mute(int mute_state_requested) { void do_mute(int mute_state_requested) { - // if a mute is requested now, then - // if an external mute request is in place, leave everything muted - // otherwise, if an external mute request is pending, action it - // otherwise, action the do_mute request - - int local_mute_state_requested = overriding_mute_state_requested; // go with whatever was asked by the external "mute" call - + // if a mute is requested now, then + // if an external mute request is in place, leave everything muted + // otherwise, if an external mute request is pending, action it + // otherwise, action the do_mute request + + int local_mute_state_requested = + overriding_mute_state_requested; // go with whatever was asked by the external "mute" call + // The mute state requested will be actioned unless mute_request_pending is set // If it is set, then that the pending request will be actioned. // If the hardware isn't there, or we are not allowed to use it, nothing will be done // The caller must have the alsa mutex - if (config.alsa_use_playback_switch_for_mute==1) { - if (mute_request_pending==0) - local_mute_state_requested = mute_state_requested; - if (open_mixer()) { - if (local_mute_state_requested) { - // debug(1,"Playback Switch mute actually done"); - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); - } else if (overriding_mute_state_requested==0) { - // debug(1,"Playback Switch unmute actually done"); - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); - } - close_mixer(); - } - } - mute_request_pending = 0; + if (config.alsa_use_playback_switch_for_mute == 1) { + if (mute_request_pending == 0) + local_mute_state_requested = mute_state_requested; + if (open_mixer()) { + if (local_mute_state_requested) { + // debug(1,"Playback Switch mute actually done"); + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); + } else if (overriding_mute_state_requested == 0) { + // debug(1,"Playback Switch unmute actually done"); + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); + } + close_mixer(); + } + } + mute_request_pending = 0; } diff --git a/audio_ao.c b/audio_ao.c index 49ff4efe..78fbea3f 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -47,7 +47,7 @@ static int init(int argc, char **argv) { ao_initialize(); int driver = ao_default_driver_id(); ao_option *ao_opts = NULL; - + // set up default values first config.audio_backend_buffer_desired_length = 1.0; @@ -55,7 +55,7 @@ static int init(int argc, char **argv) { // get settings from settings file first, allow them to be overridden by // command line options - + // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); @@ -118,8 +118,7 @@ static void deinit(void) { ao_shutdown(); } -static void start(int sample_rate, int sample_format) { -} +static void start(int sample_rate, int sample_format) {} static void play(short buf[], int samples) { ao_play(dev, (char *)buf, samples * 4); } diff --git a/audio_pa.c b/audio_pa.c index 451d4d78..76a2eb13 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -39,15 +39,14 @@ void stream_state_cb(pa_stream *s, void *mainloop); void stream_success_cb(pa_stream *stream, int success, void *userdata); void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata); - static int init(int argc, char **argv) { // set up default values first config.audio_backend_buffer_desired_length = 0.35; config.audio_backend_latency_offset = 0; - + // get settings from settings file - + // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); @@ -60,10 +59,9 @@ static int init(int argc, char **argv) { config.pa_application_name = (char *)str; } } - - + // finish collecting settings - + // allocate space for the audio buffer audio_lmb = malloc(audio_size); if (audio_lmb == NULL) @@ -71,7 +69,7 @@ static int init(int argc, char **argv) { audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + audio_size; audio_occupancy = 0; - + // Get a mainloop and its context mainloop = pa_threaded_mainloop_new(); assert(mainloop); @@ -79,7 +77,7 @@ static int init(int argc, char **argv) { if (config.pa_application_name) context = pa_context_new(mainloop_api, config.pa_application_name); else - context = pa_context_new(mainloop_api, "Shairport Sync"); + context = pa_context_new(mainloop_api, "Shairport Sync"); assert(context); // Set a callback so we can wait for the context to be ready @@ -135,9 +133,9 @@ static void start(int sample_rate, int sample_format) { // recommended settings, i.e. server uses sensible values pa_buffer_attr buffer_attr; - buffer_attr.maxlength = (uint32_t) -1; + buffer_attr.maxlength = (uint32_t)-1; buffer_attr.tlength = buffer_size_in_bytes; - buffer_attr.prebuf = (uint32_t) 0; + buffer_attr.prebuf = (uint32_t)0; buffer_attr.minreq = (uint32_t)-1; // Settings copied as per the chromium browser source @@ -145,7 +143,7 @@ static void start(int sample_rate, int sample_format) { stream_flags = PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_NOT_MONOTONIC | // PA_STREAM_AUTO_TIMING_UPDATE; PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY; - + // Connect stream to the default audio output sink assert(pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) == 0); @@ -187,7 +185,6 @@ static void play(short buf[], int samples) { pa_threaded_mainloop_lock(mainloop); pa_stream_cork(stream, 0, stream_success_cb, mainloop); pa_threaded_mainloop_unlock(mainloop); - } } @@ -216,7 +213,7 @@ int pa_delay(long *the_delay) { void flush(void) { // Cork the stream so it will stop playing pa_threaded_mainloop_lock(mainloop); - if (pa_stream_is_corked(stream)==0) { + if (pa_stream_is_corked(stream) == 0) { // debug(1,"Flush and cork for flush."); pa_stream_flush(stream, stream_success_cb, NULL); pa_stream_cork(stream, 1, stream_success_cb, mainloop); @@ -230,7 +227,7 @@ void flush(void) { static void stop(void) { // Cork the stream so it will stop playing pa_threaded_mainloop_lock(mainloop); - if (pa_stream_is_corked(stream)==0) { + if (pa_stream_is_corked(stream) == 0) { // debug(1,"Flush and cork for stop."); pa_stream_flush(stream, stream_success_cb, NULL); pa_stream_cork(stream, 1, stream_success_cb, mainloop); @@ -244,9 +241,7 @@ static void stop(void) { pa_stream_disconnect(stream); } -static void help(void) { - printf(" no settings.\n"); -} +static void help(void) { printf(" no settings.\n"); } audio_output audio_pa = {.name = "pa", .help = &help, @@ -269,35 +264,35 @@ void stream_state_cb(pa_stream *s, void *mainloop) { pa_threaded_mainloop_signal void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata) { -/* - // play with timing information - const struct pa_timing_info *ti = pa_stream_get_timing_info(stream); - if ((ti == NULL) || (ti->write_index_corrupt)) { - debug(2, "Timing info invalid"); - } else { - struct timeval time_now; + /* + // play with timing information + const struct pa_timing_info *ti = pa_stream_get_timing_info(stream); + if ((ti == NULL) || (ti->write_index_corrupt)) { + debug(2, "Timing info invalid"); + } else { + struct timeval time_now; - pa_gettimeofday(&time_now); + pa_gettimeofday(&time_now); - uint64_t time_now_fp = ((uint64_t)time_now.tv_sec << 32) + - ((uint64_t)time_now.tv_usec << 32) / 1000000; // types okay - uint64_t time_of_ti_fp = ((uint64_t)(ti->timestamp.tv_sec) << 32) + - ((uint64_t)(ti->timestamp.tv_usec) << 32) / 1000000; // types okay + uint64_t time_now_fp = ((uint64_t)time_now.tv_sec << 32) + + ((uint64_t)time_now.tv_usec << 32) / 1000000; // types okay + uint64_t time_of_ti_fp = ((uint64_t)(ti->timestamp.tv_sec) << 32) + + ((uint64_t)(ti->timestamp.tv_usec) << 32) / 1000000; // types okay - if (time_now_fp >= time_of_ti_fp) { - uint64_t estimate_age = ((time_now_fp - time_of_ti_fp) * 1000000) >> 32; - uint64_t bytes_in_buffer = ti->write_index - ti->read_index; - pa_usec_t microseconds_to_write_buffer = (bytes_in_buffer * 1000000) / (44100 * 2 * 2); - pa_usec_t ea = (pa_usec_t)estimate_age; - pa_usec_t pa_latency = ti->sink_usec + ti->transport_usec + microseconds_to_write_buffer; - pa_usec_t estimated_latency = pa_latency - estimate_age; - // debug(1,"Estimated latency is %d microseconds.",estimated_latency); + if (time_now_fp >= time_of_ti_fp) { + uint64_t estimate_age = ((time_now_fp - time_of_ti_fp) * 1000000) >> 32; + uint64_t bytes_in_buffer = ti->write_index - ti->read_index; + pa_usec_t microseconds_to_write_buffer = (bytes_in_buffer * 1000000) / (44100 * 2 * 2); + pa_usec_t ea = (pa_usec_t)estimate_age; + pa_usec_t pa_latency = ti->sink_usec + ti->transport_usec + microseconds_to_write_buffer; + pa_usec_t estimated_latency = pa_latency - estimate_age; + // debug(1,"Estimated latency is %d microseconds.",estimated_latency); -// } else { -// debug(1, "Time now is earlier than time of timing information"); + // } else { + // debug(1, "Time now is earlier than time of timing information"); + } } - } -*/ + */ int bytes_to_transfer = requested_bytes; int bytes_transferred = 0; uint8_t *buffer = NULL; @@ -307,13 +302,13 @@ void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata) if (audio_occupancy < bytes_we_can_transfer) { // debug(1, "Underflow? We have %d bytes but we are asked for %d bytes", audio_occupancy, // bytes_we_can_transfer); - pa_stream_cork(stream,1, stream_success_cb, mainloop); + pa_stream_cork(stream, 1, stream_success_cb, mainloop); // debug(1, "Corked"); bytes_we_can_transfer = audio_occupancy; } // bytes we can transfer will never be greater than the bytes available - + pa_stream_begin_write(stream, (void **)&buffer, &bytes_we_can_transfer); if (bytes_we_can_transfer <= (audio_umb - audio_toq)) { // the bytes are all in a row in the audo buffer @@ -375,4 +370,3 @@ void alt_stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userda } void stream_success_cb(pa_stream *stream, int success, void *userdata) { return; } - diff --git a/audio_pipe.c b/audio_pipe.c index 7ab4969d..131244c1 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -47,8 +47,8 @@ int warned = 0; 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); - if ((fd<-1) && (warned==0)) { - warn("Error %d opening the pipe named \"%s\".",errno,pipename); + if ((fd < -1) && (warned == 0)) { + warn("Error %d opening the pipe named \"%s\".", errno, pipename); warned = 1; } } @@ -62,16 +62,16 @@ static void play(short buf[], int samples) { // if it's got a reader, write to it. if (fd > 0) { int rc = non_blocking_write(fd, buf, samples * 4); - if ((rc<0) && (warned==0)) { - strerror_r(errno,(char*)errorstring,1024); - warn("Error %d writing to the pipe named \"%s\": \"%s\".",errno,pipename,errorstring); + if ((rc < 0) && (warned == 0)) { + strerror_r(errno, (char *)errorstring, 1024); + warn("Error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring); warned = 1; } - } else if ((fd == -1) && (warned==0)) { - strerror_r(errno,(char*)errorstring,1024); - warn("Error %d opening the pipe named \"%s\": \"%s\".",errno,pipename,errorstring); + } else if ((fd == -1) && (warned == 0)) { + strerror_r(errno, (char *)errorstring, 1024); + warn("Error %d opening the pipe named \"%s\": \"%s\".", errno, pipename, errorstring); warned = 1; - } + } } static void stop(void) { @@ -90,7 +90,7 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; - + // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); @@ -100,10 +100,9 @@ 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."); - } if ((pipename == NULL) && (argc != 1)) diff --git a/audio_pulse.c b/audio_pulse.c index 7d117980..96d7c320 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -110,8 +110,7 @@ static void deinit(void) { pa_dev = NULL; } -static void start(int sample_rate, int sample_format) { -} +static void start(int sample_rate, int sample_format) {} static void play(short buf[], int samples) { if (pa_simple_write(pa_dev, (char *)buf, (size_t)samples * 4, &pa_error) < 0) { diff --git a/audio_sndio.c b/audio_sndio.c index e7fd787b..5d980b7e 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -71,23 +71,20 @@ struct sndio_formats { unsigned int le; }; -static struct sndio_formats formats[] = { - {"S8", SPS_FORMAT_S8, 8, 1, 1, SIO_LE_NATIVE}, - {"U8", SPS_FORMAT_U8, 8, 1, 0, SIO_LE_NATIVE}, - {"S16", SPS_FORMAT_S16, 16, 2, 1, SIO_LE_NATIVE}, - {"S24", SPS_FORMAT_S24, 24, 4, 1, SIO_LE_NATIVE}, - {"S24_3LE", SPS_FORMAT_S24_3LE, 24, 3, 1, 1}, - {"S24_3BE", SPS_FORMAT_S24_3BE, 24, 3, 1, 0}, - {"S32", SPS_FORMAT_S32, 24, 4, 1, SIO_LE_NATIVE}}; +static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 8, 1, 1, SIO_LE_NATIVE}, + {"U8", SPS_FORMAT_U8, 8, 1, 0, SIO_LE_NATIVE}, + {"S16", SPS_FORMAT_S16, 16, 2, 1, SIO_LE_NATIVE}, + {"S24", SPS_FORMAT_S24, 24, 4, 1, SIO_LE_NATIVE}, + {"S24_3LE", SPS_FORMAT_S24_3LE, 24, 3, 1, 1}, + {"S24_3BE", SPS_FORMAT_S24_3BE, 24, 3, 1, 0}, + {"S32", SPS_FORMAT_S32, 24, 4, 1, SIO_LE_NATIVE}}; -static void help() { - printf(" -d output-device set the output device [default*|...]\n"); -} +static void help() { printf(" -d output-device set the output device [default*|...]\n"); } static int init(int argc, char **argv) { int i, found, opt, round, rate, bufsz; const char *devname, *tmp; - + // set up default values first sio_initpar(&par); @@ -98,17 +95,17 @@ static int init(int argc, char **argv) { par.le = 1; par.sig = 1; devname = SIO_DEVANY; - + config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; - // get settings from settings file - + // get settings from settings file + // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); // get the specific settings - + if (config.cfg != NULL) { if (!config_lookup_string(config.cfg, "sndio.device", &devname)) devname = SIO_DEVANY; @@ -186,8 +183,8 @@ static int init(int argc, char **argv) { if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) die("sndio: failed to set audio parameters"); for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { - if (formats[i].bits == par.bits && formats[i].bps == par.bps && - formats[i].sig == par.sig && formats[i].le == par.le) { + if (formats[i].bits == par.bits && formats[i].bps == par.bps && formats[i].sig == par.sig && + formats[i].le == par.le) { config.output_format = formats[i].fmt; found = 1; break; @@ -255,11 +252,12 @@ static int delay(long *_delay) { // and use it to estimate the frames that would have been output uint64_t time_difference = get_absolute_time_in_fp() - time_of_last_onmove_cb; uint64_t frame_difference = time_difference * par.rate; - uint64_t frame_difference_big_integer = frame_difference>>32; + uint64_t frame_difference_big_integer = frame_difference >> 32; estimated_extra_frames_output = frame_difference_big_integer; - // debug(1,"Frames played to last cb: %d, estimated to current time: %d.",played,estimated_extra_frames_output); + // debug(1,"Frames played to last cb: %d, estimated to current time: + // %d.",played,estimated_extra_frames_output); } - *_delay = (written / framesize) - (played+estimated_extra_frames_output); + *_delay = (written / framesize) - (played + estimated_extra_frames_output); pthread_mutex_unlock(&sndio_mutex); return 0; } diff --git a/audio_soundio.c b/audio_soundio.c index 7376f70b..6960fa35 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -84,17 +84,17 @@ static void underflow_callback(struct SoundIoOutStream *outstream) { static int init(int argc, char **argv) { int err; - + config.audio_backend_buffer_desired_length = 2.0; config.audio_backend_latency_offset = 0; - // get settings from settings file - + // get settings from settings file + // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); // get the specific settings - + soundio = soundio_create(); if (!soundio) { debug(0, "out of memory\n"); diff --git a/audio_stdout.c b/audio_stdout.c index 66b9b3bd..a97df679 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -43,10 +43,10 @@ static void start(int sample_rate, int sample_format) { fd = STDOUT_FILENO; } static void play(short buf[], int samples) { char errorstring[1024]; int warned = 0; - int rc = write(fd, buf, samples * 4); - if ((rc<0) && (warned==0)) { - strerror_r(errno,(char*)errorstring,1024); - warn("Error %d writing to stdout: \"%s\".",errno,errorstring); + int rc = write(fd, buf, samples * 4); + if ((rc < 0) && (warned == 0)) { + strerror_r(errno, (char *)errorstring, 1024); + warn("Error %d writing to stdout: \"%s\".", errno, errorstring); warned = 1; } } @@ -59,8 +59,8 @@ static int init(int argc, char **argv) { // set up default values first config.audio_backend_buffer_desired_length = 1.0; config.audio_backend_latency_offset = 0; - - // get settings from settings file + + // get settings from settings file // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); return 0; diff --git a/common.c b/common.c index fedff727..8f615be4 100644 --- a/common.c +++ b/common.c @@ -464,41 +464,40 @@ void command_set_volume(double volume) { /*Spawn a child to run the program.*/ pid_t pid = fork(); if (pid == 0) { /* child process */ - size_t command_buffer_size = strlen(config.cmd_set_volume)+32; - char* command_buffer = (char*)malloc(command_buffer_size); - if (command_buffer==NULL) { - inform("Couldn't allocate memory for set_volume argument string"); + size_t command_buffer_size = strlen(config.cmd_set_volume) + 32; + char *command_buffer = (char *)malloc(command_buffer_size); + if (command_buffer == NULL) { + inform("Couldn't allocate memory for set_volume argument string"); } else { - memset(command_buffer,0,command_buffer_size); - sprintf(command_buffer, "%s%f", config.cmd_set_volume,volume); - // debug(1,"command_buffer is \"%s\".",command_buffer); - int argC; - char **argV; - // debug(1,"set_volume command found."); - if (poptParseArgvString(command_buffer, &argC, (const char ***)&argV) != - 0) { - // note that argV should be free()'d after use, but we expect this fork to exit - // eventually. - warn("Can't decipher on-set-volume command arguments \"%s\".",command_buffer); - free(argV); - free(command_buffer); - } else { - free(command_buffer); - // debug(1,"Executing on-set-volume command %s with %d arguments.",argV[0],argC); - execv(argV[0], argV); - warn("Execution of on-set-volume command \"%s\" failed to start", config.cmd_set_volume); - // debug(1, "Error executing on-set-volume command %s", config.cmd_set_volume); - exit(127); /* only if execv fails */ - } + memset(command_buffer, 0, command_buffer_size); + sprintf(command_buffer, "%s%f", config.cmd_set_volume, volume); + // debug(1,"command_buffer is \"%s\".",command_buffer); + int argC; + char **argV; + // debug(1,"set_volume command found."); + if (poptParseArgvString(command_buffer, &argC, (const char ***)&argV) != 0) { + // note that argV should be free()'d after use, but we expect this fork to exit + // eventually. + warn("Can't decipher on-set-volume command arguments \"%s\".", command_buffer); + free(argV); + free(command_buffer); + } else { + free(command_buffer); + // debug(1,"Executing on-set-volume command %s with %d arguments.",argV[0],argC); + execv(argV[0], argV); + warn("Execution of on-set-volume command \"%s\" failed to start", config.cmd_set_volume); + // debug(1, "Error executing on-set-volume command %s", config.cmd_set_volume); + exit(127); /* only if execv fails */ + } } - + } else { if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ if (rc != pid) { warn("Execution of on-set-volume command returned an error."); - debug(1, "on-set-volume command %s finished with error %d", config.cmd_set_volume,errno); + debug(1, "on-set-volume command %s finished with error %d", config.cmd_set_volume, errno); } } // debug(1,"Continue after on-set-volume command"); @@ -545,9 +544,10 @@ void command_start(void) { exit(127); /* only if execv fails */ } } else { - if (config.cmd_blocking || config.cmd_start_returns_output) { /* pid!=0 means parent process and if blocking is true, wait for + if (config.cmd_blocking || config.cmd_start_returns_output) { /* pid!=0 means parent process + and if blocking is true, wait for process to finish */ - pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ + pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ if (rc != pid) { warn("Execution of on-start command returned an error."); debug(1, "on-start command %s finished with error %d", config.cmd_start, errno); diff --git a/common.h b/common.h index 9327cab3..40bf594d 100644 --- a/common.h +++ b/common.h @@ -40,8 +40,8 @@ enum endian_type { enum stuffing_type { ST_basic = 0, // straight deletion or insertion of a frame in a 352-frame packet - ST_soxr, // use libsoxr to make a 352 frame packet one frame longer or shorter - } type; + ST_soxr, // use libsoxr to make a 352 frame packet one frame longer or shorter +} type; enum playback_mode_type { ST_stereo = 0, @@ -56,7 +56,8 @@ enum decoders_supported_type { decoder_apple_alac, } decoders_supported_type; -// the following enum is for the formats recognised -- currently only S16LE is recognised for input, so these are output only for the present +// the following enum is for the formats recognised -- currently only S16LE is recognised for input, +// so these are output only for the present enum sps_format_t { SPS_FORMAT_UNKNOWN = 0, @@ -75,8 +76,9 @@ typedef struct { char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running // on host %h" #ifdef CONFIG_PA - char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in the Sound Preferences in most desktop Linuxes. - // Defaults to "Shairport Sync". Shairport Sync must be playing to see it. + char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in + // the Sound Preferences in most desktop Linuxes. +// Defaults to "Shairport Sync". Shairport Sync must be playing to see it. #endif #ifdef CONFIG_METADATA int metadata_enabled; @@ -114,9 +116,9 @@ typedef struct { int64_t AirPlayLatency; // supplied with --AirPlayLatency option int64_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option int daemonise; - int daemonise_store_pid; // don't try to save a PID file - char *piddir; - int logOutputLevel; // log output level + int daemonise_store_pid; // don't try to save a PID file + char *piddir; + int logOutputLevel; // log output level int statistics_requested, use_negotiated_latencies; enum playback_mode_type playback_mode; char *cmd_start, *cmd_stop, *cmd_set_volume; @@ -143,18 +145,18 @@ typedef struct { // native range. enum sps_format_t output_format; int output_rate; - + #ifdef CONFIG_CONVOLUTION int convolution; - const char* convolution_ir_file; + const char *convolution_ir_file; float convolution_gain; int convolution_max_length; #endif - + int loudness; float loudness_reference_volume_db; int alsa_use_playback_switch_for_mute; - + } shairport_cfg; // true if Shairport Sync is supposed to be sending output to the output device, false otherwise diff --git a/loudness.c b/loudness.c index 5db17a8c..b4dbc3c3 100644 --- a/loudness.c +++ b/loudness.c @@ -1,54 +1,49 @@ #include "loudness.h" -#include #include "common.h" +#include loudness_processor loudness_r; loudness_processor loudness_l; - -void _loudness_set_volume(loudness_processor *p, float volume) -{ - float gain = -(volume-config.loudness_reference_volume_db)*0.5; +void _loudness_set_volume(loudness_processor *p, float volume) { + float gain = -(volume - config.loudness_reference_volume_db) * 0.5; if (gain < 0) gain = 0; - + float Fc = 10.0; float Q = 0.5; - + // Formula from http://www.earlevel.com/main/2011/01/02/biquad-formulas/ float Fs = 44100.0; - + float K = tan(M_PI * Fc / Fs); float V = pow(10.0, gain / 20.0); - - float norm = 1 / (1 + 1/Q * K + K * K); - p->a0 = (1 + V/Q * K + K * K) * norm; + + float norm = 1 / (1 + 1 / Q * K + K * K); + p->a0 = (1 + V / Q * K + K * K) * norm; p->a1 = 2 * (K * K - 1) * norm; - p->a2 = (1 - V/Q * K + K * K) * norm; + p->a2 = (1 - V / Q * K + K * K) * norm; p->b1 = p->a1; - p->b2 = (1 - 1/Q * K + K * K) * norm; + p->b2 = (1 - 1 / Q * K + K * K) * norm; } -float loudness_process(loudness_processor *p, float i0) -{ - float o0 = p->a0*i0 + p->a1*p->i1 + p->a2*p->i2 - p->b1*p->o1 - p->b2*p->o2; - +float loudness_process(loudness_processor *p, float i0) { + float o0 = p->a0 * i0 + p->a1 * p->i1 + p->a2 * p->i2 - p->b1 * p->o1 - p->b2 * p->o2; + p->o2 = p->o1; p->o1 = o0; - + p->i2 = p->i1; p->i1 = i0; - + return o0; } - -void loudness_set_volume(float volume) -{ - float gain = -(volume-config.loudness_reference_volume_db)*0.5; +void loudness_set_volume(float volume) { + float gain = -(volume - config.loudness_reference_volume_db) * 0.5; if (gain < 0) gain = 0; - + inform("Volume: %.1f dB - Loudness gain @10Hz: %.1f dB", volume, gain); _loudness_set_volume(&loudness_l, volume); _loudness_set_volume(&loudness_r, volume); diff --git a/loudness.h b/loudness.h index c1a219a2..1e20c9b8 100644 --- a/loudness.h +++ b/loudness.h @@ -7,10 +7,8 @@ typedef struct { float i1, i2, o1, o2; } loudness_processor; - extern loudness_processor loudness_r; extern loudness_processor loudness_l; void loudness_set_volume(float volume); float loudness_process(loudness_processor *p, float sample); - diff --git a/mdns.h b/mdns.h index 7aa9dbb2..49bae008 100644 --- a/mdns.h +++ b/mdns.h @@ -24,7 +24,7 @@ typedef struct { // "sr=44100", "vn=3", "txtvers=1", config.password ? "pw=true" : "pw=false" #define MDNS_RECORD_WITH_METADATA \ - "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ + "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", \ METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", \ "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false" @@ -35,7 +35,7 @@ typedef struct { // "txtvers=1", config.password ? "pw=true" : "pw=false" #define MDNS_RECORD_WITHOUT_METADATA \ - "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \ + "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \ "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", \ config.password ? "pw=true" : "pw=false" diff --git a/player.c b/player.c index 450c3d17..7b8b419b 100644 --- a/player.c +++ b/player.c @@ -869,13 +869,16 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (abs_delta << 32) / config.output_rate; // int64_t which is positive conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } - + // now, the size of the initial silence must be affected by the lead-in time. - // it must be somewhat less than the lead-in time so that dynamic adjustments can be made + // it must be somewhat less than the lead-in time so that dynamic adjustments can be + // made // to compensate for delays due to paging, etc. // The suggestion is that it should be at least 100 ms less than the lead-in time. - - int64_t max_dac_delay = config.output_rate / 10; // so the lead-in time must be greater than this, say 0.2 sec, to allow for dynamic adjustment + + int64_t max_dac_delay = config.output_rate / 10; // so the lead-in time must be greater + // than this, say 0.2 sec, to allow for + // dynamic adjustment int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC if (local_time_now >= conn->first_packet_time_to_play) { @@ -889,27 +892,29 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // flush.",(((tn-conn->first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read, // ab_write)); -/* - if (config.output->flush) - config.output->flush(); - ab_resync(conn); - conn->first_packet_timestamp = 0; - conn->first_packet_time_to_play = 0; - conn->time_since_play_started = 0; -*/ + /* + if (config.output->flush) + config.output->flush(); + ab_resync(conn); + conn->first_packet_timestamp = 0; + conn->first_packet_time_to_play = 0; + conn->time_since_play_started = 0; + */ } else { // do some calculations - int64_t lead_time = conn->first_packet_time_to_play-local_time_now; - int64_t lead_in_time = (int64_t)(config.audio_backend_silent_lead_in_time*(int64_t)0x100000000); - // debug(1,"Lead time is %llx at fpttp %llx.",lead_time,conn->first_packet_time_to_play); + int64_t lead_time = conn->first_packet_time_to_play - local_time_now; + int64_t lead_in_time = + (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)0x100000000); + // debug(1,"Lead time is %llx at fpttp + // %llx.",lead_time,conn->first_packet_time_to_play); // an audio_backend_silent_lead_in_time of less than zero means start filling ASAP - if ((lead_in_time<0) || (lead_time<=lead_in_time)) { + if ((lead_in_time < 0) || (lead_time <= lead_in_time)) { // debug(1,"Checking"); if (config.output->delay) { // conn->first_packet_time_to_play is definitely later than local_time_now if (have_sent_prefiller_silence != 0) { int resp = config.output->delay(&dac_delay); - + if (resp != 0) { debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp); dac_delay = 0; @@ -918,7 +923,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { dac_delay = 0; } int64_t gross_frame_gap = - ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >> 32; + ((conn->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... @@ -937,14 +943,16 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { 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", + 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 <= conn->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, + // 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 // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, // ab_write),ab_read,ab_write); @@ -954,8 +962,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // 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 + // 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(conn->output_bytes_per_frame * fs); @@ -963,27 +973,30 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(1, "Failed to allocate %d byte silence buffer.", fs); else { memset(silence, 0, conn->output_bytes_per_frame * fs); - // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, conn->ab_write, conn->ab_read)); + // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d + // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, + // conn->ab_write, conn->ab_read)); config.output->play(silence, fs); free(silence); } } - have_sent_prefiller_silence = 1; // even if we haven't sent silence because it's zero frames long... + have_sent_prefiller_silence = + 1; // even if we haven't sent silence because it's zero frames long... } } else { - //no delay function on back end -- just send the prefiller silence + // no delay function on back end -- just send the prefiller silence // debug(1,"Back end has no delay function."); // send the appropriate prefiller here... - + signed short *silence; if (lead_time != 0) { int64_t frame_gap = (lead_time * config.output_rate) >> 32; // debug(1,"%d frames needed.",frame_gap); - while (frame_gap>0) { - size_t fs = config.output_rate/10; - if (fs>frame_gap) + while (frame_gap > 0) { + size_t fs = config.output_rate / 10; + if (fs > frame_gap) fs = frame_gap; - + silence = malloc(conn->output_bytes_per_frame * fs); if (silence == NULL) debug(1, "Failed to allocate %d frame silence buffer.", fs); @@ -992,7 +1005,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { memset(silence, 0, conn->output_bytes_per_frame * fs); config.output->play(silence, fs); free(silence); - } frame_gap -= fs; } @@ -1085,7 +1097,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { 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 - #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; uint64_t sec = time_of_wakeup_fp >> 32; diff --git a/player.h b/player.h index 0e58b3ba..d69792c6 100644 --- a/player.h +++ b/player.h @@ -19,8 +19,8 @@ #include #endif -#include "audio.h" #include "alac.h" +#include "audio.h" #define time_ping_history 8 @@ -62,8 +62,8 @@ typedef struct { pthread_t player_thread; abuf_t audio_buffer[BUFFER_FRAMES]; - int max_frames_per_packet,input_num_channels,input_bit_depth,input_rate; - int input_bytes_per_frame,output_bytes_per_frame,output_sample_ratio; + int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate; + int input_bytes_per_frame, output_bytes_per_frame, output_sample_ratio; int max_frame_size_change; int64_t previous_random_number; alac_file *decoder_info; @@ -73,24 +73,25 @@ typedef struct { int connection_state_to_output; int player_thread_please_stop; int64_t first_packet_time_to_play, time_since_play_started; // nanoseconds - // stats - uint64_t missing_packets, late_packets, too_late_packets, resend_requests; - int decoder_in_use; - // debug variables - int32_t last_seqno_read; -// mutexes and condition variables - pthread_cond_t flowcontrol; - pthread_mutex_t ab_mutex,flush_mutex; - pthread_mutex_t vol_mutex; - int fix_volume; - uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 - // or 1. - int ab_buffering,ab_synced; - int64_t first_packet_timestamp; - int flush_requested; - int64_t flush_rtp_timestamp; - uint64_t time_of_last_audio_packet; - seq_t ab_read, ab_write; + // stats + uint64_t missing_packets, late_packets, too_late_packets, resend_requests; + int decoder_in_use; + // debug variables + int32_t last_seqno_read; + // mutexes and condition variables + pthread_cond_t flowcontrol; + pthread_mutex_t ab_mutex, flush_mutex; + pthread_mutex_t vol_mutex; + int fix_volume; + uint32_t timestamp_epoch, last_timestamp, + maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 + // or 1. + int ab_buffering, ab_synced; + int64_t first_packet_timestamp; + int flush_requested; + int64_t flush_rtp_timestamp; + uint64_t time_of_last_audio_packet; + seq_t ab_read, ab_write; #ifdef HAVE_LIBMBEDTLS mbedtls_aes_context dctx; @@ -104,7 +105,7 @@ typedef struct { AES_KEY aes; #endif - int amountStuffed; + int amountStuffed; int32_t framesProcessedInThisEpoch; int32_t framesGeneratedInThisEpoch; @@ -118,9 +119,9 @@ typedef struct { char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client 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 - uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope - short connection_ip_family; // AF_INET / AF_INET6 - uint32_t client_active_remote; // used when you want to control the client... + uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope + short connection_ip_family; // AF_INET / AF_INET6 + uint32_t client_active_remote; // used when you want to control the client... SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client @@ -147,27 +148,29 @@ typedef struct { int timing_sender_stop; // for asking the timing-sending thread to stop int last_stuff_request; - + int64_t play_segment_reference_frame; uint64_t play_segment_reference_frame_remote_time; - + int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative int64_t session_corrections; - + int play_number_after_flush; - + } rtsp_conn_info; -int player_play(pthread_t *thread, rtsp_conn_info* conn); -void player_stop(pthread_t *thread, rtsp_conn_info* conn); +int player_play(pthread_t *thread, rtsp_conn_info *conn); +void player_stop(pthread_t *thread, rtsp_conn_info *conn); -void player_volume(double f, rtsp_conn_info* conn); -void player_flush(int64_t timestamp, rtsp_conn_info* conn); -void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info* conn); +void player_volume(double f, rtsp_conn_info *conn); +void player_flush(int64_t timestamp, rtsp_conn_info *conn); +void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, + rtsp_conn_info *conn); -int64_t monotonic_timestamp(uint32_t timestamp,rtsp_conn_info* conn); // 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 +int64_t monotonic_timestamp(uint32_t timestamp, + rtsp_conn_info *conn); // 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. diff --git a/rtp.c b/rtp.c index af98b349..dfd15b95 100644 --- a/rtp.c +++ b/rtp.c @@ -42,30 +42,28 @@ #include "player.h" #include "rtp.h" -void rtp_initialise(rtsp_conn_info* conn) { +void rtp_initialise(rtsp_conn_info *conn) { conn->rtp_running = 0; -// initialise the timer mutex + // initialise the timer mutex int rc = pthread_mutex_init(&conn->reference_time_mutex, NULL); if (rc) debug(1, "Error initialising reference_time_mutex."); - } -void rtp_terminate(rtsp_conn_info* conn) { +void rtp_terminate(rtsp_conn_info *conn) { -// destroy the timer mutex + // destroy the timer mutex int rc = pthread_mutex_destroy(&conn->reference_time_mutex); if (rc) debug(1, "Error destroying reference_time_mutex variable."); } - -void *rtp_audio_receiver(void* arg) { +void *rtp_audio_receiver(void *arg) { debug(2, "Audio receiver -- Server RTP thread starting."); // we inherit the signal mask (SIGUSR1) - rtsp_conn_info *conn = (rtsp_conn_info*)arg; + rtsp_conn_info *conn = (rtsp_conn_info *)arg; int32_t last_seqno = -1; uint8_t packet[2048], *pktp; @@ -131,7 +129,7 @@ void *rtp_audio_receiver(void* arg) { // debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno); last_seqno = seqno; // reset warning... } - int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)),conn); + int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)), conn); // if (packet[1]&0x10) // debug(1,"Audio packet Extension bit set."); @@ -141,7 +139,7 @@ void *rtp_audio_receiver(void* arg) { // check if packet contains enough content to be reasonable if (plen >= 16) { - player_put_packet(seqno, timestamp, pktp, plen,conn); + player_put_packet(seqno, timestamp, pktp, plen, conn); continue; } if (type == 0x56 && seqno == 0) { @@ -160,12 +158,12 @@ void *rtp_audio_receiver(void* arg) { return NULL; } -void *rtp_control_receiver(void* arg) { +void *rtp_control_receiver(void *arg) { // we inherit the signal mask (SIGUSR1) debug(2, "Control receiver -- Server RTP thread starting."); - - rtsp_conn_info *conn = (rtsp_conn_info*)arg; + + rtsp_conn_info *conn = (rtsp_conn_info *)arg; conn->reference_timestamp = 0; // nothing valid received yet uint8_t packet[2048], *pktp; @@ -202,8 +200,8 @@ void *rtp_control_receiver(void* arg) { // debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync); - rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4])),conn); - sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])),conn); + rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4])), conn); + sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])), conn); if (config.use_negotiated_latencies) { int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + 11025; @@ -222,7 +220,8 @@ void *rtp_control_receiver(void* arg) { } pthread_mutex_lock(&conn->reference_time_mutex); conn->remote_reference_timestamp_time = remote_time_of_sync; - conn->reference_timestamp_time = remote_time_of_sync - conn->local_to_remote_time_difference; + conn->reference_timestamp_time = + remote_time_of_sync - conn->local_to_remote_time_difference; conn->reference_timestamp = sync_rtp_timestamp; pthread_mutex_unlock(&conn->reference_time_mutex); // debug(1,"New Reference timestamp and timestamp time..."); @@ -242,7 +241,7 @@ void *rtp_control_receiver(void* arg) { plen -= 4; seq_t seqno = ntohs(*(unsigned short *)(pktp + 2)); - int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)),conn); + int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)), conn); pktp += 12; plen -= 12; @@ -266,7 +265,7 @@ void *rtp_control_receiver(void* arg) { void *rtp_timing_sender(void *arg) { debug(2, "Timing sender thread starting."); - rtsp_conn_info *conn = (rtsp_conn_info*)arg; + rtsp_conn_info *conn = (rtsp_conn_info *)arg; struct timing_request { char leader; char type; @@ -306,8 +305,8 @@ void *rtp_timing_sender(void *arg) { msgsize = sizeof(struct sockaddr_in6); } #endif - if (sendto(conn->timing_socket, &req, sizeof(req), 0, (struct sockaddr *)&conn->rtp_client_timing_socket, - msgsize) == -1) { + if (sendto(conn->timing_socket, &req, sizeof(req), 0, + (struct sockaddr *)&conn->rtp_client_timing_socket, msgsize) == -1) { perror("Error sendto-ing to timing socket"); } request_number++; @@ -323,7 +322,7 @@ void *rtp_timing_sender(void *arg) { void *rtp_timing_receiver(void *arg) { debug(2, "Timing receiver -- Server RTP thread starting."); // we inherit the signal mask (SIGUSR1) - rtsp_conn_info *conn = (rtsp_conn_info*)arg; + rtsp_conn_info *conn = (rtsp_conn_info *)arg; uint8_t packet[2048], *pktp; ssize_t nread; @@ -391,11 +390,11 @@ void *rtp_timing_receiver(void *arg) { uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2; - unsigned int cc,chosen; + unsigned int cc, chosen; for (cc = time_ping_history - 1; cc > 0; cc--) { conn->time_pings[cc] = conn->time_pings[cc - 1]; conn->time_pings[cc].dispersion = (conn->time_pings[cc].dispersion * 110) / - 100; // make the dispersions 'age' by this rational factor + 100; // make the dispersions 'age' by this rational factor } // these are for diagnostics only -- not used conn->time_pings[0].local_time = arrival_time; @@ -463,9 +462,8 @@ void *rtp_timing_receiver(void *arg) { clock_drift_in_usec = (clock_drift * 1000000) >> 32; else clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32); - clock_drift_ppm = (1.0*clock_drift_in_usec)/(local_time_change>>32); - } - + clock_drift_ppm = (1.0 * clock_drift_in_usec) / (local_time_change >> 32); + } int64_t source_drift_usec; if (conn->play_segment_reference_frame != 0) { @@ -475,15 +473,16 @@ void *rtp_timing_receiver(void *arg) { &remote_reference_timestamp_time, conn); uint64_t frame_difference = 0; if (reference_timestamp >= conn->play_segment_reference_frame) - frame_difference = (uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame; - else // rollover frame_difference = - (uint64_t)reference_timestamp + 0x100000000 - (uint64_t)conn->play_segment_reference_frame; + (uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame; + else // rollover + frame_difference = (uint64_t)reference_timestamp + 0x100000000 - + (uint64_t)conn->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 - conn->play_segment_reference_frame_remote_time; // this is all done by reference to the - // sources' system clock + // 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); @@ -505,13 +504,14 @@ void *rtp_timing_receiver(void *arg) { // config.output->delay(¤t_delay); //} // Useful for troubleshooting: - //debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec %10.1lld\treturn_time_in_usec %10.1llu", - //clock_drift_ppm, - //chosen, + // debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec %10.1lld\treturn_time_in_usec + // %10.1llu", + // clock_drift_ppm, + // chosen, //(session_corrections*1000000)/44100, - //current_delay, - //source_drift_usec, - //buffer_occupancy, + // current_delay, + // source_drift_usec, + // buffer_occupancy, //(return_time*1000000)>>32); } else { @@ -568,7 +568,8 @@ static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_ if (ret < 0) { close(local_socket); - die("error: could not bind a UDP port! Check the udp_port_range is large enough (>= 10) or check for restrictive firewall settings or a bad router!"); + die("error: could not bind a UDP port! Check the udp_port_range is large enough (>= 10) or " + "check for restrictive firewall settings or a bad router!"); } int sport; @@ -610,7 +611,8 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t char client_port_str[64]; char self_addr_str[64]; - conn->connection_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client + conn->connection_ip_family = + remote->SAFAMILY; // keep information about the kind of ip of the client #ifdef AF_INET6 if (conn->connection_ip_family == AF_INET6) { @@ -632,10 +634,13 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t self_port = ntohs(sa4->sin_port); } - inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string, sizeof(conn->client_ip_string)); - inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string, sizeof(conn->self_ip_string)); + inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string, + sizeof(conn->client_ip_string)); + inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string, + sizeof(conn->self_ip_string)); - debug(1, "Set up play connection from %s to self at %s.", conn->client_ip_string, conn->self_ip_string); + debug(1, "Set up play connection from %s to self at %s.", conn->client_ip_string, + conn->self_ip_string); // set up a the record of the remote's control socket struct addrinfo hints; @@ -683,9 +688,12 @@ 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(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->audio_socket); - *lcport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->control_socket); - *ltport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->timing_socket); + *lsport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, + &conn->audio_socket); + *lcport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, + &conn->control_socket); + *ltport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, + &conn->timing_socket); debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport, *ltport); @@ -752,8 +760,8 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { msgsize = sizeof(struct sockaddr_in6); } #endif - if (sendto(conn->audio_socket, req, sizeof(req), 0, (struct sockaddr *)&conn->rtp_client_control_socket, - msgsize) == -1) { + if (sendto(conn->audio_socket, req, sizeof(req), 0, + (struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) { perror("Error sendto-ing to audio socket"); } } else { diff --git a/rtp.h b/rtp.h index a78548ab..e0ebefa9 100644 --- a/rtp.h +++ b/rtp.h @@ -5,8 +5,8 @@ #include "player.h" -void rtp_initialise(rtsp_conn_info* conn); -void rtp_terminate(rtsp_conn_info* conn); +void rtp_initialise(rtsp_conn_info *conn); +void rtp_terminate(rtsp_conn_info *conn); void *rtp_audio_receiver(void *arg); void *rtp_control_receiver(void *arg); @@ -14,14 +14,14 @@ 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, rtsp_conn_info* conn); -void rtp_shutdown(rtsp_conn_info* conn); -void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info* conn); -void rtp_request_client_pause(rtsp_conn_info* conn); // ask the client to pause + int *local_timing_port, rtsp_conn_info *conn); +void rtp_shutdown(rtsp_conn_info *conn); +void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn); +void rtp_request_client_pause(rtsp_conn_info *conn); // ask the client to pause void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time, - uint64_t *remote_timestamp_time, rtsp_conn_info* conn); -void clear_reference_timestamp(rtsp_conn_info* conn); + uint64_t *remote_timestamp_time, rtsp_conn_info *conn); +void clear_reference_timestamp(rtsp_conn_info *conn); uint64_t static local_to_remote_time_jitters; uint64_t static local_to_remote_time_jitters_count; diff --git a/rtsp.c b/rtsp.c index 42565914..c4c2dd72 100644 --- a/rtsp.c +++ b/rtsp.c @@ -646,7 +646,7 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 rtptime--; // debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime); - player_flush(rtptime,conn); + player_flush(rtptime, conn); } } } @@ -665,7 +665,7 @@ static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag "it's sending a response to teardown anyway"); resp->respcode = 200; msg_add_header(resp, "Connection", "close"); - debug(2,"TEARDOWN asking connection to stop"); + debug(2, "TEARDOWN asking connection to stop"); conn->stop = 1; } @@ -687,14 +687,14 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 } } - // debug(1,"RTSP Flush Requested: %u.",rtptime); - #ifdef CONFIG_METADATA +// debug(1,"RTSP Flush Requested: %u.",rtptime); +#ifdef CONFIG_METADATA if (p) send_metadata('ssnc', 'flsr', p, strlen(p), req, 1); else - send_metadata('ssnc', 'flsr', NULL, 0, NULL,0); - #endif - player_flush(rtptime,conn); + send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0); +#endif + player_flush(rtptime, conn); resp->respcode = 200; } @@ -799,7 +799,8 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * tport = atoi(p); // rtsp_take_player(); - rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port, conn); + rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port, + conn); if (!lsport) goto error; char *q; @@ -818,7 +819,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message * strcat(hdr, q); // should unsplice the timing port entry } - player_play(&conn->player_thread,conn); // the thread better be 0 + player_play(&conn->player_thread, conn); // the thread better be 0 char *resphdr = alloca(200); *resphdr = 0; @@ -857,7 +858,7 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r if (!strncmp(cp, "volume: ", 8)) { float volume = atof(cp + 8); // debug(1, "AirPlay request to set volume to: %f\n", volume); - player_volume(volume,conn); + player_volume(volume, conn); } else #ifdef CONFIG_METADATA if (!strncmp(cp, "progress: ", 10)) { @@ -943,13 +944,10 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r // `clip` -- the payload is the IP number of the client, i.e. the sender of audio. // Can be an IPv4 or an IPv6 number. - - // A special sub-protocol is used for sending large data items over UDP // If the payload exceeded 4 MB, it is chunked using the following format: // "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data. -// Notice that the number of items is different to the standard - +// Notice that the number of items is different to the standard // including a simple base64 encoder to minimise malloc/free activity @@ -1093,15 +1091,15 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) } else if (metadata_sock >= 0) { // send metadata in numbered chunks using the protocol: // ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data) - + uint32_t chunk_ix = 0; uint32_t chunk_total = length / (config.metadata_sockmsglength - 24); if (chunk_total * (config.metadata_sockmsglength - 24) < length) { - chunk_total++; + chunk_total++; } uint32_t remaining = length; uint32_t v; - char* data_crsr = data; + char *data_crsr = data; do { char *ptr = metadata_sockmsg; memcpy(ptr, "ssncchnk", 8); @@ -1120,15 +1118,16 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) ptr += 4; uint32_t datalen = remaining; if (datalen > config.metadata_sockmsglength - 24) { - datalen = config.metadata_sockmsglength - 24; + datalen = config.metadata_sockmsglength - 24; } memcpy(ptr, data_crsr, datalen); data_crsr += datalen; - sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0, (struct sockaddr *)&metadata_sockaddr, - sizeof(metadata_sockaddr)); + sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0, + (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr)); chunk_ix++; remaining -= datalen; - if (remaining == 0) break; + if (remaining == 0) + break; } while (1); } @@ -1400,14 +1399,15 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (config.allow_session_interruption == 1) { // some other thread has the player ... ask it to relinquish the thread if (playing_conn) { - debug(1,"Playing connection asked to stop"); - if (playing_conn==conn) { - debug(1,"ANNOUNCE asking to stop itself."); + debug(1, "Playing connection asked to stop"); + if (playing_conn == conn) { + debug(1, "ANNOUNCE asking to stop itself."); } else { playing_conn->stop = 1; memory_barrier(); pthread_kill(playing_conn->thread, SIGUSR1); - usleep(1000000); // here, it is possible for other connections to come in and nab the player. + usleep( + 1000000); // here, it is possible for other connections to come in and nab the player. } } else { die("Non existent the_playing_conn with play_lock enabled."); @@ -1417,7 +1417,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (pthread_mutex_trylock(&play_lock) == 0) have_the_player = 1; else - debug(1,"ANNOUNCE failed to get the player"); + debug(1, "ANNOUNCE failed to get the player"); } } @@ -1496,7 +1496,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); // here we should check the sanity ot the fmtp values - //for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) + // for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) // debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]); char *hdr = msg_get_header(req, "X-Apple-Client-Name"); @@ -1762,7 +1762,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { pthread_sigmask(SIG_UNBLOCK, &set, NULL); rtsp_conn_info *conn = pconn; - + rtp_initialise(conn); rtsp_message *req, *resp; @@ -1812,7 +1812,7 @@ static void *rtsp_conversation_thread_func(void *pconn) { debug(1, "Closing down RTSP conversation thread..."); if (rtsp_playing()) { - player_stop(&conn->player_thread,conn); // might be less noisy doing this first + player_stop(&conn->player_thread, conn); // might be less noisy doing this first rtp_shutdown(conn); // usleep(400000); // let an angel pass... pthread_mutex_unlock(&play_lock); @@ -1974,7 +1974,7 @@ void rtsp_listen_loop(void) { continue; rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info)); - if (conn==0) + if (conn == 0) die("Couldn't allocate memory for an rtsp_conn_info record."); memset(conn, 0, sizeof(rtsp_conn_info)); socklen_t slen = sizeof(conn->remote); @@ -2033,13 +2033,14 @@ void rtsp_listen_loop(void) { } else { debug(1, "Error figuring out Shairport Sync's own IP number."); } -// usleep(500000); -// pthread_t rtsp_conversation_thread; -// conn->thread = rtsp_conversation_thread; -// conn->stop = 0; // record's memory has been zeroed -// conn->authorized = 0; // record's memory has been zeroed + // usleep(500000); + // pthread_t rtsp_conversation_thread; + // conn->thread = rtsp_conversation_thread; + // conn->stop = 0; // record's memory has been zeroed + // conn->authorized = 0; // record's memory has been zeroed conn->running = 1; - ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func, conn); // also acts as a memory barrier + ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func, + conn); // also acts as a memory barrier if (ret) die("Failed to create RTSP receiver thread!"); track_thread(conn); diff --git a/shairport.c b/shairport.c index bad20077..53d0fdcd 100644 --- a/shairport.c +++ b/shairport.c @@ -254,8 +254,9 @@ void usage(char *progname) { "88) before trying to correct it.\n"); printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a " "password.\n"); - printf(" --logOutputLevel log the output level setting -- useful for setting maximum volume.\n"); - #ifdef CONFIG_METADATA + printf(" --logOutputLevel log the output level setting -- useful for setting maximum " + "volume.\n"); +#ifdef CONFIG_METADATA printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. " "--metadata-pipename=/tmp/shairport-sync-metadata.\n"); printf(" The default is /tmp/shairport-sync-metadata.\n"); @@ -285,7 +286,7 @@ int parse_options(int argc, char **argv) { {"reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL}, {"kill", 'k', POPT_ARG_NONE, NULL, 0, NULL}, {"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL}, - {"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE,&daemonisewithout, 0, NULL}, + {"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL}, {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL}, {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL}, {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL}, @@ -363,17 +364,18 @@ int parse_options(int argc, char **argv) { } if ((daemonisewith) && (daemonisewithout)) - die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected both!"); + die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected " + "both!"); if ((daemonisewith) || (daemonisewithout)) { config.daemonise = 1; if (daemonisewith) config.daemonise_store_pid = 1; - }; + }; config.resyncthreshold = 1.0 * fResyncthreshold / 44100; config.tolerance = 1.0 * fTolerance / 44100; config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set - + config_setting_t *setting; const char *str = 0; int value = 0; @@ -405,7 +407,8 @@ int parse_options(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) daemonisewith = 1; else - die("Invalid daemonize_with_pid_file option choice \"%s\". It should be \"yes\" or \"no\""); + die("Invalid daemonize_with_pid_file option choice \"%s\". It should be \"yes\" or " + "\"no\""); } /* Get the Just_Daemonize setting. */ @@ -415,15 +418,17 @@ int parse_options(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) daemonisewithout = 1; else - die("Invalid daemonize_without_pid_file option choice \"%s\". It should be \"yes\" or \"no\""); + die("Invalid daemonize_without_pid_file option choice \"%s\". It should be \"yes\" or " + "\"no\""); } if ((daemonisewith) && (daemonisewithout)) - die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected both!"); + die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have " + "selected both!"); if ((daemonisewith) || (daemonisewithout)) { config.daemonise = 1; if (daemonisewith) config.daemonise_store_pid = 1; - } + } /* Get the directory path for the pid file created when the program is daemonised. */ if (config_lookup_string(config.cfg, "sessioncontrol.daemon_pid_dir", &str)) config.piddir = (char *)str; @@ -695,13 +700,15 @@ int parse_options(int argc, char **argv) { "\"yes\" or \"no\""); } - if (config_lookup_string(config.cfg, "sessioncontrol.before_play_begins_returns_output", &str)) { + if (config_lookup_string(config.cfg, "sessioncontrol.before_play_begins_returns_output", + &str)) { if (strcasecmp(str, "no") == 0) config.cmd_start_returns_output = 0; else if (strcasecmp(str, "yes") == 0) config.cmd_start_returns_output = 1; else - die("Invalid session control before_play_begins_returns_output option choice \"%s\". It should be " + die("Invalid session control before_play_begins_returns_output option choice \"%s\". It " + "should be " "\"yes\" or \"no\""); } @@ -721,9 +728,9 @@ int parse_options(int argc, char **argv) { config.timeout = value; config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0 } - + #ifdef CONFIG_CONVOLUTION - + if (config_lookup_string(config.cfg, "dsp.convolution", &str)) { if (strcasecmp(str, "no") == 0) config.convolution = 0; @@ -731,33 +738,33 @@ int parse_options(int argc, char **argv) { config.convolution = 1; else die("Invalid dsp.convolution. It should be \"yes\" or \"no\""); - } - + if (config_lookup_float(config.cfg, "dsp.convolution_gain", &dvalue)) { config.convolution_gain = dvalue; if (dvalue > 10 || dvalue < -50) - die("Invalid value \"%f\" for dsp.convolution_gain. It should be between -50 and +10 dB", dvalue); + die("Invalid value \"%f\" for dsp.convolution_gain. It should be between -50 and +10 dB", + dvalue); } - + config.convolution_max_length = 8192; if (config_lookup_int(config.cfg, "dsp.convolution_max_length", &value)) { config.convolution_max_length = value; - + if (value < 1 || value > 200000) die("dsp.convolution_max_length must be within 1 and 200000"); } - + if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) { config.convolution_ir_file = str; convolver_init(config.convolution_ir_file, config.convolution_max_length); } - + if (config.convolution && config.convolution_ir_file == NULL) { die("Convolution enabled but no convolution_ir_file provided"); } #endif - + if (config_lookup_string(config.cfg, "dsp.loudness", &str)) { if (strcasecmp(str, "no") == 0) config.loudness = 0; @@ -766,17 +773,20 @@ int parse_options(int argc, char **argv) { else die("Invalid dsp.convolution. It should be \"yes\" or \"no\""); } - + config.loudness_reference_volume_db = -20; if (config_lookup_float(config.cfg, "dsp.loudness_reference_volume_db", &dvalue)) { config.loudness_reference_volume_db = dvalue; if (dvalue > 0 || dvalue < -100) - die("Invalid value \"%f\" for dsp.loudness_reference_volume_db. It should be between -100 and 0", dvalue); + die("Invalid value \"%f\" for dsp.loudness_reference_volume_db. It should be between " + "-100 and 0", + dvalue); } - + if (config.loudness == 1 && config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) - die("Loudness activated but hardware volume is active. You must remove \"alsa.mixer_control_name\" to use the loudness filter."); - + die("Loudness activated but hardware volume is active. You must remove " + "\"alsa.mixer_control_name\" to use the loudness filter."); + } else { if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO) debug(1, "Error reading configuration file \"%s\": \"%s\".", @@ -924,21 +934,20 @@ void shairport_startup_complete(void) { } } - const char *pid_file_proc(void) { - #ifdef USE_CUSTOM_PID_DIR - char * use_this_pid_dir = PIDDIR; - #else - char * use_this_pid_dir = "/var/run/shairport-sync"; - #endif - // debug(1,"config.piddir \"%s\".",config.piddir); - if (config.piddir) - use_this_pid_dir = config.piddir; - char fn[8192]; - snprintf(fn, sizeof(fn), "%s/%s.pid", use_this_pid_dir, - daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); - //debug(1,"fn \"%s\".",fn); - return strdup(fn); +#ifdef USE_CUSTOM_PID_DIR + char *use_this_pid_dir = PIDDIR; +#else + char *use_this_pid_dir = "/var/run/shairport-sync"; +#endif + // debug(1,"config.piddir \"%s\".",config.piddir); + if (config.piddir) + use_this_pid_dir = config.piddir; + char fn[8192]; + snprintf(fn, sizeof(fn), "%s/%s.pid", use_this_pid_dir, + daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); + // debug(1,"fn \"%s\".",fn); + return strdup(fn); } void exit_function() { @@ -982,7 +991,8 @@ int main(int argc, char **argv) { die("Can not recognise the endianness of the processor."); strcpy(configuration_file_path, SYSCONFDIR); - // strcat(configuration_file_path, "/shairport-sync"); // thinking about adding a special shairport-sync directory + // strcat(configuration_file_path, "/shairport-sync"); // thinking about adding a special + // shairport-sync directory strcat(configuration_file_path, "/"); strcat(configuration_file_path, appName); strcat(configuration_file_path, ".conf"); @@ -1102,7 +1112,7 @@ int main(int argc, char **argv) { // parse arguments into config -- needed to locate pid_dir int audio_arg = parse_options(argc, argv); - /* Check if we are called with -k or --kill parameter */ + /* Check if we are called with -k or --kill parameter */ if (argc >= 2 && ((strcmp(argv[1], "-k") == 0) || (strcmp(argv[1], "--kill") == 0))) { int ret; @@ -1279,7 +1289,7 @@ int main(int argc, char **argv) { /* Print out options */ debug(1, "statistics_requester status is %d.", config.statistics_requested); debug(1, "daemon status is %d.", config.daemonise); - debug(1, "deamon pid file is \"%s\".",pid_file_proc()); + debug(1, "deamon pid file is \"%s\".", pid_file_proc()); debug(1, "rtsp listening port is %d.", config.port); debug(1, "udp base port is %d.", config.udp_port_base); debug(1, "udp port range is %d.", config.udp_port_range); @@ -1316,7 +1326,8 @@ int main(int argc, char **argv) { debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); - debug(1, "audio backend silence lead-in time is %f seconds. A value -1.0 means use the default.", config.audio_backend_silent_lead_in_time); + debug(1, "audio backend silence lead-in time is %f seconds. A value -1.0 means use the default.", + config.audio_backend_silent_lead_in_time); 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); @@ -1352,7 +1363,7 @@ int main(int argc, char **argv) { #endif debug(1, "loudness is %d.", config.loudness); debug(1, "loudness reference level is %f", config.loudness_reference_volume_db); - + uint8_t ap_md5[16]; #ifdef HAVE_LIBSSL From aed8f0d57176d47dea6f29df12ddf953572780b6 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 31 Jul 2017 13:04:30 +0200 Subject: [PATCH 227/239] Update RELEASENOTES.md --- RELEASENOTES.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9dead90f..190cd58d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,32 @@ +Version 3.1 [Forthcoming, under revision...] +==== +Version 3.1 brings two new backends, optional loudness and convolution filters, improvements in non-synchronised backends, enhancements, stability improvements and bug fixes. + +**New Features** +* A `sndio` backend gives Shairport Sync a fully synchronised backend that works natively on OpenBSD and FreeBSD, thanks to the work of [t6](https://github.com/t6). +* A `pa` backend now allows Shairport Sync to work well with a PulseAudio-equipped system -- many desktop Linuxes use PulseAudio as their sound manager. +* Optional loudness and convolution filters can be incorporated in the audio processing chain, thanks to [yannpom](https://github.com/yannpom). +* A volume-change program hook has been added to execute an application whenever the volume is changed. + +**Perky Changes You Shouldn't Ignore** +* The `audio_backend_buffer_desired_length_in_seconds` and `audio_backend_latency_offset_in_seconds` settings have been moved from individual backend stanzas to the `general` stanza. They now have an effect on every type of backend. +* If you are using a System V (aka `systemv`) installation, please note that the default location for PID file has moved -- it is now stored at `/var/run/shairport-sync/shairport-sync.pid`. This change is needed to improve security a little and to improve compatability across platforms. If you're not doing anything strange, this should make no difference. + + +**Enhancements** +* Resynchronisation, which happens when the synchronisation is incorrect by more than 50 ms by default, should be a lot less intrusive when it occurs – it should now either insert silence or skip frames, as appropriate. +* The Linux installer has been improved and simplified and a FreeBSD installer introduced. +* A new setting, `audio_backend_silent_lead_in_time`, allows you to set the duration of the period of silence played (the "silent lead-in") before a play session starts. +* A new command-line option, `--logOutputLevel`, allows you to output the volume levels to the log whenever they are changed. This may be useful during setup. +* Improvements have been made to the handling of large items of metadata over UDP. +* A new command line option, `-j`, deamonizes Shairport Sync without creating a PID file. +* A new `alsa`-only setting, `mute_using_playback_switch`, is available for advanced use. + +**Bug Fixes** +* Stability improvements. More care has been taken (!) to make code thread-safe resulting in improved stability. +* Conversion from stereo to mono has been fixed to avoid clipping while preserving full resolution. +* Short intrusions of audio from the end of the previous session to the start of a new session have been eliminated. + Version 3.1.d24 ==== **Enhancements** @@ -39,7 +68,7 @@ some metadata (typically the cover art) cannot be sent within a single IPv4 UDP These commits fix both issues by setting larger send buffer of 4MB and chunking any metadata with a new sub-protocol ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data). Thanks to [Paul Guyot](https://github.com/pguyot) for this work. -* A new command line option `-j` to deamonize Shairport Sync without creating a PID file. (The "j" is from the "j" in "just daemonize".) +* A new command line option `-j` to demonize Shairport Sync without creating a PID file. (The "j" is from the "j" in "just daemonize".) * Small change in startup behaviour. Shairport Sync now reads the configuration file before executing the `-k` option. Version 3.1d16 @@ -159,7 +188,7 @@ Version 3.1d0 Version 3.0 ==== -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. +Version 3 brings 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. From d72bcca372116e0b181f160d907bfdd01caf117d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 31 Jul 2017 13:11:35 +0200 Subject: [PATCH 228/239] Update RELEASENOTES.md --- RELEASENOTES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 190cd58d..45166a82 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,13 +19,13 @@ Version 3.1 brings two new backends, optional loudness and convolution filters, * A new setting, `audio_backend_silent_lead_in_time`, allows you to set the duration of the period of silence played (the "silent lead-in") before a play session starts. * A new command-line option, `--logOutputLevel`, allows you to output the volume levels to the log whenever they are changed. This may be useful during setup. * Improvements have been made to the handling of large items of metadata over UDP. -* A new command line option, `-j`, deamonizes Shairport Sync without creating a PID file. +* A new command line option, `-j`, demonizes Shairport Sync without creating a PID file. * A new `alsa`-only setting, `mute_using_playback_switch`, is available for advanced use. **Bug Fixes** -* Stability improvements. More care has been taken (!) to make code thread-safe resulting in improved stability. +* Stability improvements. More care has been taken (!) to make code thread-safe, resulting in improved stability. * Conversion from stereo to mono has been fixed to avoid clipping while preserving full resolution. -* Short intrusions of audio from the end of the previous session to the start of a new session have been eliminated. +* Short intrusions of audio at the start of a new session from the end of the previous session have been eliminated. Version 3.1.d24 ==== From 95e24dd8c706e057265d54014e44cf40f55e6d88 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 31 Jul 2017 14:01:11 +0200 Subject: [PATCH 229/239] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 961575bd..e9e28149 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Shairport Sync is a substantial rewrite of the fantastic work done in Shairport Shairport Sync is designed for audio back ends that offer accurate timing and synchronisation information, including `alsa` on Linux and `sndio` on FreeBSD. 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). -Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing and synchronsiation information is not as accurate as that of `alsa` or `sndio`, removing or disabling PulseAudio so that Shairport Sync can have direct access to a sound card via `alsa` is often impractical. +Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing and synchronsiation information is not as accurate as that of `alsa` or `sndio`, removing or disabling PulseAudio so that Shairport Sync can have direct access to a sound card via `alsa` is often impractical. In that case, the `pa` backend can be used. For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. @@ -42,7 +42,7 @@ Status ------ Shairport Sync works on a wide variety of Linux devices and FreeBSD. 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. -Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normallys run in the *user mode* but can be configured to run in *system mode*, and Shairport Sync can work with it in either mode. +Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normallys run in the *user mode* but can be configured to run in *system mode*, though this is not recommended. Shairport Sync can work with it in either mode. Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card – see the note below on configuring the Raspberry Pi to make best use of it. @@ -158,17 +158,15 @@ $ autoreconf -i -f - `--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. -- `--with-systemv` to install a System V init script at the `make install` stage. Default is not to to install. -- `--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. -- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. -- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. +- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. Default is not to to install. +- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. Default is not to to install. **Determine if it's a `systemd` or a "System V" installation:** -If you wish to have Shairport Sync start automatically when your systewm reboots, you need to figure out whether the system is using `systemd` or the older System V startup facilities. If you are using Shairport Sync with PulseAudio as installed in many desktop systems, this doesn't apply. +If you wish to have Shairport Sync start automatically when your systewm reboots, you need to figure out whether the system is using `systemd` or the older System V startup facilities. If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply. At the time of writing, there are two widely-used systems for starting programs automatically at startup: `systemd` and "System V" . (There are others, but they are not considered here.) To see if the `systemd` process is running on your system, enter the following command: @@ -265,7 +263,7 @@ general = // ... other general settings }; ``` -(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) No backend is specified here, so it will default to the `alsa` backend if more than one back end. To route the output to PulseAudio, add: +(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add: ``` output_backend = "pa"; @@ -288,11 +286,13 @@ alsa = }; ``` -The `pa` group is used to specify settings relevant to the PulseAudio backend. At present, there are no settings in this stanza. +The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing. -Please note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the standard `#!/bin/...` first line. (This behaviour may be different from other Shairports.) +Note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the standard `#!/bin/...` first line. (This behaviour may be different from other Shairports.) + +Shairport Sync can run a program whenever the volume is set or changed. You specify them using the `general` group settings `run_this_when_volume_changes`. This is to facilitate situations where something has to be done when the volume changes, e.g. adjust an external mixer value. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the command has been completed before continuing. Again, please note that the full path to the program must be specified, and script files will not be executed unless they are marked as executable and have the standard `#!/bin/...` first line. Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method. From 70eb4e902a4c632b25a29f7dd5f54d6c7a8d6c85 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 1 Aug 2017 12:05:15 +0200 Subject: [PATCH 230/239] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9e28149..7af5437c 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ For information about changes and updates, please refer to the RELEASENOTES.md f Building And Installing the Development Version --------------------- -The following procedures will install the `shairport-sync` application into your system. +The following procedures will build and install the `shairport-sync` application into your system. **Remove Old Versions Of Shairport Sync** @@ -68,7 +68,7 @@ To build Shairport Sync from sources on FreeBSD please refer to [FREEBSD.md](htt **Determine The Configuration Needed** -Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. If your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary: +Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary: ``` $ pactl info Server String: unix:/run/user/1000/pulse/native From 9369ca42e7f31db521cb61e9027456f67281a7f5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 1 Aug 2017 12:19:37 +0200 Subject: [PATCH 231/239] Add info about the volume set hook --- man/shairport-sync.7 | 7 ++++++- man/shairport-sync.7.xml | 14 +++++++++++--- man/shairport-sync.html | 12 ++++++++++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 9a3192e1..e2eed3ab 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -148,6 +148,11 @@ The purpose of this silent lead-in is to give the backend sufficient time to pre If the value chosen is too short for synchronised backends such as the ALSA, sndio or PA backends, then audio will not be synchronised correctly at the start of play. The default is to have a silent lead-in of approximately the same time as the latency set by the client. .TP +\fBrun_this_when_volume_is_set=\f1\fI"/full/path/to/application/and/args"\f1\fB;\f1 +Here you can specify a program and its arguments that will be run when the volume is set or changed. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate. + +The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument. AirPlay volume goes from 0.0 to -30.0 and -144.0 means "mute". +.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: .TP @@ -259,7 +264,7 @@ Here you can specify a program and its arguments that will be run just before a Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate. .TP \fBwait_for_completion=\f1\fI"choice"\f1\fB;\f1 -Set \fIchoice\f1 to "yes" to make shairport-sync wait until the programs specified in the \fBrun_this_before_play_begins\f1 and \fBrun_this_after_play_ends\f1 have completed execution before continuing. The default is "no". +Set \fIchoice\f1 to "yes" to make shairport-sync wait until the programs specified in the \fBrun_this_before_play_begins\f1, \fBrun_this_after_play_ends\f1 and \fBrun_this_when_volume_is_set\f1 have completed execution before continuing. The default is "no". .TP \fBallow_session_interruption=\f1\fI"choice"\f1\fB;\f1 If \fBchoice\f1 is set to "yes", then another source will be able to interrupt an existing play session and start a new one. When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 476a67c5..243babc3 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -271,7 +271,15 @@

If the value chosen is too short for synchronised backends such as the ALSA, sndio or PA backends, then audio will not be synchronised correctly at the start of play. The default is to have a silent lead-in of approximately the same time as the latency set by the client.

- + + +

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 alsamixer or aplay to discover what devices are available.) @@ -439,8 +447,8 @@

wait_for_completion="choice";

-

Set choice to "yes" to make shairport-sync wait until the programs specified in the run_this_before_play_begins - and run_this_after_play_ends have completed execution before continuing. The default is "no".

+

Set choice to "yes" to make shairport-sync wait until the programs specified in the run_this_before_play_begins, + run_this_after_play_ends and run_this_when_volume_is_set have completed execution before continuing. The default is "no".

- - - + +

These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.