3.3 release candidate 0
This commit is contained in:
@@ -32,6 +32,7 @@ shairport-sync.core
|
||||
/org.gnome.ShairportSync.service
|
||||
/dbus-interface.*
|
||||
/mpris-interface.*
|
||||
/lib_*.*
|
||||
|
||||
# Some eclipse project files
|
||||
.cproject
|
||||
|
||||
@@ -1,34 +1,52 @@
|
||||
Installing to Cygwin
|
||||
Installing Shairport Sync into Cygwin
|
||||
====
|
||||
|
||||
This guide is based on installing onto a fresh installation of Cygwin 2.895 (64-bit installation) running in Windows 10
|
||||
inside VMWare Fusion on a Mac.
|
||||
|
||||
The end result is a new Windows Service called `CYGWIN Shairport Sync`, providing an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through the Windows device.
|
||||
|
||||
Windows Firewall
|
||||
----
|
||||
While getting everything working, it is suggested that you temporarily disable the Windows Firewall. Shairport Sync uses port 5000 for TCP and uses three ports for UDP, so you should leave a minimum of three, and preferably at least 10, open from 6001 upwards. The Bonjour Service, used in conjunction with the Avahi daemon, advertises Shairport Sync over a number of further ports. Once everything is working, the firewall can be re-enabled gradually.
|
||||
|
||||
This is based on installing onto a fresh default installation of Cygwin 2.4.1 (64-bit installation) running in Windows 10
|
||||
inside VMWare Fusion on a Mac.
|
||||
Setting up Windows
|
||||
----
|
||||
Set up Windows 10 and install all updates. Install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2".
|
||||
|
||||
Note: to enable Cygwin to resolve domain names, it was found necessary to go to Windows `Control Panel` > `Network and Internet` > `Network Connections` >
|
||||
`Ethernet0` > `Properties` > `Internet Protocol Version 4 (TCP/IPv4)` > `Properties`, select `Use the following DNS server addresses`
|
||||
and enter valid DNS server addresses, e.g. `8.8.8.8`.
|
||||
* Download and run `Bonjour Print Services for Windows v2.0.2`
|
||||
* After accepting conditions and clicking the `Install` button, the installer will do a preliminary installation, installing just the Bonjour Service. It will then pause, inviting you to install Bonjour Print Services. You can decline this, as the Bonjour Service will have been installed during the first part of the installation.
|
||||
|
||||
* Use `Cygwin Setup` to install the following packages:
|
||||
* `pkg-config`
|
||||
* `autoconf`
|
||||
* `automake`
|
||||
* `clang`
|
||||
* `libdaemon-devel`
|
||||
* `popt-devel`
|
||||
* `make`
|
||||
* `libao-devel`
|
||||
* `openssl-devel`
|
||||
* `libtool`
|
||||
* `git`
|
||||
* `wget` for convenience,
|
||||
* `flex` for compiling `libconfig`
|
||||
* `bison` for compiling `libconfig`
|
||||
|
||||
Assuming the Cygwin setup program (`setup-x86_64.exe` in this case) is in your Downloads directory and the default directory is your home directory, the following command should work:
|
||||
* Check Bonjour Service is running. In Windows, open the `Services` desktop application and ensure that you can see `Bonjour Service` running.
|
||||
|
||||
Setting up Cygwin
|
||||
----
|
||||
* Download the Cygwin installer from the [official website](https://cygwin.com/install.html). Save the installer in the Downloads folder.
|
||||
|
||||
* Open a Windows `Command Prompt` window and enter the following multi-line command, omitting the `C:\Users\mike>` prompt:
|
||||
```
|
||||
.\Downloads\setup-x86_64.exe -P pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,make,libao-devel,openssl-devel,libtool,git,wget,flex,bison
|
||||
C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel,^
|
||||
libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,^
|
||||
make,libao-devel,openssl-devel,libtool,git,wget,flex,bison
|
||||
```
|
||||
This will do a complete installation of Cygwin and all necessary packages.
|
||||
* Set up the D-Bus and Avahi Services:
|
||||
|
||||
Open a `Cygwin64 Terminal` window in Administrator mode. Enter the following command:
|
||||
```
|
||||
$ messagebus-config
|
||||
```
|
||||
Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN D-Bus system service`. Open it and start it.
|
||||
|
||||
Next, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command:
|
||||
```
|
||||
$ /usr/sbin/avahi-daemon-config
|
||||
```
|
||||
Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Avahi service`. Open it and start it.
|
||||
|
||||
The `libconfig` Library
|
||||
----
|
||||
Shairport Sync relies on a library – `libconfig` – that is not a Cygwin package, so it must be downloaded, compiled and installed:
|
||||
* Download, configure, compile and install `libconfig`:
|
||||
```
|
||||
$ git clone https://github.com/hyperrealm/libconfig.git
|
||||
@@ -39,13 +57,34 @@ $ make
|
||||
$ make install
|
||||
$ cd ..
|
||||
```
|
||||
* Next, download, configure and compile Shairport Sync:
|
||||
|
||||
Shairport Sync
|
||||
----
|
||||
* Download, configure and compile Shairport Sync:
|
||||
```
|
||||
$ git clone https://github.com/mikebrady/shairport-sync.git
|
||||
$ cd shairport-sync
|
||||
$ git checkout development // this is temporary
|
||||
$ autoreconf -fi
|
||||
$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl --with-tinysvcmdns
|
||||
$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl \
|
||||
--with-avahi --with-dbus-interface --sysconfdir=/etc --with-cygwin-service
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
* That's it. There should be a `shairport-sync.exe` file in your directory.
|
||||
* The last step above installs the `shairport-sync` application into `/usr/local/bin` and also installs a configuration file, a service configuration script and two D-Bus policy files.
|
||||
|
||||
Shairport Sync Service
|
||||
----
|
||||
* To install Shairport Sync as a Cygwin Service, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command:
|
||||
```
|
||||
$ shairport-sync-config
|
||||
```
|
||||
Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it.
|
||||
|
||||
An AirPlay player on the local network should now be able to see an AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`.
|
||||
|
||||
Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to launch it – it should launch automatically when Windows is booted up.
|
||||
|
||||
Known Issues
|
||||
----
|
||||
* Shairport Sync cannot access the D-Bus system bus to make its D-Bus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus system bus, Shairport Sync can not. The two applications use different D-Bus libraries, so perhaps the issue lies there.)
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#include <sndfile.h>
|
||||
#include "FFTConvolver.h"
|
||||
#include "Utilities.h"
|
||||
extern "C" {
|
||||
#include "../common.h"
|
||||
}
|
||||
|
||||
extern "C" void die(const char *format, ...);
|
||||
extern "C" void debug(int level, const char *format, ...);
|
||||
|
||||
static fftconvolver::FFTConvolver convolver_l;
|
||||
static fftconvolver::FFTConvolver convolver_r;
|
||||
@@ -40,7 +39,7 @@ void convolver_init(const char* filename, int max_length)
|
||||
float buffer_l[size];
|
||||
float buffer_r[size];
|
||||
|
||||
int i;
|
||||
unsigned int i;
|
||||
for (i=0; i<size; ++i)
|
||||
{
|
||||
buffer_l[i] = buffer[2*i+0];
|
||||
|
||||
+51
-14
@@ -1,19 +1,25 @@
|
||||
SUBDIRS = man
|
||||
ARFLAGS = cr
|
||||
lib_dbus_interface_a_CFLAGS = -pthread
|
||||
lib_mpris_interface_a_CFLAGS = -pthread
|
||||
|
||||
|
||||
bin_PROGRAMS = shairport-sync
|
||||
|
||||
# See below for the flags for the test client program
|
||||
|
||||
shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c
|
||||
shairport_sync_SOURCES = shairport.c rtsp.c mdns.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c
|
||||
|
||||
AM_CFLAGS = -Wno-multichar -Wall -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
if BUILD_FOR_FREEBSD
|
||||
AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" -O2
|
||||
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
else
|
||||
if BUILD_FOR_OPENBSD
|
||||
AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
else
|
||||
AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CXXFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -34,10 +40,18 @@ if USE_TINYSVCMDNS
|
||||
shairport_sync_SOURCES += mdns_tinysvcmdns.c tinysvcmdns.c
|
||||
endif
|
||||
|
||||
if USE_EXTERNAL_MDNS
|
||||
shairport_sync_SOURCES += mdns_external.c
|
||||
endif
|
||||
|
||||
if USE_ALSA
|
||||
shairport_sync_SOURCES += audio_alsa.c
|
||||
endif
|
||||
|
||||
if USE_JACK
|
||||
shairport_sync_SOURCES += audio_jack.c
|
||||
endif
|
||||
|
||||
if USE_SNDIO
|
||||
shairport_sync_SOURCES += audio_sndio.c
|
||||
endif
|
||||
@@ -68,7 +82,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
|
||||
AM_CXXFLAGS += -std=c++11
|
||||
endif
|
||||
|
||||
if USE_DNS_SD
|
||||
@@ -86,29 +100,41 @@ endif
|
||||
BUILT_SOURCES =
|
||||
noinst_HEADERS =
|
||||
CLEANFILES =
|
||||
shairport_sync_LDADD =
|
||||
noinst_LIBRARIES =
|
||||
|
||||
if USE_DBUS
|
||||
shairport_sync_SOURCES += dbus-service.c dbus-interface.c
|
||||
shairport_sync_LDADD += lib_dbus_interface.a
|
||||
noinst_LIBRARIES += lib_dbus_interface.a
|
||||
lib_dbus_interface_a_SOURCES = dbus-interface.c
|
||||
shairport_sync_SOURCES += dbus-service.c
|
||||
BUILT_SOURCES += dbus-interface.h dbus-interface.c
|
||||
# We don't want to install this header
|
||||
noinst_HEADERS += $(BUILT_SOURCES)
|
||||
# Correctly clean the generated headers, but keep the xml description
|
||||
CLEANFILES += $(BUILT_SOURCES)
|
||||
|
||||
dbus-interface.h dbus-interface.c: org.gnome.ShairportSync.xml
|
||||
dbus-interface.c: org.gnome.ShairportSync.xml
|
||||
gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-interface org.gnome.ShairportSync.xml
|
||||
dbus-interface.h: dbus-interface.c
|
||||
touch dbus-interface.h
|
||||
endif
|
||||
|
||||
if USE_MPRIS
|
||||
shairport_sync_SOURCES += mpris-service.c mpris-interface.c
|
||||
shairport_sync_LDADD += lib_mpris_interface.a
|
||||
noinst_LIBRARIES += lib_mpris_interface.a
|
||||
lib_mpris_interface_a_SOURCES = mpris-interface.c
|
||||
shairport_sync_SOURCES += mpris-service.c
|
||||
BUILT_SOURCES += mpris-interface.h mpris-interface.c
|
||||
# We don't want to install this header
|
||||
noinst_HEADERS += $(BUILT_SOURCES)
|
||||
# Correctly clean the generated headers, but keep the xml description
|
||||
CLEANFILES += $(BUILT_SOURCES)
|
||||
|
||||
mpris-interface.h mpris-interface.c: org.mpris.MediaPlayer2.xml
|
||||
mpris-interface.c: org.mpris.MediaPlayer2.xml
|
||||
gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml
|
||||
mpris-interface.h: mpris-interface.c
|
||||
touch mpris-interface.h
|
||||
endif
|
||||
|
||||
noinst_PROGRAMS =
|
||||
@@ -116,13 +142,15 @@ noinst_PROGRAMS =
|
||||
if USE_DBUS_CLIENT
|
||||
#Make it, but don't install it anywhere
|
||||
noinst_PROGRAMS += shairport-sync-dbus-test-client
|
||||
shairport_sync_dbus_test_client_SOURCES = dbus-interface.c dbus-interface.h shairport-sync-dbus-test-client.c
|
||||
shairport_sync_dbus_test_client_SOURCES = shairport-sync-dbus-test-client.c
|
||||
shairport_sync_dbus_test_client_LDADD = lib_dbus_interface.a
|
||||
endif
|
||||
|
||||
if USE_MPRIS_CLIENT
|
||||
#Make it, but don't install it anywhere
|
||||
noinst_PROGRAMS += shairport-sync-mpris-test-client
|
||||
shairport_sync_mpris_test_client_SOURCES = mpris-interface.c mpris-interface.h shairport-sync-mpris-test-client.c
|
||||
shairport_sync_mpris_test_client_SOURCES = shairport-sync-mpris-test-client.c
|
||||
shairport_sync_mpris_test_client_LDADD = lib_mpris_interface.a
|
||||
endif
|
||||
|
||||
install-exec-hook:
|
||||
@@ -131,17 +159,23 @@ if INSTALL_CONFIG_FILES
|
||||
cp scripts/shairport-sync.conf $(DESTDIR)$(sysconfdir)/shairport-sync.conf.sample
|
||||
[ -f $(DESTDIR)$(sysconfdir)/shairport-sync.conf ] || cp scripts/shairport-sync.conf $(DESTDIR)$(sysconfdir)/shairport-sync.conf
|
||||
if USE_DBUS
|
||||
if INSTALL_CYGWIN_SERVICE
|
||||
cp scripts/shairport-sync-dbus-policy-cygwin.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-dbus.conf
|
||||
else
|
||||
cp scripts/shairport-sync-dbus-policy.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-dbus.conf
|
||||
endif
|
||||
endif
|
||||
if USE_MPRIS
|
||||
if INSTALL_CYGWIN_SERVICE
|
||||
cp scripts/shairport-sync-mpris-policy-cygwin.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-dbus.conf
|
||||
else
|
||||
cp scripts/shairport-sync-mpris-policy.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-mpris.conf
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
if INSTALL_SYSTEMV
|
||||
getent group shairport-sync &>/dev/null || groupadd -r shairport-sync >/dev/null
|
||||
getent passwd shairport-sync &> /dev/null || useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync >/dev/null
|
||||
[ -e /var/run/shairport-sync ] || mkdir -p /var/run/shairport-sync
|
||||
chown shairport-sync:shairport-sync /var/run/shairport-sync
|
||||
[ -e $(DESTDIR)$(sysconfdir)/init.d ] || mkdir -p $(DESTDIR)$(sysconfdir)/init.d
|
||||
[ -f $(DESTDIR)$(sysconfdir)/init.d/shairport-sync ] || cp scripts/shairport-sync $(DESTDIR)$(sysconfdir)/init.d/
|
||||
endif
|
||||
@@ -159,3 +193,6 @@ if INSTALL_FREEBSD_SERVICE
|
||||
[ -f /usr/local/etc/rc.d/shairport_sync ] || cp scripts/shairport-sync.freebsd /usr/local/etc/rc.d/shairport_sync
|
||||
chmod 555 /usr/local/etc/rc.d/shairport_sync
|
||||
endif
|
||||
if INSTALL_CYGWIN_SERVICE
|
||||
cp scripts/shairport-sync-config /usr/local/bin
|
||||
endif
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Activity Monitor
|
||||
*
|
||||
* Contains code to run an activity flag and associated timer
|
||||
* A pthread implements a simple state machine with three states,
|
||||
* "idle", "active" and "timing out".
|
||||
*
|
||||
*
|
||||
* This file is part of Shairport Sync.
|
||||
* Copyright (c) Mike Brady 2019
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "activity_monitor.h"
|
||||
#include "common.h"
|
||||
#include "rtsp.h"
|
||||
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
#include "dbus-service.h"
|
||||
#endif
|
||||
|
||||
|
||||
enum am_state state;
|
||||
enum ps_state { ps_inactive, ps_active } player_state;
|
||||
|
||||
int activity_monitor_running = 0;
|
||||
|
||||
pthread_t activity_monitor_thread;
|
||||
pthread_mutex_t activity_monitor_mutex;
|
||||
pthread_cond_t activity_monitor_cv;
|
||||
|
||||
void going_active(int block) {
|
||||
// debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" :
|
||||
// "out");
|
||||
if (config.cmd_active_start)
|
||||
command_execute(config.cmd_active_start, "", block);
|
||||
#ifdef CONFIG_METADATA
|
||||
debug(2, "abeg"); // active mode begin
|
||||
send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
if (dbus_service_is_running())
|
||||
shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
|
||||
#endif
|
||||
|
||||
if (config.disable_standby_mode == disable_standby_while_active) {
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
if (dbus_service_is_running())
|
||||
shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
|
||||
else
|
||||
config.keep_dac_busy = 1;
|
||||
#else
|
||||
config.keep_dac_busy = 1;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void going_inactive(int block) {
|
||||
// debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" :
|
||||
// "out");
|
||||
if (config.cmd_active_stop)
|
||||
command_execute(config.cmd_active_stop, "", block);
|
||||
#ifdef CONFIG_METADATA
|
||||
debug(2, "aend"); // active mode end
|
||||
send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
if (dbus_service_is_running())
|
||||
shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
|
||||
#endif
|
||||
|
||||
if (config.disable_standby_mode == disable_standby_while_active) {
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
if (dbus_service_is_running())
|
||||
shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
|
||||
else
|
||||
config.keep_dac_busy = 0;
|
||||
#else
|
||||
config.keep_dac_busy = 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void activity_monitor_signify_activity(int active) {
|
||||
// this could be pthread_cancelled and they is likely to be cancellation points in the
|
||||
// hooked-on procedures
|
||||
pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 1);
|
||||
player_state = active == 0 ? ps_inactive : ps_active;
|
||||
// Now, although we could simply let the state machine in the activity monitor thread
|
||||
// look after eveything, we will change state here in two situations:
|
||||
// 1. If the state machine is am_inactive and the player is ps_active
|
||||
// we will change the state to am_active and execute the going_active() function.
|
||||
// 2. If the state machine is am_active and the player is ps_inactive and
|
||||
// the activity_idle_timeout is 0, then we will change the state to am_inactive and
|
||||
// execute the going_inactive() function.
|
||||
//
|
||||
// The reason for all this is that we might want to perform the attached scripts
|
||||
// and wait for them to complete before continuing. If they were perfomed in the
|
||||
// activity monitor thread, then we couldn't wait for them to complete.
|
||||
|
||||
// Thus, the only time the thread will execute a going_... function is when a non-zero
|
||||
// timeout actually matures.
|
||||
|
||||
if ((state == am_inactive) && (player_state == ps_active)) {
|
||||
going_active(
|
||||
config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay
|
||||
} else if ((state == am_active) && (player_state == ps_inactive) &&
|
||||
(config.active_state_timeout == 0.0)) {
|
||||
going_inactive(
|
||||
config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay
|
||||
}
|
||||
|
||||
pthread_cond_signal(&activity_monitor_cv);
|
||||
pthread_cleanup_pop(1); // release the mutex
|
||||
}
|
||||
|
||||
void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||
debug(3, "activity_monitor: thread exit.");
|
||||
pthread_cond_destroy(&activity_monitor_cv);
|
||||
pthread_mutex_destroy(&activity_monitor_mutex);
|
||||
}
|
||||
|
||||
void *activity_monitor_thread_code(void *arg) {
|
||||
int rc = pthread_mutex_init(&activity_monitor_mutex, NULL);
|
||||
if (rc)
|
||||
die("activity_monitor: error %d initialising activity_monitor_mutex.", rc);
|
||||
|
||||
// set the flowcontrol condition variable to wait on a monotonic clock
|
||||
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
||||
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(&activity_monitor_cv, &attr);
|
||||
pthread_condattr_destroy(&attr);
|
||||
|
||||
#endif
|
||||
#ifdef COMPILE_FOR_OSX
|
||||
rc = pthread_cond_init(&activity_monitor_cv, NULL);
|
||||
#endif
|
||||
if (rc)
|
||||
die("activity_monitor: error %d initialising activity_monitor_cv.");
|
||||
pthread_cleanup_push(activity_thread_cleanup_handler, arg);
|
||||
|
||||
uint64_t sec;
|
||||
uint64_t nsec;
|
||||
struct timespec time_for_wait;
|
||||
|
||||
state = am_inactive;
|
||||
player_state = ps_inactive;
|
||||
|
||||
pthread_mutex_lock(&activity_monitor_mutex);
|
||||
do {
|
||||
switch (state) {
|
||||
case am_inactive:
|
||||
// debug(1,"am_state: am_inactive");
|
||||
while (player_state != ps_active)
|
||||
pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
|
||||
state = am_active;
|
||||
// going_active(); // this is done in activity_monitor_signify_activity
|
||||
break;
|
||||
case am_active:
|
||||
// debug(1,"am_state: am_active");
|
||||
while (player_state != ps_inactive)
|
||||
pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
|
||||
if (config.active_state_timeout == 0.0) {
|
||||
state = am_inactive;
|
||||
// going_inactive(); // this is done in activity_monitor_signify_activity
|
||||
} else {
|
||||
state = am_timing_out;
|
||||
|
||||
uint64_t time_to_wait_for_wakeup_fp =
|
||||
(uint64_t)(config.active_state_timeout * 1000000); // resolution of microseconds
|
||||
time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32;
|
||||
time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000;
|
||||
|
||||
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
||||
uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp;
|
||||
sec = time_of_wakeup_fp >> 32;
|
||||
nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
|
||||
time_for_wait.tv_sec = sec;
|
||||
time_for_wait.tv_nsec = nsec;
|
||||
#endif
|
||||
#ifdef COMPILE_FOR_OSX
|
||||
sec = time_to_wait_for_wakeup_fp >> 32;
|
||||
nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
|
||||
time_for_wait.tv_sec = sec;
|
||||
time_for_wait.tv_nsec = nsec;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case am_timing_out:
|
||||
// debug(1,"am_state: am_timing_out");
|
||||
rc = 0;
|
||||
while ((player_state != ps_active) && (rc != ETIMEDOUT)) {
|
||||
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
||||
rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex,
|
||||
&time_for_wait); // this is a pthread cancellation point
|
||||
#endif
|
||||
#ifdef COMPILE_FOR_OSX
|
||||
rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex,
|
||||
&time_for_wait);
|
||||
#endif
|
||||
}
|
||||
if (player_state == ps_active)
|
||||
state = am_active; // player has gone active -- do nothing, because it's still active
|
||||
else if (rc == ETIMEDOUT) {
|
||||
state = am_inactive;
|
||||
pthread_mutex_unlock(&activity_monitor_mutex);
|
||||
going_inactive(0); // don't wait for completion -- it makes no sense
|
||||
pthread_mutex_lock(&activity_monitor_mutex);
|
||||
} else {
|
||||
// activity monitor was woken up in the state am_timing_out, but not by a timeout and player
|
||||
// is not in ps_active state
|
||||
debug(1,
|
||||
"activity monitor was woken up in the state am_timing_out, but didn't change state");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
debug(1, "activity monitor in an illegal state!");
|
||||
state = am_inactive;
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
pthread_mutex_unlock(&activity_monitor_mutex);
|
||||
pthread_cleanup_pop(0); // should never happen
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
enum am_state activity_status() {
|
||||
return (state);
|
||||
}
|
||||
|
||||
void activity_monitor_start() {
|
||||
// debug(1,"activity_monitor_start");
|
||||
pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL);
|
||||
activity_monitor_running = 1;
|
||||
}
|
||||
|
||||
void activity_monitor_stop() {
|
||||
if (activity_monitor_running) {
|
||||
debug(3, "activity_monitor_stop start...");
|
||||
pthread_cancel(activity_monitor_thread);
|
||||
pthread_join(activity_monitor_thread, NULL);
|
||||
debug(2, "activity_monitor_stop complete");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
enum am_state { am_inactive, am_active, am_timing_out };
|
||||
|
||||
void activity_monitor_start();
|
||||
void activity_monitor_stop();
|
||||
void activity_monitor_signify_activity(int active); // 0 means inactive, non-zero means active
|
||||
enum am_state activity_status(); // true if non inactive; false if inactive
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Audio driver handler. This file is part of Shairport.
|
||||
* Copyright (c) James Laird 2013
|
||||
* Modifications (c) Mike Brady 2014 -- 2018
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
@@ -30,6 +31,9 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef CONFIG_JACK
|
||||
extern audio_output audio_jack;
|
||||
#endif
|
||||
#ifdef CONFIG_SNDIO
|
||||
extern audio_output audio_sndio;
|
||||
#endif
|
||||
@@ -65,6 +69,9 @@ static audio_output *outputs[] = {
|
||||
#ifdef CONFIG_PA
|
||||
&audio_pa,
|
||||
#endif
|
||||
#ifdef CONFIG_JACK
|
||||
&audio_jack,
|
||||
#endif
|
||||
#ifdef CONFIG_AO
|
||||
&audio_ao,
|
||||
#endif
|
||||
@@ -99,14 +106,18 @@ audio_output *audio_get_output(char *name) {
|
||||
void audio_ls_outputs(void) {
|
||||
audio_output **out;
|
||||
|
||||
printf("Available audio outputs:\n");
|
||||
printf("Available audio backends:\n");
|
||||
for (out = outputs; *out; out++)
|
||||
printf(" %s%s\n", (*out)->name, out == outputs ? " (default)" : "");
|
||||
|
||||
for (out = outputs; *out; out++) {
|
||||
printf("\n");
|
||||
printf("Options for output %s:\n", (*out)->name);
|
||||
(*out)->help();
|
||||
if ((*out)->help) {
|
||||
printf("Settings and options for the audio backend \"%s\":\n", (*out)->name);
|
||||
(*out)->help();
|
||||
} else {
|
||||
printf("There are no settings or options for the audio backend \"%s\".\n", (*out)->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +157,21 @@ void parse_general_audio_options(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the minumum buffer size for fancy interpolation setting in seconds. */
|
||||
if (config_lookup_float(config.cfg,
|
||||
"general.audio_backend_buffer_interpolation_threshold_in_seconds",
|
||||
&dvalue)) {
|
||||
if ((dvalue < 0) || (dvalue > config.audio_backend_buffer_desired_length)) {
|
||||
die("Invalid audio_backend_buffer_interpolation_threshold_in_seconds value: \"%f\". It "
|
||||
"should be between 0 and "
|
||||
"audio_backend_buffer_desired_length_in_seconds of %.3f, default is %.3f seconds",
|
||||
dvalue, config.audio_backend_buffer_desired_length,
|
||||
config.audio_backend_buffer_interpolation_threshold_in_seconds);
|
||||
} else {
|
||||
config.audio_backend_buffer_interpolation_threshold_in_seconds = dvalue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the latency offset (deprecated). */
|
||||
if (config_lookup_int(config.cfg, "general.audio_backend_latency_offset", &value)) {
|
||||
if ((value < -66150) || (value > 66150)) {
|
||||
@@ -164,9 +190,9 @@ 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 ((dvalue < -1.0) || (dvalue > 1.5)) {
|
||||
if ((dvalue < -1.75) || (dvalue > 1.75)) {
|
||||
die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It "
|
||||
"should be between -1.0 and +1.5, default is 0 seconds",
|
||||
"should be between -1.75 and +1.75, default is 0 seconds",
|
||||
dvalue);
|
||||
} else {
|
||||
config.audio_backend_latency_offset = dvalue;
|
||||
|
||||
@@ -22,9 +22,13 @@ typedef struct {
|
||||
void (*start)(int sample_rate, int sample_format);
|
||||
|
||||
// block of samples
|
||||
void (*play)(void *buf, int samples);
|
||||
int (*play)(void *buf, int samples);
|
||||
void (*stop)(void);
|
||||
|
||||
// may be null if no implemented
|
||||
int (*is_running)(
|
||||
void); // if implemented, will return 0 if everything is okay, non-zero otherwise
|
||||
|
||||
// may be null if not implemented
|
||||
void (*flush)(void);
|
||||
|
||||
@@ -33,6 +37,8 @@ typedef struct {
|
||||
// will change dynamically, so keep watching it. Implemented in ALSA only.
|
||||
// returns a negative error code if there's a problem
|
||||
int (*delay)(long *the_delay); // snd_pcm_sframes_t is a signed long
|
||||
int (*rate_info)(uint64_t *elapsed_time,
|
||||
uint64_t *frames_played); // use this to get the true rate of the DAC
|
||||
|
||||
// may be NULL, in which case soft volume is applied
|
||||
void (*volume)(double vol);
|
||||
@@ -41,7 +47,8 @@ typedef struct {
|
||||
void (*parameters)(audio_parameters *info);
|
||||
|
||||
// may be NULL, in which case software muting is used.
|
||||
void (*mute)(int do_mute);
|
||||
// also, will return a 1 if it is actually using the mute facility, 0 otherwise
|
||||
int (*mute)(int do_mute);
|
||||
|
||||
} audio_output;
|
||||
|
||||
|
||||
+1161
-568
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -121,7 +121,7 @@ static void deinit(void) {
|
||||
static void start(__attribute__((unused)) int sample_rate,
|
||||
__attribute__((unused)) int sample_format) {}
|
||||
|
||||
static void play(void *buf, int samples) { ao_play(dev, buf, samples * 4); }
|
||||
static int play(void *buf, int samples) { return ao_play(dev, buf, samples * 4); }
|
||||
|
||||
static void stop(void) {}
|
||||
|
||||
@@ -131,6 +131,7 @@ audio_output audio_ao = {.name = "ao",
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.is_running = NULL,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
|
||||
+5
-3
@@ -48,18 +48,20 @@ static void start(int sample_rate, __attribute__((unused)) int sample_format) {
|
||||
debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
|
||||
}
|
||||
|
||||
static void play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {}
|
||||
static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stop(void) { debug(1, "dummy audio stopped\n"); }
|
||||
|
||||
static void help(void) { printf(" There are no options for dummy audio.\n"); }
|
||||
|
||||
audio_output audio_dummy = {.name = "dummy",
|
||||
.help = &help,
|
||||
.help = NULL,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.is_running = NULL,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* jack output driver. This file is part of Shairport Sync.
|
||||
* Copyright (c) 2019 Mike Brady <mikebrady@iercom.net>,
|
||||
* Jörn Nettingsmeier <nettings@luchtbeweging.nl>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "audio.h"
|
||||
#include "common.h"
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
|
||||
// Two-channel, 16bit audio:
|
||||
static const int bytes_per_frame = 4;
|
||||
// Four seconds buffer -- should be plenty
|
||||
#define buffer_size (44100 * 4 * bytes_per_frame)
|
||||
|
||||
static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
int jack_init(int, char **);
|
||||
void jack_deinit(void);
|
||||
void jack_start(int, int);
|
||||
int play(void *, int);
|
||||
void jack_stop(void);
|
||||
int jack_delay(long *);
|
||||
void jack_flush(void);
|
||||
|
||||
audio_output audio_jack = {.name = "jack",
|
||||
.help = NULL,
|
||||
.init = &jack_init,
|
||||
.deinit = &jack_deinit,
|
||||
.start = &jack_start,
|
||||
.stop = NULL,
|
||||
.is_running = NULL,
|
||||
.flush = &jack_flush,
|
||||
.delay = &jack_delay,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL,
|
||||
.mute = NULL};
|
||||
|
||||
// This also affects deinterlacing.
|
||||
// So make it exactly the number of incoming audio channels!
|
||||
#define NPORTS 2
|
||||
static jack_port_t *port[NPORTS];
|
||||
static const char* port_name[NPORTS] = { "out_L", "out_R" };
|
||||
|
||||
static jack_client_t *client;
|
||||
static jack_nframes_t sample_rate;
|
||||
static jack_nframes_t jack_latency;
|
||||
|
||||
static jack_ringbuffer_t *jackbuf;
|
||||
static int flush_please = 0;
|
||||
|
||||
static jack_latency_range_t latest_latency_range[NPORTS];
|
||||
static int64_t time_of_latest_transfer;
|
||||
|
||||
|
||||
static inline jack_default_audio_sample_t sample_conv(short sample) {
|
||||
// It sounds correct, but I don't understand it.
|
||||
// Zero int needs to be zero float. Check.
|
||||
// Plus 32767 int is 1.0. Check.
|
||||
// Minus 32767 int is -0.99997. And here my brain shuts down.
|
||||
// In my head, it should be 1.0, and we should tolerate an overflow
|
||||
// at minus 32768. But I'm sure there's a textbook explanation somewhere.
|
||||
return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX));
|
||||
}
|
||||
|
||||
static void deinterleave_and_convert(const char *interleaved_input_buffer,
|
||||
jack_default_audio_sample_t* jack_output_buffer[],
|
||||
jack_nframes_t offset,
|
||||
jack_nframes_t nframes) {
|
||||
jack_nframes_t f;
|
||||
// We're dealing with 16bit audio here:
|
||||
short *ifp = (short *)interleaved_input_buffer;
|
||||
// Zero-copy, we're working directly on the target and destination buffers,
|
||||
// so deal with an offset for the second part of the input ringbuffer
|
||||
for (f = offset; f < (nframes + offset); f++) {
|
||||
for (int i = 0; i < NPORTS; i++) {
|
||||
jack_output_buffer[i][f] = sample_conv(*ifp++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is the JACK process callback. We don't decide when it runs.
|
||||
// It must be hard-realtime safe (i.e. fully deterministic, with constant CPU
|
||||
// usage. No calls to anything that could ever block: no syscalls, no screen
|
||||
// output, no file access, no mutexes...
|
||||
// The JACK ringbuffer we use to get the data in here is explicitly lock-free.
|
||||
static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) {
|
||||
jack_default_audio_sample_t *buffer[NPORTS];
|
||||
// Expect an array of two elements because of possible ringbuffer wrap-around:
|
||||
jack_ringbuffer_data_t v[2] = { 0 };
|
||||
jack_nframes_t i, thisbuf;
|
||||
int frames_written = 0;
|
||||
int frames_required = 0;
|
||||
|
||||
for (i = 0; i < NPORTS; i++) {
|
||||
buffer[i] = (jack_default_audio_sample_t *)jack_port_get_buffer(port[i], nframes);
|
||||
}
|
||||
if (flush_please) {
|
||||
// We just move the read pointer ahead without doing anything with the data.
|
||||
jack_ringbuffer_read_advance(jackbuf, jack_ringbuffer_read_space(jackbuf));
|
||||
flush_please = 0;
|
||||
// Since we don't change nframes, the whole buffer will be zeroed later.
|
||||
} else {
|
||||
jack_ringbuffer_get_read_vector(jackbuf, v);
|
||||
for (i = 0; i < 2; i++) {
|
||||
thisbuf = v[i].len / bytes_per_frame;
|
||||
if (thisbuf > nframes) {
|
||||
frames_required = nframes;
|
||||
} else {
|
||||
frames_required = thisbuf;
|
||||
}
|
||||
deinterleave_and_convert(v[i].buf, buffer, frames_written, frames_required);
|
||||
frames_written += frames_required;
|
||||
nframes -= frames_required;
|
||||
}
|
||||
jack_ringbuffer_read_advance(jackbuf, frames_written * bytes_per_frame);
|
||||
}
|
||||
// If there are any more frames to put into the buffer, fill them with
|
||||
// silence. This is a critical underflow situation. Let's at least keep the JACK
|
||||
// graph humming along while preventing the motorboat sound of a repeating buffer.
|
||||
while (nframes > 0) {
|
||||
for (i = 0; i < NPORTS; i++) {
|
||||
buffer[i][frames_written] = 0.0;
|
||||
}
|
||||
frames_written++;
|
||||
nframes--;
|
||||
}
|
||||
return 0; // Tell JACK that all is well.
|
||||
}
|
||||
|
||||
// This is the JACK graph reorder callback. Now we know some JACK connections
|
||||
// have changed, so we recompute the latency.
|
||||
static int graph(__attribute__((unused)) void * arg) {
|
||||
int latency = 0;
|
||||
debug(2, "JACK graph reorder callback called.");
|
||||
for (int i=0; i<NPORTS; i++) {
|
||||
jack_port_get_latency_range(port[i], JackPlaybackLatency, &latest_latency_range[i]);
|
||||
debug(2, "JACK latency for port %s\tmin: %d\t max: %d",
|
||||
port_name[i], latest_latency_range[i].min, latest_latency_range[i].max);
|
||||
latency += latest_latency_range[i].max;
|
||||
}
|
||||
latency /= NPORTS;
|
||||
jack_latency = latency;
|
||||
debug(1, "Average maximum JACK latency across all ports: %d", jack_latency);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This the function JACK will call in case of an error in the library.
|
||||
static void error(const char *desc) {
|
||||
warn("JACK error: \"%s\"", desc);
|
||||
}
|
||||
|
||||
// This is the function JACK will call in case of a non-critical event in the library.
|
||||
static void info(const char *desc) {
|
||||
inform("JACK information: \"%s\"", desc);
|
||||
}
|
||||
|
||||
int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
|
||||
int i;
|
||||
config.audio_backend_latency_offset = 0;
|
||||
config.audio_backend_buffer_desired_length = 0.500;
|
||||
// Below this, soxr interpolation will not occur -- it'll be basic interpolation
|
||||
// instead.
|
||||
config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25;
|
||||
|
||||
// Do the "general" audio options. Note, these options are in the "general" stanza!
|
||||
parse_general_audio_options();
|
||||
|
||||
// Now the options specific to the backend, from the "jack" stanza:
|
||||
if (config.cfg != NULL) {
|
||||
const char *str;
|
||||
if (config_lookup_string(config.cfg, "jack.client_name", &str)) {
|
||||
config.jack_client_name = (char *)str;
|
||||
}
|
||||
if (config_lookup_string(config.cfg, "jack.autoconnect_pattern", &str)) {
|
||||
config.jack_autoconnect_pattern = (char *)str;
|
||||
}
|
||||
}
|
||||
if (config.jack_client_name == NULL)
|
||||
config.jack_client_name = strdup("shairport-sync");
|
||||
|
||||
jackbuf = jack_ringbuffer_create(buffer_size);
|
||||
if (jackbuf == NULL)
|
||||
die("Can't allocate %d bytes for the JACK ringbuffer.", buffer_size);
|
||||
// Lock the ringbuffer into memory so that it never gets paged out, which would
|
||||
// break realtime constraints.
|
||||
jack_ringbuffer_mlock(jackbuf);
|
||||
// This mutex should not be necessary, but removing it causes segfaults on
|
||||
// shutdown. Apparently, there are multiple threads in the main program trying
|
||||
// to do stuff. FIXME: Try to consolidate into one thread and get rid of this lock.
|
||||
pthread_mutex_lock(&client_mutex);
|
||||
jack_status_t status;
|
||||
client = jack_client_open(config.jack_client_name, JackNoStartServer, &status);
|
||||
if (!client) {
|
||||
die("Could not start JACK server. JackStatus is %x", status);
|
||||
}
|
||||
sample_rate = jack_get_sample_rate(client);
|
||||
if (sample_rate != 44100) {
|
||||
die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync."
|
||||
" Must be 44100 Hz.", sample_rate);
|
||||
}
|
||||
jack_set_process_callback(client, &process, NULL);
|
||||
jack_set_graph_order_callback(client, &graph, NULL);
|
||||
jack_set_error_function(&error);
|
||||
jack_set_info_function(&info);
|
||||
for (i=0; i < NPORTS; i++) {
|
||||
port[i] = jack_port_register(client, port_name[i], JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
}
|
||||
if (jack_activate(client)) {
|
||||
die("Could not activate %s JACK client.", config.jack_client_name);
|
||||
} else {
|
||||
debug(2, "JACK client %s activated sucessfully.", config.jack_client_name);
|
||||
}
|
||||
if (config.jack_autoconnect_pattern != NULL) {
|
||||
inform("config.jack_autoconnect_pattern is %s. If you see the program die after this,"
|
||||
"you made a syntax error.", config.jack_autoconnect_pattern);
|
||||
// Sadly, this will throw a segfault if the user provides a syntactically incorrect regex.
|
||||
// I've reported it to the jack-devel mailing list, they're in a better place to fix it.
|
||||
const char** port_list = jack_get_ports(client, config.jack_autoconnect_pattern,
|
||||
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput);
|
||||
if (port_list != NULL) {
|
||||
for (i = 0; i < NPORTS ; i++) {
|
||||
char* full_port_name[NPORTS];
|
||||
full_port_name[i] = malloc(sizeof(char) * jack_port_name_size());
|
||||
sprintf(full_port_name[i], "%s:%s", config.jack_client_name, port_name[i]);
|
||||
if (port_list[i] != NULL) {
|
||||
int err;
|
||||
debug(2, "Connecting %s to %s.", full_port_name[i], port_list[i]);
|
||||
err = jack_connect(client, full_port_name[i], port_list[i]);
|
||||
switch (err) {
|
||||
case EEXIST:
|
||||
inform("The requested connection from %s to %s already exists.",
|
||||
full_port_name[i], port_list[i]);
|
||||
break;
|
||||
case 0:
|
||||
// success
|
||||
break;
|
||||
default:
|
||||
warn("JACK error no. %d occured while trying to connect %s to %s.",
|
||||
err, full_port_name[i], port_list[i]);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
inform("No matching port found in %s to connect %s to. You may not hear audio.",
|
||||
config.jack_autoconnect_pattern, full_port_name[i]);
|
||||
}
|
||||
free(full_port_name[i]);
|
||||
}
|
||||
while (port_list[i++] != NULL) {
|
||||
inform("Additional matching port %s found. Check that the connections are what you intended.");
|
||||
}
|
||||
jack_free(port_list);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&client_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void jack_deinit() {
|
||||
pthread_mutex_lock(&client_mutex);
|
||||
if (jack_deactivate(client))
|
||||
warn("Error deactivating jack client");
|
||||
if (jack_client_close(client))
|
||||
warn("Error closing jack client");
|
||||
pthread_mutex_unlock(&client_mutex);
|
||||
jack_ringbuffer_free(jackbuf);
|
||||
}
|
||||
|
||||
void jack_start(__attribute__((unused)) int i_sample_rate,
|
||||
__attribute__((unused)) int i_sample_format) {
|
||||
// Nothing to do, JACK client has already been set up at jack_init().
|
||||
// Also, we have no say over the sample rate or sample format of JACK,
|
||||
// We convert the 16bit samples to float, and die if the sample rate is != 44k1.
|
||||
// FIXME: later, resampling would be nice. Fold into soxr if possible.
|
||||
}
|
||||
|
||||
void jack_flush() {
|
||||
debug(2, "Only the consumer can safely flush a lock-free ringbuffer. Asking the"
|
||||
" process callback to do it...");
|
||||
flush_please = 1;
|
||||
}
|
||||
|
||||
int jack_delay(long *the_delay) {
|
||||
// Semantics change: we now look at the last transfer into the lock-free
|
||||
// ringbuffer, not into the jack buffers directly (because locking those would
|
||||
// violate real-time constraints). On average, that should lead to just a
|
||||
// constant additional latency.
|
||||
// Without the mutex, we could get the time of what is the last transfer of data
|
||||
// to a jack buffer, but then a transfer could occur and we would get the buffer
|
||||
// occupancy after another transfer had occurred, so we could "lose" a full transfer
|
||||
// (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds)
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
int64_t time_now = get_absolute_time_in_fp();
|
||||
int64_t delta = time_now - time_of_latest_transfer;
|
||||
size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame;
|
||||
debug(2, "audio_occupancy_now is %d.", audio_occupancy_now);
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
|
||||
int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32;
|
||||
// debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check);
|
||||
// jack_latency is set by the graph() callback, it's the average of the maximum
|
||||
// latencies of all our output ports. Adjust this constant baseline delay according
|
||||
// to the buffer fill level:
|
||||
*the_delay = jack_latency + audio_occupancy_now - frames_processed_since_latest_latency_check;
|
||||
// debug(1,"reporting a delay of %d frames",*the_delay);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int play(void *buf, int samples) {
|
||||
// debug(1,"jack_play of %d samples.",samples);
|
||||
// copy the samples into the queue
|
||||
size_t bytes_to_transfer, bytes_transferred;
|
||||
bytes_to_transfer = samples * bytes_per_frame;
|
||||
// It's ok to lock here since we're not in the realtime callback:
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
bytes_transferred = jack_ringbuffer_write(jackbuf, buf, bytes_to_transfer);
|
||||
time_of_latest_transfer = get_absolute_time_in_fp();
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
if (bytes_transferred < bytes_to_transfer) {
|
||||
warn("JACK ringbuffer overrun. Only wrote %d of %d bytes.",
|
||||
bytes_transferred, bytes_to_transfer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+26
-6
@@ -70,6 +70,10 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
|
||||
|
||||
// set up default values first
|
||||
config.audio_backend_buffer_desired_length = 0.35;
|
||||
config.audio_backend_buffer_interpolation_threshold_in_seconds =
|
||||
0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation
|
||||
// instead.
|
||||
|
||||
config.audio_backend_latency_offset = 0;
|
||||
|
||||
// get settings from settings file
|
||||
@@ -85,6 +89,11 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
|
||||
if (config_lookup_string(config.cfg, "pa.application_name", &str)) {
|
||||
config.pa_application_name = (char *)str;
|
||||
}
|
||||
|
||||
/* Get the PulseAudio sink name. */
|
||||
if (config_lookup_string(config.cfg, "pa.sink", &str)) {
|
||||
config.pa_sink = (char *)str;
|
||||
}
|
||||
}
|
||||
|
||||
// finish collecting settings
|
||||
@@ -177,8 +186,19 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
// PA_STREAM_AUTO_TIMING_UPDATE;
|
||||
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
|
||||
|
||||
// Connect stream to the default audio output sink
|
||||
if (pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) != 0)
|
||||
int connect_result;
|
||||
|
||||
if (config.pa_sink) {
|
||||
// Connect stream to the sink specified in the config
|
||||
connect_result =
|
||||
pa_stream_connect_playback(stream, config.pa_sink, &buffer_attr, stream_flags, NULL, NULL);
|
||||
} else {
|
||||
// Connect stream to the default audio output sink
|
||||
connect_result =
|
||||
pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL);
|
||||
}
|
||||
|
||||
if (connect_result != 0)
|
||||
die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".",
|
||||
pa_strerror(pa_context_errno(context)));
|
||||
|
||||
@@ -197,7 +217,7 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
static void play(void *buf, int samples) {
|
||||
static int play(void *buf, int samples) {
|
||||
// debug(1,"pa_play of %d samples.",samples);
|
||||
// copy the samples into the queue
|
||||
size_t bytes_to_transfer = samples * 2 * 2;
|
||||
@@ -223,6 +243,7 @@ static void play(void *buf, int samples) {
|
||||
pa_stream_cork(stream, 0, stream_success_cb, mainloop);
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_delay(long *the_delay) {
|
||||
@@ -278,14 +299,13 @@ static void stop(void) {
|
||||
pa_stream_disconnect(stream);
|
||||
}
|
||||
|
||||
static void help(void) { printf(" no settings.\n"); }
|
||||
|
||||
audio_output audio_pa = {.name = "pa",
|
||||
.help = &help,
|
||||
.help = NULL,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.is_running = NULL,
|
||||
.flush = &flush,
|
||||
.delay = &pa_delay,
|
||||
.play = &play,
|
||||
|
||||
+4
-2
@@ -54,7 +54,7 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
}
|
||||
}
|
||||
|
||||
static void play(void *buf, int samples) {
|
||||
static int play(void *buf, int samples) {
|
||||
// if the file is not open, try to open it.
|
||||
char errorstring[1024];
|
||||
if (fd == -1) {
|
||||
@@ -73,6 +73,7 @@ static void play(void *buf, int samples) {
|
||||
warn("Error %d opening the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
|
||||
warned = 1;
|
||||
}
|
||||
return warned;
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
@@ -126,7 +127,7 @@ static void deinit(void) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void help(void) { printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); }
|
||||
static void help(void) { printf(" specify the pathname of the pipe to write to.\n"); }
|
||||
|
||||
audio_output audio_pipe = {.name = "pipe",
|
||||
.help = &help,
|
||||
@@ -134,6 +135,7 @@ audio_output audio_pipe = {.name = "pipe",
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.is_running = NULL,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
|
||||
+22
-14
@@ -33,7 +33,7 @@ static int init(int, char **);
|
||||
static void onmove_cb(void *, int);
|
||||
static void deinit(void);
|
||||
static void start(int, int);
|
||||
static void play(void *, int);
|
||||
static int play(void *, int);
|
||||
static void stop(void);
|
||||
static void onmove_cb(void *, int);
|
||||
static int delay(long *);
|
||||
@@ -45,6 +45,7 @@ audio_output audio_sndio = {.name = "sndio",
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.is_running = NULL,
|
||||
.flush = &flush,
|
||||
.delay = &delay,
|
||||
.play = &play,
|
||||
@@ -64,20 +65,21 @@ struct sio_par par;
|
||||
struct sndio_formats {
|
||||
const char *name;
|
||||
enum sps_format_t fmt;
|
||||
|
||||
unsigned int rate;
|
||||
unsigned int bits;
|
||||
unsigned int bps;
|
||||
unsigned int sig;
|
||||
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, 44100, 8, 1, 1, SIO_LE_NATIVE},
|
||||
{"U8", SPS_FORMAT_U8, 44100, 8, 1, 0, SIO_LE_NATIVE},
|
||||
{"S16", SPS_FORMAT_S16, 44100, 16, 2, 1, SIO_LE_NATIVE},
|
||||
{"AUTOMATIC", SPS_FORMAT_S16, 44100, 16, 2, 1, SIO_LE_NATIVE}, // TODO: make this really automatic?
|
||||
{"S24", SPS_FORMAT_S24, 44100, 24, 4, 1, SIO_LE_NATIVE},
|
||||
{"S24_3LE", SPS_FORMAT_S24_3LE, 44100, 24, 3, 1, 1},
|
||||
{"S24_3BE", SPS_FORMAT_S24_3BE, 44100, 24, 3, 1, 0},
|
||||
{"S32", SPS_FORMAT_S32, 44100, 24, 4, 1, SIO_LE_NATIVE}};
|
||||
|
||||
static void help() { printf(" -d output-device set the output device [default*|...]\n"); }
|
||||
|
||||
@@ -98,6 +100,9 @@ static int init(int argc, char **argv) {
|
||||
devname = SIO_DEVANY;
|
||||
|
||||
config.audio_backend_buffer_desired_length = 1.0;
|
||||
config.audio_backend_buffer_interpolation_threshold_in_seconds =
|
||||
0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation
|
||||
// instead.
|
||||
config.audio_backend_latency_offset = 0;
|
||||
|
||||
// get settings from settings file
|
||||
@@ -142,7 +147,7 @@ static int init(int argc, char **argv) {
|
||||
}
|
||||
if (!found)
|
||||
die("Invalid output format \"%s\". Should be one of: S8, U8, S16, S24, "
|
||||
"S24_3LE, S24_3BE, S32",
|
||||
"S24_3LE, S24_3BE, S32, Automatic",
|
||||
tmp);
|
||||
}
|
||||
}
|
||||
@@ -170,6 +175,7 @@ static int init(int argc, char **argv) {
|
||||
written = played = 0;
|
||||
time_of_last_onmove_cb = 0;
|
||||
at_least_one_onmove_cb_seen = 0;
|
||||
|
||||
|
||||
for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
|
||||
if (formats[i].fmt == config.output_format) {
|
||||
@@ -180,19 +186,20 @@ static int init(int argc, char **argv) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
formats[i].le == par.le && formats[i].rate == par.rate) {
|
||||
config.output_format = formats[i].fmt;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
die("sndio: failed to negotiate audio parameters");
|
||||
die("sndio: could not set output device to the required format and rate.");
|
||||
|
||||
|
||||
framesize = par.bps * par.pchan;
|
||||
config.output_rate = par.rate;
|
||||
@@ -222,12 +229,13 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
pthread_mutex_unlock(&sndio_mutex);
|
||||
}
|
||||
|
||||
static void play(void *buf, int frames) {
|
||||
static int play(void *buf, int frames) {
|
||||
if (frames > 0) {
|
||||
pthread_mutex_lock(&sndio_mutex);
|
||||
written += sio_write(hdl, buf, frames * framesize);
|
||||
pthread_mutex_unlock(&sndio_mutex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stop() {
|
||||
|
||||
+6
-3
@@ -27,8 +27,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m
|
||||
int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer);
|
||||
int fill_count = fill_bytes / outstream->bytes_per_frame;
|
||||
|
||||
debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
|
||||
"outstream->bytes_per_frame: %d",
|
||||
debug(3,
|
||||
"[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
|
||||
"outstream->bytes_per_frame: %d",
|
||||
frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame);
|
||||
|
||||
if (frame_count_min > fill_count) {
|
||||
@@ -167,7 +168,7 @@ static void start(int sample_rate, int sample_format) {
|
||||
debug(1, "libsoundio output started\n");
|
||||
}
|
||||
|
||||
static void play(void *buf, int samples) {
|
||||
static int play(void *buf, int samples) {
|
||||
// int err;
|
||||
int free_bytes = soundio_ring_buffer_free_count(ring_buffer);
|
||||
int written_bytes = 0;
|
||||
@@ -186,6 +187,7 @@ static void play(void *buf, int samples) {
|
||||
soundio_ring_buffer_advance_write_ptr(ring_buffer, write_bytes);
|
||||
debug(3, "[<<---] Written to buffer : %d\n", written_bytes);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void parameters(audio_parameters *info) {
|
||||
@@ -216,6 +218,7 @@ audio_output audio_soundio = {.name = "soundio",
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.is_running = NULL,
|
||||
.flush = &flush,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
|
||||
+4
-4
@@ -43,7 +43,7 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
fd = STDOUT_FILENO;
|
||||
}
|
||||
|
||||
static void play(void *buf, int samples) {
|
||||
static int play(void *buf, int samples) {
|
||||
char errorstring[1024];
|
||||
int warned = 0;
|
||||
int rc = write(fd, buf, samples * 4);
|
||||
@@ -52,6 +52,7 @@ static void play(void *buf, int samples) {
|
||||
warn("Error %d writing to stdout: \"%s\".", errno, errorstring);
|
||||
warned = 1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
@@ -73,14 +74,13 @@ static void deinit(void) {
|
||||
// don't close stdout
|
||||
}
|
||||
|
||||
static void help(void) { printf(" stdout takes no arguments\n"); }
|
||||
|
||||
audio_output audio_stdout = {.name = "stdout",
|
||||
.help = &help,
|
||||
.help = NULL,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.is_running = NULL,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Utility routines. This file is part of Shairport.
|
||||
* Copyright (c) James Laird 2013
|
||||
* The volume to attenuation function vol2attn copyright (c) Mike Brady 2014
|
||||
* Further changes and additions (c) Mike Brady 2014 -- 2019
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
@@ -46,7 +47,7 @@
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
#include <openssl/evp.h>
|
||||
@@ -54,7 +55,7 @@
|
||||
#include <openssl/rsa.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
#include "polarssl/ctr_drbg.h"
|
||||
#include "polarssl/entropy.h"
|
||||
#include <polarssl/base64.h>
|
||||
@@ -67,17 +68,20 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#include "mbedtls/ctr_drbg.h"
|
||||
#include "mbedtls/entropy.h"
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/version.h>
|
||||
#include <mbedtls/x509.h>
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
#include <libdaemon/dlog.h>
|
||||
#else
|
||||
#include <syslog.h>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ALSA
|
||||
void set_alsa_out_dev(char *);
|
||||
@@ -87,17 +91,68 @@ void set_alsa_out_dev(char *);
|
||||
|
||||
static volatile int requested_connection_state_to_output = 1;
|
||||
|
||||
// this stuff is to direct logging to syslog via libdaemon or directly
|
||||
// alternatively you can direct it to stderr using a command line option
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
static void (*sps_log)(int prio, const char *t, ...) = daemon_log;
|
||||
#else
|
||||
static void (*sps_log)(int prio, const char *t, ...) = syslog;
|
||||
#endif
|
||||
|
||||
void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
|
||||
char s[1024];
|
||||
va_list args;
|
||||
va_start(args, t);
|
||||
vsnprintf(s, sizeof(s), t, args);
|
||||
va_end(args);
|
||||
fprintf(stderr,"%s\n",s);
|
||||
}
|
||||
|
||||
void log_to_stderr() {
|
||||
sps_log = do_sps_log;
|
||||
}
|
||||
|
||||
shairport_cfg config;
|
||||
|
||||
int debuglev = 0;
|
||||
volatile int debuglev = 0;
|
||||
|
||||
sigset_t pselect_sigset;
|
||||
|
||||
int usleep_uncancellable(useconds_t usec) {
|
||||
int response;
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
response = usleep(usec);
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
return response;
|
||||
}
|
||||
|
||||
static uint16_t UDPPortIndex = 0;
|
||||
|
||||
void resetFreeUDPPort() {
|
||||
debug(3, "Resetting UDP Port Suggestion to %u", config.udp_port_base);
|
||||
UDPPortIndex = 0;
|
||||
}
|
||||
|
||||
uint16_t nextFreeUDPPort() {
|
||||
if (UDPPortIndex == 0)
|
||||
UDPPortIndex = config.udp_port_base;
|
||||
else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1))
|
||||
UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can
|
||||
// be assigned by resetFreeUDPPort without checking
|
||||
else
|
||||
UDPPortIndex++;
|
||||
return UDPPortIndex;
|
||||
}
|
||||
|
||||
int get_requested_connection_state_to_output() { return requested_connection_state_to_output; }
|
||||
|
||||
void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; }
|
||||
|
||||
void die(const char *format, ...) {
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
char s[1024];
|
||||
s[0] = 0;
|
||||
uint64_t time_now = get_absolute_time_in_fp();
|
||||
@@ -113,18 +168,20 @@ void die(const char *format, ...) {
|
||||
va_end(args);
|
||||
|
||||
if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
|
||||
daemon_log(LOG_EMERG, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s);
|
||||
sps_log(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s);
|
||||
else if ((debuglev) && (config.debugger_show_relative_time))
|
||||
daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tsl, s);
|
||||
sps_log(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s);
|
||||
else if ((debuglev) && (config.debugger_show_elapsed_time))
|
||||
daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tss, s);
|
||||
sps_log(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s);
|
||||
else
|
||||
daemon_log(LOG_EMERG, "fatal error: %s", s);
|
||||
shairport_shutdown();
|
||||
sps_log(LOG_ERR, "fatal error: %s", s);
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void warn(const char *format, ...) {
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
char s[1024];
|
||||
s[0] = 0;
|
||||
uint64_t time_now = get_absolute_time_in_fp();
|
||||
@@ -138,20 +195,22 @@ void warn(const char *format, ...) {
|
||||
va_start(args, format);
|
||||
vsnprintf(s, sizeof(s), format, args);
|
||||
va_end(args);
|
||||
|
||||
if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
|
||||
daemon_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s);
|
||||
sps_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s);
|
||||
else if ((debuglev) && (config.debugger_show_relative_time))
|
||||
daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s);
|
||||
sps_log(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s);
|
||||
else if ((debuglev) && (config.debugger_show_elapsed_time))
|
||||
daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s);
|
||||
sps_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s);
|
||||
else
|
||||
daemon_log(LOG_WARNING, "%s", s);
|
||||
sps_log(LOG_WARNING, "%s", s);
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
}
|
||||
|
||||
void debug(int level, const char *format, ...) {
|
||||
if (level > debuglev)
|
||||
return;
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
char s[1024];
|
||||
s[0] = 0;
|
||||
uint64_t time_now = get_absolute_time_in_fp();
|
||||
@@ -166,23 +225,41 @@ void debug(int level, const char *format, ...) {
|
||||
vsnprintf(s, sizeof(s), format, args);
|
||||
va_end(args);
|
||||
if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
|
||||
daemon_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
|
||||
sps_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
|
||||
else if (config.debugger_show_relative_time)
|
||||
daemon_log(LOG_DEBUG, "% 20.9f|%s", tsl, s);
|
||||
sps_log(LOG_DEBUG, "% 20.9f|%s", tsl, s);
|
||||
else if (config.debugger_show_elapsed_time)
|
||||
daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s);
|
||||
sps_log(LOG_DEBUG, "% 20.9f|%s", tss, s);
|
||||
else
|
||||
daemon_log(LOG_DEBUG, "%s", s);
|
||||
sps_log(LOG_DEBUG, "%s", s);
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
}
|
||||
|
||||
void inform(const char *format, ...) {
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
char s[1024];
|
||||
s[0] = 0;
|
||||
uint64_t time_now = get_absolute_time_in_fp();
|
||||
uint64_t time_since_start = time_now - fp_time_at_startup;
|
||||
uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
|
||||
fp_time_at_last_debug_message = time_now;
|
||||
uint64_t divisor = (uint64_t)1 << 32;
|
||||
double tss = 1.0 * time_since_start / divisor;
|
||||
double tsl = 1.0 * time_since_last_debug_message / divisor;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(s, sizeof(s), format, args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_INFO, "%s", s);
|
||||
if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
|
||||
sps_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
|
||||
else if ((debuglev) && (config.debugger_show_relative_time))
|
||||
sps_log(LOG_INFO, "% 20.9f|%s", tsl, s);
|
||||
else if ((debuglev) && (config.debugger_show_elapsed_time))
|
||||
sps_log(LOG_INFO, "% 20.9f|%s", tss, s);
|
||||
else
|
||||
sps_log(LOG_INFO, "%s", s);
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
}
|
||||
|
||||
// The following two functions are adapted slightly and with thanks from Jonathan Leffler's sample
|
||||
@@ -233,7 +310,7 @@ int mkpath(const char *path, mode_t mode) {
|
||||
return (status);
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
char *base64_enc(uint8_t *input, int length) {
|
||||
char *buf = NULL;
|
||||
size_t dlen = 0;
|
||||
@@ -286,7 +363,7 @@ uint8_t *base64_dec(char *input, int *outlen) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
char *base64_enc(uint8_t *input, int length) {
|
||||
char *buf = NULL;
|
||||
size_t dlen = 0;
|
||||
@@ -339,8 +416,10 @@ uint8_t *base64_dec(char *input, int *outlen) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
char *base64_enc(uint8_t *input, int length) {
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
BIO *bmem, *b64;
|
||||
BUF_MEM *bptr;
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
@@ -348,7 +427,7 @@ char *base64_enc(uint8_t *input, int length) {
|
||||
b64 = BIO_push(b64, bmem);
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
BIO_write(b64, input, length);
|
||||
BIO_flush(b64);
|
||||
(void) BIO_flush(b64);
|
||||
BIO_get_mem_ptr(b64, &bptr);
|
||||
|
||||
char *buf = (char *)malloc(bptr->length);
|
||||
@@ -359,12 +438,15 @@ char *base64_enc(uint8_t *input, int length) {
|
||||
buf[bptr->length - 1] = 0;
|
||||
}
|
||||
|
||||
BIO_free_all(bmem);
|
||||
BIO_free_all(b64);
|
||||
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
return buf;
|
||||
}
|
||||
|
||||
uint8_t *base64_dec(char *input, int *outlen) {
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
BIO *bmem, *b64;
|
||||
int inlen = strlen(input);
|
||||
|
||||
@@ -377,7 +459,7 @@ uint8_t *base64_dec(char *input, int *outlen) {
|
||||
BIO_write(bmem, input, inlen);
|
||||
while (inlen++ & 3)
|
||||
BIO_write(bmem, "=", 1);
|
||||
BIO_flush(bmem);
|
||||
(void) BIO_flush(bmem);
|
||||
|
||||
int bufsize = strlen(input) * 3 / 4 + 1;
|
||||
uint8_t *buf = malloc(bufsize);
|
||||
@@ -385,9 +467,10 @@ uint8_t *base64_dec(char *input, int *outlen) {
|
||||
|
||||
nread = BIO_read(b64, buf, bufsize);
|
||||
|
||||
BIO_free_all(bmem);
|
||||
BIO_free_all(b64);
|
||||
|
||||
*outlen = nread;
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
@@ -417,10 +500,11 @@ static char super_secret_key[] =
|
||||
"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
|
||||
"-----END RSA PRIVATE KEY-----\0";
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
static RSA *rsa = NULL;
|
||||
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
RSA *rsa = NULL;
|
||||
if (!rsa) {
|
||||
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
|
||||
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
|
||||
@@ -438,11 +522,13 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
default:
|
||||
die("bad rsa mode");
|
||||
}
|
||||
RSA_free(rsa);
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
mbedtls_pk_context pkctx;
|
||||
mbedtls_rsa_context *trsa;
|
||||
@@ -499,7 +585,7 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
rsa_context trsa;
|
||||
const char *pers = "rsa_encrypt";
|
||||
@@ -554,7 +640,26 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
}
|
||||
#endif
|
||||
|
||||
int config_set_lookup_bool(config_t *cfg, char *where, int *dst) {
|
||||
const char *str = 0;
|
||||
if (config_lookup_string(cfg, where, &str)) {
|
||||
if (strcasecmp(str, "no") == 0) {
|
||||
(*dst) = 0;
|
||||
return 1;
|
||||
} else if (strcasecmp(str, "yes") == 0) {
|
||||
(*dst) = 1;
|
||||
return 1;
|
||||
} else {
|
||||
die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void command_set_volume(double volume) {
|
||||
// this has a cancellation point if waiting is enabled
|
||||
if (config.cmd_set_volume) {
|
||||
/*Spawn a child to run the program.*/
|
||||
pid_t pid = fork();
|
||||
@@ -601,6 +706,7 @@ void command_set_volume(double volume) {
|
||||
}
|
||||
|
||||
void command_start(void) {
|
||||
// this has a cancellation point if waiting is enabled or a response is awaited
|
||||
if (config.cmd_start) {
|
||||
pid_t pid;
|
||||
int pipes[2];
|
||||
@@ -643,7 +749,8 @@ void command_start(void) {
|
||||
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) {
|
||||
if ((rc != pid) && (errno != ECHILD)) {
|
||||
// In this context, ECHILD means that the child process has already completed, I think!
|
||||
warn("Execution of on-start command returned an error.");
|
||||
debug(1, "on-start command %s finished with error %d", config.cmd_start, errno);
|
||||
}
|
||||
@@ -666,40 +773,54 @@ void command_start(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
void command_execute(const char *command, const char *extra_argument, const int block) {
|
||||
// this has a cancellation point if waiting is enabled
|
||||
if (command) {
|
||||
char new_command_buffer[1024];
|
||||
char *full_command = (char *)command;
|
||||
if (extra_argument != NULL) {
|
||||
memset(new_command_buffer, 0, sizeof(new_command_buffer));
|
||||
snprintf(new_command_buffer, sizeof(new_command_buffer), "%s %s", command, extra_argument);
|
||||
full_command = new_command_buffer;
|
||||
}
|
||||
|
||||
void command_stop(void) {
|
||||
if (config.cmd_stop) {
|
||||
/*Spawn a child to run the program.*/
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) { /* child process */
|
||||
int argC;
|
||||
char **argV;
|
||||
// debug(1,"on-stop command found.");
|
||||
if (poptParseArgvString(config.cmd_stop, &argC, (const char ***)&argV) !=
|
||||
if (poptParseArgvString(full_command, &argC, (const char ***)&argV) !=
|
||||
0) // note that argV should be free()'d after use, but we expect this fork to exit
|
||||
// eventually.
|
||||
debug(1, "Can't decipher on-stop command arguments");
|
||||
debug(1, "Can't decipher command arguments in \"%s\".", full_command);
|
||||
else {
|
||||
// debug(1,"Executing on-stop command %s",config.cmd_stop);
|
||||
// debug(1,"Executing command %s",full_command);
|
||||
execv(argV[0], argV);
|
||||
warn("Execution of on-stop command failed to start");
|
||||
debug(1, "Error executing on-stop command %s", config.cmd_stop);
|
||||
warn("Execution of command \"%s\" failed to start", full_command);
|
||||
debug(1, "Error executing command \"%s\".", full_command);
|
||||
exit(127); /* only if execv fails */
|
||||
}
|
||||
} else {
|
||||
if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
|
||||
if (block) { /* 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-stop command returned an error.");
|
||||
debug(1, "Stop command %s finished with error %d", config.cmd_stop, errno);
|
||||
if ((rc != pid) && (errno != ECHILD)) {
|
||||
// In this context, ECHILD means that the child process has already completed, I think!
|
||||
warn("Execution of command \"%s\" returned an error.", full_command);
|
||||
debug(1, "Command \"%s\" finished with error %d", full_command, errno);
|
||||
}
|
||||
}
|
||||
// debug(1,"Continue after on-stop command");
|
||||
// debug(1,"Continue after on-unfixable command");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void command_stop(void) {
|
||||
// this has a cancellation point if waiting is enabled
|
||||
if (config.cmd_stop)
|
||||
command_execute(config.cmd_stop, "", config.cmd_blocking);
|
||||
}
|
||||
|
||||
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
|
||||
|
||||
uint32_t uatoi(const char *nptr) {
|
||||
@@ -732,21 +853,17 @@ double flat_vol2attn(double vol, long max_db, long min_db) {
|
||||
|
||||
double vol2attn(double vol, long max_db, long min_db) {
|
||||
|
||||
// We use a little coordinate geometry to build a transfer function from the volume passed in to the
|
||||
// device's dynamic range.
|
||||
// (See the diagram in the documents folder.)
|
||||
// The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out"
|
||||
// which will be from the bottom of the range to the top.
|
||||
// We build the transfer function from one or more lines. We characterise each line with two
|
||||
// numbers:
|
||||
// the first is where on x the line starts when y=0 (x can be from 0 to -30); the second is where on
|
||||
// y the line stops when when x is -30.
|
||||
// thus, if the line was characterised as {0,-30}, it would be an identity transfer.
|
||||
// Assuming, for example, a dynamic range of lv=-60 to hv=0
|
||||
// Typically we'll use three lines -- a three order transfer function
|
||||
// First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
|
||||
// Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
|
||||
// Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30
|
||||
// We use a little coordinate geometry to build a transfer function from the volume passed in to
|
||||
// the device's dynamic range. (See the diagram in the documents folder.) The x axis is the
|
||||
// "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from
|
||||
// the bottom of the range to the top. We build the transfer function from one or more lines. We
|
||||
// characterise each line with two numbers: the first is where on x the line starts when y=0 (x
|
||||
// can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the
|
||||
// line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a
|
||||
// dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer
|
||||
// function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
|
||||
// Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
|
||||
// Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30
|
||||
|
||||
#define order 3
|
||||
|
||||
@@ -838,7 +955,8 @@ uint64_t get_absolute_time_in_fp() {
|
||||
return time_now_fp;
|
||||
}
|
||||
|
||||
ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
|
||||
ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout) {
|
||||
// timeout is in milliseconds
|
||||
void *ibuf = (void *)buf;
|
||||
size_t bytes_remaining = count;
|
||||
int rc = 1;
|
||||
@@ -847,7 +965,7 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
|
||||
// check that we can do some writing
|
||||
ufds[0].fd = fd;
|
||||
ufds[0].events = POLLOUT;
|
||||
rc = poll(ufds, 1, 5000);
|
||||
rc = poll(ufds, 1, timeout);
|
||||
if (rc < 0) {
|
||||
// debug(1, "non-blocking write error waiting for pipe to become ready for writing...");
|
||||
} else if (rc == 0) {
|
||||
@@ -858,20 +976,24 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
|
||||
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;
|
||||
rc = bytes_written; // to imitate the return from write()
|
||||
} else {
|
||||
ibuf += bytes_written;
|
||||
bytes_remaining -= bytes_written;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rc == 0)
|
||||
if (rc > 0)
|
||||
return count - bytes_remaining; // this is just to mimic a normal write/3.
|
||||
else
|
||||
return rc;
|
||||
// return write(fd,buf,count);
|
||||
}
|
||||
|
||||
ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
|
||||
return non_blocking_write_with_timeout(fd,buf,count,5000); // default is 5 seconds.
|
||||
}
|
||||
|
||||
/* from
|
||||
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
|
||||
*/
|
||||
@@ -948,9 +1070,7 @@ uint64_t r64u() { return (ranval(&rx)); }
|
||||
int64_t r64i() { return (ranval(&rx) >> 1); }
|
||||
|
||||
/* generate an array of 64-bit random numbers */
|
||||
const int ranarraylength = 1009; // these will be 8-byte numbers.
|
||||
|
||||
uint64_t *ranarray;
|
||||
const int ranarraylength = 1009 * 203; // these will be 8-byte numbers.
|
||||
|
||||
int ranarraynext;
|
||||
|
||||
@@ -975,9 +1095,11 @@ uint64_t ranarrayval() {
|
||||
|
||||
void r64arrayinit() { ranarrayinit(); }
|
||||
|
||||
uint64_t ranarray64u() { return (ranarrayval()); }
|
||||
// uint64_t ranarray64u() { return (ranarrayval()); }
|
||||
uint64_t ranarray64u() { return (ranval(&rx)); }
|
||||
|
||||
int64_t ranarray64i() { return (ranarrayval() >> 1); }
|
||||
// int64_t ranarray64i() { return (ranarrayval() >> 1); }
|
||||
int64_t ranarray64i() { return (ranval(&rx) >> 1); }
|
||||
|
||||
uint32_t nctohl(const uint8_t *p) { // read 4 characters from *p and do ntohl on them
|
||||
// this is to avoid possible aliasing violations
|
||||
@@ -1020,6 +1142,8 @@ void sps_nanosleep(const time_t sec, const long nanosec) {
|
||||
int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
|
||||
const char *debugmessage, int debuglevel) {
|
||||
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
struct timespec tn;
|
||||
clock_gettime(CLOCK_REALTIME, &tn);
|
||||
uint64_t tnfpsec = tn.tv_sec;
|
||||
@@ -1048,19 +1172,23 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
|
||||
|
||||
timeoutTime.tv_sec = time_then;
|
||||
timeoutTime.tv_nsec = time_then_nsec;
|
||||
|
||||
int64_t start_time = get_absolute_time_in_fp();
|
||||
int r = pthread_mutex_timedlock(mutex, &timeoutTime);
|
||||
int64_t et = get_absolute_time_in_fp() - start_time;
|
||||
|
||||
if ((r != 0) && (debugmessage != NULL)) {
|
||||
if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
|
||||
et = (et * 1000000) >> 32; // microseconds
|
||||
char errstr[1000];
|
||||
if (r == ETIMEDOUT)
|
||||
debug(debuglevel,
|
||||
"waiting for a mutex, maximum expected time of %d microseconds exceeded \"%s\".",
|
||||
dally_time, debugmessage);
|
||||
"timed out waiting for a mutex, having waiting %f seconds, with a maximum "
|
||||
"waiting time of %d microseconds. \"%s\".",
|
||||
(1.0 * et) / 1000000, dally_time, debugmessage);
|
||||
else
|
||||
debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
|
||||
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
|
||||
}
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
return r;
|
||||
}
|
||||
#endif
|
||||
@@ -1068,17 +1196,20 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
|
||||
int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
|
||||
const char *debugmessage, int debuglevel) {
|
||||
|
||||
useconds_t time_to_wait = dally_time;
|
||||
// this is not pthread_cancellation safe because is contains a cancellation point
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
int time_to_wait = dally_time;
|
||||
int r = pthread_mutex_trylock(mutex);
|
||||
while ((r == EBUSY) && (time_to_wait > 0)) {
|
||||
useconds_t st = time_to_wait;
|
||||
int st = time_to_wait;
|
||||
if (st > 1000)
|
||||
st = 1000;
|
||||
sps_nanosleep(0, st * 1000);
|
||||
sps_nanosleep(0, st * 1000); // this contains a cancellation point
|
||||
time_to_wait -= st;
|
||||
r = pthread_mutex_trylock(mutex);
|
||||
}
|
||||
if ((r != 0) && (debugmessage != NULL)) {
|
||||
if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
|
||||
char errstr[1000];
|
||||
if (r == EBUSY) {
|
||||
debug(debuglevel,
|
||||
@@ -1090,17 +1221,23 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
|
||||
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
|
||||
}
|
||||
}
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
return r;
|
||||
}
|
||||
#endif
|
||||
|
||||
int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *filename,
|
||||
const int line, int debuglevel) {
|
||||
int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexname,
|
||||
const char *filename, const int line, int debuglevel) {
|
||||
if ((debuglevel > debuglev) || (debuglevel == 0))
|
||||
return pthread_mutex_lock(mutex);
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
uint64_t time_at_start = get_absolute_time_in_fp();
|
||||
char dstring[1000];
|
||||
memset(dstring, 0, sizeof(dstring));
|
||||
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
|
||||
debug(3, "debug_mutex_lock at \"%s\".", dstring);
|
||||
if (debuglevel != 0)
|
||||
debug(3, "mutex_lock \"%s\" at \"%s\".", mutexname, dstring); // only if you really ask for it!
|
||||
int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel);
|
||||
if (result == ETIMEDOUT) {
|
||||
result = pthread_mutex_lock(mutex);
|
||||
@@ -1108,37 +1245,50 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
|
||||
uint64_t divisor = (uint64_t)1 << 32;
|
||||
double delay = 1.0 * time_delay / divisor;
|
||||
debug(debuglevel,
|
||||
"debug_mutex_lock at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.", dstring,
|
||||
(1.0 * dally_time) / 1000000, delay);
|
||||
"mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.",
|
||||
mutexname, dstring, (1.0 * dally_time) / 1000000, delay);
|
||||
}
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int line,
|
||||
int debuglevel) {
|
||||
int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const char *filename,
|
||||
const int line, int debuglevel) {
|
||||
if ((debuglevel > debuglev) || (debuglevel == 0))
|
||||
return pthread_mutex_unlock(mutex);
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
char dstring[1000];
|
||||
char errstr[512];
|
||||
memset(dstring, 0, sizeof(dstring));
|
||||
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
|
||||
debug(debuglevel, "debug_mutex_unlock at \"%s\".", dstring);
|
||||
debug(debuglevel, "mutex_unlock \"%s\" at \"%s\".", mutexname, dstring);
|
||||
int r = pthread_mutex_unlock(mutex);
|
||||
if (r != 0)
|
||||
debug(1, "error %d: \"%s\" unlocking a mutex: \"%s\".", r,
|
||||
strerror_r(r, errstr, sizeof(errstr)), dstring);
|
||||
if ((debuglevel != 0) && (r != 0))
|
||||
debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r,
|
||||
strerror_r(r, errstr, sizeof(errstr)), mutexname, dstring);
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
return r;
|
||||
}
|
||||
|
||||
void malloc_cleanup(void *arg) {
|
||||
// debug(1, "malloc cleanup called.");
|
||||
free(arg);
|
||||
}
|
||||
|
||||
void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); }
|
||||
|
||||
char *get_version_string() {
|
||||
char *version_string = malloc(200);
|
||||
char *version_string = malloc(1024);
|
||||
if (version_string) {
|
||||
strcpy(version_string, PACKAGE_VERSION);
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
strcat(version_string, "-mbedTLS");
|
||||
#endif
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
strcat(version_string, "-PolarSSL");
|
||||
#endif
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
strcat(version_string, "-OpenSSL");
|
||||
#endif
|
||||
#ifdef CONFIG_TINYSVCMDNS
|
||||
@@ -1174,7 +1324,7 @@ char *get_version_string() {
|
||||
#ifdef CONFIG_PIPE
|
||||
strcat(version_string, "-pipe");
|
||||
#endif
|
||||
#ifdef HAVE_LIBSOXR
|
||||
#ifdef CONFIG_SOXR
|
||||
strcat(version_string, "-soxr");
|
||||
#endif
|
||||
#ifdef CONFIG_CONVOLUTION
|
||||
@@ -1183,10 +1333,13 @@ char *get_version_string() {
|
||||
#ifdef CONFIG_METADATA
|
||||
strcat(version_string, "-metadata");
|
||||
#endif
|
||||
#ifdef HAVE_DBUS
|
||||
#ifdef CONFIG_MQTT
|
||||
strcat(version_string, "-mqtt");
|
||||
#endif
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
strcat(version_string, "-dbus");
|
||||
#endif
|
||||
#ifdef HAVE_MPRIS
|
||||
#ifdef CONFIG_MPRIS_INTERFACE
|
||||
strcat(version_string, "-mpris");
|
||||
#endif
|
||||
strcat(version_string, "-sysconfdir:");
|
||||
@@ -1194,3 +1347,122 @@ char *get_version_string() {
|
||||
}
|
||||
return version_string;
|
||||
}
|
||||
|
||||
int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format,
|
||||
int with_dither, int64_t random_number_in) {
|
||||
// return the last random number used
|
||||
// assuming the buffer has been assigned
|
||||
|
||||
int64_t previous_random_number = random_number_in;
|
||||
char *p = outp;
|
||||
size_t sample_number;
|
||||
for (sample_number = 0; sample_number < number_of_frames * 2; sample_number++) {
|
||||
|
||||
int64_t hyper_sample = 0;
|
||||
// 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.
|
||||
|
||||
int64_t dither_mask = 0;
|
||||
switch (format) {
|
||||
case SPS_FORMAT_S32:
|
||||
dither_mask = (int64_t)1 << (64 + 1 - 32);
|
||||
break;
|
||||
case SPS_FORMAT_S24:
|
||||
case SPS_FORMAT_S24_3LE:
|
||||
case SPS_FORMAT_S24_3BE:
|
||||
dither_mask = (int64_t)1 << (64 + 1 - 24);
|
||||
break;
|
||||
case SPS_FORMAT_S16:
|
||||
dither_mask = (int64_t)1 << (64 + 1 - 16);
|
||||
break;
|
||||
case SPS_FORMAT_S8:
|
||||
case SPS_FORMAT_U8:
|
||||
dither_mask = (int64_t)1 << (64 + 1 - 8);
|
||||
break;
|
||||
case SPS_FORMAT_UNKNOWN:
|
||||
die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask.");
|
||||
break;
|
||||
}
|
||||
dither_mask -= 1;
|
||||
// int64_t r = r64i();
|
||||
int64_t r = ranarray64i();
|
||||
|
||||
int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask);
|
||||
|
||||
// add dither if permitted -- no need to check for clipping, as the sample is, uh, zero
|
||||
|
||||
if (with_dither != 0)
|
||||
hyper_sample += tpdf;
|
||||
|
||||
// move the result to the desired position in the int64_t
|
||||
char *op = p;
|
||||
int result; // this is the length of the sample
|
||||
|
||||
uint8_t byt;
|
||||
switch (format) {
|
||||
case SPS_FORMAT_S32:
|
||||
hyper_sample >>= (64 - 32);
|
||||
*(int32_t *)op = hyper_sample;
|
||||
result = 4;
|
||||
break;
|
||||
case SPS_FORMAT_S24_3LE:
|
||||
hyper_sample >>= (64 - 24);
|
||||
byt = (uint8_t)hyper_sample;
|
||||
*op++ = byt;
|
||||
byt = (uint8_t)(hyper_sample >> 8);
|
||||
*op++ = byt;
|
||||
byt = (uint8_t)(hyper_sample >> 16);
|
||||
*op++ = byt;
|
||||
result = 3;
|
||||
break;
|
||||
case SPS_FORMAT_S24_3BE:
|
||||
hyper_sample >>= (64 - 24);
|
||||
byt = (uint8_t)(hyper_sample >> 16);
|
||||
*op++ = byt;
|
||||
byt = (uint8_t)(hyper_sample >> 8);
|
||||
*op++ = byt;
|
||||
byt = (uint8_t)hyper_sample;
|
||||
*op++ = byt;
|
||||
result = 3;
|
||||
break;
|
||||
case SPS_FORMAT_S24:
|
||||
hyper_sample >>= (64 - 24);
|
||||
*(int32_t *)op = hyper_sample;
|
||||
result = 4;
|
||||
break;
|
||||
case SPS_FORMAT_S16:
|
||||
hyper_sample >>= (64 - 16);
|
||||
*(int16_t *)op = (int16_t)hyper_sample;
|
||||
result = 2;
|
||||
break;
|
||||
case SPS_FORMAT_S8:
|
||||
hyper_sample >>= (int8_t)(64 - 8);
|
||||
*op = hyper_sample;
|
||||
result = 1;
|
||||
break;
|
||||
case SPS_FORMAT_U8:
|
||||
hyper_sample >>= (uint8_t)(64 - 8);
|
||||
hyper_sample += 128;
|
||||
*op = hyper_sample;
|
||||
result = 1;
|
||||
break;
|
||||
default:
|
||||
result = 0; // stop a compiler warning
|
||||
die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples");
|
||||
}
|
||||
p += result;
|
||||
previous_random_number = r;
|
||||
}
|
||||
// hack
|
||||
// memset(outp,0,number_of_frames * 4);
|
||||
return previous_random_number;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define _COMMON_H
|
||||
|
||||
#include <libconfig.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/socket.h>
|
||||
@@ -22,13 +23,16 @@
|
||||
#define SAFAMILY sa_family
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
|
||||
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
|
||||
enum dbus_session_type {
|
||||
DBT_system = 0, // use the session bus
|
||||
DBT_session, // use the system bus
|
||||
} dbt_type;
|
||||
#endif
|
||||
|
||||
#define sps_extra_code_output_stalled 32768
|
||||
#define sps_extra_code_output_state_cannot_make_ready 32769
|
||||
|
||||
enum endian_type {
|
||||
SS_LITTLE_ENDIAN = 0,
|
||||
SS_PDP_ENDIAN,
|
||||
@@ -58,6 +62,12 @@ enum decoders_supported_type {
|
||||
decoder_apple_alac,
|
||||
} decoders_supported_type;
|
||||
|
||||
enum disable_standby_mode_type {
|
||||
disable_standby_off = 0,
|
||||
disable_standby_while_active,
|
||||
disable_standby_always
|
||||
};
|
||||
|
||||
// the following enum is for the formats recognised -- currently only S16LE is recognised for input,
|
||||
// so these are output only for the present
|
||||
|
||||
@@ -79,10 +89,13 @@ typedef struct {
|
||||
char *password;
|
||||
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.
|
||||
// Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
|
||||
|
||||
char *pa_sink; // the name (or id) of the sink that Shairport Sync will play on.
|
||||
#endif
|
||||
#ifdef CONFIG_METADATA
|
||||
int metadata_enabled;
|
||||
@@ -114,28 +127,31 @@ typedef struct {
|
||||
char *mdns_name;
|
||||
mdns_backend *mdns;
|
||||
int buffer_start_fill;
|
||||
int64_t userSuppliedLatency; // overrides all other latencies -- use with caution
|
||||
int64_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual
|
||||
// total latency
|
||||
uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution
|
||||
uint32_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual
|
||||
// total latency
|
||||
// the total latency will be limited to the min and max-latency values, if supplied
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
int daemonise;
|
||||
int daemonise_store_pid; // don't try to save a PID file
|
||||
char *piddir;
|
||||
char *computed_piddir; // the actual pid directory to create, if any
|
||||
char *pidfile;
|
||||
#endif
|
||||
|
||||
int logOutputLevel; // log output level
|
||||
int debugger_show_elapsed_time; // in the debug message, display the time since startup
|
||||
int debugger_show_relative_time; // in the debug message, display the time since the last one
|
||||
int statistics_requested, use_negotiated_latencies;
|
||||
enum playback_mode_type playback_mode;
|
||||
char *cmd_start, *cmd_stop, *cmd_set_volume;
|
||||
char *cmd_start, *cmd_stop, *cmd_set_volume, *cmd_unfixable;
|
||||
char *cmd_active_start, *cmd_active_stop;
|
||||
int cmd_blocking, cmd_start_returns_output;
|
||||
double tolerance; // allow this much drift before attempting to correct it
|
||||
enum stuffing_type packet_stuffing;
|
||||
int decoders_supported;
|
||||
int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by
|
||||
// David Hammerton
|
||||
char *pidfile;
|
||||
// char *logfile;
|
||||
// char *errfile;
|
||||
char *configfile;
|
||||
@@ -145,11 +161,21 @@ typedef struct {
|
||||
int interface_index; // only valid if the interface string is non-NULL
|
||||
double audio_backend_buffer_desired_length; // this will be the length in seconds of the
|
||||
// audio backend buffer -- the DAC buffer for ALSA
|
||||
double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation
|
||||
// will not occur -- it'll be
|
||||
// basic interpolation instead.
|
||||
double audio_backend_silence_threshold; // below this, silence will be added to the output buffer
|
||||
double audio_backend_silence_scan_interval; // check the threshold this often
|
||||
|
||||
double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any
|
||||
// fixed latency there might be in the audio path
|
||||
double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
|
||||
uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
|
||||
// native range.
|
||||
double active_state_timeout; // the amount of time from when play ends to when the system leaves
|
||||
// into the "active" mode.
|
||||
uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
|
||||
// native range.
|
||||
int volume_range_hw_priority; // when extending the volume range by combining sw and hw attenuators, lowering the volume, use all the hw attenuation before using
|
||||
// sw attenuation
|
||||
enum sps_format_t output_format;
|
||||
enum volume_control_profile_type volume_control_profile;
|
||||
int output_rate;
|
||||
@@ -164,14 +190,18 @@ typedef struct {
|
||||
int loudness;
|
||||
float loudness_reference_volume_db;
|
||||
int alsa_use_hardware_mute;
|
||||
#if defined(HAVE_DBUS)
|
||||
double alsa_maximum_stall_time;
|
||||
enum disable_standby_mode_type disable_standby_mode;
|
||||
volatile int keep_dac_busy;
|
||||
|
||||
#if defined(CONFIG_DBUS_INTERFACE)
|
||||
enum dbus_session_type dbus_service_bus_type;
|
||||
#endif
|
||||
#if defined(HAVE_MPRIS)
|
||||
#if defined(CONFIG_MPRIS_INTERFACE)
|
||||
enum dbus_session_type mpris_service_bus_type;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_METADATA_HUB
|
||||
#ifdef CONFIG_METADATA_HUB
|
||||
char *cover_art_cache_dir;
|
||||
int scan_interval_when_active; // number of seconds between DACP server scans when playing
|
||||
// something (1)
|
||||
@@ -185,6 +215,11 @@ typedef struct {
|
||||
int disable_resend_requests; // set this to stop resend request being made for missing packets
|
||||
double diagnostic_drop_packet_fraction; // pseudo randomly drop this fraction of packets, for
|
||||
// debugging. Currently audio packets only...
|
||||
#ifdef CONFIG_JACK
|
||||
char *jack_client_name;
|
||||
char *jack_autoconnect_pattern;
|
||||
#endif
|
||||
|
||||
} shairport_cfg;
|
||||
|
||||
uint32_t nctohl(const uint8_t *p); // read 4 characters from *p and do ntohl on them
|
||||
@@ -192,12 +227,16 @@ uint16_t nctohs(const uint8_t *p); // read 2 characters from *p and do ntohs on
|
||||
|
||||
void memory_barrier();
|
||||
|
||||
void log_to_stderr(); // call this to director logging to stderr;
|
||||
|
||||
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
|
||||
|
||||
int get_requested_connection_state_to_output();
|
||||
|
||||
void set_requested_connection_state_to_output(int v);
|
||||
|
||||
ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout); // timeout in milliseconds
|
||||
|
||||
ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places
|
||||
|
||||
/* from
|
||||
@@ -211,11 +250,24 @@ void r64init(uint64_t seed);
|
||||
uint64_t r64u();
|
||||
int64_t r64i();
|
||||
|
||||
uint64_t *ranarray;
|
||||
void r64arrayinit();
|
||||
uint64_t ranarray64u();
|
||||
int64_t ranarray64i();
|
||||
|
||||
extern int debuglev;
|
||||
// if you are breaking in to a session, you need to avoid the ports of the current session
|
||||
// if you are law-abiding, then you can reuse the ports.
|
||||
// so, you can reset the free UDP ports minder when you're legit, and leave it otherwise
|
||||
|
||||
// the downside of using different ports each time is that it might make the firewall
|
||||
// rules a bit more complex, as they need to allow more than the minimum three ports.
|
||||
// a range of 10 is suggested anyway
|
||||
|
||||
void resetFreeUDPPort();
|
||||
uint16_t nextFreeUDPPort();
|
||||
|
||||
volatile int debuglev;
|
||||
|
||||
void die(const char *format, ...);
|
||||
void warn(const char *format, ...);
|
||||
void inform(const char *format, ...);
|
||||
@@ -247,17 +299,22 @@ uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
|
||||
long endianness;
|
||||
uint32_t uatoi(const char *nptr);
|
||||
|
||||
// this is for allowing us to cancel the whole program
|
||||
pthread_t main_thread_id;
|
||||
|
||||
shairport_cfg config;
|
||||
config_t config_file_stuff;
|
||||
|
||||
int config_set_lookup_bool(config_t *cfg, char *where, int *dst);
|
||||
|
||||
void command_start(void);
|
||||
void command_stop(void);
|
||||
void command_execute(const char *command, const char *extra_argument, const int block);
|
||||
void command_set_volume(double volume);
|
||||
|
||||
int mkpath(const char *path, mode_t mode);
|
||||
|
||||
void shairport_shutdown();
|
||||
// void shairport_startup_complete(void);
|
||||
|
||||
extern sigset_t pselect_sigset;
|
||||
|
||||
@@ -266,19 +323,30 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
|
||||
const char *debugmessage, int debuglevel);
|
||||
// wait for the specified time, checking every 20 milliseconds, and block if it can't acquire the
|
||||
// lock
|
||||
int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *filename,
|
||||
const int line, int debuglevel);
|
||||
int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexName,
|
||||
const char *filename, const int line, int debuglevel);
|
||||
|
||||
#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, __FILE__, __LINE__, d)
|
||||
#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d)
|
||||
|
||||
int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int line,
|
||||
int debuglevel);
|
||||
int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexName, const char *filename,
|
||||
const int line, int debuglevel);
|
||||
|
||||
#define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, __FILE__, __LINE__, d)
|
||||
#define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, #mu, __FILE__, __LINE__, d)
|
||||
|
||||
void pthread_cleanup_debug_mutex_unlock(void *arg);
|
||||
|
||||
#define pthread_cleanup_debug_mutex_lock(mu, t, d) \
|
||||
if (_debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d) == 0) \
|
||||
pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock, (void *)mu)
|
||||
|
||||
char *get_version_string(); // mallocs a string space -- remember to free it afterwards
|
||||
|
||||
void sps_nanosleep(const time_t sec,
|
||||
const long nanosec); // waits for this time, even through interruptions
|
||||
|
||||
int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format,
|
||||
int with_dither, int64_t random_number_in);
|
||||
|
||||
void malloc_cleanup(void *arg);
|
||||
|
||||
#endif // _COMMON_H
|
||||
|
||||
+137
-81
@@ -2,10 +2,15 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.50])
|
||||
<<<<<<< HEAD
|
||||
AC_INIT([shairport-sync], [3.2.2], [mikebrady@eircom.net])
|
||||
=======
|
||||
AC_INIT([shairport-sync], [3.3rc0], [mikebrady@eircom.net])
|
||||
>>>>>>> development
|
||||
AM_INIT_AUTOMAKE
|
||||
AC_CONFIG_SRCDIR([shairport.c])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_PROG_RANLIB
|
||||
|
||||
# Derived from the Avahi configure.ac file
|
||||
# Specifying the OS type, defaulting to linux.
|
||||
@@ -42,14 +47,35 @@ AM_CONDITIONAL([BUILD_FOR_OPENBSD], [test "x${with_os}" = xopenbsd ])
|
||||
AC_ARG_WITH([pkg_config],
|
||||
[ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=yes])
|
||||
|
||||
AC_MSG_RESULT(>>Including libdaemon)
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[DAEMON], [libdaemon],
|
||||
[LIBS="${DAEMON_LIBS} ${LIBS}"])
|
||||
else
|
||||
AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed))
|
||||
fi
|
||||
# Look for libdaemon
|
||||
AC_ARG_WITH(libdaemon, [ --with-libdaemon = include support for daemonising in older non-systemd systems],
|
||||
if test "x${with_libdaemon}" = xyes ; then
|
||||
[AC_MSG_RESULT(>>Including libdaemon support for older systems)
|
||||
AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon])
|
||||
REQUESTED_LIBDAEMON=1
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[DAEMON], [libdaemon],
|
||||
[LIBS="${DAEMON_LIBS} ${LIBS}"])
|
||||
else
|
||||
AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed))
|
||||
fi
|
||||
]
|
||||
fi,
|
||||
#default is to include libdaemon
|
||||
[AC_MSG_RESULT(>>Including libdaemon support for older systems)
|
||||
AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon])
|
||||
REQUESTED_LIBDAEMON=1
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[DAEMON], [libdaemon],
|
||||
[LIBS="${DAEMON_LIBS} ${LIBS}"])
|
||||
else
|
||||
AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed))
|
||||
fi
|
||||
]
|
||||
)
|
||||
AM_CONDITIONAL([USE_LIBDAEMON], [test "x$REQUESTED_LIBDAEMON" = "x1"])
|
||||
|
||||
##### The following check for the pthreads library doesn't put the compiler into the correct pthread mode
|
||||
##### so we add the -pthread compilation flag in AMFLAGS in the Makefile.am as well.
|
||||
@@ -62,18 +88,15 @@ if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[POPT], [popt],
|
||||
[CFLAGS="${POPT_CFLAGS} ${CFLAGS}"
|
||||
LIBS="${POPT_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])])
|
||||
LIBS="${POPT_LIBS} ${LIBS}"])
|
||||
else
|
||||
AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed))
|
||||
AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])
|
||||
fi
|
||||
|
||||
AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], )
|
||||
AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "xyes" ])
|
||||
|
||||
AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], )
|
||||
|
||||
AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "xyes" ])
|
||||
|
||||
AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], )
|
||||
@@ -95,6 +118,13 @@ AC_ARG_WITH([freebsd-service],
|
||||
[ --with-freebsd-service = install a FreeBSD startup script during a make install], , )
|
||||
AM_CONDITIONAL([INSTALL_FREEBSD_SERVICE], [test "x$with_freebsd_service" = "xyes"])
|
||||
|
||||
AC_ARG_WITH([cygwin-service],
|
||||
[ --with-cygwin-service = install a CYGWIN config script during a make install], , )
|
||||
AM_CONDITIONAL([INSTALL_CYGWIN_SERVICE], [test "x$with_cygwin_service" = "xyes"])
|
||||
|
||||
AC_ARG_WITH([external-mdns],[ --with-external-mdns = support the use of 'avahi-publish-service' or 'mDNSPublish' to advertise the service on Bonjour/ZeroConf ],[ AC_MSG_RESULT(>>Including external mdns support) AC_DEFINE([CONFIG_EXTERNAL_MDNS], 1, [Needed by the compiler.]) ], )
|
||||
AM_CONDITIONAL([USE_EXTERNAL_MDNS], [test "x$with_external_mdns" = "xyes" ])
|
||||
|
||||
# Add the libconfig package
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
@@ -111,19 +141,16 @@ AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "xyes"])
|
||||
# Look for Apple ALAC flag
|
||||
AC_ARG_WITH(apple-alac, [ --with-apple-alac = include support for the Apple ALAC decoder],
|
||||
[AC_MSG_RESULT(>>Including the Apple ALAC Decoder)
|
||||
HAS_APPLE_ALAC=1
|
||||
AM_CONDITIONAL([USE_APPLE_ALAC], [test 0])
|
||||
AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Needed by the compiler.])
|
||||
AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Include support for using the Apple ALAC Decoder])
|
||||
REQUESTED_APPLE_ALAC=1
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[ALAC], [alac],
|
||||
[LIBS="${ALAC_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_APPLE_ALAC],[1],[Define to 1 if you are using the Apple ALAC Decoder])])
|
||||
[LIBS="${ALAC_LIBS} ${LIBS}"])
|
||||
else
|
||||
AC_CHECK_LIB([alac], [BitBufferInit], , AC_MSG_ERROR(Apple ALAC Decoder support requires the alac library!))
|
||||
AC_DEFINE([HAVE_APPLE_ALAC],[1],[Define to 1 if you have the Apple ALAC library])
|
||||
fi ])
|
||||
AM_CONDITIONAL([USE_APPLE_ALAC], [test "x$HAS_APPLE_ALAC" = "x1"])
|
||||
AM_CONDITIONAL([USE_APPLE_ALAC], [test "x$REQUESTED_APPLE_ALAC" = "x1"])
|
||||
|
||||
# Look for piddir flag
|
||||
AC_ARG_WITH(piddir, [ --with-piddir=<pathname> Specify a pathname to a directory in which to write the PID file.], [
|
||||
@@ -133,10 +160,10 @@ AC_ARG_WITH(piddir, [ --with-piddir=<pathname> Specify a pathname to a directory
|
||||
AC_MSG_ERROR(when you use the --with-piddir directive you must specify the pathname of the directory into which the PID file will be written)
|
||||
fi
|
||||
AC_MSG_RESULT(${with_piddir})
|
||||
HAS_CUSTOMPIDDIR=1
|
||||
REQUESTED_CUSTOMPIDDIR=1
|
||||
AC_SUBST(CUSTOM_PID_DIR,["${with_piddir}"])
|
||||
AC_DEFINE([USE_CUSTOM_PID_DIR],1,[Hook up special proc to libdaemon to point to this directory])], )
|
||||
AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$HAS_CUSTOMPIDDIR" = "x1"])
|
||||
AC_DEFINE([DEFINED_CUSTOM_PID_DIR],1,[Hook up special proc to libdaemon to point to this directory])], )
|
||||
AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$REQUESTED_CUSTOMPIDDIR" = "x1"])
|
||||
|
||||
# Check --with-ssl=argument
|
||||
AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=polarssl (deprecated) for encryption services ], [
|
||||
@@ -146,19 +173,17 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
|
||||
AC_MSG_ERROR(choose "openssl", "mbedtls" or "polarssl" encryption)
|
||||
fi
|
||||
if test "x${with_ssl}" = xopenssl ; then
|
||||
AC_DEFINE([CONFIG_OPENSSL], 1, [Use the OpenSSL libraries for encryption and encoding and decoding])
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[SSL], [libssl,libcrypto],
|
||||
[LIBS="${SSL_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto])
|
||||
AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])])
|
||||
[LIBS="${SSL_LIBS} ${LIBS}"])
|
||||
else
|
||||
AC_CHECK_LIB([crypto], [main], , AC_MSG_ERROR(libcrypto selected but the library cannot be found!))
|
||||
AC_CHECK_LIB([ssl], [main], , AC_MSG_ERROR(libssl selected but the library cannot be found!))
|
||||
AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto])
|
||||
AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])
|
||||
fi
|
||||
elif test "x${with_ssl}" = xmbedtls ; then
|
||||
AC_DEFINE([CONFIG_MBEDTLS], 1, [Use the mbed TLS libraries for encryption and encoding and decoding])
|
||||
AC_CHECK_LIB([mbedtls],[mbedtls_ssl_init],,
|
||||
[AC_MSG_ERROR([mbed tls support requires the mbedtls library -- libmbedtls-dev suggested],1)])
|
||||
AC_CHECK_LIB([mbedcrypto], [mbedtls_entropy_func],,
|
||||
@@ -166,6 +191,7 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
|
||||
AC_CHECK_LIB([mbedx509], [mbedtls_pk_init],,
|
||||
[AC_MSG_ERROR([mbed tls support requires the mbedx509 library -- libmbedx509-0 suggested],1)])
|
||||
elif test "x${with_ssl}" = xpolarssl ; then
|
||||
AC_DEFINE([CONFIG_POLARSSL], 1, [Use the PolarSSL libraries for encryption and encoding and decoding])
|
||||
AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL is selected but the library cannot be found and is deprecated. Consider selecting mbed TLS instead using --with-ssl=mbedtls.))
|
||||
else
|
||||
AC_MSG_ERROR(unknown option "${with_ssl}"." Please choose with "openssl", "mbedtls" or "polarssl")
|
||||
@@ -175,13 +201,26 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
|
||||
# Look for soxr flag
|
||||
AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation], [
|
||||
AC_MSG_RESULT(>>Including support for soxr-based interpolation)
|
||||
AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!))], )
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
AC_DEFINE([CONFIG_SOXR], 1, [Include support for using the SoX Resampler library for interpolation])
|
||||
PKG_CHECK_MODULES(
|
||||
[SOXR], [soxr],
|
||||
[LIBS="${SOXR_LIBS} ${LIBS}"],
|
||||
[AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!)])
|
||||
else
|
||||
AC_CHECK_LIB([avutil],[av_get_cpu_flags])
|
||||
if test "x${ac_cv_lib_avutil_av_get_cpu_flags}" = xyes ; then
|
||||
# soxr may link against libavutil, depending on the architecture, but for the sake of simplicity link with it if it is found
|
||||
AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!), [-lavutil])
|
||||
else
|
||||
AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!))
|
||||
fi
|
||||
fi
|
||||
], )
|
||||
|
||||
# Look for metadata flag -- set flag for conditional compilation
|
||||
# Look for metadata flag and resolve it further down the script
|
||||
AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [
|
||||
AC_MSG_RESULT(>>Including metadata support)
|
||||
AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])], )
|
||||
AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"])
|
||||
REQUESTED_METADATA=1], )
|
||||
|
||||
# What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation
|
||||
# and the relevant link files are added.
|
||||
@@ -189,64 +228,76 @@ AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"])
|
||||
# Look for avahi flag
|
||||
AC_ARG_WITH(avahi, [ --with-avahi = choose Avahi-based mDNS support], [
|
||||
AC_MSG_RESULT(>>Including Avahi mDNS support)
|
||||
HAS_AVAHI=1
|
||||
REQUESTED_AVAHI=1
|
||||
AC_DEFINE([CONFIG_AVAHI], 1, [Needed by the compiler.])
|
||||
AC_CHECK_LIB([avahi-client], [avahi_client_new], , AC_MSG_ERROR(Avahi support requires the avahi-client library!))
|
||||
AC_CHECK_LIB([avahi-common],[avahi_strerror], , AC_MSG_ERROR(Avahi support requires the avahi-common library!))], )
|
||||
AM_CONDITIONAL([USE_AVAHI], [test "x$HAS_AVAHI" = "x1"])
|
||||
AM_CONDITIONAL([USE_AVAHI], [test "x$REQUESTED_AVAHI" = "x1"])
|
||||
|
||||
# Look for tinysvcmdns flag
|
||||
AC_ARG_WITH(tinysvcmdns, [ --with-tinysvcmdns = choose tinysvcmdns-based mDNS support], [
|
||||
AC_MSG_RESULT(>>Including tinysvcmdns mDNS support)
|
||||
HAS_TINYSVCMDNS=1
|
||||
REQUESTED_TINYSVCMDNS=1
|
||||
AC_DEFINE([CONFIG_TINYSVCMDNS], 1, [Needed by the compiler.])], )
|
||||
AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$HAS_TINYSVCMDNS" = "x1"])
|
||||
AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$REQUESTED_TINYSVCMDNS" = "x1"])
|
||||
|
||||
# Look for ALSA flag
|
||||
AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)],
|
||||
[AC_MSG_RESULT(>>Including an ALSA back end)
|
||||
HAS_ALSA=1
|
||||
AM_CONDITIONAL([USE_ALSA], [test 0])
|
||||
REQUESTED_ALSA=1
|
||||
AC_DEFINE([CONFIG_ALSA], 1, [Needed by the compiler.])
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[ALSA], [alsa],
|
||||
[LIBS="${ALSA_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])])
|
||||
[LIBS="${ALSA_LIBS} ${LIBS}"])
|
||||
else
|
||||
AC_CHECK_LIB([asound], [snd_pcm_open], , AC_MSG_ERROR(ALSA support requires the asound library!))
|
||||
AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])
|
||||
fi ])
|
||||
AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"])
|
||||
AM_CONDITIONAL([USE_ALSA], [test "x$REQUESTED_ALSA" = "x1"])
|
||||
|
||||
# Look for jack flag
|
||||
AC_ARG_WITH(jack, [ --with-jack = include a Jack Audio Connection Kit (jack) backend], [
|
||||
AC_MSG_RESULT(>>Including a Jack Audio Connection Kit (jack) back end)
|
||||
REQUESTED_JACK=1
|
||||
AC_DEFINE([CONFIG_JACK], 1, [Needed by the compiler.])
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[JACK], [jack],
|
||||
[LIBS="${JACK_LIBS} ${LIBS}"],
|
||||
[AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)])
|
||||
else
|
||||
AC_CHECK_LIB([jack], [jack_client_open], , AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!))
|
||||
fi ])
|
||||
AM_CONDITIONAL([USE_JACK], [test "x$REQUESTED_JACK" = "x1"])
|
||||
|
||||
# Look for SNDIO flag
|
||||
AC_ARG_WITH(sndio, [ --with-sndio = choose SNDIO API support], [
|
||||
AC_MSG_RESULT(>>Including a SNDIO back end)
|
||||
HAS_SNDIO=1
|
||||
REQUESTED_SNDIO=1
|
||||
AC_DEFINE([CONFIG_SNDIO], 1, [Needed by the compiler.])
|
||||
AC_CHECK_LIB([sndio], [sio_open], , AC_MSG_ERROR(SNDIO support requires the sndio library -- libsndio-dev suggested))], )
|
||||
AM_CONDITIONAL([USE_SNDIO], [test "x$HAS_SNDIO" = "x1"])
|
||||
AM_CONDITIONAL([USE_SNDIO], [test "x$REQUESTED_SNDIO" = "x1"])
|
||||
|
||||
# Look for AO flag
|
||||
AC_ARG_WITH(ao, [ --with-ao = choose AO (Audio Output?) API support. N.B. no synchronisation -- so underflow or overflow is inevitable!], [
|
||||
AC_MSG_RESULT(>>Including an AO back end. N.B. no synchronisation -- so underflow or overflow is inevitable!)
|
||||
HAS_AO=1
|
||||
REQUESTED_AO=1
|
||||
AC_DEFINE([CONFIG_AO], 1, [Needed by the compiler.])
|
||||
AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library -- libao-dev suggested))], )
|
||||
AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"])
|
||||
AM_CONDITIONAL([USE_AO], [test "x$REQUESTED_AO" = "x1"])
|
||||
|
||||
# Look for Soundio flag
|
||||
AC_ARG_WITH(soundio, [ --with-soundio = choose soundio API support.], [
|
||||
AC_MSG_RESULT(>>Including an soundio back end)
|
||||
HAS_SOUNDIO=1
|
||||
REQUESTED_SOUNDIO=1
|
||||
AC_DEFINE([CONFIG_SOUNDIO], 1, [Needed by the compiler.])
|
||||
AC_CHECK_LIB([soundio], [soundio_create], , AC_MSG_ERROR(soundio support requires the soundio library -- libsoundio-dev suggested))], )
|
||||
AM_CONDITIONAL([USE_SOUNDIO], [test "x$HAS_SOUNDIO" = "x1"])
|
||||
AM_CONDITIONAL([USE_SOUNDIO], [test "x$REQUESTED_SOUNDIO" = "x1"])
|
||||
|
||||
# Look for pulseaudio flag
|
||||
AC_ARG_WITH(pa, [ --with-pa = choose PulseAudio support.], [
|
||||
AC_MSG_RESULT(>>Including a PulseAudio back end.)
|
||||
HAS_PA=1
|
||||
REQUESTED_PA=1
|
||||
AC_DEFINE([CONFIG_PA], 1, [Needed by the compiler.])
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
@@ -256,74 +307,82 @@ AC_ARG_WITH(pa, [ --with-pa = choose PulseAudio support.], [
|
||||
AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the libpulse library!))
|
||||
AC_CHECK_LIB([pulse], [pa_stream_peek], , AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library.))
|
||||
fi ])
|
||||
AM_CONDITIONAL([USE_PA], [test "x$HAS_PA" = "x1"])
|
||||
AM_CONDITIONAL([USE_PA], [test "x$REQUESTED_PA" = "x1"])
|
||||
|
||||
# Look for Convolution flag
|
||||
AC_ARG_WITH(convolution, [ --with-convolution = choose audio DSP convolution support], [
|
||||
AC_MSG_RESULT(>>Including convolution support)
|
||||
HAS_CONVOLUTION=1
|
||||
REQUESTED_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"])
|
||||
AM_CONDITIONAL([USE_CONVOLUTION], [test "x$REQUESTED_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)
|
||||
HAS_DNS_SD=1
|
||||
AC_DEFINE([CONFIG_HAVE_DNS_SD_H], 1, [Needed by the compiler.])
|
||||
REQUESTED_DNS_SD=1
|
||||
AC_DEFINE([CONFIG_DNS_SD], 1, [Needed by the compiler.])
|
||||
AC_SEARCH_LIBS([DNSServiceRefDeallocate], [dns_sd], , AC_MSG_ERROR(dns_sd support requires the dns_sd library!))], )
|
||||
AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"])
|
||||
AM_CONDITIONAL([USE_DNS_SD], [test "x$REQUESTED_DNS_SD" = "x1"])
|
||||
|
||||
# Look for dbus flag
|
||||
AC_ARG_WITH(dbus-interface, [ --with-dbus-interface = include support for the native Shairport Sync D-Bus interface], [
|
||||
AC_MSG_RESULT(>>Including dbus support)
|
||||
HAS_DBUS=1
|
||||
AC_DEFINE([HAVE_DBUS], 1, [Needed by the compiler.])
|
||||
AC_DEFINE([CONFIG_DBUS_INTERFACE], 1, [Include support for the native Shairport Sync D-Bus interface])
|
||||
REQUESTED_DBUS=1
|
||||
PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support requires the glib 2.0 library -- libglib2.0-dev suggested!)])
|
||||
], )
|
||||
AM_CONDITIONAL([USE_DBUS], [test "x$HAS_DBUS" = "x1"])
|
||||
AM_CONDITIONAL([USE_DBUS], [test "x$REQUESTED_DBUS" = "x1"])
|
||||
|
||||
# Look for dbus test client flag
|
||||
AC_ARG_WITH(dbus-test-client, [ --with-dbus-test-client = compile dbus test client], [
|
||||
AC_MSG_RESULT(>>Including dbus test client)
|
||||
HAS_DBUS_CLIENT=1
|
||||
AC_DEFINE([HAVE_DBUS_CLIENT], 1, [Needed by the compiler.])
|
||||
REQUESTED_DBUS_CLIENT=1
|
||||
PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus client support requires the glib 2.0 library -- libglib2.0-dev suggested!)])
|
||||
], )
|
||||
AM_CONDITIONAL([USE_DBUS_CLIENT], [test "x$HAS_DBUS_CLIENT" = "x1"])
|
||||
AM_CONDITIONAL([USE_DBUS_CLIENT], [test "x$REQUESTED_DBUS_CLIENT" = "x1"])
|
||||
|
||||
# Look for mpris flag
|
||||
AC_ARG_WITH(mpris-interface, [ --with-mpris-interface = include support for a Shairport Sync D-Bus interface conforming to the MPRIS standard], [
|
||||
AC_ARG_WITH(mpris-interface, [ --with-mpris-interface = include support for a D-Bus interface conforming to the MPRIS standard], [
|
||||
AC_MSG_RESULT(>>Including the MPRIS D-Bus Interface)
|
||||
HAS_MPRIS=1
|
||||
AC_DEFINE([HAVE_MPRIS], 1, [Needed by the compiler.])
|
||||
AC_DEFINE([CONFIG_MPRIS_INTERFACE], 1, [Include support for a D-Bus interface conforming to the MPRIS standard])
|
||||
REQUESTED_MPRIS=1
|
||||
PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support for mpris requires the glib 2.0 library -- libglib2.0-dev suggested!)])
|
||||
], )
|
||||
AM_CONDITIONAL([USE_MPRIS], [test "x$HAS_MPRIS" = "x1"])
|
||||
AM_CONDITIONAL([USE_MPRIS], [test "x$REQUESTED_MPRIS" = "x1"])
|
||||
|
||||
# Look for mpris test client flag
|
||||
AC_ARG_WITH(mpris-test-client, [ --with-mpris-test-client = compile mpris test client], [
|
||||
AC_MSG_RESULT(>>Including mpris test client)
|
||||
HAS_MPRIS_CLIENT=1
|
||||
AC_DEFINE([HAVE_MPRIS_CLIENT], 1, [Needed by the compiler.])
|
||||
REQUESTED_MPRIS_CLIENT=1
|
||||
PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(mpris client support requires the glib 2.0 library -- libglib2.0-dev suggested!)])
|
||||
], )
|
||||
AM_CONDITIONAL([USE_MPRIS_CLIENT], [test "x$HAS_MPRIS_CLIENT" = "x1"])
|
||||
AM_CONDITIONAL([USE_MPRIS_CLIENT], [test "x$REQUESTED_MPRIS_CLIENT" = "x1"])
|
||||
|
||||
#AM_CONDITIONAL([USE_DBUS_CORE_AND_DACP], [test "x$HAS_MPRIS" = "x1" -o "x$HAS_DBUS" = "x1"])
|
||||
# Look for mqtt flag
|
||||
AC_ARG_WITH(mqtt-client, [ --with-mqtt-client = include a client for MQTT -- the Message Queuing Telemetry Transport protocol], [
|
||||
AC_DEFINE([CONFIG_MQTT], 1, [Include a client for MQTT, the Message Queuing Telemetry Transport protocol])
|
||||
AC_MSG_RESULT(>>Including MQTT support)
|
||||
REQUESTED_MQTT=1
|
||||
AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], , AC_MSG_ERROR(MQTT support requires the mosquitto library -- libmosquitto-dev suggested!))
|
||||
],)
|
||||
AM_CONDITIONAL([USE_MQTT], [test "x$REQUESTED_MQTT" = "x1"])
|
||||
|
||||
if test "x$HAS_MPRIS" = "x1" -o "x$HAS_DBUS" = "x1" ; then
|
||||
AC_MSG_RESULT(>>Including the metadata hub)
|
||||
HAS_METADATA_HUB=1
|
||||
AC_DEFINE([HAVE_METADATA_HUB], 1, [Needed by the compiler.])
|
||||
AC_MSG_RESULT(>>Including the DACP client)
|
||||
HAS_DACP_CLIENT=1
|
||||
AC_DEFINE([HAVE_DACP_CLIENT], 1, [Needed by the compiler.])
|
||||
if test "x$REQUESTED_MPRIS" = "x1" || test "x$REQUESTED_DBUS" = "x1" || test "x$REQUESTED_MQTT" = "x1"; then
|
||||
AC_MSG_RESULT(>>Including extended metadata and DACP client support)
|
||||
REQUESTED_EXTENDED_METADATA_SUPPORT=1
|
||||
AC_DEFINE([CONFIG_METADATA_HUB], 1, [Needed by the compiler.])
|
||||
AC_DEFINE([CONFIG_DACP_CLIENT], 1, [Needed by the compiler.])
|
||||
fi
|
||||
AM_CONDITIONAL([USE_METADATA_HUB], [test "x$HAS_METADATA_HUB" = "x1"])
|
||||
AM_CONDITIONAL([USE_DACP_CLIENT], [test "x$HAS_DACP_CLIENT" = "x1"])
|
||||
AM_CONDITIONAL([USE_METADATA_HUB], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"])
|
||||
AM_CONDITIONAL([USE_DACP_CLIENT], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"])
|
||||
|
||||
if test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1" || test "x$REQUESTED_METADATA" = "x1"; then
|
||||
AC_MSG_RESULT(>>Including metadata support)
|
||||
AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])
|
||||
fi
|
||||
AM_CONDITIONAL([USE_METADATA], [test "x$REQUESTED_METADATA" = "x1"])
|
||||
|
||||
if test "x${with_systemd}" = xyes ; then
|
||||
# Find systemd unit dir
|
||||
@@ -340,9 +399,6 @@ if test "x${with_systemd}" = xyes ; then
|
||||
[with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
|
||||
AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
|
||||
[AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
|
||||
AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
|
||||
else
|
||||
AM_CONDITIONAL([HAVE_SYSTEMD], false)
|
||||
fi
|
||||
|
||||
# Look for xmltoman
|
||||
|
||||
@@ -58,8 +58,10 @@ typedef struct {
|
||||
void *port_monitor_private_storage;
|
||||
} dacp_server_record;
|
||||
|
||||
int dacp_monitor_initialised = 0;
|
||||
pthread_t dacp_monitor_thread;
|
||||
dacp_server_record dacp_server;
|
||||
void *mdns_dacp_monitor_private_storage_pointer;
|
||||
|
||||
// HTTP Response data/funcs (See the tinyhttp example.cpp file for more on this.)
|
||||
struct HttpResponse {
|
||||
@@ -110,7 +112,10 @@ static void response_code(void *opaque, int code) {
|
||||
}
|
||||
|
||||
static const struct http_funcs responseFuncs = {
|
||||
response_realloc, response_body, response_header, response_code,
|
||||
response_realloc,
|
||||
response_body,
|
||||
response_header,
|
||||
response_code,
|
||||
};
|
||||
|
||||
// static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
@@ -119,6 +124,30 @@ static pthread_mutex_t dacp_conversation_lock;
|
||||
static pthread_mutex_t dacp_server_information_lock;
|
||||
static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
void addrinfo_cleanup(void *arg) {
|
||||
// debug(1, "addrinfo cleanup called.");
|
||||
struct addrinfo **info = (struct addrinfo **)arg;
|
||||
freeaddrinfo(*info);
|
||||
}
|
||||
|
||||
void mutex_lock_cleanup(void *arg) {
|
||||
pthread_mutex_t *m = (pthread_mutex_t *)arg;
|
||||
if (pthread_mutex_unlock(m))
|
||||
debug(1, "Error releasing mutex.");
|
||||
}
|
||||
|
||||
void connect_cleanup(void *arg) {
|
||||
int *fd = (int *)arg;
|
||||
// debug(2, "dacp_send_command: close socket %d.",*fd);
|
||||
close(*fd);
|
||||
}
|
||||
|
||||
void http_cleanup(void *arg) {
|
||||
// debug(1, "http cleanup called.");
|
||||
struct http_roundtripper *rt = (struct http_roundtripper *)arg;
|
||||
http_free(rt);
|
||||
}
|
||||
|
||||
int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
|
||||
|
||||
// will malloc space for the body or set it to NULL -- the caller should free it.
|
||||
@@ -163,13 +192,14 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
|
||||
// debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
|
||||
response.code = 498; // Bad Address information for the DACP server
|
||||
} else {
|
||||
|
||||
uint64_t start_time = get_absolute_time_in_fp();
|
||||
pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
|
||||
// only do this one at a time -- not sure it is necessary, but better safe than sorry
|
||||
|
||||
int mutex_reply = sps_pthread_mutex_timedlock(&dacp_conversation_lock, 2000000, command, 1);
|
||||
// int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
|
||||
if (mutex_reply == 0) {
|
||||
// debug(1,"dacp_conversation_lock acquired for command \"%s\".",command);
|
||||
pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock);
|
||||
|
||||
// make a socket:
|
||||
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
@@ -178,18 +208,21 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
|
||||
// debug(1, "DACP socket could not be created -- error %d: \"%s\".",errno,strerror(errno));
|
||||
response.code = 497; // Can't establish a socket to the DACP server
|
||||
} else {
|
||||
pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
|
||||
// debug(2, "dacp_send_command: open socket %d.",sockfd);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 2;
|
||||
tv.tv_usec = 0;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 80000;
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
|
||||
debug(1, "Error %d setting receive timeout for DACP service.", errno);
|
||||
debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
|
||||
debug(1, "Error %d setting send timeout for DACP service.", errno);
|
||||
debug(1, "dacp_send_command: error %d setting send timeout.", errno);
|
||||
|
||||
// connect!
|
||||
// debug(1, "DACP socket created.");
|
||||
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
|
||||
debug(3, "DACP connect failed with errno %d.", errno);
|
||||
debug(3, "dacp_send_command: connect failed with errno %d.", errno);
|
||||
response.code = 496; // Can't connect to the DACP server
|
||||
} else {
|
||||
// debug(1,"DACP connect succeeded.");
|
||||
@@ -199,20 +232,32 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
|
||||
command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id);
|
||||
|
||||
// Send command
|
||||
// debug(1,"DACP connect message: \"%s\".",message);
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
|
||||
debug(1, "Error %d setting send timeout for DACP service.", errno);
|
||||
if (send(sockfd, message, strlen(message), 0) != (ssize_t)strlen(message)) {
|
||||
// debug(1, "Send failed");
|
||||
debug(3, "dacp_send_command: \"%s\".", command);
|
||||
ssize_t wresp = send(sockfd, message, strlen(message), 0);
|
||||
if (wresp == -1) {
|
||||
char errorstring[1024];
|
||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||
debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring);
|
||||
struct linger so_linger;
|
||||
so_linger.l_onoff = 1; // "true"
|
||||
so_linger.l_linger = 0;
|
||||
int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
|
||||
if (err)
|
||||
debug(1, "Could not set the dacp socket to abort due to a write error on closing.");
|
||||
}
|
||||
if (wresp != (ssize_t)strlen(message)) {
|
||||
// debug(1, "dacp_send_command: send failed.");
|
||||
response.code = 493; // Client failed to send a message
|
||||
|
||||
} else {
|
||||
|
||||
response.body = malloc(2048); // it can resize this if necessary
|
||||
response.malloced_size = 2048;
|
||||
pthread_cleanup_push(malloc_cleanup, response.body);
|
||||
|
||||
struct http_roundtripper rt;
|
||||
http_init(&rt, responseFuncs, &response);
|
||||
pthread_cleanup_push(http_cleanup, &rt);
|
||||
|
||||
int needmore = 1;
|
||||
int looperror = 0;
|
||||
@@ -221,12 +266,24 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
|
||||
while (needmore && !looperror) {
|
||||
const char *data = buffer;
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
|
||||
debug(1, "Error %d setting receive timeout for DACP service.", errno);
|
||||
int ndata = recv(sockfd, buffer, sizeof(buffer), 0);
|
||||
debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
|
||||
ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0);
|
||||
// debug(3, "Received %d bytes: \"%s\".", ndata, buffer);
|
||||
if (ndata <= 0) {
|
||||
debug(1, "dacp_send_command -- error receiving response for command \"%s\".",
|
||||
command);
|
||||
if (ndata == -1) {
|
||||
char errorstring[1024];
|
||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||
debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,
|
||||
(char *)errorstring);
|
||||
struct linger so_linger;
|
||||
so_linger.l_onoff = 1; // "true"
|
||||
so_linger.l_linger = 0;
|
||||
int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
|
||||
if (err)
|
||||
debug(1,
|
||||
"Could not set the dacp socket to abort due to a read error on closing.");
|
||||
}
|
||||
|
||||
free(response.body);
|
||||
response.body = NULL;
|
||||
response.malloced_size = 0;
|
||||
@@ -244,28 +301,41 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
|
||||
}
|
||||
|
||||
if (http_iserror(&rt)) {
|
||||
debug(1, "Error parsing data.");
|
||||
debug(3, "dacp_send_command: error parsing data.");
|
||||
free(response.body);
|
||||
response.body = NULL;
|
||||
response.malloced_size = 0;
|
||||
response.size = 0;
|
||||
}
|
||||
// debug(1,"Size of response body is %d",response.size);
|
||||
http_free(&rt);
|
||||
pthread_cleanup_pop(1); // this should call http_cleanup
|
||||
// http_free(&rt);
|
||||
pthread_cleanup_pop(
|
||||
0); // this should *not* free the malloced buffer -- just pop the malloc cleanup
|
||||
}
|
||||
}
|
||||
close(sockfd);
|
||||
// debug(1,"DACP socket closed.");
|
||||
pthread_cleanup_pop(1); // this should close the socket
|
||||
// close(sockfd);
|
||||
// debug(1,"DACP socket closed.");
|
||||
}
|
||||
pthread_mutex_unlock(&dacp_conversation_lock);
|
||||
pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
|
||||
// pthread_mutex_unlock(&dacp_conversation_lock);
|
||||
// debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size);
|
||||
// debug(1,"dacp_conversation_lock released.");
|
||||
} else {
|
||||
debug(3, "Could not acquire a lock on the dacp transmit/receive section when attempting to "
|
||||
"send the command \"%s\". Possible timeout?",
|
||||
debug(3,
|
||||
"dacp_send_command: could not acquire a lock on the dacp transmit/receive section "
|
||||
"when attempting to "
|
||||
"send the command \"%s\". Possible timeout?",
|
||||
command);
|
||||
response.code = 494; // This client is already busy
|
||||
}
|
||||
pthread_cleanup_pop(1); // this should free the addrinfo
|
||||
// freeaddrinfo(res);
|
||||
uint64_t et = get_absolute_time_in_fp() - start_time;
|
||||
et = (et * 1000000) >> 32; // microseconds
|
||||
debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
|
||||
(1.0 * et) / 1000000, response.code, command);
|
||||
}
|
||||
*body = response.body;
|
||||
*bodysize = response.size;
|
||||
@@ -290,12 +360,10 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) {
|
||||
// as the conn's connection number
|
||||
// this is to signify that the player has stopped, but only if another thread (with a different
|
||||
// index) hasn't already taken over the dacp service
|
||||
sps_pthread_mutex_timedlock(
|
||||
&dacp_server_information_lock, 500000,
|
||||
"set_dacp_server_information couldn't get DACP server information lock in 0.5 second!.", 2);
|
||||
debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
|
||||
if (dacp_server.players_connection_thread_index == conn->connection_number)
|
||||
dacp_server.players_connection_thread_index = 0;
|
||||
pthread_mutex_unlock(&dacp_server_information_lock);
|
||||
debug_mutex_unlock(&dacp_server_information_lock, 3);
|
||||
}
|
||||
|
||||
// this will be running on the thread of its caller, not of the conversation thread...
|
||||
@@ -305,9 +373,7 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) {
|
||||
// Thus, we can keep the DACP port that might have previously been discovered
|
||||
void set_dacp_server_information(rtsp_conn_info *conn) {
|
||||
// debug(1, "set_dacp_server_information");
|
||||
sps_pthread_mutex_timedlock(
|
||||
&dacp_server_information_lock, 500000,
|
||||
"set_dacp_server_information couldn't get DACP server information lock in 0.5 second!.", 2);
|
||||
debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
|
||||
dacp_server.players_connection_thread_index = conn->connection_number;
|
||||
|
||||
if ((conn->dacp_id == NULL) || (strcmp(conn->dacp_id, dacp_server.dacp_id) != 0)) {
|
||||
@@ -320,14 +386,10 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
|
||||
dacp_server.connection_family = conn->connection_ip_family;
|
||||
dacp_server.scope_id = conn->self_scope_id;
|
||||
strncpy(dacp_server.ip_string, conn->client_ip_string, INET6_ADDRSTRLEN);
|
||||
debug(2, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
|
||||
debug(3, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
|
||||
dacp_server.ip_string, dacp_server.dacp_id);
|
||||
|
||||
if (dacp_server.port_monitor_private_storage) // if there's is a monitor already active...
|
||||
mdns_dacp_dont_monitor(dacp_server.port_monitor_private_storage); // let it go.
|
||||
dacp_server.port_monitor_private_storage =
|
||||
mdns_dacp_monitor(dacp_server.dacp_id); // create a new one for us if a DACP-ID is provided,
|
||||
// otherwise will return a NULL
|
||||
mdns_dacp_monitor_set_id(dacp_server.dacp_id);
|
||||
|
||||
metadata_hub_modify_prolog();
|
||||
int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
|
||||
@@ -353,26 +415,25 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
|
||||
}
|
||||
dacp_server.active_remote_id = conn->dacp_active_remote; // even if the dacp_id remains the same,
|
||||
// the active remote will change.
|
||||
debug(2, "set_dacp_server_information set active-remote id to %" PRIu32 ".",
|
||||
debug(3, "set_dacp_server_information set active-remote id to %" PRIu32 ".",
|
||||
dacp_server.active_remote_id);
|
||||
pthread_cond_signal(&dacp_server_information_cv);
|
||||
pthread_mutex_unlock(&dacp_server_information_lock);
|
||||
debug_mutex_unlock(&dacp_server_information_lock, 3);
|
||||
}
|
||||
|
||||
void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
|
||||
debug(2, "dacp_monitor_port_update_callback with Remote ID \"%s\" and port number %d.", dacp_id,
|
||||
port);
|
||||
sps_pthread_mutex_timedlock(
|
||||
&dacp_server_information_lock, 500000,
|
||||
"dacp_monitor_port_update_callback couldn't get DACP server information lock in 0.5 second!.",
|
||||
2);
|
||||
debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
|
||||
debug(3,
|
||||
"dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port "
|
||||
"number %d.",
|
||||
dacp_id, dacp_server.dacp_id, port);
|
||||
if (strcmp(dacp_id, dacp_server.dacp_id) == 0) {
|
||||
dacp_server.port = port;
|
||||
if (port == 0)
|
||||
dacp_server.scan_enable = 0;
|
||||
else {
|
||||
dacp_server.scan_enable = 1;
|
||||
debug(2, "dacp_monitor_port_update_callback enables scan");
|
||||
// debug(2, "dacp_monitor_port_update_callback enables scan");
|
||||
}
|
||||
// metadata_hub_modify_prolog();
|
||||
// int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
|
||||
@@ -382,8 +443,14 @@ void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
|
||||
debug(1, "dacp port monitor reporting on an out-of-use remote.");
|
||||
}
|
||||
pthread_cond_signal(&dacp_server_information_cv);
|
||||
debug_mutex_unlock(&dacp_server_information_lock, 3);
|
||||
}
|
||||
|
||||
void dacp_monitor_thread_code_cleanup(__attribute__((unused)) void *arg) {
|
||||
// debug(1, "dacp_monitor_thread_code_cleanup called.");
|
||||
pthread_mutex_unlock(&dacp_server_information_lock);
|
||||
}
|
||||
|
||||
void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
||||
int scan_index = 0;
|
||||
// char server_reply[10000];
|
||||
@@ -397,14 +464,18 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
||||
sps_pthread_mutex_timedlock(
|
||||
&dacp_server_information_lock, 500000,
|
||||
"dacp_monitor_thread_code couldn't get DACP server information lock in 0.5 second!.", 2);
|
||||
int32_t the_volume;
|
||||
|
||||
pthread_cleanup_push(dacp_monitor_thread_code_cleanup, NULL);
|
||||
if (dacp_server.scan_enable == 0) {
|
||||
metadata_hub_modify_prolog();
|
||||
int ch = (metadata_store.dacp_server_active != 0) ||
|
||||
(metadata_store.advanced_dacp_server_active != 0);
|
||||
metadata_store.dacp_server_active = 0;
|
||||
metadata_store.advanced_dacp_server_active = 0;
|
||||
debug(2, "setting dacp_server_active and advanced_dacp_server_active to 0 with an update "
|
||||
"flag value of %d",
|
||||
debug(2,
|
||||
"setting dacp_server_active and advanced_dacp_server_active to 0 with an update "
|
||||
"flag value of %d",
|
||||
ch);
|
||||
metadata_hub_modify_epilog(ch);
|
||||
while (dacp_server.scan_enable == 0) {
|
||||
@@ -416,7 +487,6 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
||||
idle_scan_count = 0;
|
||||
}
|
||||
scan_index++;
|
||||
int32_t the_volume;
|
||||
result = dacp_get_volume(&the_volume); // just want the http code
|
||||
|
||||
if ((result == 496) || (result == 403) || (result == 501)) {
|
||||
@@ -435,10 +505,12 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
||||
|
||||
if ((bad_result_count == config.scan_max_bad_response_count) ||
|
||||
(idle_scan_count == config.scan_max_inactive_count)) {
|
||||
debug(1, "DACP server status scanning stopped.");
|
||||
debug(2, "DACP server status scanning stopped.");
|
||||
dacp_server.scan_enable = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&dacp_server_information_lock);
|
||||
pthread_cleanup_pop(1);
|
||||
|
||||
// pthread_mutex_unlock(&dacp_server_information_lock);
|
||||
// debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
|
||||
// dacp_server.ip_string, dacp_server.port, scan_index);
|
||||
|
||||
@@ -764,7 +836,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
|
||||
sleep(config.scan_interval_when_inactive);
|
||||
}
|
||||
}
|
||||
debug(1, "DACP monitor thread exiting.");
|
||||
debug(1, "DACP monitor thread exiting -- should never happen.");
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
@@ -787,6 +859,8 @@ void dacp_monitor_start() {
|
||||
rc = pthread_mutex_init(&dacp_conversation_lock, &mta);
|
||||
if (rc)
|
||||
debug(1, "Error creating the DACP Conversation Lock Mutex Init");
|
||||
// else
|
||||
// debug(1, "DACP Conversation Lock Mutex Init");
|
||||
|
||||
rc = pthread_mutexattr_destroy(&mta);
|
||||
if (rc)
|
||||
@@ -813,7 +887,20 @@ void dacp_monitor_start() {
|
||||
debug(1, "Error creating the DACP Server Information Lock Attr Destroy");
|
||||
|
||||
memset(&dacp_server, 0, sizeof(dacp_server_record));
|
||||
|
||||
pthread_create(&dacp_monitor_thread, NULL, dacp_monitor_thread_code, NULL);
|
||||
dacp_monitor_initialised = 1;
|
||||
}
|
||||
|
||||
void dacp_monitor_stop() {
|
||||
if (dacp_monitor_initialised) { // only if it's been started and initialised
|
||||
debug(2, "dacp_monitor_stop");
|
||||
pthread_cancel(dacp_monitor_thread);
|
||||
pthread_join(dacp_monitor_thread, NULL);
|
||||
pthread_mutex_destroy(&dacp_server_information_lock);
|
||||
debug(3, "DACP Conversation Lock Mutex Destroyed");
|
||||
pthread_mutex_destroy(&dacp_conversation_lock);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
|
||||
@@ -857,10 +944,14 @@ int dacp_get_client_volume(int32_t *result) {
|
||||
debug(1, "Too short a response from getproperty?properties=dmcp.volume");
|
||||
}
|
||||
// debug(1, "Overall Volume is %d.", overall_volume);
|
||||
}
|
||||
|
||||
if (server_reply) {
|
||||
// debug(1, "Freeing response memory.");
|
||||
free(server_reply);
|
||||
} /* else {
|
||||
debug(1, "Unexpected response %d to dacp volume control request", response);
|
||||
} */
|
||||
server_reply = NULL;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
*result = overall_volume;
|
||||
// debug(1,"dacp_get_client_volume returns: %" PRId32 ".",overall_volume);
|
||||
@@ -914,12 +1005,13 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
|
||||
sp -= item_size;
|
||||
le -= 8;
|
||||
speaker_index++;
|
||||
if (speaker_index == max_size_of_array)
|
||||
if (speaker_index == max_size_of_array) {
|
||||
return 413; // Payload Too Large -- too many speakers
|
||||
}
|
||||
speaker_info[speaker_index].active = 0;
|
||||
speaker_info[speaker_index].speaker_number = 0;
|
||||
speaker_info[speaker_index].volume = 0;
|
||||
speaker_info[speaker_index].name = NULL;
|
||||
speaker_info[speaker_index].name[0] = '\0';
|
||||
} else {
|
||||
le -= item_size + 8;
|
||||
char *t;
|
||||
@@ -929,8 +1021,10 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
|
||||
switch (type) {
|
||||
case 'minm':
|
||||
t = sp - item_size;
|
||||
speaker_info[speaker_index].name = strndup(t, item_size);
|
||||
// debug(1," \"%s\"",speaker_info[speaker_index].name);
|
||||
strncpy((char *)&speaker_info[speaker_index].name, t,
|
||||
sizeof(speaker_info[speaker_index].name));
|
||||
speaker_info[speaker_index].name[sizeof(speaker_info[speaker_index].name) - 1] =
|
||||
'\0'; // just in case
|
||||
break;
|
||||
case 'cmvo':
|
||||
t = sp - item_size;
|
||||
@@ -995,7 +1089,12 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
|
||||
free(server_reply);
|
||||
server_reply = NULL;
|
||||
} else {
|
||||
debug(1, "Unexpected response %d to dacp speakers request", response);
|
||||
// debug(1, "Unexpected response %d to dacp speakers request", response);
|
||||
if (server_reply) {
|
||||
debug(1, "Freeing response memory.");
|
||||
free(server_reply);
|
||||
server_reply = NULL;
|
||||
}
|
||||
}
|
||||
if (actual_speaker_count)
|
||||
*actual_speaker_count = speaker_count;
|
||||
|
||||
@@ -10,10 +10,11 @@ typedef struct dacp_speaker_stuff {
|
||||
int64_t speaker_number;
|
||||
int active;
|
||||
int32_t volume;
|
||||
char *name; // this is really just for debugging
|
||||
char name[128]; // this is really just for debugging
|
||||
} dacp_spkr_stuff;
|
||||
|
||||
void dacp_monitor_start();
|
||||
void dacp_monitor_stop();
|
||||
|
||||
uint32_t dacp_tlv_crawl(
|
||||
char **p,
|
||||
@@ -26,6 +27,9 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_array, int max_size_of_array,
|
||||
void set_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that
|
||||
// the dacp server information has been set
|
||||
// or changed
|
||||
void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread
|
||||
// that the player thread is no
|
||||
// longer associated with it.
|
||||
void dacp_monitor_port_update_callback(
|
||||
char *dacp_id, uint16_t port); // a callback to say the port is no longer in use
|
||||
int send_simple_dacp_command(const char *command);
|
||||
|
||||
+158
-26
@@ -15,10 +15,14 @@
|
||||
|
||||
#include "dbus-service.h"
|
||||
|
||||
int service_is_running = 0;
|
||||
|
||||
ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL;
|
||||
ShairportSyncRemoteControl *shairportSyncRemoteControlSkeleton = NULL;
|
||||
ShairportSyncAdvancedRemoteControl *shairportSyncAdvancedRemoteControlSkeleton = NULL;
|
||||
|
||||
guint ownerID = 0;
|
||||
|
||||
void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
|
||||
char response[100];
|
||||
const char *th;
|
||||
@@ -43,16 +47,15 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
|
||||
shairport_sync_advanced_remote_control_set_available(shairportSyncAdvancedRemoteControlSkeleton,
|
||||
FALSE);
|
||||
}
|
||||
|
||||
|
||||
if (argc->progress_string) {
|
||||
// debug(1, "Check progress string");
|
||||
th = shairport_sync_remote_control_get_progress_string(
|
||||
shairportSyncRemoteControlSkeleton);
|
||||
if ((th == NULL) || (strcasecmp(th, argc->progress_string) != 0)) {
|
||||
// debug(1, "Progress string should be changed");
|
||||
shairport_sync_remote_control_set_progress_string(
|
||||
shairportSyncRemoteControlSkeleton, argc->progress_string);
|
||||
}
|
||||
// debug(1, "Check progress string");
|
||||
th = shairport_sync_remote_control_get_progress_string(shairportSyncRemoteControlSkeleton);
|
||||
if ((th == NULL) || (strcasecmp(th, argc->progress_string) != 0)) {
|
||||
// debug(1, "Progress string should be changed");
|
||||
shairport_sync_remote_control_set_progress_string(shairportSyncRemoteControlSkeleton,
|
||||
argc->progress_string);
|
||||
}
|
||||
}
|
||||
|
||||
switch (argc->player_state) {
|
||||
@@ -162,7 +165,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
|
||||
if ((argc->track_metadata) && (argc->track_metadata->item_id)) {
|
||||
char trackidstring[128];
|
||||
// debug(1, "Set ID using mper ID: \"%u\".",argc->item_id);
|
||||
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", argc->track_metadata->item_id);
|
||||
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
|
||||
argc->track_metadata->item_id);
|
||||
GVariant *trackid = g_variant_new("o", trackidstring);
|
||||
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
|
||||
}
|
||||
@@ -376,14 +380,27 @@ gboolean notify_verbosity_callback(ShairportSyncDiagnostics *skeleton,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean notify_disable_standby_callback(ShairportSync *skeleton,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
// debug(1, "\"notify_disable_standby_callback\" called.");
|
||||
if (shairport_sync_get_disable_standby(skeleton)) {
|
||||
debug(1, ">> activating disable standby");
|
||||
config.keep_dac_busy = 1;
|
||||
} else {
|
||||
debug(1, ">> deactivating disable standby");
|
||||
config.keep_dac_busy = 0;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean notify_loudness_filter_active_callback(ShairportSync *skeleton,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
debug(1, "\"notify_loudness_filter_active_callback\" called.");
|
||||
// debug(1, "\"notify_loudness_filter_active_callback\" called.");
|
||||
if (shairport_sync_get_loudness_filter_active(skeleton)) {
|
||||
debug(1, "activating loudness filter");
|
||||
debug(1, ">> activating loudness filter");
|
||||
config.loudness = 1;
|
||||
} else {
|
||||
debug(1, "deactivating loudness filter");
|
||||
debug(1, ">> deactivating loudness filter");
|
||||
config.loudness = 0;
|
||||
}
|
||||
return TRUE;
|
||||
@@ -393,19 +410,62 @@ gboolean notify_loudness_threshold_callback(ShairportSync *skeleton,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
gdouble th = shairport_sync_get_loudness_threshold(skeleton);
|
||||
if ((th <= 0.0) && (th >= -100.0)) {
|
||||
debug(1, "Setting loudness threshhold to %f.", th);
|
||||
debug(1, ">> setting loudness threshhold to %f.", th);
|
||||
config.loudness_reference_volume_db = th;
|
||||
} else {
|
||||
debug(1, "Invalid loudness threshhold: %f. Ignored.", th);
|
||||
debug(1, ">> invalid loudness threshhold: %f. Ignored.", th);
|
||||
shairport_sync_set_loudness_threshold(skeleton, config.loudness_reference_volume_db);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean notify_drift_tolerance_callback(ShairportSync *skeleton,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
gdouble dt = shairport_sync_get_drift_tolerance(skeleton);
|
||||
if ((dt >= 0.0) && (dt <= 2.0)) {
|
||||
debug(1, ">> setting drift tolerance to %f seconds", dt);
|
||||
config.tolerance = dt;
|
||||
} else {
|
||||
debug(1, ">> invalid drift tolerance: %f seconds. Ignored.", dt);
|
||||
shairport_sync_set_drift_tolerance(skeleton, config.tolerance);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
char *th = (char *)shairport_sync_get_disable_standby_mode(skeleton);
|
||||
if ((strcasecmp(th, "no") == 0) || (strcasecmp(th, "off") == 0) || (strcasecmp(th, "never") == 0)) {
|
||||
config.disable_standby_mode = disable_standby_off;
|
||||
config.keep_dac_busy = 0;
|
||||
} else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || (strcasecmp(th, "always") == 0)) {
|
||||
config.disable_standby_mode = disable_standby_always;
|
||||
config.keep_dac_busy = 1;
|
||||
} else if (strcasecmp(th, "while_active") == 0)
|
||||
config.disable_standby_mode = disable_standby_while_active;
|
||||
else {
|
||||
warn("An unrecognised disable_standby_mode: \"%s\" was requested via D-Bus interface.", th);
|
||||
switch (config.disable_standby_mode) {
|
||||
case disable_standby_off:
|
||||
shairport_sync_set_disable_standby_mode(skeleton, "off");
|
||||
break;
|
||||
case disable_standby_always:
|
||||
shairport_sync_set_disable_standby_mode(skeleton, "always");
|
||||
break;
|
||||
case disable_standby_while_active:
|
||||
shairport_sync_set_disable_standby_mode(skeleton, "while_active");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
|
||||
#ifdef HAVE_APPLE_ALAC
|
||||
#ifdef CONFIG_APPLE_ALAC
|
||||
if (strcasecmp(th, "hammerton") == 0)
|
||||
config.use_apple_decoder = 0;
|
||||
else if (strcasecmp(th, "apple") == 0)
|
||||
@@ -436,7 +496,7 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
|
||||
gboolean notify_interpolation_callback(ShairportSync *skeleton,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
char *th = (char *)shairport_sync_get_interpolation(skeleton);
|
||||
#ifdef HAVE_LIBSOXR
|
||||
#ifdef CONFIG_SOXR
|
||||
if (strcasecmp(th, "basic") == 0)
|
||||
config.packet_stuffing = ST_basic;
|
||||
else if (strcasecmp(th, "soxr") == 0)
|
||||
@@ -544,6 +604,18 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
|
||||
__attribute__((unused)) const gchar *command,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
debug(1, "quit requested (native interface)");
|
||||
if (main_thread_id)
|
||||
debug(1, "Cancelling main thread results in %d.", pthread_cancel(main_thread_id));
|
||||
else
|
||||
debug(1, "Main thread ID is NULL.");
|
||||
shairport_sync_complete_quit(skeleton, invocation);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
|
||||
const gchar *command,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
@@ -582,12 +654,20 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
|
||||
G_CALLBACK(notify_interpolation_callback), NULL);
|
||||
g_signal_connect(shairportSyncSkeleton, "notify::alacdecoder",
|
||||
G_CALLBACK(notify_alacdecoder_callback), NULL);
|
||||
g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile",
|
||||
g_signal_connect(shairportSyncSkeleton, "notify::disable-standby-mode",
|
||||
G_CALLBACK(notify_disable_standby_mode_callback), NULL);
|
||||
g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile",
|
||||
G_CALLBACK(notify_volume_control_profile_callback), NULL);
|
||||
g_signal_connect(shairportSyncSkeleton, "notify::disable-standby",
|
||||
G_CALLBACK(notify_disable_standby_callback), NULL);
|
||||
g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active",
|
||||
G_CALLBACK(notify_loudness_filter_active_callback), NULL);
|
||||
g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold",
|
||||
G_CALLBACK(notify_loudness_threshold_callback), NULL);
|
||||
g_signal_connect(shairportSyncSkeleton, "notify::drift-tolerance",
|
||||
G_CALLBACK(notify_drift_tolerance_callback), NULL);
|
||||
|
||||
g_signal_connect(shairportSyncSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL);
|
||||
|
||||
g_signal_connect(shairportSyncSkeleton, "handle-remote-command",
|
||||
G_CALLBACK(on_handle_remote_command), NULL);
|
||||
@@ -644,23 +724,55 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
|
||||
|
||||
shairport_sync_set_loudness_threshold(SHAIRPORT_SYNC(shairportSyncSkeleton),
|
||||
config.loudness_reference_volume_db);
|
||||
shairport_sync_set_drift_tolerance(SHAIRPORT_SYNC(shairportSyncSkeleton), config.tolerance);
|
||||
|
||||
#ifdef HAVE_APPLE_ALAC
|
||||
if (config.use_apple_decoder == 0)
|
||||
#ifdef CONFIG_APPLE_ALAC
|
||||
if (config.use_apple_decoder == 0) {
|
||||
shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton");
|
||||
else
|
||||
debug(1, ">> ALACDecoder set to \"hammerton\"");
|
||||
} else {
|
||||
shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "apple");
|
||||
debug(1, ">> ALACDecoder set to \"apple\"");
|
||||
}
|
||||
#else
|
||||
shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton");
|
||||
debug(1, ">> ALACDecoder set to \"hammerton\"");
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SOXR
|
||||
if (config.packet_stuffing == ST_basic)
|
||||
shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
|
||||
debug(1, ">> Active set to \"false\"");
|
||||
|
||||
switch (config.disable_standby_mode) {
|
||||
case disable_standby_off:
|
||||
shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "off");
|
||||
debug(1, ">> disable standby mode set to \"off\"");
|
||||
break;
|
||||
case disable_standby_always:
|
||||
shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "always");
|
||||
debug(1, ">> disable standby mode set to \"always\"");
|
||||
break;
|
||||
case disable_standby_while_active:
|
||||
shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while_active");
|
||||
debug(1, ">> disable standby mode set to \"while_active\"");
|
||||
break;
|
||||
default:
|
||||
debug(1,"invalid disable_standby mode!");
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SOXR
|
||||
if (config.packet_stuffing == ST_basic) {
|
||||
shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic");
|
||||
else
|
||||
debug(1, ">> interpolation set to \"basic\" (soxr support built in)");
|
||||
} else {
|
||||
shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "soxr");
|
||||
debug(1, ">> interpolation set to \"soxr\"");
|
||||
}
|
||||
#else
|
||||
shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic");
|
||||
debug(1, ">> interpolation set to \"basic\" (no soxr support)");
|
||||
|
||||
#endif
|
||||
|
||||
if (config.volume_control_profile == VCP_standard)
|
||||
@@ -668,6 +780,12 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
|
||||
else
|
||||
shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "flat");
|
||||
|
||||
if (config.keep_dac_busy == 0) {
|
||||
shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
|
||||
} else {
|
||||
shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
|
||||
}
|
||||
|
||||
if (config.loudness == 0) {
|
||||
shairport_sync_set_loudness_filter_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
|
||||
} else {
|
||||
@@ -725,6 +843,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
|
||||
|
||||
debug(1, "Shairport Sync native D-Bus service started at \"%s\" on the %s bus.", name,
|
||||
(config.dbus_service_bus_type == DBT_session) ? "session" : "system");
|
||||
service_is_running = 1;
|
||||
}
|
||||
|
||||
static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
|
||||
@@ -760,7 +879,20 @@ int start_dbus_service() {
|
||||
dbus_bus_type = G_BUS_TYPE_SESSION;
|
||||
// debug(1, "Looking for a Shairport Sync native D-Bus interface \"org.gnome.ShairportSync\" on
|
||||
// the %s bus.",(config.dbus_service_bus_type == DBT_session) ? "session" : "system");
|
||||
g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE, NULL,
|
||||
on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL);
|
||||
ownerID = g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
NULL, on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL);
|
||||
return 0; // this is just to quieten a compiler warning
|
||||
}
|
||||
|
||||
void stop_dbus_service() {
|
||||
debug(2, "stopping dbus service");
|
||||
if (ownerID)
|
||||
g_bus_unown_name(ownerID);
|
||||
else
|
||||
debug(1, "Zero OwnerID for \"org.gnome.ShairportSync\".");
|
||||
service_is_running = 0;
|
||||
}
|
||||
|
||||
int dbus_service_is_running() {
|
||||
return service_is_running;
|
||||
}
|
||||
|
||||
@@ -6,5 +6,7 @@
|
||||
ShairportSync *shairportSyncSkeleton;
|
||||
|
||||
int start_dbus_service();
|
||||
void stop_dbus_service();
|
||||
int dbus_service_is_running();
|
||||
|
||||
#endif /* #ifndef DBUS_SERVICE_H */
|
||||
|
||||
@@ -20,4 +20,9 @@ dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSy
|
||||
#Remote Control commands include: Play, Pause, PlayPause, Resume, Stop, Next, Previous, VolumeUp, VolumeDown, ToggleMute, FastForward, Rewind, ShuffleSongs
|
||||
|
||||
# Set Volume using Advanced Remote Control
|
||||
dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50
|
||||
dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50
|
||||
|
||||
# Get Drift Tolerance
|
||||
dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:DriftTolerance
|
||||
# Set Drift Tolerance to 1 millisecond
|
||||
dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:DriftTolerance variant:double:0.001
|
||||
|
||||
@@ -34,9 +34,8 @@
|
||||
|
||||
#ifdef CONFIG_AVAHI
|
||||
extern mdns_backend mdns_avahi;
|
||||
extern mdns_backend mdns_external_avahi;
|
||||
#endif
|
||||
#ifdef CONFIG_HAVE_DNS_SD_H
|
||||
#ifdef CONFIG_DNS_SD
|
||||
extern mdns_backend mdns_dns_sd;
|
||||
extern mdns_backend mdns_external_dns_sd;
|
||||
#endif
|
||||
@@ -44,17 +43,23 @@ extern mdns_backend mdns_external_dns_sd;
|
||||
extern mdns_backend mdns_tinysvcmdns;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EXTERNAL_MDNS
|
||||
extern mdns_backend mdns_external_avahi;
|
||||
#endif
|
||||
|
||||
static mdns_backend *mdns_backends[] = {
|
||||
#ifdef CONFIG_AVAHI
|
||||
&mdns_avahi,
|
||||
&mdns_external_avahi,
|
||||
#endif
|
||||
#ifdef CONFIG_HAVE_DNS_SD_H
|
||||
#ifdef CONFIG_DNS_SD
|
||||
&mdns_dns_sd,
|
||||
&mdns_external_dns_sd,
|
||||
#endif
|
||||
#ifdef CONFIG_TINYSVCMDNS
|
||||
&mdns_tinysvcmdns,
|
||||
#endif
|
||||
#ifdef CONFIG_EXTERNAL_MDNS
|
||||
&mdns_external_avahi,
|
||||
#endif
|
||||
NULL};
|
||||
|
||||
@@ -96,34 +101,38 @@ void mdns_register(void) {
|
||||
|
||||
if (config.mdns == NULL)
|
||||
die("Could not establish mDNS advertisement!");
|
||||
|
||||
mdns_dacp_monitor_start(); // create a dacp monitor thread
|
||||
}
|
||||
|
||||
void mdns_unregister(void) {
|
||||
mdns_dacp_monitor_stop();
|
||||
if (config.mdns) {
|
||||
config.mdns->mdns_unregister();
|
||||
}
|
||||
}
|
||||
|
||||
void *mdns_dacp_monitor(char *dacp_id) {
|
||||
void *reply = NULL;
|
||||
if ((dacp_id != NULL) && (*dacp_id != '\0')) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor)) {
|
||||
reply = config.mdns->mdns_dacp_monitor(dacp_id);
|
||||
if (reply == NULL) {
|
||||
debug(1, "Error starting a DACP monitor.");
|
||||
}
|
||||
} else
|
||||
debug(3, "Can't start a DACP monitor -- none registered.");
|
||||
}
|
||||
return reply;
|
||||
void mdns_dacp_monitor_start(void) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor_start)) {
|
||||
config.mdns->mdns_dacp_monitor_start();
|
||||
} else
|
||||
debug(3, "Can't start a DACP monitor -- no mdns_dacp_monitor start registered.");
|
||||
}
|
||||
|
||||
void mdns_dacp_dont_monitor(void *userdata) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_dont_monitor)) {
|
||||
config.mdns->mdns_dacp_dont_monitor(userdata);
|
||||
void mdns_dacp_monitor_stop() {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor_stop)) {
|
||||
config.mdns->mdns_dacp_monitor_stop();
|
||||
} else
|
||||
debug(3, "Can't stop a DACP monitor -- none registered.");
|
||||
debug(3, "Can't stop a DACP monitor -- no mdns_dacp_monitor_stop registered.");
|
||||
}
|
||||
|
||||
void mdns_dacp_monitor_set_id(const char *dacp_id) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor_set_id)) {
|
||||
config.mdns->mdns_dacp_monitor_set_id(dacp_id);
|
||||
} else
|
||||
debug(3, "Can't set dacp_id -- no mdns_dacp_set_id registered.");
|
||||
}
|
||||
|
||||
void mdns_ls_backends(void) {
|
||||
mdns_backend **b = NULL;
|
||||
printf("Available mDNS backends: \n");
|
||||
|
||||
@@ -9,8 +9,9 @@ extern int mdns_pid;
|
||||
|
||||
void mdns_unregister(void);
|
||||
void mdns_register(void);
|
||||
void *mdns_dacp_monitor(char *dacp_id);
|
||||
void mdns_dacp_dont_monitor(void *userdata);
|
||||
void mdns_dacp_monitor_start();
|
||||
void mdns_dacp_monitor_stop(void);
|
||||
void mdns_dacp_monitor_set_id(const char *dacp_id);
|
||||
|
||||
void mdns_ls_backends(void);
|
||||
|
||||
@@ -18,8 +19,9 @@ typedef struct {
|
||||
char *name;
|
||||
int (*mdns_register)(char *apname, int port);
|
||||
void (*mdns_unregister)(void);
|
||||
void *(*mdns_dacp_monitor)(char *);
|
||||
void (*mdns_dacp_dont_monitor)(void *);
|
||||
void (*mdns_dacp_monitor_start)();
|
||||
void (*mdns_dacp_monitor_set_id)(const char *);
|
||||
void (*mdns_dacp_monitor_stop)();
|
||||
} mdns_backend;
|
||||
|
||||
#ifdef CONFIG_METADATA
|
||||
|
||||
+112
-107
@@ -33,7 +33,7 @@
|
||||
#include "common.h"
|
||||
#include "mdns.h"
|
||||
#include "rtsp.h"
|
||||
#ifdef HAVE_DACP_CLIENT
|
||||
#ifdef CONFIG_DACP_CLIENT
|
||||
#include "dacp.h"
|
||||
#endif
|
||||
#include <string.h>
|
||||
@@ -61,6 +61,8 @@ typedef struct {
|
||||
char *dacp_id;
|
||||
} dacp_browser_struct;
|
||||
|
||||
dacp_browser_struct private_dbs;
|
||||
|
||||
// static AvahiServiceBrowser *sb = NULL;
|
||||
static AvahiClient *client = NULL;
|
||||
// static AvahiClient *service_client = NULL;
|
||||
@@ -84,33 +86,35 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
|
||||
|
||||
/* Called whenever a service has been resolved successfully or timed out */
|
||||
switch (event) {
|
||||
case AVAHI_RESOLVER_FAILURE:
|
||||
debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name,
|
||||
type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
|
||||
break;
|
||||
case AVAHI_RESOLVER_FOUND: {
|
||||
// char a[AVAHI_ADDRESS_STR_MAX], *t;
|
||||
// debug(1, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain);
|
||||
case AVAHI_RESOLVER_FAILURE:
|
||||
debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name,
|
||||
type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
|
||||
break;
|
||||
case AVAHI_RESOLVER_FOUND: {
|
||||
// char a[AVAHI_ADDRESS_STR_MAX], *t;
|
||||
debug(3, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain);
|
||||
if (dbs->dacp_id) {
|
||||
char *dacpid = strstr(name, "iTunes_Ctrl_");
|
||||
if (dacpid) {
|
||||
dacpid += strlen("iTunes_Ctrl_");
|
||||
if (strcmp(dacpid, dbs->dacp_id) == 0) {
|
||||
debug(3, "Client's DACP port: %u.", port);
|
||||
#ifdef HAVE_DACP_CLIENT
|
||||
debug(3, "Client \"%s\"'s DACP port: %u.", dbs->dacp_id, port);
|
||||
#ifdef CONFIG_DACP_CLIENT
|
||||
dacp_monitor_port_update_callback(dacpid, port);
|
||||
#endif
|
||||
#ifdef CONFIG_METADATA
|
||||
#endif
|
||||
#ifdef CONFIG_METADATA
|
||||
char portstring[20];
|
||||
memset(portstring, 0, sizeof(portstring));
|
||||
snprintf(portstring, sizeof(portstring), "%u", port);
|
||||
send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// debug(1,"service resolver freed by resolve_callback");
|
||||
check_avahi_response(1, avahi_service_resolver_free(r));
|
||||
}
|
||||
@@ -128,7 +132,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
|
||||
avahi_threaded_poll_quit(tpoll);
|
||||
break;
|
||||
case AVAHI_BROWSER_NEW:
|
||||
debug(3, "(Browser) NEW: service '%s' of type '%s' in domain '%s'.", name, type, domain);
|
||||
// debug(1, "(Browser) NEW: service '%s' of type '%s' in domain '%s'.", name, type, domain);
|
||||
/* We ignore the returned resolver object. In the callback
|
||||
function we free it. If the server is terminated before
|
||||
the callback function is called the server will free
|
||||
@@ -140,11 +144,11 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
|
||||
break;
|
||||
case AVAHI_BROWSER_REMOVE:
|
||||
debug(3, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'.", name, type, domain);
|
||||
#ifdef HAVE_DACP_CLIENT
|
||||
#ifdef CONFIG_DACP_CLIENT
|
||||
char *dacpid = strstr(name, "iTunes_Ctrl_");
|
||||
if (dacpid) {
|
||||
dacpid += strlen("iTunes_Ctrl_");
|
||||
if (strcmp(dacpid, dbs->dacp_id) == 0)
|
||||
if ((dbs->dacp_id) && (strcmp(dacpid, dbs->dacp_id) == 0))
|
||||
dacp_monitor_port_update_callback(dbs->dacp_id, 0); // say the port is withdrawn
|
||||
} else {
|
||||
debug(1, "Browse callback: Can't see a DACP string in a DACP Record!");
|
||||
@@ -164,47 +168,47 @@ static void register_service(AvahiClient *c);
|
||||
static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
|
||||
AVAHI_GCC_UNUSED void *userdata) {
|
||||
switch (state) {
|
||||
case AVAHI_ENTRY_GROUP_ESTABLISHED:
|
||||
/* The entry group has been established successfully */
|
||||
debug(1, "avahi: service '%s' successfully added.", service_name);
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_ESTABLISHED:
|
||||
/* The entry group has been established successfully */
|
||||
debug(1, "avahi: service '%s' successfully added.", service_name);
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_COLLISION: {
|
||||
char *n;
|
||||
case AVAHI_ENTRY_GROUP_COLLISION: {
|
||||
char *n;
|
||||
|
||||
/* A service name collision with a remote service
|
||||
* happened. Let's pick a new name */
|
||||
debug(1, "avahi name collision -- look for another");
|
||||
n = avahi_alternative_service_name(service_name);
|
||||
if (service_name)
|
||||
avahi_free(service_name);
|
||||
else
|
||||
debug(1, "avahi attempt to free a NULL service name");
|
||||
service_name = n;
|
||||
/* A service name collision with a remote service
|
||||
* happened. Let's pick a new name */
|
||||
debug(1, "avahi name collision -- look for another");
|
||||
n = avahi_alternative_service_name(service_name);
|
||||
if (service_name)
|
||||
avahi_free(service_name);
|
||||
else
|
||||
debug(1, "avahi attempt to free a NULL service name");
|
||||
service_name = n;
|
||||
|
||||
debug(2, "avahi: service name collision, renaming service to '%s'", service_name);
|
||||
debug(2, "avahi: service name collision, renaming service to '%s'", service_name);
|
||||
|
||||
/* And recreate the services */
|
||||
register_service(avahi_entry_group_get_client(g));
|
||||
break;
|
||||
}
|
||||
/* And recreate the services */
|
||||
register_service(avahi_entry_group_get_client(g));
|
||||
break;
|
||||
}
|
||||
|
||||
case AVAHI_ENTRY_GROUP_FAILURE:
|
||||
debug(1, "avahi: entry group failure: %s",
|
||||
avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_FAILURE:
|
||||
debug(1, "avahi: entry group failure: %s",
|
||||
avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||
debug(2, "avahi: service '%s' group is not yet committed.", service_name);
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||
debug(2, "avahi: service '%s' group is not yet committed.", service_name);
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
||||
debug(2, "avahi: service '%s' group is registering.", service_name);
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
||||
debug(2, "avahi: service '%s' group is registering.", service_name);
|
||||
break;
|
||||
|
||||
default:
|
||||
debug(1, "avahi: unhandled egroup state: %d", state);
|
||||
break;
|
||||
default:
|
||||
debug(1, "avahi: unhandled egroup state: %d", state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,45 +389,40 @@ static int avahi_register(char *srvname, int srvport) {
|
||||
static void avahi_unregister(void) {
|
||||
debug(1, "avahi: avahi_unregister.");
|
||||
if (tpoll) {
|
||||
debug(1, "avahi: stop the threaded poll.");
|
||||
avahi_threaded_poll_stop(tpoll);
|
||||
|
||||
if (client) {
|
||||
debug(1, "avahi: free the client.");
|
||||
avahi_client_free(client);
|
||||
client = NULL;
|
||||
} else {
|
||||
debug(1, "avahi attempting to unregister a NULL client");
|
||||
}
|
||||
debug(1, "avahi: free the threaded poll.");
|
||||
avahi_threaded_poll_free(tpoll);
|
||||
tpoll = NULL;
|
||||
} else {
|
||||
debug(1, "No avahi threaded poll.");
|
||||
}
|
||||
|
||||
if (service_name)
|
||||
if (service_name) {
|
||||
debug(1, "avahi: free the service name.");
|
||||
free(service_name);
|
||||
else
|
||||
} else
|
||||
debug(1, "avahi attempt to free NULL service name");
|
||||
service_name = NULL;
|
||||
}
|
||||
|
||||
void *avahi_dacp_monitor(char *dacp_id) {
|
||||
dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct));
|
||||
|
||||
if (dbs == NULL)
|
||||
die("can not allocate a dacp_browser_struct.");
|
||||
|
||||
char *t = strdup(dacp_id);
|
||||
if (t)
|
||||
dbs->dacp_id = t;
|
||||
else {
|
||||
die("can not allocate a dacp_id string in dacp_browser_struct.");
|
||||
}
|
||||
void avahi_dacp_monitor_start(void) {
|
||||
dacp_browser_struct *dbs = &private_dbs;
|
||||
memset((void *)&private_dbs, 0, sizeof(dacp_browser_struct));
|
||||
|
||||
// create the threaded poll code
|
||||
int err;
|
||||
if (!(dbs->service_poll = avahi_threaded_poll_new())) {
|
||||
warn("couldn't create avahi threaded service_poll!");
|
||||
free(dbs->dacp_id);
|
||||
free((char *)dbs);
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// create the service client
|
||||
@@ -432,66 +431,72 @@ void *avahi_dacp_monitor(char *dacp_id) {
|
||||
service_client_callback, (void *)dbs, &err))) {
|
||||
warn("couldn't create avahi service client: %s!", avahi_strerror(err));
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
free(dbs->dacp_id);
|
||||
free((char *)dbs);
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the service browser */
|
||||
if (!(dbs->service_browser =
|
||||
avahi_service_browser_new(dbs->service_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
|
||||
"_dacp._tcp", NULL, 0, browse_callback, (void *)dbs))) {
|
||||
warn("failed to create avahi service browser: %s\n",
|
||||
avahi_strerror(avahi_client_errno(dbs->service_client)));
|
||||
avahi_client_free(dbs->service_client);
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
free(dbs->dacp_id);
|
||||
free((char *)dbs);
|
||||
return NULL;
|
||||
}
|
||||
// start the polling thread
|
||||
if (avahi_threaded_poll_start(dbs->service_poll) < 0) {
|
||||
warn("couldn't start avahi service_poll thread");
|
||||
avahi_service_browser_free(dbs->service_browser);
|
||||
avahi_client_free(dbs->service_client);
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
free(dbs->dacp_id);
|
||||
free((char *)dbs);
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
debug(3, "Avahi DACP monitor successfully started");
|
||||
return (void *)dbs;
|
||||
return;
|
||||
}
|
||||
|
||||
void avahi_dacp_dont_monitor(void *userdata) {
|
||||
debug(3,"avahi_dacp_dont_monitor");
|
||||
if (userdata) {
|
||||
dacp_browser_struct *dbs = (dacp_browser_struct *)userdata;
|
||||
// stop and dispose of everything
|
||||
/*if (dbs->service_poll)
|
||||
avahi_threaded_poll_stop((dbs)->service_poll);
|
||||
*/
|
||||
if (dbs->service_poll) {
|
||||
avahi_threaded_poll_stop(dbs->service_poll);
|
||||
void avahi_dacp_monitor_set_id(const char *dacp_id) {
|
||||
debug(3, "avahi_dacp_dont_monitor");
|
||||
dacp_browser_struct *dbs = &private_dbs;
|
||||
|
||||
if (dbs->dacp_id)
|
||||
free(dbs->dacp_id);
|
||||
if (dacp_id == NULL)
|
||||
dbs->dacp_id = NULL;
|
||||
else {
|
||||
char *t = strdup(dacp_id);
|
||||
if (t) {
|
||||
dbs->dacp_id = t;
|
||||
avahi_threaded_poll_lock(dbs->service_poll);
|
||||
if (dbs->service_browser)
|
||||
avahi_service_browser_free(dbs->service_browser);
|
||||
if (dbs->service_client)
|
||||
avahi_client_free(dbs->service_client);
|
||||
avahi_threaded_poll_unlock(dbs->service_poll);
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
|
||||
if (!(dbs->service_browser =
|
||||
avahi_service_browser_new(dbs->service_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
|
||||
"_dacp._tcp", NULL, 0, browse_callback, (void *)dbs))) {
|
||||
warn("failed to create avahi service browser: %s\n",
|
||||
avahi_strerror(avahi_client_errno(dbs->service_client)));
|
||||
}
|
||||
avahi_threaded_poll_unlock(dbs->service_poll);
|
||||
} else {
|
||||
warn("avahi_dacp_set_id: can not allocate a dacp_id string in dacp_browser_struct.");
|
||||
}
|
||||
free(dbs->dacp_id);
|
||||
free(userdata);
|
||||
debug(3, "Avahi DACP monitor successfully stopped");
|
||||
} else {
|
||||
debug(1, "Avahi DACP Monitor is not running.");
|
||||
debug(3, "Search for DACP ID \"%s\".", t);
|
||||
}
|
||||
debug(3,"avahi_dacp_dont_monitor exit");
|
||||
}
|
||||
|
||||
void avahi_dacp_monitor_stop() {
|
||||
debug(3, "avahi_dacp_dont_monitor");
|
||||
dacp_browser_struct *dbs = &private_dbs;
|
||||
// stop and dispose of everything
|
||||
if (dbs->service_poll) {
|
||||
avahi_threaded_poll_stop(dbs->service_poll);
|
||||
avahi_threaded_poll_lock(dbs->service_poll);
|
||||
if (dbs->service_browser)
|
||||
avahi_service_browser_free(dbs->service_browser);
|
||||
if (dbs->service_client)
|
||||
avahi_client_free(dbs->service_client);
|
||||
avahi_threaded_poll_unlock(dbs->service_poll);
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
}
|
||||
free(dbs->dacp_id);
|
||||
debug(3, "Avahi DACP monitor successfully stopped");
|
||||
}
|
||||
|
||||
mdns_backend mdns_avahi = {.name = "avahi",
|
||||
.mdns_register = avahi_register,
|
||||
.mdns_unregister = avahi_unregister,
|
||||
.mdns_dacp_monitor = avahi_dacp_monitor,
|
||||
.mdns_dacp_dont_monitor = avahi_dacp_dont_monitor};
|
||||
.mdns_dacp_monitor_start = avahi_dacp_monitor_start,
|
||||
.mdns_dacp_monitor_set_id = avahi_dacp_monitor_set_id,
|
||||
.mdns_dacp_monitor_stop = avahi_dacp_monitor_stop};
|
||||
|
||||
+4
-3
@@ -24,8 +24,8 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mdns.h"
|
||||
#include "common.h"
|
||||
#include "mdns.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <dns_sd.h>
|
||||
#include <stdlib.h>
|
||||
@@ -93,5 +93,6 @@ static void mdns_dns_sd_unregister(void) {
|
||||
mdns_backend mdns_dns_sd = {.name = "dns-sd",
|
||||
.mdns_register = mdns_dns_sd_register,
|
||||
.mdns_unregister = mdns_dns_sd_unregister,
|
||||
.mdns_dacp_monitor = NULL,
|
||||
.mdns_dacp_dont_monitor = NULL};
|
||||
.mdns_dacp_monitor_start = NULL,
|
||||
.mdns_dacp_monitor_set_id = NULL,
|
||||
.mdns_dacp_monitor_stop = NULL};
|
||||
|
||||
+7
-5
@@ -24,8 +24,8 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mdns.h"
|
||||
#include "common.h"
|
||||
#include "mdns.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
@@ -162,11 +162,13 @@ static void kill_mdns_child(void) {
|
||||
mdns_backend mdns_external_avahi = {.name = "external-avahi",
|
||||
.mdns_register = mdns_external_avahi_register,
|
||||
.mdns_unregister = kill_mdns_child,
|
||||
.mdns_dacp_monitor = NULL,
|
||||
.mdns_dacp_dont_monitor = NULL};
|
||||
.mdns_dacp_monitor_start = NULL,
|
||||
.mdns_dacp_monitor_set_id = NULL,
|
||||
.mdns_dacp_monitor_stop = NULL};
|
||||
|
||||
mdns_backend mdns_external_dns_sd = {.name = "external-dns-sd",
|
||||
.mdns_register = mdns_external_dns_sd_register,
|
||||
.mdns_unregister = kill_mdns_child,
|
||||
.mdns_dacp_monitor = NULL,
|
||||
.mdns_dacp_dont_monitor = NULL};
|
||||
.mdns_dacp_monitor_start = NULL,
|
||||
.mdns_dacp_monitor_set_id = NULL,
|
||||
.mdns_dacp_monitor_stop = NULL};
|
||||
|
||||
+3
-2
@@ -164,5 +164,6 @@ static void mdns_tinysvcmdns_unregister(void) {
|
||||
mdns_backend mdns_tinysvcmdns = {.name = "tinysvcmdns",
|
||||
.mdns_register = mdns_tinysvcmdns_register,
|
||||
.mdns_unregister = mdns_tinysvcmdns_unregister,
|
||||
.mdns_dacp_monitor = NULL,
|
||||
.mdns_dacp_dont_monitor = NULL};
|
||||
.mdns_dacp_monitor_start = NULL,
|
||||
.mdns_dacp_monitor_set_id = NULL,
|
||||
.mdns_dacp_monitor_stop = NULL};
|
||||
|
||||
+81
-48
@@ -45,18 +45,21 @@
|
||||
#include "dacp.h"
|
||||
#include "metadata_hub.h"
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#include <mbedtls/version.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
#include <polarssl/md5.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
#include <openssl/md5.h>
|
||||
#endif
|
||||
|
||||
int metadata_hub_initialised = 0;
|
||||
|
||||
pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store
|
||||
|
||||
@@ -90,10 +93,31 @@ void metadata_hub_release_track_metadata(struct track_metadata_bundle *track_met
|
||||
}
|
||||
}
|
||||
|
||||
void metadata_hub_release_track_artwork(void) {
|
||||
// debug(1,"release track artwork");
|
||||
release_char_string(&metadata_store.cover_art_pathname);
|
||||
}
|
||||
|
||||
void metadata_hub_init(void) {
|
||||
// debug(1, "Metadata bundle initialisation.");
|
||||
memset(&metadata_store, 0, sizeof(metadata_store));
|
||||
track_metadata = NULL;
|
||||
metadata_hub_initialised = 1;
|
||||
}
|
||||
|
||||
void metadata_hub_stop(void) {
|
||||
if (metadata_hub_initialised) {
|
||||
debug(2, "metadata_hub_stop.");
|
||||
metadata_hub_release_track_artwork();
|
||||
if (metadata_store.track_metadata) {
|
||||
metadata_hub_release_track_metadata(metadata_store.track_metadata);
|
||||
metadata_store.track_metadata = NULL;
|
||||
}
|
||||
if (track_metadata) {
|
||||
metadata_hub_release_track_metadata(track_metadata);
|
||||
track_metadata = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_metadata_watcher(metadata_watcher fn, void *userdata) {
|
||||
@@ -108,6 +132,26 @@ void add_metadata_watcher(metadata_watcher fn, void *userdata) {
|
||||
}
|
||||
}
|
||||
|
||||
void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) {
|
||||
// debug(1, "metadata_hub_unlock_hub_mutex_cleanup called.");
|
||||
pthread_rwlock_unlock(&metadata_hub_re_lock);
|
||||
}
|
||||
|
||||
void run_metadata_watchers(void) {
|
||||
int i;
|
||||
// debug(1, "locking metadata hub for reading");
|
||||
pthread_rwlock_rdlock(&metadata_hub_re_lock);
|
||||
pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL);
|
||||
for (i = 0; i < number_of_watchers; i++) {
|
||||
if (metadata_store.watchers[i]) {
|
||||
metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]);
|
||||
}
|
||||
}
|
||||
// debug(1, "unlocking metadata hub for reading");
|
||||
// pthread_rwlock_unlock(&metadata_hub_re_lock);
|
||||
pthread_cleanup_pop(1);
|
||||
}
|
||||
|
||||
void metadata_hub_modify_prolog(void) {
|
||||
// always run this before changing an entry or a sequence of entries in the metadata_hub
|
||||
// debug(1, "locking metadata hub for writing");
|
||||
@@ -118,24 +162,6 @@ void metadata_hub_modify_prolog(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void metadata_hub_release_track_artwork(void) {
|
||||
// debug(1,"release track artwork");
|
||||
release_char_string(&metadata_store.cover_art_pathname);
|
||||
}
|
||||
|
||||
void run_metadata_watchers(void) {
|
||||
int i;
|
||||
// debug(1, "locking metadata hub for reading");
|
||||
pthread_rwlock_rdlock(&metadata_hub_re_lock);
|
||||
for (i = 0; i < number_of_watchers; i++) {
|
||||
if (metadata_store.watchers[i]) {
|
||||
metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]);
|
||||
}
|
||||
}
|
||||
// debug(1, "unlocking metadata hub for reading");
|
||||
pthread_rwlock_unlock(&metadata_hub_re_lock);
|
||||
}
|
||||
|
||||
void metadata_hub_modify_epilog(int modified) {
|
||||
// always run this after changing an entry or a sequence of entries in the metadata_hub
|
||||
// debug(1, "unlocking metadata hub for writing");
|
||||
@@ -150,7 +176,7 @@ void metadata_hub_modify_epilog(int modified) {
|
||||
|
||||
if ((metadata_store.dacp_server_active == 0) &&
|
||||
(metadata_store.dacp_server_has_been_active != 0)) {
|
||||
debug(1, "dacp_scanner going inactive -- release track metadata and artwork");
|
||||
debug(2, "dacp_scanner going inactive -- release track metadata and artwork");
|
||||
if (metadata_store.track_metadata) {
|
||||
m = 1;
|
||||
metadata_hub_release_track_metadata(metadata_store.track_metadata);
|
||||
@@ -197,23 +223,30 @@ char *metadata_write_image_file(const char *buf, int len) {
|
||||
char *path = NULL; // this will be what is returned
|
||||
|
||||
uint8_t img_md5[16];
|
||||
// uint8_t ap_md5[16];
|
||||
// uint8_t ap_md5[16];
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
MD5_CTX ctx;
|
||||
MD5_Init(&ctx);
|
||||
MD5_Update(&ctx, buf, len);
|
||||
MD5_Final(img_md5, &ctx);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
mbedtls_md5_context tctx;
|
||||
mbedtls_md5_starts(&tctx);
|
||||
mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
|
||||
mbedtls_md5_finish(&tctx, img_md5);
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#if MBEDTLS_VERSION_MINOR >= 7
|
||||
mbedtls_md5_context tctx;
|
||||
mbedtls_md5_starts_ret(&tctx);
|
||||
mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len);
|
||||
mbedtls_md5_finish_ret(&tctx, img_md5);
|
||||
#else
|
||||
mbedtls_md5_context tctx;
|
||||
mbedtls_md5_starts(&tctx);
|
||||
mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
|
||||
mbedtls_md5_finish(&tctx, img_md5);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
md5_context tctx;
|
||||
md5_starts(&tctx);
|
||||
md5_update(&tctx, (const unsigned char *)buf, len);
|
||||
@@ -469,27 +502,15 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
|
||||
track_metadata = (struct track_metadata_bundle *)malloc(sizeof(struct track_metadata_bundle));
|
||||
if (track_metadata == NULL)
|
||||
die("Could not allocate memory for track metadata.");
|
||||
memset(track_metadata, 0, sizeof(struct track_metadata_bundle)); // now we have a valid track
|
||||
// metadata space, but the
|
||||
// metadata itself
|
||||
// might turin out to be invalid. Specifically, YouTube on iOS can generate a sequence of
|
||||
// track metadata that is invalid.
|
||||
// it is distinguished by having an item_id of zero.
|
||||
memset(track_metadata, 0, sizeof(struct track_metadata_bundle));
|
||||
break;
|
||||
case 'mden':
|
||||
if (track_metadata) {
|
||||
if ((track_metadata->item_id_received == 0) || (track_metadata->item_id != 0)) {
|
||||
// i.e. it's only invalid if it has definitely been given an item_id of zero
|
||||
metadata_hub_modify_prolog();
|
||||
metadata_hub_release_track_metadata(metadata_store.track_metadata);
|
||||
metadata_store.track_metadata = track_metadata;
|
||||
track_metadata = NULL;
|
||||
metadata_hub_modify_epilog(1);
|
||||
} else {
|
||||
debug(1, "The track information received is invalid -- dropping it");
|
||||
metadata_hub_release_track_metadata(track_metadata);
|
||||
track_metadata = NULL;
|
||||
}
|
||||
metadata_hub_modify_prolog();
|
||||
metadata_hub_release_track_metadata(metadata_store.track_metadata);
|
||||
metadata_store.track_metadata = track_metadata;
|
||||
track_metadata = NULL;
|
||||
metadata_hub_modify_epilog(1);
|
||||
}
|
||||
debug(2, "MH Metadata stream processing end.");
|
||||
break;
|
||||
@@ -539,6 +560,18 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
|
||||
break;
|
||||
// these could tell us about play / pause etc. but will only occur if metadata is enabled, so
|
||||
// we'll just ignore them
|
||||
case 'abeg': {
|
||||
metadata_hub_modify_prolog();
|
||||
int changed = (metadata_store.active_state != AM_ACTIVE);
|
||||
metadata_store.active_state = AM_ACTIVE;
|
||||
metadata_hub_modify_epilog(changed);
|
||||
} break;
|
||||
case 'aend': {
|
||||
metadata_hub_modify_prolog();
|
||||
int changed = (metadata_store.active_state != AM_INACTIVE);
|
||||
metadata_store.active_state = AM_INACTIVE;
|
||||
metadata_hub_modify_epilog(changed);
|
||||
} break;
|
||||
case 'pbeg': {
|
||||
metadata_hub_modify_prolog();
|
||||
int changed = (metadata_store.player_state != PS_PLAYING);
|
||||
|
||||
+8
-1
@@ -12,6 +12,11 @@ enum play_status_type {
|
||||
PS_PLAYING,
|
||||
} play_status_type;
|
||||
|
||||
enum active_state_type {
|
||||
AM_INACTIVE = 0,
|
||||
AM_ACTIVE,
|
||||
} active_state_type;
|
||||
|
||||
enum shuffle_status_type {
|
||||
SS_NOT_AVAILABLE = 0,
|
||||
SS_OFF,
|
||||
@@ -56,7 +61,7 @@ typedef struct metadata_bundle {
|
||||
char *client_ip; // IP number used by the audio source (i.e. the "client"), which is also the DACP
|
||||
// server
|
||||
char *server_ip; // IP number used by Shairport Sync
|
||||
char *progress_string; // progress string, emitted by the source from time to time
|
||||
char *progress_string; // progress string, emitted by the source from time to time
|
||||
int player_thread_active; // true if a play thread is running
|
||||
int dacp_server_active; // true if there's a reachable DACP server (assumed to be the Airplay
|
||||
// client) ; false otherwise
|
||||
@@ -77,6 +82,7 @@ typedef struct metadata_bundle {
|
||||
|
||||
enum play_status_type
|
||||
player_state; // this is the state of the actual player itself, which can be a bit noisy.
|
||||
enum active_state_type active_state;
|
||||
|
||||
int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the
|
||||
// speaker volume control
|
||||
@@ -92,6 +98,7 @@ struct metadata_bundle metadata_store;
|
||||
void add_metadata_watcher(metadata_watcher fn, void *userdata);
|
||||
|
||||
void metadata_hub_init(void);
|
||||
void metadata_hub_stop(void);
|
||||
void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length);
|
||||
void metadata_hub_reset_track_metadata(void);
|
||||
void metadata_hub_release_track_artwork(void);
|
||||
|
||||
+14
-3
@@ -123,7 +123,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
|
||||
} else if ((argc->track_metadata) && (argc->track_metadata->item_id)) {
|
||||
char trackidstring[128];
|
||||
// debug(1, "Set ID using mper ID: \"%u\".",argc->item_id);
|
||||
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", argc->track_metadata->item_id);
|
||||
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
|
||||
argc->track_metadata->item_id);
|
||||
GVariant *trackid = g_variant_new("o", trackidstring);
|
||||
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
|
||||
}
|
||||
@@ -182,6 +183,14 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
|
||||
// media_player2_player_set_volume(mprisPlayerPlayerSkeleton, metadata_store.speaker_volume);
|
||||
}
|
||||
|
||||
static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
debug(1, "quit requested (MPRIS interface).");
|
||||
pthread_cancel(main_thread_id);
|
||||
media_player2_complete_quit(skeleton, invocation);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
send_simple_dacp_command("nextitem");
|
||||
@@ -242,7 +251,7 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
|
||||
|
||||
media_player2_set_desktop_entry(mprisPlayerSkeleton, "shairport-sync");
|
||||
media_player2_set_identity(mprisPlayerSkeleton, "Shairport Sync");
|
||||
media_player2_set_can_quit(mprisPlayerSkeleton, FALSE);
|
||||
media_player2_set_can_quit(mprisPlayerSkeleton, TRUE);
|
||||
media_player2_set_can_raise(mprisPlayerSkeleton, FALSE);
|
||||
media_player2_set_has_track_list(mprisPlayerSkeleton, FALSE);
|
||||
media_player2_set_supported_uri_schemes(mprisPlayerSkeleton, empty_string_array);
|
||||
@@ -260,6 +269,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
|
||||
media_player2_player_set_can_seek(mprisPlayerPlayerSkeleton, FALSE);
|
||||
media_player2_player_set_can_control(mprisPlayerPlayerSkeleton, TRUE);
|
||||
|
||||
g_signal_connect(mprisPlayerSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL);
|
||||
|
||||
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play", G_CALLBACK(on_handle_play), NULL);
|
||||
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-pause", G_CALLBACK(on_handle_pause), NULL);
|
||||
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play-pause", G_CALLBACK(on_handle_play_pause),
|
||||
@@ -291,7 +302,7 @@ static void on_mpris_name_lost(__attribute__((unused)) GDBusConnection *connecti
|
||||
// name,(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
|
||||
pid_t pid = getpid();
|
||||
char interface_name[256] = "";
|
||||
snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid);
|
||||
snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid);
|
||||
GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM;
|
||||
if (config.mpris_service_bus_type == DBT_session)
|
||||
mpris_bus_type = G_BUS_TYPE_SESSION;
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "player.h"
|
||||
#include "rtsp.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
#include "dacp.h"
|
||||
#include "mqtt.h"
|
||||
#include <mosquitto.h>
|
||||
|
||||
// this holds the mosquitto client
|
||||
struct mosquitto *global_mosq = NULL;
|
||||
char *topic = NULL;
|
||||
int connected = 0;
|
||||
|
||||
// mosquitto logging
|
||||
void _cb_log(__attribute__((unused)) struct mosquitto *mosq, __attribute__((unused)) void *userdata,
|
||||
int level, const char *str) {
|
||||
switch (level) {
|
||||
case MOSQ_LOG_DEBUG:
|
||||
debug(1, str);
|
||||
break;
|
||||
case MOSQ_LOG_INFO:
|
||||
debug(2, str);
|
||||
break;
|
||||
case MOSQ_LOG_NOTICE:
|
||||
debug(3, str);
|
||||
break;
|
||||
case MOSQ_LOG_WARNING:
|
||||
inform(str);
|
||||
break;
|
||||
case MOSQ_LOG_ERR: {
|
||||
die("MQTT: Error: %s\n", str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mosquitto message handler
|
||||
void on_message(__attribute__((unused)) struct mosquitto *mosq,
|
||||
__attribute__((unused)) void *userdata, const struct mosquitto_message *msg) {
|
||||
|
||||
// null-terminate the payload
|
||||
char payload[msg->payloadlen + 1];
|
||||
memcpy(payload, msg->payload, msg->payloadlen);
|
||||
payload[msg->payloadlen] = 0;
|
||||
|
||||
debug(1, "[MQTT]: received Message on topic %s: %s\n", msg->topic, payload);
|
||||
|
||||
// All recognized commands
|
||||
char *commands[] = {"command", "beginff", "beginrew", "mutetoggle", "nextitem",
|
||||
"previtem", "pause", "playpause", "play", "stop",
|
||||
"playresume", "shuffle_songs", "volumedown", "volumeup", NULL};
|
||||
|
||||
int it = 0;
|
||||
|
||||
// send command if it's a valid one
|
||||
while (commands[it] != NULL) {
|
||||
if ((size_t)msg->payloadlen >= strlen(commands[it]) &&
|
||||
strncmp(msg->payload, commands[it], strlen(commands[it])) == 0) {
|
||||
debug(1, "[MQTT]: DACP Command: %s\n", commands[it]);
|
||||
send_simple_dacp_command(commands[it]);
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void on_disconnect(__attribute__((unused)) struct mosquitto *mosq,
|
||||
__attribute__((unused)) void *userdata, __attribute__((unused)) int rc) {
|
||||
connected = 0;
|
||||
debug(1, "[MQTT]: disconnected");
|
||||
}
|
||||
|
||||
void on_connect(struct mosquitto *mosq, __attribute__((unused)) void *userdata,
|
||||
__attribute__((unused)) int rc) {
|
||||
connected = 1;
|
||||
debug(1, "[MQTT]: connected");
|
||||
|
||||
// subscribe if requested
|
||||
if (config.mqtt_enable_remote) {
|
||||
char remotetopic[strlen(config.mqtt_topic) + 8];
|
||||
snprintf(remotetopic, strlen(config.mqtt_topic) + 8, "%s/remote", config.mqtt_topic);
|
||||
mosquitto_subscribe(mosq, NULL, remotetopic, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to publish under a topic and automatically append the main topic
|
||||
void mqtt_publish(char *topic, char *data, uint32_t length) {
|
||||
char fulltopic[strlen(config.mqtt_topic) + strlen(topic) + 3];
|
||||
snprintf(fulltopic, strlen(config.mqtt_topic) + strlen(topic) + 2, "%s/%s", config.mqtt_topic,
|
||||
topic);
|
||||
debug(1, "[MQTT]: publishing under %s", fulltopic);
|
||||
|
||||
int rc;
|
||||
if ((rc = mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0, 0)) !=
|
||||
MOSQ_ERR_SUCCESS) {
|
||||
switch (rc) {
|
||||
case MOSQ_ERR_NO_CONN:
|
||||
debug(1, "[MQTT]: Publish failed: not connected to broker");
|
||||
break;
|
||||
default:
|
||||
debug(1, "[MQTT]: Publish failed: unknown error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handler for incoming metadata
|
||||
void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length) {
|
||||
if (global_mosq == NULL || connected != 1) {
|
||||
debug(3, "[MQTT]: Client not connected, skipping metadata handling");
|
||||
return;
|
||||
}
|
||||
if (config.mqtt_publish_raw) {
|
||||
uint32_t val;
|
||||
char topic[] = "____/____";
|
||||
|
||||
val = htonl(type);
|
||||
memcpy(topic, &val, 4);
|
||||
val = htonl(code);
|
||||
memcpy(topic + 5, &val, 4);
|
||||
mqtt_publish(topic, data, length);
|
||||
}
|
||||
if (config.mqtt_publish_parsed) {
|
||||
if (type == 'core') {
|
||||
switch (code) {
|
||||
case 'asar':
|
||||
mqtt_publish("artist", data, length);
|
||||
break;
|
||||
case 'asal':
|
||||
mqtt_publish("album", data, length);
|
||||
break;
|
||||
case 'minm':
|
||||
mqtt_publish("title", data, length);
|
||||
break;
|
||||
case 'asgn':
|
||||
mqtt_publish("genre", data, length);
|
||||
break;
|
||||
case 'asfm':
|
||||
mqtt_publish("format", data, length);
|
||||
break;
|
||||
}
|
||||
} else if (type == 'ssnc') {
|
||||
switch (code) {
|
||||
case 'asal':
|
||||
mqtt_publish("songalbum", data, length);
|
||||
break;
|
||||
case 'pvol':
|
||||
mqtt_publish("volume", data, length);
|
||||
break;
|
||||
case 'clip':
|
||||
mqtt_publish("client_ip", data, length);
|
||||
break;
|
||||
case 'pbeg':
|
||||
mqtt_publish("play_start", data, length);
|
||||
break;
|
||||
case 'pend':
|
||||
mqtt_publish("play_end", data, length);
|
||||
break;
|
||||
case 'pfls':
|
||||
mqtt_publish("play_flush", data, length);
|
||||
break;
|
||||
case 'prsm':
|
||||
mqtt_publish("play_resume", data, length);
|
||||
break;
|
||||
case 'PICT':
|
||||
if (config.mqtt_publish_parsed) {
|
||||
mqtt_publish("cover", data, length);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int initialise_mqtt() {
|
||||
debug(1, "Initialising MQTT");
|
||||
if (config.mqtt_hostname == NULL) {
|
||||
debug(1, "[MQTT]: Not initialized, as the hostname is not set");
|
||||
return 0;
|
||||
}
|
||||
int keepalive = 60;
|
||||
mosquitto_lib_init();
|
||||
if (!(global_mosq = mosquitto_new(config.service_name, true, NULL))) {
|
||||
die("[MQTT]: FATAL: Could not create mosquitto object! %d\n", global_mosq);
|
||||
}
|
||||
|
||||
if (config.mqtt_cafile != NULL || config.mqtt_capath != NULL || config.mqtt_certfile != NULL ||
|
||||
config.mqtt_keyfile != NULL) {
|
||||
if (mosquitto_tls_set(global_mosq, config.mqtt_cafile, config.mqtt_capath, config.mqtt_certfile,
|
||||
config.mqtt_keyfile, NULL) != MOSQ_ERR_SUCCESS) {
|
||||
die("[MQTT]: TLS Setup failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.mqtt_username != NULL || config.mqtt_password != NULL) {
|
||||
if (mosquitto_username_pw_set(global_mosq, config.mqtt_username, config.mqtt_password) !=
|
||||
MOSQ_ERR_SUCCESS) {
|
||||
die("[MQTT]: Username/Password set failed");
|
||||
}
|
||||
}
|
||||
mosquitto_log_callback_set(global_mosq, _cb_log);
|
||||
|
||||
if (config.mqtt_enable_remote) {
|
||||
mosquitto_message_callback_set(global_mosq, on_message);
|
||||
}
|
||||
|
||||
mosquitto_disconnect_callback_set(global_mosq, on_disconnect);
|
||||
mosquitto_connect_callback_set(global_mosq, on_connect);
|
||||
if (mosquitto_connect(global_mosq, config.mqtt_hostname, config.mqtt_port, keepalive)) {
|
||||
inform("[MQTT]: Could not establish a mqtt connection");
|
||||
}
|
||||
if (mosquitto_loop_start(global_mosq) != MOSQ_ERR_SUCCESS) {
|
||||
inform("[MQTT]: Could start MQTT Main loop");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef MQTT_H
|
||||
#define MQTT_H
|
||||
#include <mosquitto.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int initialise_mqtt();
|
||||
void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length);
|
||||
void mqtt_publish(char *topic, char *data, uint32_t length);
|
||||
void mqtt_setup();
|
||||
void on_connect(struct mosquitto *mosq, void *userdata, int rc);
|
||||
void on_disconnect(struct mosquitto *mosq, void *userdata, int rc);
|
||||
void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg);
|
||||
void _cb_log(struct mosquitto *mosq, void *userdata, int level, const char *str);
|
||||
#endif /* #ifndef MQTT_H */
|
||||
@@ -1,8 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
|
||||
<interface name="org.gnome.ShairportSync">
|
||||
<method name="Quit"/>
|
||||
<property name='Active' type='b' access='read'/>
|
||||
<property name="DisableStandby" type="b" access="readwrite" />
|
||||
<property name="DisableStandbyMode" type="s" access="readwrite" />
|
||||
<property name="LoudnessFilterActive" type="b" access="readwrite" />
|
||||
<property name="LoudnessThreshold" type="d" access="readwrite" />
|
||||
<property name="DriftTolerance" type="d" access="readwrite" />
|
||||
<method name="RemoteCommand">
|
||||
<arg name="command" type="s" direction="in" />
|
||||
</method>
|
||||
|
||||
@@ -7,30 +7,32 @@
|
||||
#include "config.h"
|
||||
#include "definitions.h"
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/havege.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
#include <polarssl/aes.h>
|
||||
#include <polarssl/havege.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
#include <openssl/aes.h>
|
||||
#endif
|
||||
|
||||
#include "alac.h"
|
||||
#include "audio.h"
|
||||
|
||||
#define time_ping_history 8
|
||||
#define time_ping_history 128 // at 1 per three seconds, approximately six minutes of records
|
||||
|
||||
typedef struct time_ping_record {
|
||||
uint64_t local_to_remote_difference;
|
||||
uint64_t dispersion;
|
||||
uint64_t local_time;
|
||||
uint64_t remote_time;
|
||||
int sequence_number;
|
||||
int chosen;
|
||||
} time_ping_record;
|
||||
|
||||
typedef uint16_t seq_t;
|
||||
@@ -38,7 +40,7 @@ typedef uint16_t seq_t;
|
||||
typedef struct audio_buffer_entry { // decoded audio packets
|
||||
int ready;
|
||||
int resend_level;
|
||||
int64_t timestamp;
|
||||
// int64_t timestamp;
|
||||
seq_t sequence_number;
|
||||
uint32_t given_timestamp; // for debugging and checking
|
||||
signed short *data;
|
||||
@@ -66,36 +68,62 @@ typedef struct {
|
||||
} stream_cfg;
|
||||
|
||||
typedef struct {
|
||||
int connection_number; // for debug ID purposes, nothing else...
|
||||
int resend_interval; // this is really just for debugging
|
||||
int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency
|
||||
int64_t latency; // the actual latency used for this play session
|
||||
int64_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero
|
||||
// otherwise
|
||||
int64_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero
|
||||
// otherwise
|
||||
int connection_number; // for debug ID purposes, nothing else...
|
||||
int resend_interval; // this is really just for debugging
|
||||
int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency
|
||||
uint32_t latency; // the actual latency used for this play session
|
||||
uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero
|
||||
// otherwise
|
||||
uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero
|
||||
// otherwise
|
||||
int software_mute_enabled; // if we don't have a real mute that we can use
|
||||
|
||||
int fd;
|
||||
int authorized; // set if a password is required and has been supplied
|
||||
int authorized; // set if a password is required and has been supplied
|
||||
char *auth_nonce; // the session nonce, if needed
|
||||
stream_cfg stream;
|
||||
SOCKADDR remote, local;
|
||||
int stop;
|
||||
int running;
|
||||
pthread_t thread, timer_requester;
|
||||
volatile int stop;
|
||||
volatile int running;
|
||||
volatile uint64_t watchdog_bark_time;
|
||||
volatile int watchdog_barks; // number of times the watchdog has timed out and done something
|
||||
int unfixable_error_reported; // set when an unfixable error command has been executed.
|
||||
|
||||
// pthread_t *ptp;
|
||||
time_t playstart;
|
||||
pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread,
|
||||
player_watchdog_thread;
|
||||
|
||||
// buffers to delete on exit
|
||||
signed short *tbuf;
|
||||
int32_t *sbuf;
|
||||
char *outbuf;
|
||||
|
||||
// for holding the output rate information until printed out at the end of a session
|
||||
double frame_rate;
|
||||
int frame_rate_status;
|
||||
|
||||
// for holding input rate information until printed out at the end of a session
|
||||
|
||||
double input_frame_rate;
|
||||
int input_frame_rate_starting_point_is_valid;
|
||||
|
||||
uint64_t frames_inward_measurement_start_time;
|
||||
uint32_t frames_inward_frames_received_at_measurement_start_time;
|
||||
|
||||
uint64_t frames_inward_measurement_time;
|
||||
uint32_t frames_inward_frames_received_at_measurement_time;
|
||||
|
||||
// other stuff...
|
||||
pthread_t *player_thread;
|
||||
pthread_rwlock_t player_thread_lock; // used to control access by "outsiders"
|
||||
|
||||
abuf_t audio_buffer[BUFFER_FRAMES];
|
||||
int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate;
|
||||
unsigned 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;
|
||||
uint64_t packet_count;
|
||||
uint64_t packet_count_since_flush;
|
||||
int connection_state_to_output;
|
||||
int player_thread_please_stop;
|
||||
uint64_t first_packet_time_to_play;
|
||||
int64_t time_since_play_started; // nanoseconds
|
||||
// stats
|
||||
@@ -105,7 +133,7 @@ typedef struct {
|
||||
int32_t last_seqno_read;
|
||||
// mutexes and condition variables
|
||||
pthread_cond_t flowcontrol;
|
||||
pthread_mutex_t ab_mutex, flush_mutex;
|
||||
pthread_mutex_t ab_mutex, flush_mutex, volume_control_mutex;
|
||||
int fix_volume;
|
||||
uint32_t timestamp_epoch, last_timestamp,
|
||||
maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2
|
||||
@@ -113,19 +141,19 @@ typedef struct {
|
||||
int ab_buffering, ab_synced;
|
||||
int64_t first_packet_timestamp;
|
||||
int flush_requested;
|
||||
int64_t flush_rtp_timestamp;
|
||||
uint32_t flush_rtp_timestamp;
|
||||
uint64_t time_of_last_audio_packet;
|
||||
seq_t ab_read, ab_write;
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
mbedtls_aes_context dctx;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
aes_context dctx;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
AES_KEY aes;
|
||||
#endif
|
||||
|
||||
@@ -161,27 +189,49 @@ typedef struct {
|
||||
uint16_t local_timing_port;
|
||||
|
||||
int64_t latency_delayed_timestamp; // this is for debugging only...
|
||||
int64_t reference_timestamp;
|
||||
uint64_t reference_timestamp_time;
|
||||
|
||||
// this is what connects an rtp timestamp to the remote time
|
||||
|
||||
uint32_t reference_timestamp;
|
||||
uint64_t remote_reference_timestamp_time;
|
||||
|
||||
int packet_stream_established; // true if a stream of packets is flowing, made true by a first
|
||||
// packet, false by a flush
|
||||
|
||||
// used as the initials values for calculating the rate at which the source thinks it's sending
|
||||
// frames
|
||||
uint32_t initial_reference_timestamp;
|
||||
uint64_t initial_reference_time;
|
||||
double remote_frame_rate;
|
||||
|
||||
// the ratio of the following should give us the operating rate, nominally 44,100
|
||||
int64_t reference_to_previous_frame_difference;
|
||||
uint64_t reference_to_previous_time_difference;
|
||||
|
||||
// debug variables
|
||||
int request_sent;
|
||||
|
||||
uint8_t time_ping_count;
|
||||
int 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;
|
||||
pthread_mutex_t watchdog_mutex;
|
||||
|
||||
double local_to_remote_time_gradient; // if no drift, this would be exactly 1.0; likely it's
|
||||
// slightly above or below.
|
||||
int local_to_remote_time_gradient_sample_count; // the number of samples used to calculate the
|
||||
// gradient
|
||||
// add the following to the local time to get the remote time modulo 2^64
|
||||
uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks
|
||||
uint64_t local_to_remote_time_difference_measurement_time; // when the above was calculated
|
||||
|
||||
int last_stuff_request;
|
||||
|
||||
int64_t play_segment_reference_frame;
|
||||
uint64_t play_segment_reference_frame_remote_time;
|
||||
// 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;
|
||||
@@ -197,16 +247,22 @@ typedef struct {
|
||||
// zero
|
||||
uint32_t dacp_active_remote; // key to send to the remote controller
|
||||
void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled.
|
||||
|
||||
int enable_dither; // needed for filling silences before play actually starts
|
||||
int64_t dac_buffer_queue_minimum_length;
|
||||
} rtsp_conn_info;
|
||||
|
||||
uint32_t modulo_32_offset(uint32_t from, uint32_t to);
|
||||
uint64_t modulo_64_offset(uint64_t from, uint64_t to);
|
||||
|
||||
int player_play(rtsp_conn_info *conn);
|
||||
int player_stop(rtsp_conn_info *conn);
|
||||
|
||||
void player_volume(double f, rtsp_conn_info *conn);
|
||||
void player_volume_without_notification(double f, rtsp_conn_info *conn);
|
||||
void player_flush(int64_t timestamp, rtsp_conn_info *conn);
|
||||
void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data,
|
||||
int len, rtsp_conn_info *conn);
|
||||
void player_flush(uint32_t timestamp, rtsp_conn_info *conn);
|
||||
void player_put_packet(seq_t seqno, uint32_t actual_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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Apple RTP protocol handler. This file is part of Shairport.
|
||||
* Copyright (c) James Laird 2013
|
||||
* Copyright (c) Mike Brady 2014 -- 2018
|
||||
* Copyright (c) Mike Brady 2014 -- 2019
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
@@ -45,8 +45,8 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
uint64_t local_to_remote_time_jitters;
|
||||
uint64_t local_to_remote_time_jitters_count;
|
||||
uint64_t local_to_remote_time_jitter;
|
||||
uint64_t local_to_remote_time_jitter_count;
|
||||
|
||||
void rtp_initialise(rtsp_conn_info *conn) {
|
||||
conn->rtp_time_of_last_resend_request_error_fp = 0;
|
||||
@@ -58,21 +58,47 @@ void rtp_initialise(rtsp_conn_info *conn) {
|
||||
}
|
||||
|
||||
void rtp_terminate(rtsp_conn_info *conn) {
|
||||
|
||||
conn->reference_timestamp = 0;
|
||||
// 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_cleanup_handler(void *arg) {
|
||||
debug(3, "Audio Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(1,"shutdown audio socket.");
|
||||
shutdown(conn->audio_socket,SHUT_RDWR);
|
||||
debug(1,"close audio socket.");
|
||||
close(conn->audio_socket);
|
||||
debug(3, "Audio Receiver Cleanup Successful.");
|
||||
uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) {
|
||||
// this is an attempt to compensate for clock drift since the last time ping that was used
|
||||
// so, if we have a non-zero clock drift, we will calculate the drift there would
|
||||
// be from the time of the last time ping
|
||||
uint64_t local_time_now_fp = get_absolute_time_in_fp();
|
||||
uint64_t time_since_last_local_to_remote_time_difference_measurement =
|
||||
local_time_now_fp - conn->local_to_remote_time_difference_measurement_time;
|
||||
|
||||
uint64_t remote_time_since_last_local_to_remote_time_difference_measurement =
|
||||
(uint64_t)(conn->local_to_remote_time_gradient *
|
||||
time_since_last_local_to_remote_time_difference_measurement);
|
||||
|
||||
double drift;
|
||||
if (remote_time_since_last_local_to_remote_time_difference_measurement >=
|
||||
time_since_last_local_to_remote_time_difference_measurement)
|
||||
drift = (1.0 * (remote_time_since_last_local_to_remote_time_difference_measurement -
|
||||
time_since_last_local_to_remote_time_difference_measurement)) /
|
||||
(uint64_t)0x100000000;
|
||||
else
|
||||
drift = -((1.0 * (time_since_last_local_to_remote_time_difference_measurement -
|
||||
remote_time_since_last_local_to_remote_time_difference_measurement)) /
|
||||
(uint64_t)0x100000000);
|
||||
|
||||
// double interval_ms =
|
||||
// 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32);
|
||||
// debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f
|
||||
// milliseconds with drift of %.2f
|
||||
// ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000);
|
||||
// return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000));
|
||||
return conn->local_to_remote_time_difference + (uint64_t)(drift * (uint64_t)0x100000000);
|
||||
}
|
||||
|
||||
void rtp_audio_receiver_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||
debug(3, "Audio Receiver Cleanup Done.");
|
||||
}
|
||||
|
||||
void *rtp_audio_receiver(void *arg) {
|
||||
@@ -92,10 +118,13 @@ void *rtp_audio_receiver(void *arg) {
|
||||
float stat_mean = 0.0;
|
||||
float stat_M2 = 0.0;
|
||||
|
||||
int frame_count = 0;
|
||||
ssize_t nread;
|
||||
while (1) {
|
||||
nread = recv(conn->audio_socket, packet, sizeof(packet), 0);
|
||||
|
||||
frame_count++;
|
||||
|
||||
uint64_t local_time_now_fp = get_absolute_time_in_fp();
|
||||
if (time_of_previous_packet_fp) {
|
||||
float time_interval_us =
|
||||
@@ -108,8 +137,9 @@ 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 "
|
||||
"2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",
|
||||
debug(2,
|
||||
"Packet reception interval stats: mean, standard deviation and max for the last "
|
||||
"2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",
|
||||
stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us);
|
||||
stat_n = 0;
|
||||
stat_mean = 0.0;
|
||||
@@ -134,6 +164,19 @@ void *rtp_audio_receiver(void *arg) {
|
||||
// increment last_seqno and see if it's the same as the incoming seqno
|
||||
|
||||
if (type == 0x60) { // regular audio data
|
||||
|
||||
/*
|
||||
char obf[4096];
|
||||
char *obfp = obf;
|
||||
int obfc;
|
||||
for (obfc=0;obfc<plen;obfc++) {
|
||||
snprintf(obfp, 3, "%02X", pktp[obfc]);
|
||||
obfp+=2;
|
||||
};
|
||||
*obfp=0;
|
||||
debug(1,"Audio Packet Received: \"%s\"",obf);
|
||||
*/
|
||||
|
||||
if (last_seqno == -1)
|
||||
last_seqno = seqno;
|
||||
else {
|
||||
@@ -147,7 +190,9 @@ void *rtp_audio_receiver(void *arg) {
|
||||
}
|
||||
|
||||
uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4));
|
||||
int64_t timestamp = monotonic_timestamp(actual_timestamp, conn);
|
||||
|
||||
// uint32_t ssid = ntohl(*(uint32_t *)(pktp + 8));
|
||||
// debug(1, "Audio packet SSID: %08X,%u", ssid,ssid);
|
||||
|
||||
// if (packet[1]&0x10)
|
||||
// debug(1,"Audio packet Extension bit set.");
|
||||
@@ -159,7 +204,7 @@ void *rtp_audio_receiver(void *arg) {
|
||||
if (plen >= 16) {
|
||||
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
|
||||
(drand48() > config.diagnostic_drop_packet_fraction))
|
||||
player_put_packet(seqno, actual_timestamp, timestamp, pktp, plen, conn);
|
||||
player_put_packet(seqno, actual_timestamp, pktp, plen, conn);
|
||||
else
|
||||
debug(3, "Dropping audio packet %u to simulate a bad connection.", seqno);
|
||||
continue;
|
||||
@@ -188,14 +233,8 @@ void *rtp_audio_receiver(void *arg) {
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void rtp_control_handler_cleanup_handler(void *arg) {
|
||||
debug(3, "Control Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(1,"shutdown control socket.");
|
||||
shutdown(conn->control_socket,SHUT_RDWR);
|
||||
debug(1,"close control socket.");
|
||||
close(conn->control_socket);
|
||||
debug(3, "Control Receiver Cleanup Successful.");
|
||||
void rtp_control_handler_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||
debug(3, "Control Receiver Cleanup Done.");
|
||||
}
|
||||
|
||||
void *rtp_control_receiver(void *arg) {
|
||||
@@ -206,7 +245,7 @@ void *rtp_control_receiver(void *arg) {
|
||||
uint8_t packet[2048], *pktp;
|
||||
// struct timespec tn;
|
||||
uint64_t remote_time_of_sync;
|
||||
int64_t sync_rtp_timestamp;
|
||||
uint32_t sync_rtp_timestamp;
|
||||
ssize_t nread;
|
||||
while (1) {
|
||||
nread = recv(conn->control_socket, packet, sizeof(packet), 0);
|
||||
@@ -246,22 +285,21 @@ void *rtp_control_receiver(void *arg) {
|
||||
// There must be more to it -- there something missing.
|
||||
|
||||
// In addition, it seems that if the value of the short represented by the second
|
||||
// pair of bytes in the packe is 7
|
||||
// pair of bytes in the packet is 7
|
||||
// then an extra time lag is expected to be added, presumably by
|
||||
// the AirPort Express.
|
||||
|
||||
// Best guess is that this delay is 11,025 frames.
|
||||
|
||||
// uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
|
||||
// uint32_t rt = nctohl(&packet[16]); // raw timestamp
|
||||
uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
|
||||
uint32_t rt = nctohl(&packet[16]); // raw timestamp
|
||||
|
||||
// uint32_t fl = nctohs(&packet[2]); //
|
||||
uint32_t fl = nctohs(&packet[2]); //
|
||||
|
||||
// debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and
|
||||
%u,
|
||||
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
|
||||
// debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
|
||||
respectively.",monotonic_timestamp(rt, conn),monotonic_timestamp(rtlt, conn));
|
||||
debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u,
|
||||
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
|
||||
//debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
|
||||
respectively.",monotonic_timestamp(rt, conn),monotonic_timestamp(rtlt, conn));
|
||||
}
|
||||
*/
|
||||
if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged
|
||||
@@ -272,14 +310,14 @@ void *rtp_control_receiver(void *arg) {
|
||||
|
||||
// debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync);
|
||||
|
||||
sync_rtp_timestamp = monotonic_timestamp(nctohl(&packet[16]), conn);
|
||||
int64_t rtp_timestamp_less_latency = monotonic_timestamp(nctohl(&packet[4]), conn);
|
||||
sync_rtp_timestamp = nctohl(&packet[16]);
|
||||
uint32_t rtp_timestamp_less_latency = nctohl(&packet[4]);
|
||||
|
||||
// debug(1,"Sync timestamp is %u.",ntohl(*((uint32_t *)&packet[16])));
|
||||
|
||||
if (config.userSuppliedLatency) {
|
||||
if (config.userSuppliedLatency != conn->latency) {
|
||||
debug(1, "Using the user-supplied latency: %" PRId64 ".",
|
||||
debug(1, "Using the user-supplied latency: %" PRIu32 ".",
|
||||
config.userSuppliedLatency);
|
||||
}
|
||||
conn->latency = config.userSuppliedLatency;
|
||||
@@ -298,9 +336,14 @@ void *rtp_control_receiver(void *arg) {
|
||||
// Sigh, it would be nice to have a published protocol...
|
||||
|
||||
uint16_t flags = nctohs(&packet[2]);
|
||||
int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency;
|
||||
// debug(3, "Latency derived just from the sync packet is %" PRId64 " frames.", la);
|
||||
if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353)) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion >= 371))) {
|
||||
uint32_t la = sync_rtp_timestamp - rtp_timestamp_less_latency; // note, this might
|
||||
// loop around in
|
||||
// modulo. Not sure if
|
||||
// you'll get an error!
|
||||
// debug(3, "Latency derived just from the sync packet is %" PRIu32 " frames.", la);
|
||||
|
||||
if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353)) ||
|
||||
((conn->AirPlayVersion > 0) && (conn->AirPlayVersion >= 371))) {
|
||||
la += config.fixedLatencyOffset;
|
||||
// debug(3, "A fixed latency offset of %d frames has been added, giving a latency of
|
||||
// "
|
||||
@@ -314,51 +357,84 @@ void *rtp_control_receiver(void *arg) {
|
||||
if ((conn->minimum_latency) && (conn->minimum_latency > la))
|
||||
la = conn->minimum_latency;
|
||||
|
||||
const int max_frames = ((3 * BUFFER_FRAMES * 352) / 4) - 11025;
|
||||
const uint32_t max_frames = ((3 * BUFFER_FRAMES * 352) / 4) - 11025;
|
||||
|
||||
if ((la < 0) || (la > max_frames)) {
|
||||
warn("An out-of-range latency request of %" PRId64
|
||||
" frames was ignored. Must be %d frames or less (44,100 frames per second). "
|
||||
"Latency remains at %" PRId64 " frames.",
|
||||
if (la > max_frames) {
|
||||
warn("An out-of-range latency request of %" PRIu32
|
||||
" frames was ignored. Must be %" PRIu32
|
||||
" frames or less (44,100 frames per second). "
|
||||
"Latency remains at %" PRIu32 " frames.",
|
||||
la, max_frames, conn->latency);
|
||||
} else {
|
||||
|
||||
if (la != conn->latency) {
|
||||
conn->latency = la;
|
||||
debug(3, "New latency detected: %" PRId64 ", sync latency: %" PRId64
|
||||
", minimum latency: %" PRId64 ", maximum "
|
||||
"latency: %" PRId64 ", fixed offset: %" PRId64 ".",
|
||||
debug(3,
|
||||
"New latency detected: %" PRIu32 ", sync latency: %" PRIu32
|
||||
", minimum latency: %" PRIu32 ", maximum "
|
||||
"latency: %" PRIu32 ", fixed offset: %" PRIu32 ".",
|
||||
la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency,
|
||||
conn->maximum_latency, config.fixedLatencyOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 1);
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
|
||||
|
||||
if (conn->packet_stream_established) {
|
||||
if (conn->initial_reference_time == 0) {
|
||||
conn->initial_reference_time = remote_time_of_sync;
|
||||
conn->initial_reference_timestamp = sync_rtp_timestamp;
|
||||
} else {
|
||||
uint64_t remote_frame_time_interval =
|
||||
conn->remote_reference_timestamp_time -
|
||||
conn->initial_reference_time; // here, this should never be zero
|
||||
if (remote_frame_time_interval) {
|
||||
conn->remote_frame_rate =
|
||||
(1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) /
|
||||
remote_frame_time_interval; // an IEEE double calculation with a 32-bit
|
||||
// numerator and 64-bit denominator
|
||||
// integers
|
||||
conn->remote_frame_rate = conn->remote_frame_rate *
|
||||
(uint64_t)0x100000000; // this should just change the
|
||||
// [binary] exponent in the IEEE
|
||||
// FP representation; the
|
||||
// mantissa should be unaffected.
|
||||
} else {
|
||||
conn->remote_frame_rate = 0.0; // use as a flag.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is for debugging
|
||||
// uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time;
|
||||
// int64_t old_reference_timestamp = conn->reference_timestamp;
|
||||
uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time;
|
||||
uint32_t old_reference_timestamp = conn->reference_timestamp;
|
||||
// int64_t old_latency_delayed_timestamp = conn->latency_delayed_timestamp;
|
||||
|
||||
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 - local_to_remote_time_difference_now(conn);
|
||||
conn->reference_timestamp = sync_rtp_timestamp;
|
||||
conn->latency_delayed_timestamp = rtp_timestamp_less_latency;
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 3);
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 0);
|
||||
|
||||
conn->reference_to_previous_time_difference =
|
||||
remote_time_of_sync - old_remote_reference_time;
|
||||
if (old_reference_timestamp == 0)
|
||||
conn->reference_to_previous_frame_difference = 0;
|
||||
else
|
||||
conn->reference_to_previous_frame_difference =
|
||||
sync_rtp_timestamp - old_reference_timestamp;
|
||||
|
||||
// int64_t delayed_frame_difference = rtp_timestamp_less_latency -
|
||||
// old_latency_delayed_timestamp;
|
||||
|
||||
// this is for debugging
|
||||
/*
|
||||
uint64_t time_difference = remote_time_of_sync - old_remote_reference_time;
|
||||
int64_t reference_frame_difference = sync_rtp_timestamp - old_reference_timestamp;
|
||||
int64_t delayed_frame_difference = rtp_timestamp_less_latency -
|
||||
old_latency_delayed_timestamp;
|
||||
|
||||
if (old_remote_reference_time)
|
||||
debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %"
|
||||
PRId64 "
|
||||
and %" PRId64 ", giving rates of %f and %f respectively.",
|
||||
(time_difference*1000000)>>32,reference_frame_difference,delayed_frame_difference,(1.0*(reference_frame_difference*10000000))/((time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((time_difference*10000000)>>32));
|
||||
PRId64 " and %" PRId64 ", giving rates _at source!!_ of %f and %f respectively.",
|
||||
(conn->reference_to_previous_time_difference*1000000)>>32,conn->reference_to_previous_frame_difference,delayed_frame_difference,
|
||||
(1.0*(conn->reference_to_previous_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32));
|
||||
else
|
||||
debug(1,"First sync received");
|
||||
*/
|
||||
@@ -381,14 +457,13 @@ void *rtp_control_receiver(void *arg) {
|
||||
debug(3, "Control Receiver -- Retransmitted Audio Data Packet %u received.", seqno);
|
||||
|
||||
uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4));
|
||||
int64_t timestamp = monotonic_timestamp(actual_timestamp, conn);
|
||||
|
||||
pktp += 12;
|
||||
plen -= 12;
|
||||
|
||||
// check if packet contains enough content to be reasonable
|
||||
if (plen >= 16) {
|
||||
player_put_packet(seqno, actual_timestamp, timestamp, pktp, plen, conn);
|
||||
player_put_packet(seqno, actual_timestamp, pktp, plen, conn);
|
||||
continue;
|
||||
} else {
|
||||
debug(3, "Too-short retransmitted audio packet received in control port, ignored.");
|
||||
@@ -409,7 +484,13 @@ void *rtp_control_receiver(void *arg) {
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void rtp_timing_sender_cleanup_handler(void *arg) {
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(3, "Connection %d: Timing Sender Cleanup.", conn->connection_number);
|
||||
}
|
||||
|
||||
void *rtp_timing_sender(void *arg) {
|
||||
pthread_cleanup_push(rtp_timing_sender_cleanup_handler, arg);
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
struct timing_request {
|
||||
char leader;
|
||||
@@ -469,19 +550,21 @@ void *rtp_timing_sender(void *arg) {
|
||||
usleep(3000000);
|
||||
}
|
||||
debug(3, "rtp_timing_sender thread interrupted. This should never happen.");
|
||||
pthread_cleanup_pop(0); // don't execute anything here.
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void rtp_timing_receiver_cleanup_handler(void *arg) {
|
||||
debug(3, "Timing Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(3, "Cancel Timing Requester.");
|
||||
pthread_cancel(conn->timer_requester);
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
debug(3, "Join Timing Requester.");
|
||||
pthread_join(conn->timer_requester, NULL);
|
||||
debug(1,"shutdown timing socket.");
|
||||
shutdown(conn->timing_socket,SHUT_RDWR);
|
||||
debug(1,"close timing socket.");
|
||||
close(conn->timing_socket);
|
||||
debug(3, "Timing Receiver Cleanup Successful.");
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
}
|
||||
|
||||
void *rtp_timing_receiver(void *arg) {
|
||||
@@ -493,14 +576,21 @@ void *rtp_timing_receiver(void *arg) {
|
||||
pthread_create(&conn->timer_requester, NULL, &rtp_timing_sender, arg);
|
||||
// struct timespec att;
|
||||
uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time;
|
||||
local_to_remote_time_jitters = 0;
|
||||
local_to_remote_time_jitters_count = 0;
|
||||
local_to_remote_time_jitter = 0;
|
||||
local_to_remote_time_jitter_count = 0;
|
||||
// uint64_t first_remote_time = 0;
|
||||
uint64_t first_local_time = 0;
|
||||
// uint64_t first_local_time = 0;
|
||||
|
||||
uint64_t first_local_to_remote_time_difference = 0;
|
||||
// uint64_t first_local_to_remote_time_difference_time;
|
||||
// uint64_t l2rtd = 0;
|
||||
int sequence_number = 0;
|
||||
|
||||
// for getting mean and sd of return times
|
||||
int32_t stat_n = 0;
|
||||
double stat_mean = 0.0;
|
||||
double stat_M2 = 0.0;
|
||||
|
||||
while (1) {
|
||||
nread = recv(conn->timing_socket, packet, sizeof(packet), 0);
|
||||
|
||||
@@ -522,7 +612,7 @@ void *rtp_timing_receiver(void *arg) {
|
||||
obfp+=2;
|
||||
};
|
||||
*obfp=0;
|
||||
//debug(1,"Timing Packet Received: \"%s\"",obf);
|
||||
debug(1,"Timing Packet Received: \"%s\"",obf);
|
||||
*/
|
||||
|
||||
// arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000;
|
||||
@@ -548,23 +638,43 @@ void *rtp_timing_receiver(void *arg) {
|
||||
distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32;
|
||||
distant_transmit_time += nctohl(&packet[28]);
|
||||
|
||||
// processing_time = distant_transmit_time - distant_receive_time;
|
||||
uint64_t remote_processing_time = 0;
|
||||
|
||||
// debug(1,"Return trip time: %lluuS, remote processing time:
|
||||
// %lluuS.",(return_time*1000000)>>32,(processing_time*1000000)>>32);
|
||||
if (distant_transmit_time >= distant_receive_time)
|
||||
remote_processing_time = distant_transmit_time - distant_receive_time;
|
||||
else {
|
||||
debug(1, "Yikes: distant_transmit_time is before distant_receive_time; remote "
|
||||
"processing time set to zero.");
|
||||
}
|
||||
// debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 "
|
||||
// uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32);
|
||||
|
||||
uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2;
|
||||
|
||||
unsigned int cc;
|
||||
// remove the remote processing time from the record of the return time, as long at the
|
||||
// processing time looks sensible.
|
||||
|
||||
if (remote_processing_time < return_time)
|
||||
return_time -= remote_processing_time;
|
||||
else
|
||||
debug(1, "Remote processing time greater than return time -- ignored.");
|
||||
|
||||
int cc;
|
||||
for (cc = time_ping_history - 1; cc > 0; cc--) {
|
||||
conn->time_pings[cc] = conn->time_pings[cc - 1];
|
||||
// if ((conn->time_ping_count) && (conn->time_ping_count < 10))
|
||||
// conn->time_pings[cc].dispersion =
|
||||
// conn->time_pings[cc].dispersion * pow(2.14,
|
||||
// 1.0/conn->time_ping_count);
|
||||
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
|
||||
// these are used for doing a least squares calculation to get the drift
|
||||
conn->time_pings[0].local_time = arrival_time;
|
||||
conn->time_pings[0].remote_time = distant_transmit_time;
|
||||
conn->time_pings[0].sequence_number = sequence_number++;
|
||||
conn->time_pings[0].chosen = 0;
|
||||
|
||||
conn->time_pings[0].local_to_remote_difference =
|
||||
local_time_by_remote_clock - arrival_time;
|
||||
@@ -572,127 +682,150 @@ void *rtp_timing_receiver(void *arg) {
|
||||
if (conn->time_ping_count < time_ping_history)
|
||||
conn->time_ping_count++;
|
||||
|
||||
uint64_t local_time_chosen = arrival_time;
|
||||
// here, calculate the mean and standard deviation of the return times
|
||||
|
||||
// mean and variance calculations from "online_variance" algorithm at
|
||||
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
|
||||
|
||||
double rtfus = 1.0 * ((return_time * 1000000) >> 32);
|
||||
stat_n += 1;
|
||||
double stat_delta = rtfus - stat_mean;
|
||||
stat_mean += stat_delta / stat_n;
|
||||
stat_M2 += stat_delta * (rtfus - stat_mean);
|
||||
// debug(1, "Timing packet return time stats: current, mean and standard deviation over
|
||||
// %d packets: %.1f, %.1f, %.1f (microseconds).",
|
||||
// stat_n,rtfus,stat_mean, sqrtf(stat_M2 / (stat_n - 1)));
|
||||
|
||||
// here, pick the record with the least dispersion, and record that it's been chosen
|
||||
|
||||
// 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 = conn->time_pings[0].local_to_remote_difference;
|
||||
uint64_t lt = conn->time_pings[0].local_time;
|
||||
uint64_t tld = conn->time_pings[0].dispersion;
|
||||
// chosen = 0;
|
||||
int chosen = 0;
|
||||
for (cc = 1; cc < conn->time_ping_count; cc++)
|
||||
if (conn->time_pings[cc].dispersion < tld) {
|
||||
chosen = cc;
|
||||
l2rtd = conn->time_pings[cc].local_to_remote_difference;
|
||||
// chosen = cc;
|
||||
lt = conn->time_pings[cc].local_time;
|
||||
tld = conn->time_pings[cc].dispersion;
|
||||
local_time_chosen = conn->time_pings[cc].local_time;
|
||||
// local_time_chosen = conn->time_pings[cc].local_time;
|
||||
// remote_time_chosen = conn->time_pings[cc].remote_time;
|
||||
}
|
||||
// int64_t ji;
|
||||
// debug(1,"Record %d has the lowest dispersion with %0.2f us
|
||||
// dispersion.",chosen,1.0*((tld * 1000000) >> 32));
|
||||
conn->time_pings[chosen].chosen = 1; // record the fact that it has been used for timing
|
||||
|
||||
/*
|
||||
// calculate the jitter -- the absolute time between the current
|
||||
local_to_remote_time_difference and the new one and add it to the total jitter count
|
||||
int64_t ji;
|
||||
int64_t ltd =0; // local time difference for the jitter
|
||||
|
||||
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 - conn->local_to_remote_time_difference;
|
||||
// ji = l2rtd - conn->local_to_remote_time_difference;
|
||||
local_to_remote_time_jitter =
|
||||
local_to_remote_time_jitter + l2rtd - conn->local_to_remote_time_difference;
|
||||
ji = l2rtd - conn->local_to_remote_time_difference; // this is the difference
|
||||
between the present local-to-remote-time-difference and the new one, i.e. the jitter
|
||||
step
|
||||
} else {
|
||||
local_to_remote_time_jitters =
|
||||
local_to_remote_time_jitters + conn->local_to_remote_time_difference - l2rtd;
|
||||
// ji = -(conn->local_to_remote_time_difference - l2rtd);
|
||||
local_to_remote_time_jitter =
|
||||
local_to_remote_time_jitter + conn->local_to_remote_time_difference - l2rtd;
|
||||
ji = -(conn->local_to_remote_time_difference - l2rtd);
|
||||
}
|
||||
local_to_remote_time_jitters_count += 1;
|
||||
local_to_remote_time_jitter_count += 1;
|
||||
}
|
||||
// uncomment below to print jitter between client's clock and oour clock
|
||||
// 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);
|
||||
if (conn->local_to_remote_time_difference_measurement_time < lt)
|
||||
ltd = lt-conn->local_to_remote_time_difference_measurement_time;
|
||||
else
|
||||
ltd = -(conn->local_to_remote_time_difference_measurement_time-lt);
|
||||
|
||||
if (ltd) {
|
||||
debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji *
|
||||
(int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32);
|
||||
debug(1,"Source clock to local clock drift: %.2f ppm.",((1.0*ji)/ltd)*1000000.0);
|
||||
}
|
||||
// uncomment below to print jitter between client's clock and our clock
|
||||
|
||||
if (ji) {
|
||||
int64_t rtus = (tld*1000000)>>32;
|
||||
debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an
|
||||
adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32);
|
||||
}
|
||||
*/
|
||||
conn->local_to_remote_time_difference =
|
||||
l2rtd; // make this the new local-to-remote-time-difference
|
||||
conn->local_to_remote_time_difference_measurement_time = lt; // done at this time.
|
||||
|
||||
conn->local_to_remote_time_difference = l2rtd;
|
||||
if (first_local_to_remote_time_difference == 0) {
|
||||
first_local_to_remote_time_difference = conn->local_to_remote_time_difference;
|
||||
// first_local_to_remote_time_difference_time = get_absolute_time_in_fp();
|
||||
}
|
||||
|
||||
// int64_t clock_drift;
|
||||
// int64_t 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;
|
||||
// clock_drift = 0;
|
||||
// here, let's try to use the timing pings that were selected because of their short
|
||||
// return times to
|
||||
// estimate a figure for drift between the local clock (x) and the remote clock (y)
|
||||
|
||||
// if we plug in a local interval, we will get back what that is in remote time
|
||||
|
||||
// calculate the line of best fit for relating the local time and the remote time
|
||||
// we will calculate the slope, which is the drift
|
||||
// see https://www.varsitytutors.com/hotmath/hotmath_help/topics/line-of-best-fit
|
||||
|
||||
uint64_t y_bar = 0; // remote timestamp average
|
||||
uint64_t x_bar = 0; // local timestamp average
|
||||
int sample_count = 0;
|
||||
|
||||
// approximate time in seconds to let the system settle down
|
||||
const int settling_time = 60;
|
||||
// number of points to have for calculating a valid drift
|
||||
const int sample_point_minimum = 8;
|
||||
for (cc = 0; cc < conn->time_ping_count; cc++)
|
||||
if ((conn->time_pings[cc].chosen) &&
|
||||
(conn->time_pings[cc].sequence_number >
|
||||
(settling_time / 3))) { // wait for a approximate settling time
|
||||
y_bar += (conn->time_pings[cc].remote_time >>
|
||||
12); // precision is down to 1/4th of a microsecond
|
||||
x_bar += (conn->time_pings[cc].local_time >> 12);
|
||||
sample_count++;
|
||||
}
|
||||
if (sample_count > sample_point_minimum) {
|
||||
y_bar = y_bar / sample_count;
|
||||
x_bar = x_bar / sample_count;
|
||||
|
||||
int64_t xid, yid;
|
||||
int64_t mtl, mbl;
|
||||
mtl = 0;
|
||||
mbl = 0;
|
||||
for (cc = 0; cc < conn->time_ping_count; cc++)
|
||||
if ((conn->time_pings[cc].chosen) &&
|
||||
(conn->time_pings[cc].sequence_number > (settling_time / 3))) {
|
||||
|
||||
uint64_t slt = conn->time_pings[cc].local_time >> 12;
|
||||
if (slt > x_bar)
|
||||
xid = slt - x_bar;
|
||||
else
|
||||
xid = -(x_bar - slt);
|
||||
|
||||
uint64_t srt = conn->time_pings[cc].remote_time >> 12;
|
||||
if (srt > y_bar)
|
||||
yid = srt - y_bar;
|
||||
else
|
||||
yid = -(y_bar - srt);
|
||||
|
||||
mtl = mtl + xid * yid;
|
||||
mbl = mbl + xid * xid;
|
||||
}
|
||||
conn->local_to_remote_time_gradient_sample_count = sample_count;
|
||||
conn->local_to_remote_time_gradient = (1.0 * mtl) / mbl;
|
||||
} else {
|
||||
// uint64_t local_time_change = local_time_chosen - first_local_time;
|
||||
// uint64_t remote_time_change = remote_time_chosen - first_remote_time;
|
||||
|
||||
/*
|
||||
if (remote_time_change >= local_time_change)
|
||||
clock_drift = remote_time_change - local_time_change;
|
||||
else
|
||||
clock_drift = -(local_time_change - remote_time_change);
|
||||
*/
|
||||
/*
|
||||
if (clock_drift >= 0)
|
||||
clock_drift_in_usec = (clock_drift * 1000000) >> 32;
|
||||
else
|
||||
clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32);
|
||||
*/
|
||||
|
||||
// clock_drift_ppm = (1.0 * clock_drift_in_usec) / (local_time_change >> 32);
|
||||
conn->local_to_remote_time_gradient = 1.0;
|
||||
}
|
||||
|
||||
int64_t source_drift_usec;
|
||||
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 >= 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 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
|
||||
// debug(1,"%llu frames since play started, %llu usec calculated, %llu usec
|
||||
// actual",frame_difference, (frame_time_difference_calculated*1000000)>>32,
|
||||
// (frame_time_difference_actual*1000000)>>32);
|
||||
if (frame_time_difference_calculated >=
|
||||
frame_time_difference_actual) // i.e. if the time it should have taken to send the
|
||||
// packets is greater than the actual time difference
|
||||
// measured on the source clock
|
||||
// then the source DAC's clock is running fast relative to the source system clock
|
||||
source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual;
|
||||
else
|
||||
// otherwise the source DAC's clock is running slow relative to the source system
|
||||
// clock
|
||||
source_drift_usec =
|
||||
-(frame_time_difference_actual - frame_time_difference_calculated);
|
||||
} else
|
||||
source_drift_usec = 0;
|
||||
source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds
|
||||
|
||||
// long current_delay = 0;
|
||||
// if (config.output->delay) {
|
||||
// config.output->delay(¤t_delay);
|
||||
//}
|
||||
// Useful for troubleshooting:
|
||||
// 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);
|
||||
// debug(1,"local to remote time gradient is %12.2f ppm, based on %d
|
||||
// samples.",conn->local_to_remote_time_gradient*1000000,sample_count);
|
||||
} else {
|
||||
debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.",
|
||||
(return_time * 1000000) >> 32);
|
||||
@@ -717,14 +850,28 @@ void *rtp_timing_receiver(void *arg) {
|
||||
static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id,
|
||||
int *sock) {
|
||||
// look for a port in the range, if any was specified.
|
||||
uint16_t desired_port = config.udp_port_base;
|
||||
int ret = 0;
|
||||
|
||||
int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (local_socket == -1)
|
||||
die("Could not allocate a socket.");
|
||||
|
||||
/*
|
||||
int val = 1;
|
||||
ret = setsockopt(local_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
|
||||
if (ret < 0) {
|
||||
char errorstring[1024];
|
||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||
debug(1, "Error %d: \"%s\". Couldn't set SO_REUSEADDR");
|
||||
}
|
||||
*/
|
||||
|
||||
SOCKADDR myaddr;
|
||||
int tryCount = 0;
|
||||
uint16_t desired_port;
|
||||
do {
|
||||
tryCount++;
|
||||
desired_port = nextFreeUDPPort();
|
||||
memset(&myaddr, 0, sizeof(myaddr));
|
||||
if (ip_family == AF_INET) {
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr;
|
||||
@@ -745,14 +892,20 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s
|
||||
#endif
|
||||
|
||||
} while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) &&
|
||||
(++desired_port < config.udp_port_base + config.udp_port_range));
|
||||
(tryCount < config.udp_port_range));
|
||||
|
||||
// debug(1,"UDP port chosen: %d.",desired_port);
|
||||
|
||||
if (ret < 0) {
|
||||
close(local_socket);
|
||||
die("error: could not bind a UDP port! Check the udp_port_range is large enough (>= 10) or "
|
||||
"check for restrictive firewall settings or a bad router!");
|
||||
char errorstring[1024];
|
||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||
die("error %d: \"%s\". Could not bind a UDP port! Check the udp_port_range is large enough -- "
|
||||
"it must be "
|
||||
"at least 3, and 10 or more is suggested -- or "
|
||||
"check for restrictive firewall settings or a bad router! UDP base is %u, range is %u and "
|
||||
"current suggestion is %u.",
|
||||
errno, errorstring, config.udp_port_base, config.udp_port_range, desired_port);
|
||||
}
|
||||
|
||||
uint16_t sport;
|
||||
@@ -897,26 +1050,144 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport
|
||||
}
|
||||
}
|
||||
|
||||
void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
|
||||
void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time,
|
||||
uint64_t *remote_timestamp_time, rtsp_conn_info *conn) {
|
||||
// types okay
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 1);
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
|
||||
*timestamp = conn->reference_timestamp;
|
||||
*timestamp_time = conn->reference_timestamp_time;
|
||||
*remote_timestamp_time = conn->remote_reference_timestamp_time;
|
||||
*timestamp_time =
|
||||
conn->remote_reference_timestamp_time - local_to_remote_time_difference_now(conn);
|
||||
// if ((*timestamp == 0) && (*timestamp_time == 0)) {
|
||||
// debug(1,"Reference timestamp is invalid.");
|
||||
//}
|
||||
*remote_timestamp_time = conn->remote_reference_timestamp_time;
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 3);
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 0);
|
||||
}
|
||||
|
||||
void clear_reference_timestamp(rtsp_conn_info *conn) {
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 1);
|
||||
conn->reference_timestamp = 0;
|
||||
conn->reference_timestamp_time = 0;
|
||||
conn->remote_reference_timestamp_time = 0;
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 3);
|
||||
}
|
||||
|
||||
int have_timestamp_timing_information(rtsp_conn_info *conn) {
|
||||
if (conn->reference_timestamp == 0)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
// set this to zero to use the rates supplied by the sources, which might not always be completely
|
||||
// right...
|
||||
const int use_nominal_rate = 0; // specify whether to use the nominal input rate, usually 44100 fps
|
||||
|
||||
int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn) {
|
||||
int result = 1;
|
||||
uint32_t fs = conn->input_rate;
|
||||
*frames = fs;
|
||||
uint64_t one_fp = (uint64_t)(0x100000000); // one second in fp form
|
||||
*time = one_fp;
|
||||
if ((conn->packet_stream_established) && (conn->initial_reference_time) &&
|
||||
(conn->initial_reference_timestamp)) {
|
||||
// uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp;
|
||||
uint32_t local_frames =
|
||||
modulo_32_offset(conn->initial_reference_timestamp, conn->reference_timestamp);
|
||||
uint64_t local_time = conn->remote_reference_timestamp_time - conn->initial_reference_time;
|
||||
if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) {
|
||||
result = 1;
|
||||
} else {
|
||||
double calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp;
|
||||
if (((calculated_frame_rate / conn->input_rate) > 1.002) ||
|
||||
((calculated_frame_rate / conn->input_rate) < 0.998)) {
|
||||
debug(3, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate);
|
||||
result = 1;
|
||||
} else {
|
||||
*frames = local_frames;
|
||||
*time = local_time;
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// the timestamp is a timestamp calculated at the input rate
|
||||
// the reference timestamps are denominated in terms of the input rate
|
||||
|
||||
int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn) {
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
|
||||
int result = 0;
|
||||
uint64_t time_difference;
|
||||
uint32_t frame_difference;
|
||||
result = sanitised_source_rate_information(&frame_difference, &time_difference, conn);
|
||||
|
||||
uint64_t timestamp_interval_time;
|
||||
uint64_t remote_time_of_timestamp;
|
||||
uint32_t timestamp_interval = modulo_32_offset(conn->reference_timestamp, timestamp);
|
||||
if (timestamp_interval <=
|
||||
conn->input_rate * 3600) { // i.e. timestamp was really after the reference timestamp
|
||||
timestamp_interval_time = (timestamp_interval * time_difference) /
|
||||
frame_difference; // this is the nominal time, based on the
|
||||
// fps specified between current and
|
||||
// previous sync frame.
|
||||
remote_time_of_timestamp = conn->remote_reference_timestamp_time +
|
||||
timestamp_interval_time; // based on the reference timestamp time
|
||||
// plus the time interval calculated based
|
||||
// on the specified fps.
|
||||
} else { // i.e. timestamp was actually before the reference timestamp
|
||||
timestamp_interval =
|
||||
modulo_32_offset(timestamp, conn->reference_timestamp); // fix the calculation
|
||||
timestamp_interval_time = (timestamp_interval * time_difference) /
|
||||
frame_difference; // this is the nominal time, based on the
|
||||
// fps specified between current and
|
||||
// previous sync frame.
|
||||
remote_time_of_timestamp = conn->remote_reference_timestamp_time -
|
||||
timestamp_interval_time; // based on the reference timestamp time
|
||||
// plus the time interval calculated based
|
||||
// on the specified fps.
|
||||
}
|
||||
*time = remote_time_of_timestamp - local_to_remote_time_difference_now(conn);
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) {
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
|
||||
int result = 0;
|
||||
|
||||
uint64_t time_difference;
|
||||
uint32_t frame_difference;
|
||||
result = sanitised_source_rate_information(&frame_difference, &time_difference, conn);
|
||||
|
||||
// first, get from [local] time to remote time.
|
||||
uint64_t remote_time = time + local_to_remote_time_difference_now(conn);
|
||||
// next, get the remote time interval from the remote_time to the reference time
|
||||
uint64_t time_interval;
|
||||
|
||||
// here, we calculate the time interval, in terms of remote time
|
||||
uint64_t offset = modulo_64_offset(conn->remote_reference_timestamp_time, remote_time);
|
||||
int reference_time_was_earlier = (offset <= (uint64_t)0x100000000 * 3600);
|
||||
if (reference_time_was_earlier) // if we haven't had a reference within the last hour, it'll be
|
||||
// taken as afterwards
|
||||
time_interval = remote_time - conn->remote_reference_timestamp_time;
|
||||
else
|
||||
time_interval = conn->remote_reference_timestamp_time - remote_time;
|
||||
|
||||
// now, convert the remote time interval into frames using the frame rate we have observed or
|
||||
// which has been nominated
|
||||
uint32_t frame_interval = (time_interval * frame_difference) / time_difference;
|
||||
if (reference_time_was_earlier) {
|
||||
// debug(1,"Frame interval is %" PRId64 " frames.",frame_interval);
|
||||
*frame = (conn->reference_timestamp + frame_interval);
|
||||
} else {
|
||||
// debug(1,"Frame interval is %" PRId64 " frames.",-frame_interval);
|
||||
*frame = (conn->reference_timestamp - frame_interval);
|
||||
}
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
|
||||
if (conn->rtp_running) {
|
||||
// if (!request_sent) {
|
||||
@@ -957,8 +1228,9 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
|
||||
(struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) {
|
||||
char em[1024];
|
||||
strerror_r(errno, em, sizeof(em));
|
||||
debug(1, "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a "
|
||||
"second.",
|
||||
debug(1,
|
||||
"Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a "
|
||||
"second.",
|
||||
errno, em);
|
||||
conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
|
||||
} else {
|
||||
|
||||
@@ -17,8 +17,17 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t controlport, uint16_t
|
||||
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,
|
||||
void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time,
|
||||
uint64_t *remote_timestamp_time, rtsp_conn_info *conn);
|
||||
void clear_reference_timestamp(rtsp_conn_info *conn);
|
||||
|
||||
int have_timestamp_timing_information(rtsp_conn_info *conn);
|
||||
|
||||
int get_frame_play_time(int64_t timestamp, int sample_ratio, uint64_t *time_to_play);
|
||||
|
||||
int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn);
|
||||
int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn);
|
||||
|
||||
int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn);
|
||||
|
||||
#endif // _RTP_H
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
#include "player.h"
|
||||
|
||||
rtsp_conn_info *playing_conn;
|
||||
rtsp_conn_info **conns;
|
||||
|
||||
void rtsp_listen_loop(void);
|
||||
// void rtsp_shutdown_stream(void);
|
||||
void rtsp_request_shutdown_stream(void);
|
||||
|
||||
// initialise the metadata stuff
|
||||
void cancel_all_RTSP_threads(void);
|
||||
|
||||
// initialise and completely delete the metadata stuff
|
||||
|
||||
void metadata_init(void);
|
||||
void metadata_stop(void);
|
||||
|
||||
// sends metadata out to the metadata pipe, if enabled.
|
||||
// It is sent with the type 'ssnc' the given code, data and length
|
||||
|
||||
Executable
+169
@@ -0,0 +1,169 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# shairport-sync-config, Copyright 2019 Mike Brady
|
||||
#
|
||||
# Based, with thanks, on:
|
||||
# avahi-daemon-config, Copyright 2011 Yaakov Selkowitz
|
||||
#
|
||||
# This file is part of the Cygwin port of shairport-sync.
|
||||
|
||||
# ======================================================================
|
||||
# Initialization
|
||||
# ======================================================================
|
||||
PROGNAME=$(basename $0)
|
||||
_tdir=$(dirname $0)
|
||||
PROGDIR=$(cd $_tdir && pwd)
|
||||
|
||||
CSIH_SCRIPT=/usr/share/csih/cygwin-service-installation-helper.sh
|
||||
|
||||
# Subdirectory where the new package is being installed
|
||||
PREFIX=/usr/local/bin
|
||||
|
||||
# Directory where the config files are stored
|
||||
SYSCONFDIR=/etc
|
||||
LOGDIR=/var/log
|
||||
SOCKDIR=/var/run
|
||||
|
||||
source ${CSIH_SCRIPT}
|
||||
|
||||
# ======================================================================
|
||||
# Routine: install_service
|
||||
# Install shairport-sync as a service
|
||||
# ======================================================================
|
||||
install_service() {
|
||||
|
||||
if csih_is_nt
|
||||
then
|
||||
|
||||
# Check if shairport-sync is installed and remove on user request.
|
||||
if cygrunsrv -Q shairport-sync > /dev/null 2>&1
|
||||
then
|
||||
csih_warning "The shairport-sync service is already installed."
|
||||
echo
|
||||
if csih_request "Do you want to reinstall it with different args?"
|
||||
then
|
||||
cygrunsrv -E shairport-sync
|
||||
cygrunsrv -R shairport-sync
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install shairport-sync service if it is not already installed
|
||||
if ! cygrunsrv -Q shairport-sync > /dev/null 2>&1
|
||||
then
|
||||
echo
|
||||
csih_warning "The following function requires administrator privileges!"
|
||||
if csih_request "Do you want to install shairport-sync as a service?"
|
||||
then
|
||||
if cygrunsrv -I shairport-sync --disp "CYGWIN Shairport Sync" --desc "AirPlay Audio. Use your computer's speakers or headphones to listen to audio from your iOS devices or other AirPlay sources on your network." --path $PREFIX/shairport-sync --dep avahi-daemon --dep messagebus
|
||||
then
|
||||
echo
|
||||
csih_inform "The shairport-sync service has been installed under the LocalSystem"
|
||||
csih_inform "account (also known as SYSTEM). To start the service now, call"
|
||||
csih_inform "\`net start shairport-sync' or \`cygrunsrv -S shairport-sync'. Otherwise,"
|
||||
csih_inform "it will start automatically after the next reboot."
|
||||
echo
|
||||
csih_inform "Check ${SYSCONFDIR}/shairport-sync.conf first, if it suits your needs."
|
||||
fi
|
||||
fi # user allowed us to install shairport-sync
|
||||
fi # shairport-sync already installed
|
||||
fi # csih_is_nt
|
||||
} # --- End of install_service --- #
|
||||
|
||||
|
||||
# ======================================================================
|
||||
# Main Entry Point
|
||||
# ======================================================================
|
||||
|
||||
|
||||
# Check how the script has been started. If
|
||||
# (1) it has been started by giving the full path and
|
||||
# that path is /etc/postinstall, OR
|
||||
# (2) Otherwise, if the environment variable
|
||||
# CONFIG_AUTO_ANSWER_NO is set
|
||||
# then set auto_answer to "no". This allows automatic
|
||||
# creation of the config files in /etc w/o overwriting
|
||||
# them if they already exist. In both cases, color
|
||||
# escape sequences are suppressed, so as to prevent
|
||||
# cluttering setup's logfiles.
|
||||
if [ "$PROGDIR" = "/etc/postinstall" ]
|
||||
then
|
||||
csih_auto_answer="no"
|
||||
csih_disable_color
|
||||
fi
|
||||
if [ -n "${CONFIG_AUTO_ANSWER_NO}" ]
|
||||
then
|
||||
csih_auto_answer="no"
|
||||
csih_disable_color
|
||||
fi
|
||||
|
||||
|
||||
# ======================================================================
|
||||
# Parse options
|
||||
# ======================================================================
|
||||
while :
|
||||
do
|
||||
case $# in
|
||||
0)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
|
||||
option=$1
|
||||
shift
|
||||
|
||||
case "$option" in
|
||||
-d | --debug )
|
||||
set -x
|
||||
csih_trace_on
|
||||
;;
|
||||
|
||||
-y | --yes )
|
||||
csih_auto_answer=yes
|
||||
;;
|
||||
|
||||
-n | --no )
|
||||
csih_auto_answer=no
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "usage: ${PROGNAME} [OPTION]..."
|
||||
echo
|
||||
echo "This script creates a basic shairport-sync configuration."
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " --debug -d Enable shell's debug output."
|
||||
echo " --yes -y Answer all questions with \"yes\" automatically."
|
||||
echo " --no -n Answer all questions with \"no\" automatically."
|
||||
echo
|
||||
exit 1
|
||||
;;
|
||||
|
||||
esac
|
||||
done
|
||||
|
||||
# ======================================================================
|
||||
# Action!
|
||||
# ======================================================================
|
||||
|
||||
# Check for ${SYSCONFDIR} directory
|
||||
csih_make_dir "${SYSCONFDIR}" "Cannot create global configuration files."
|
||||
chmod 775 "${SYSCONFDIR}"
|
||||
setfacl -m u:system:rwx "${SYSCONFDIR}"
|
||||
|
||||
# Check for ${LOGDIR} directory
|
||||
csih_make_dir "${LOGDIR}" "Syslogging using shairport-sync will not work."
|
||||
chmod 775 "${LOGDIR}"
|
||||
setfacl -m u:system:rwx "${LOGDIR}"
|
||||
|
||||
# Check for ${SOCKDIR} directory
|
||||
csih_make_dir "${SOCKDIR}" "SOCKET files of running processes will not be created."
|
||||
chmod 775 "${SOCKDIR}"
|
||||
setfacl -m u:system:rwx "${SOCKDIR}"
|
||||
|
||||
# maybe: csih_auto_answer=no will skip,
|
||||
# interactive user will get a chance to override
|
||||
install_service
|
||||
|
||||
|
||||
echo
|
||||
echo "Configuration finished. Have fun!"
|
||||
@@ -0,0 +1,18 @@
|
||||
<!-- initial version, based on /etc/dbus-1/system.d/avahi-dbus.conf, with thanks -->
|
||||
<!DOCTYPE busconfig PUBLIC
|
||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
|
||||
<!-- Only user SYSTEM can own the Shairport Sync service -->
|
||||
<policy user="SYSTEM">
|
||||
<allow own="org.gnome.ShairportSync"/>
|
||||
</policy>
|
||||
|
||||
<!-- Allow anyone to invoke methods on Shairport Sync server -->
|
||||
<policy context="default">
|
||||
<allow send_destination="org.gnome.ShairportSync"/>
|
||||
<allow receive_sender="org.gnome.ShairportSync"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!-- initial version, based on /etc/dbus-1/system.d/avahi-dbus.conf, with thanks -->
|
||||
<!DOCTYPE busconfig PUBLIC
|
||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
|
||||
<!-- Only user "SYSTEM" can own the Shairport Sync MPRIS service -->
|
||||
<policy user="SYSTEM">
|
||||
<allow own="org.mpris.MediaPlayer2.ShairportSync"/>
|
||||
</policy>
|
||||
|
||||
<!-- Allow anyone to invoke methods on Shairport Sync MPRIS server -->
|
||||
<policy context="default">
|
||||
<allow send_destination="org.mpris.MediaPlayer2.ShairportSync"/>
|
||||
<allow receive_sender="org.mpris.MediaPlayer2.ShairportSync"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
||||
@@ -18,7 +18,7 @@ general =
|
||||
// mdns_backend = "avahi"; // Run "shairport-sync -h" to get a list of all mdns_backends. The default is the first one.
|
||||
// port = 5000; // Listen for service requests on this port
|
||||
// udp_port_base = 6001; // start allocating UDP ports from this port number when needed
|
||||
// udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state.
|
||||
// udp_port_range = 10; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state.
|
||||
// drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it
|
||||
// resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it
|
||||
// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to.
|
||||
@@ -29,6 +29,7 @@ general =
|
||||
// volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume.
|
||||
// "standard" makes the volume change more quickly at lower volumes and slower at higher volumes.
|
||||
// "flat" makes the volume change at the same rate at all volumes.
|
||||
// volume_range_combined_hardware_priority = "no"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware mixer first, then the built-in software attenuator.
|
||||
// run_this_when_volume_is_set = "/full/path/to/application/and/args"; // Run the specified application whenever the volume control is set or changed.
|
||||
// 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 to -30 and -144 means "mute".
|
||||
@@ -41,6 +42,7 @@ general =
|
||||
|
||||
// audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
|
||||
// audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
|
||||
// audio_backend_buffer_interpolation_threshold_in_seconds = 0.075; // Advanced feature. If the buffer size drops below this, stop using time-consuming interpolation like soxr to avoid dropouts due to underrun.
|
||||
// audio_backend_silent_lead_in_time = 2.0; // This optional advanced setting, from 0.0 and 4.0 seconds, sets the length of the period of silence that precedes the start of the audio. The default is the latency, usually 2.0 seconds. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation.
|
||||
// dbus_service_bus = "system"; // The Shairport Sync dbus interface, if selected at compilation, will appear
|
||||
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
|
||||
@@ -48,12 +50,22 @@ general =
|
||||
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
|
||||
};
|
||||
|
||||
// Advanced parameters for controlling how Shairport Sync runs a play session
|
||||
// Advanced parameters for controlling how Shairport Sync stays active and how it runs a session
|
||||
sessioncontrol =
|
||||
{
|
||||
// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
|
||||
// "active" state starts when play begins and ends when the active_state_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed.
|
||||
// run_this_before_entering_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
// run_this_after_exiting_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
// active_state_timeout = 10.0; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins.
|
||||
|
||||
// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument.
|
||||
// Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem.
|
||||
// You could hook on a program to do this automatically, but beware -- the device may then power off and restart without warning!
|
||||
// wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing
|
||||
|
||||
// allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source
|
||||
// session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again.
|
||||
};
|
||||
@@ -73,6 +85,8 @@ alsa =
|
||||
// buffer_size = <number>; // 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"
|
||||
// use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players.
|
||||
// maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms.
|
||||
// disable_standby_mode = "never"; // Some DACs make small "popping" noises when they go in and out of standby mode. This prevents entry to standby mode. Settings can be: "always", "while_active" or "never". Default is "never", but only for backwards compatibility. You can use "yes" instead of "always" and "no instead of "never".
|
||||
};
|
||||
|
||||
// Parameters for the "sndio" audio back end. All are optional.
|
||||
@@ -91,15 +105,28 @@ pa =
|
||||
// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active.
|
||||
};
|
||||
|
||||
// Parameters for the "jack" JACK Audio Connection Kit backend.
|
||||
jack =
|
||||
{
|
||||
// client_name = "shairport-sync"; // Set this to the name of the client that should appear in "Connections" when Shairport Sync is active.
|
||||
// autoconnect_pattern = ""; // Set this to a POSIX regular expression pattern that describes the ports you would like to connect to
|
||||
// automatically. Examples:
|
||||
// "system:playback_[12]"
|
||||
// "some_app_[0-9]*:in-[LR]"
|
||||
// "jack_mixer:in_2[78]"
|
||||
// Beware: if you make a syntax error, libjack might crash. In that case, fix it and start over.
|
||||
// For a good overview, look here: https://www.ibm.com/support/knowledgecenter/SS8NLW_11.0.1/com.ibm.swg.im.infosphere.dataexpl.engine.doc/c_posix-regex-examples.html
|
||||
};
|
||||
|
||||
// Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done.
|
||||
pipe =
|
||||
{
|
||||
// name = "/path/to/pipe"; // there is no default pipe name for the output
|
||||
};
|
||||
|
||||
// These are no configuration file parameters for the "stdout" audio back end. No interpolation is done.
|
||||
// There are no configuration file parameters for the "stdout" audio back end. No interpolation is done.
|
||||
|
||||
// These are no configuration file parameters for the "ao" audio back end. No interpolation is done.
|
||||
// There are no configuration file parameters for the "ao" audio back end. No interpolation is done.
|
||||
|
||||
// Static latency settings are deprecated and the settings have been removed.
|
||||
|
||||
@@ -144,6 +171,28 @@ metadata =
|
||||
// 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.
|
||||
};
|
||||
|
||||
// How to enable the MQTT-metadata/remote-service
|
||||
mqtt =
|
||||
{
|
||||
// enabled = "no"; // set this to yes to enable the mqtt-metadata-service
|
||||
// hostname = "iot.eclipse.org"; // Hostname of the MQTT Broker
|
||||
// port = "1883";
|
||||
// username = NULL; //set this to a string with your username, to enable username authentication
|
||||
// password = NULL; //set this to a string with your password, to enable username & password authentication
|
||||
// capath = NULL; //set this to the folder with the CA-Certificates to be accepted for the server certificate. If not set, TLS is not used
|
||||
// cafile = NULL; //this may be used as an (exclusive) alternative to capath with a single file for all ca-certificates
|
||||
// certfile = NULL; //set this to a string to a user certificate to enable MQTT Client certificates. keyfile must also be set!
|
||||
// keyfile = NULL; //private key for MQTT Client authentication
|
||||
// topic = NULL; //MQTT topic where this instance of shairport-sync should publish. If not set, the general.name value is used.
|
||||
// publish_raw = "no"; //whether to publish all available metadata under the codes given in the 'metadata' docs.
|
||||
// publish_parsed = "no"; //whether to publish a small (but useful) subset of metadata under human-understandable topics
|
||||
// Currently published topics:artist,album,title,genre,format,songalbum,volume,client_ip,
|
||||
// Additionally, empty messages at the topics play_start,play_end,play_flush,play_resume are published
|
||||
// publish_cover = "no"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker
|
||||
// enable_remote = "no"; //whether to remote control via MQTT. RC is available under `topic`/remote.
|
||||
// Available commands are "command", "beginff", "beginrew", "mutetoggle", "nextitem", "previtem", "pause", "playpause", "play", "stop", "playresume", "shuffle_songs", "volumedown", "volumeup"
|
||||
}
|
||||
|
||||
// Diagnostic settings. These are for diagnostic and debugging only. Normally you sould leave them commented out
|
||||
diagnostics =
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ stop_cmd="${name}_stop"
|
||||
|
||||
shairport_sync_start()
|
||||
{
|
||||
checkyesno shairport_sync_enable && echo "Starting shairport-sync." && \
|
||||
checkyesno shairport_sync_enable && echo "Starting shairport-sync." && \
|
||||
su -m shairport-sync -c "/usr/local/bin/shairport-sync -d"
|
||||
}
|
||||
|
||||
|
||||
+12
-25
@@ -23,8 +23,7 @@ DAEMON=@prefix@/bin/$NAME
|
||||
# impossible to pass as arguments.
|
||||
|
||||
# Instead, we add the arguments directly to the relevant line in the do_start() function below
|
||||
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
PIDFILE=/var/run/$NAME/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
@@ -48,31 +47,19 @@ do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
# 1 if PID directory didn't exist and couldn't be created with approproate permission
|
||||
# 2 if daemon was already running
|
||||
# 3 if daemon could not be started
|
||||
[ -e /var/run/shairport-sync ] || ( mkdir -p /var/run/shairport-sync && chown shairport-sync:shairport-sync /var/run/shairport-sync ) || return 1
|
||||
start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null || return 2
|
||||
|
||||
# This script is set to start running after all other services have started.
|
||||
# However, if you find that Shairport Sync is still being started before what it needs is ready,
|
||||
# uncomment the next line to get the script to wait for three seconds before attempting to start Shairport Sync.
|
||||
# sleep 3
|
||||
# This script is set to start running after all other services have started.
|
||||
# However, if you find that Shairport Sync is still being started before what it needs is ready,
|
||||
# uncomment the next line to get the script to wait for three seconds before attempting to start Shairport Sync.
|
||||
# sleep 3
|
||||
|
||||
# Uncomment just one of the following start-stop-daemon lines, or comment them all out and add your own.
|
||||
# Shairport Sync will read settings from the configuration file (/etc/shairport-sync.conf by default) and will then apply any command line options.
|
||||
# In the default script, the first line is uncommented, selecting daemon mode (-d). Settings will be taken from the configuration file.
|
||||
# BTW, if you're using software volume control, you may have to use alsamixer or similar to set the output device's volume to its maximum level first
|
||||
# BTW2, you can use alsamixer to find device identifiers (e.g. hw:1) and mixer names (e.g. "Speaker"). No need to change ALSA's defaults.
|
||||
# BTW3, the argument after -a is simply the name the shairport service will be visible as.
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 2
|
||||
# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "Topping TP30 or Griffin iMic on Raspberry Pi" -- -d hw:1 -t hardware -c "PCM" || return 2
|
||||
# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "'3D Sound' USB Soundcard on Raspberry Pi" -- -d hw:1 -t hardware -c "Speaker" || return 2
|
||||
# BTW, that "3D Sound" USB soundcard sometimes has the mixer name "Headphone" rather than "Speaker" -- use alsamixer to check.
|
||||
# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "IQaudIO" -- -d hw:1 -t hardware -c "Playback Digital" || return 2
|
||||
# BTW, newer versions of IQaudIO have a different mixer name -- use alsamixer to check.
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
# Settings from the configuration file (@sysconfdir@/shairport-sync.conf by default)
|
||||
start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 3
|
||||
}
|
||||
|
||||
#
|
||||
|
||||
+347
-159
@@ -2,7 +2,7 @@
|
||||
* Shairport, an Apple Airplay receiver
|
||||
* Copyright (c) James Laird 2013
|
||||
* All rights reserved.
|
||||
* Modifications (c) Mike Brady 2014--2018
|
||||
* Modifications and additions (c) Mike Brady 2014--2019
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
@@ -31,99 +31,95 @@
|
||||
#include <libconfig.h>
|
||||
#include <libgen.h>
|
||||
#include <memory.h>
|
||||
#include <net/if.h>
|
||||
#include <popt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <net/if.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#include <mbedtls/md5.h>
|
||||
#include <mbedtls/version.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
#include <polarssl/md5.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
#include <openssl/md5.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_DBUS)
|
||||
#if defined(CONFIG_DBUS_INTERFACE)
|
||||
#include <glib.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_DACP_CLIENT)
|
||||
#include "dacp.h"
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_METADATA_HUB)
|
||||
#include "metadata_hub.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include "dbus-service.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_MPRIS
|
||||
#include "mpris-service.h"
|
||||
#endif
|
||||
|
||||
#include "activity_monitor.h"
|
||||
#include "common.h"
|
||||
#include "mdns.h"
|
||||
#include "rtp.h"
|
||||
#include "rtsp.h"
|
||||
|
||||
#if defined(CONFIG_DACP_CLIENT)
|
||||
#include "dacp.h"
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_METADATA_HUB)
|
||||
#include "metadata_hub.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
#include "dbus-service.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MQTT
|
||||
#include "mqtt.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MPRIS_INTERFACE
|
||||
#include "mpris-service.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
#include <libdaemon/dexec.h>
|
||||
#include <libdaemon/dfork.h>
|
||||
#include <libdaemon/dlog.h>
|
||||
#include <libdaemon/dpid.h>
|
||||
#include <libdaemon/dsignal.h>
|
||||
#else
|
||||
#include <syslog.h>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CONVOLUTION
|
||||
#include <FFTConvolver/convolver.h>
|
||||
#endif
|
||||
|
||||
static int shutting_down = 0;
|
||||
// static int shutting_down = 0;
|
||||
char configuration_file_path[4096 + 1];
|
||||
char actual_configuration_file_path[4096 + 1];
|
||||
|
||||
void shairport_shutdown() {
|
||||
if (shutting_down)
|
||||
return;
|
||||
shutting_down = 1;
|
||||
mdns_unregister();
|
||||
rtsp_request_shutdown_stream();
|
||||
if (config.output)
|
||||
config.output->deinit();
|
||||
}
|
||||
|
||||
static void sig_ignore(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar,
|
||||
__attribute__((unused)) void *baz) {}
|
||||
static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar,
|
||||
__attribute__((unused)) void *baz) {
|
||||
debug(1, "shutdown requested...");
|
||||
shairport_shutdown();
|
||||
// daemon_log(LOG_NOTICE, "exit...");
|
||||
debug(2, "shutdown requested...");
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
daemon_retval_send(255);
|
||||
daemon_pid_file_remove();
|
||||
#endif
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void sig_child(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar,
|
||||
__attribute__((unused)) void *baz) {
|
||||
// wait for child processes to exit
|
||||
pid_t pid;
|
||||
while ((pid = waitpid((pid_t)-1, 0, WNOHANG)) > 0) {
|
||||
if (pid == mdns_pid && !shutting_down) {
|
||||
die("MDNS child process died unexpectedly!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,12 +153,12 @@ void usage(char *progname) {
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h, --help show this help.\n");
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
printf(" -d, --daemon daemonise.\n");
|
||||
printf(" -j, --justDaemoniseNoPIDFile daemonise without a PID file.\n");
|
||||
printf(" -V, --version show version information.\n");
|
||||
printf(" -k, --kill kill the existing shairport daemon.\n");
|
||||
printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n");
|
||||
printf(" -R, --reconnectToOutput reconnect to the output device.\n");
|
||||
#endif
|
||||
printf(" -V, --version show version information.\n");
|
||||
printf(" -c, --configfile=FILE read configuration settings from FILE. Default is "
|
||||
"/etc/shairport-sync.conf.\n");
|
||||
|
||||
@@ -175,7 +171,6 @@ void usage(char *progname) {
|
||||
printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown "
|
||||
"device.\n");
|
||||
printf(" The default is to set it automatically.\n");
|
||||
printf(" The default is to set it automatically.\n");
|
||||
printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, "
|
||||
"where \n");
|
||||
printf(" \"basic\" (default) inserts or deletes audio frames from "
|
||||
@@ -217,6 +212,7 @@ void usage(char *progname) {
|
||||
printf(" The default is /tmp/shairport-sync-metadata.\n");
|
||||
printf(" --get-coverart send cover art through the metadata pipe.\n");
|
||||
#endif
|
||||
printf(" -u, --use-stderr log messages through STDERR rather than syslog.\n");
|
||||
printf("\n");
|
||||
mdns_ls_backends();
|
||||
printf("\n");
|
||||
@@ -233,15 +229,17 @@ int parse_options(int argc, char **argv) {
|
||||
int fResyncthreshold = (int)(config.resyncthreshold * 44100);
|
||||
int fTolerance = (int)(config.tolerance * 44100);
|
||||
poptContext optCon; /* context for parsing command-line options */
|
||||
#if CONFIG_LIBDAEMON
|
||||
int daemonisewith = 0;
|
||||
int daemonisewithout = 0;
|
||||
#endif
|
||||
struct poptOption optionsTable[] = {
|
||||
{"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL},
|
||||
{"disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL, NULL},
|
||||
{"reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL, NULL},
|
||||
#if CONFIG_LIBDAEMON
|
||||
{"kill", 'k', POPT_ARG_NONE, NULL, 0, NULL, NULL},
|
||||
{"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL, NULL},
|
||||
{"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL, NULL},
|
||||
#endif
|
||||
{"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL, NULL},
|
||||
{"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL},
|
||||
{"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL},
|
||||
@@ -259,6 +257,7 @@ int parse_options(int argc, char **argv) {
|
||||
{"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL, NULL},
|
||||
{"password", 0, POPT_ARG_STRING, &config.password, 0, NULL, NULL},
|
||||
{"tolerance", 'z', POPT_ARG_INT, &fTolerance, 0, NULL, NULL},
|
||||
{"use-stderr", 'u', POPT_ARG_NONE, NULL, 'u', NULL, NULL},
|
||||
#ifdef CONFIG_METADATA
|
||||
{"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL, NULL},
|
||||
{"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL, NULL},
|
||||
@@ -285,6 +284,9 @@ int parse_options(int argc, char **argv) {
|
||||
case 'v':
|
||||
debuglev++;
|
||||
break;
|
||||
case 'u':
|
||||
log_to_stderr();
|
||||
break;
|
||||
case 'D':
|
||||
inform("Warning: the option -D or --disconnectFromOutput is deprecated.");
|
||||
break;
|
||||
@@ -322,6 +324,7 @@ int parse_options(int argc, char **argv) {
|
||||
|
||||
poptFreeContext(optCon);
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
if ((daemonisewith) && (daemonisewithout))
|
||||
die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected "
|
||||
"both!");
|
||||
@@ -330,6 +333,7 @@ int parse_options(int argc, char **argv) {
|
||||
if (daemonisewith)
|
||||
config.daemonise_store_pid = 1;
|
||||
};
|
||||
#endif
|
||||
|
||||
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
|
||||
config.tolerance = 1.0 * fTolerance / 44100;
|
||||
@@ -338,8 +342,13 @@ int parse_options(int argc, char **argv) {
|
||||
// nothing else comes in first.
|
||||
config.fixedLatencyOffset = 11025; // this sounds like it works properly.
|
||||
config.diagnostic_drop_packet_fraction = 0.0;
|
||||
config.active_state_timeout = 10.0;
|
||||
config.volume_range_hw_priority =
|
||||
0; // if combining software and hardware volume control, give the software priority
|
||||
// i.e. when reducing volume, reduce the sw first before reducing the software.
|
||||
// this is because some hw mixers mute at the bottom of their range, and they don't always advertise this fact
|
||||
|
||||
#ifdef HAVE_METADATA_HUB
|
||||
#ifdef CONFIG_METADATA_HUB
|
||||
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
|
||||
config.scan_interval_when_active =
|
||||
1; // number of seconds between DACP server scans when playing something
|
||||
@@ -370,12 +379,16 @@ int parse_options(int argc, char **argv) {
|
||||
debug(2, "Looking for configuration file at full path \"%s\"", config_file_real_path);
|
||||
/* Read the file. If there is an error, report it and exit. */
|
||||
if (config_read_file(&config_file_stuff, config_file_real_path)) {
|
||||
free(config_file_real_path);
|
||||
config_set_auto_convert(&config_file_stuff,
|
||||
1); // allow autoconversion from int/float to int/float
|
||||
// make config.cfg point to it
|
||||
config.cfg = &config_file_stuff;
|
||||
/* Get the Service Name. */
|
||||
if (config_lookup_string(config.cfg, "general.name", &str)) {
|
||||
raw_service_name = (char *)str;
|
||||
}
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
int daemonisewithout = 0;
|
||||
int daemonisewith = 0;
|
||||
/* Get the Daemonize setting. */
|
||||
@@ -390,15 +403,9 @@ int parse_options(int argc, char **argv) {
|
||||
}
|
||||
|
||||
/* Get the Just_Daemonize setting. */
|
||||
if (config_lookup_string(config.cfg, "sessioncontrol.daemonize_without_pid_file", &str)) {
|
||||
if (strcasecmp(str, "no") == 0)
|
||||
daemonisewithout = 0;
|
||||
else if (strcasecmp(str, "yes") == 0)
|
||||
daemonisewithout = 1;
|
||||
else
|
||||
die("Invalid daemonize_without_pid_file option choice \"%s\". It should be \"yes\" or "
|
||||
"\"no\"");
|
||||
}
|
||||
config_set_lookup_bool(config.cfg, "sessioncontrol.daemonize_without_pid_file",
|
||||
&daemonisewithout);
|
||||
|
||||
if ((daemonisewith) && (daemonisewithout))
|
||||
die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have "
|
||||
"selected both!");
|
||||
@@ -410,7 +417,7 @@ int parse_options(int argc, char **argv) {
|
||||
/* 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;
|
||||
|
||||
#endif
|
||||
/* Get the mdns_backend setting. */
|
||||
if (config_lookup_string(config.cfg, "general.mdns_backend", &str))
|
||||
config.mdns_name = (char *)str;
|
||||
@@ -440,8 +447,8 @@ int parse_options(int argc, char **argv) {
|
||||
/* Get the udp port range setting. This is number of ports that will be tried for free ports ,
|
||||
* starting at the port base. Only three ports are needed. */
|
||||
if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) {
|
||||
if ((value < 0) || (value > 65535))
|
||||
die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100",
|
||||
if ((value < 3) || (value > 65535))
|
||||
die("Invalid port range \"%sd\". It should be between 3 and 65535, default is 10",
|
||||
value);
|
||||
else
|
||||
config.udp_port_range = value;
|
||||
@@ -455,7 +462,7 @@ int parse_options(int argc, char **argv) {
|
||||
if (strcasecmp(str, "basic") == 0)
|
||||
config.packet_stuffing = ST_basic;
|
||||
else if (strcasecmp(str, "soxr") == 0)
|
||||
#ifdef HAVE_LIBSOXR
|
||||
#ifdef CONFIG_SOXR
|
||||
config.packet_stuffing = ST_soxr;
|
||||
#else
|
||||
die("The soxr option not available because this version of shairport-sync was built "
|
||||
@@ -467,7 +474,8 @@ int parse_options(int argc, char **argv) {
|
||||
}
|
||||
|
||||
/* Get the statistics setting. */
|
||||
if (config_lookup_string(config.cfg, "general.statistics", &str)) {
|
||||
if (config_set_lookup_bool(config.cfg, "general.statistics",
|
||||
&(config.statistics_requested))) {
|
||||
warn("The \"general\" \"statistics\" setting is deprecated. Please use the \"diagnostics\" "
|
||||
"\"statistics\" setting instead.");
|
||||
if (strcasecmp(str, "no") == 0)
|
||||
@@ -631,6 +639,9 @@ int parse_options(int argc, char **argv) {
|
||||
"or \"flat\"");
|
||||
}
|
||||
|
||||
config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority",
|
||||
&config.volume_range_hw_priority);
|
||||
|
||||
/* Get the interface to listen on, if specified Default is all interfaces */
|
||||
/* we keep the interface name and the index */
|
||||
|
||||
@@ -747,6 +758,30 @@ int parse_options(int argc, char **argv) {
|
||||
config.cmd_stop = (char *)str;
|
||||
}
|
||||
|
||||
if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_state",
|
||||
&str)) {
|
||||
config.cmd_active_start = (char *)str;
|
||||
}
|
||||
|
||||
if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_state",
|
||||
&str)) {
|
||||
config.cmd_active_stop = (char *)str;
|
||||
}
|
||||
|
||||
if (config_lookup_float(config.cfg, "sessioncontrol.active_state_timeout", &dvalue)) {
|
||||
if (dvalue < 0.0)
|
||||
warn("Invalid value \"%f\" for sessioncontrol.active_state_timeout. It must be positive. "
|
||||
"The default of %f will be used instead.",
|
||||
dvalue, config.active_state_timeout);
|
||||
else
|
||||
config.active_state_timeout = dvalue;
|
||||
}
|
||||
|
||||
if (config_lookup_string(config.cfg,
|
||||
"sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) {
|
||||
config.cmd_unfixable = (char *)str;
|
||||
}
|
||||
|
||||
if (config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) {
|
||||
if (strcasecmp(str, "no") == 0)
|
||||
config.cmd_blocking = 0;
|
||||
@@ -846,14 +881,14 @@ int parse_options(int argc, char **argv) {
|
||||
|
||||
} else {
|
||||
if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO)
|
||||
debug(1, "Error reading configuration file \"%s\": \"%s\".",
|
||||
debug(2, "Error reading configuration file \"%s\": \"%s\".",
|
||||
config_error_file(&config_file_stuff), config_error_text(&config_file_stuff));
|
||||
else {
|
||||
die("Line %d of the configuration file \"%s\":\n%s", config_error_line(&config_file_stuff),
|
||||
config_error_file(&config_file_stuff), config_error_text(&config_file_stuff));
|
||||
}
|
||||
}
|
||||
#if defined(HAVE_DBUS)
|
||||
#if defined(CONFIG_DBUS_INTERFACE)
|
||||
/* Get the dbus service sbus setting. */
|
||||
if (config_lookup_string(config.cfg, "general.dbus_service_bus", &str)) {
|
||||
if (strcasecmp(str, "system") == 0)
|
||||
@@ -866,7 +901,7 @@ int parse_options(int argc, char **argv) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_MPRIS)
|
||||
#if defined(CONFIG_MPRIS_INTERFACE)
|
||||
/* Get the mpris service sbus setting. */
|
||||
if (config_lookup_string(config.cfg, "general.mpris_service_bus", &str)) {
|
||||
if (strcasecmp(str, "system") == 0)
|
||||
@@ -879,7 +914,62 @@ int parse_options(int argc, char **argv) {
|
||||
}
|
||||
#endif
|
||||
|
||||
free(config_file_real_path);
|
||||
#ifdef CONFIG_MQTT
|
||||
int tmpval = 0;
|
||||
config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled);
|
||||
if (config.mqtt_enabled && !config.metadata_enabled) {
|
||||
die("You need to have metadata enabled in order to use mqtt");
|
||||
}
|
||||
if (config_lookup_string(config.cfg, "mqtt.hostname", &str)) {
|
||||
config.mqtt_hostname = (char *)str;
|
||||
// TODO: Document that, if this is false, whole mqtt func is disabled
|
||||
}
|
||||
if (config_lookup_int(config.cfg, "mqtt.port", &tmpval)) {
|
||||
config.mqtt_port = tmpval;
|
||||
} else {
|
||||
// TODO: Is this the correct way to set a default value?
|
||||
config.mqtt_port = 1883;
|
||||
}
|
||||
|
||||
if (config_lookup_string(config.cfg, "mqtt.username", &str)) {
|
||||
config.mqtt_username = (char *)str;
|
||||
}
|
||||
if (config_lookup_string(config.cfg, "mqtt.password", &str)) {
|
||||
config.mqtt_password = (char *)str;
|
||||
}
|
||||
int capath = 0;
|
||||
if (config_lookup_string(config.cfg, "mqtt.capath", &str)) {
|
||||
config.mqtt_capath = (char *)str;
|
||||
capath = 1;
|
||||
}
|
||||
if (config_lookup_string(config.cfg, "mqtt.cafile", &str)) {
|
||||
if (capath)
|
||||
die("Supply either mqtt cafile or mqtt capath -- you have supplied both!");
|
||||
config.mqtt_cafile = (char *)str;
|
||||
}
|
||||
int certkeynum = 0;
|
||||
if (config_lookup_string(config.cfg, "mqtt.certfile", &str)) {
|
||||
config.mqtt_certfile = (char *)str;
|
||||
certkeynum++;
|
||||
}
|
||||
if (config_lookup_string(config.cfg, "mqtt.keyfile", &str)) {
|
||||
config.mqtt_keyfile = (char *)str;
|
||||
certkeynum++;
|
||||
}
|
||||
if (certkeynum != 0 && certkeynum != 2) {
|
||||
die("If you want to use TLS Client Authentication, you have to specify "
|
||||
"mqtt.certfile AND mqtt.keyfile.\nYou have supplied only one of them.\n"
|
||||
"If you do not want to use TLS Client Authentication, leave both empty.");
|
||||
}
|
||||
|
||||
if (config_lookup_string(config.cfg, "mqtt.topic", &str)) {
|
||||
config.mqtt_topic = (char *)str;
|
||||
}
|
||||
config_set_lookup_bool(config.cfg, "mqtt.publish_raw", &config.mqtt_publish_raw);
|
||||
config_set_lookup_bool(config.cfg, "mqtt.publish_parsed", &config.mqtt_publish_parsed);
|
||||
config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover);
|
||||
config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote);
|
||||
#endif
|
||||
}
|
||||
|
||||
// now, do the command line options again, but this time do them fully -- it's a unix convention
|
||||
@@ -925,7 +1015,7 @@ int parse_options(int argc, char **argv) {
|
||||
if (strcmp(stuffing, "basic") == 0)
|
||||
config.packet_stuffing = ST_basic;
|
||||
else if (strcmp(stuffing, "soxr") == 0)
|
||||
#ifdef HAVE_LIBSOXR
|
||||
#ifdef CONFIG_SOXR
|
||||
config.packet_stuffing = ST_soxr;
|
||||
#else
|
||||
die("The soxr option not available because this version of shairport-sync was built "
|
||||
@@ -978,8 +1068,20 @@ int parse_options(int argc, char **argv) {
|
||||
free(i3);
|
||||
free(vs);
|
||||
|
||||
#ifdef CONFIG_MQTT
|
||||
// mqtt topic was not set. As we have the service name just now, set it
|
||||
if (config.mqtt_topic == NULL) {
|
||||
int topic_length = 1 + strlen(config.service_name) + 1;
|
||||
char *topic = malloc(topic_length + 1);
|
||||
snprintf(topic, topic_length, "/%s/", config.service_name);
|
||||
config.mqtt_topic = topic;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
|
||||
// now, check and calculate the pid directory
|
||||
#ifdef USE_CUSTOM_PID_DIR
|
||||
#ifdef DEFINED_CUSTOM_PID_DIR
|
||||
char *use_this_pid_dir = PIDDIR;
|
||||
#else
|
||||
char *use_this_pid_dir = "/var/run/shairport-sync";
|
||||
@@ -989,18 +1091,19 @@ int parse_options(int argc, char **argv) {
|
||||
use_this_pid_dir = config.piddir;
|
||||
if (use_this_pid_dir)
|
||||
config.computed_piddir = strdup(use_this_pid_dir);
|
||||
|
||||
#endif
|
||||
return optind + 1;
|
||||
}
|
||||
|
||||
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
|
||||
GMainLoop *loop;
|
||||
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
|
||||
static GMainLoop *g_main_loop = NULL;
|
||||
|
||||
pthread_t dbus_thread;
|
||||
void *dbus_thread_func(__attribute__((unused)) void *arg) {
|
||||
loop = g_main_loop_new(NULL, FALSE);
|
||||
g_main_loop_run(loop);
|
||||
return NULL;
|
||||
g_main_loop = g_main_loop_new(NULL, FALSE);
|
||||
g_main_loop_run(g_main_loop);
|
||||
debug(2, "g_main_loop thread exit");
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1044,14 +1147,7 @@ void signal_setup(void) {
|
||||
sigaction(SIGCHLD, &sa, NULL);
|
||||
}
|
||||
|
||||
// forked daemon lets the spawner know it's up and running OK
|
||||
// should be called only once!
|
||||
void shairport_startup_complete(void) {
|
||||
if (config.daemonise) {
|
||||
// daemon_ready();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
char pid_file_path_string[4096] = "\0";
|
||||
|
||||
const char *pid_file_proc(void) {
|
||||
@@ -1060,9 +1156,75 @@ const char *pid_file_proc(void) {
|
||||
// debug(1,"pid_file_path_string \"%s\".",pid_file_path_string);
|
||||
return pid_file_path_string;
|
||||
}
|
||||
#endif
|
||||
|
||||
void main_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||
// it doesn't look like this is called when the main function is cancelled eith a pthread cancel.
|
||||
debug(2, "main cleanup handler called.");
|
||||
#ifdef CONFIG_MQTT
|
||||
if (config.mqtt_enabled) {
|
||||
// terminate_mqtt();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
|
||||
#ifdef CONFIG_MPRIS_INTERFACE
|
||||
// stop_mpris_service();
|
||||
#endif
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
stop_dbus_service();
|
||||
#endif
|
||||
if (g_main_loop) {
|
||||
debug(2, "Stopping DBUS Loop Thread");
|
||||
g_main_loop_quit(g_main_loop);
|
||||
pthread_join(dbus_thread, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DACP_CLIENT
|
||||
debug(2, "Stopping DACP Monitor");
|
||||
dacp_monitor_stop();
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_METADATA_HUB
|
||||
debug(2, "Stopping metadata hub");
|
||||
metadata_hub_stop();
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_METADATA
|
||||
metadata_stop(); // close down the metadata pipe
|
||||
#endif
|
||||
|
||||
activity_monitor_stop(0);
|
||||
|
||||
if ((config.output) && (config.output->deinit)) {
|
||||
debug(2, "Deinitialise the audio backend.");
|
||||
config.output->deinit();
|
||||
}
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
daemon_retval_send(0);
|
||||
daemon_pid_file_remove();
|
||||
daemon_signal_done();
|
||||
#endif
|
||||
debug(2, "Exit...");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void exit_function() {
|
||||
// debug(1, "exit function called...");
|
||||
debug(2, "exit function called...");
|
||||
main_cleanup_handler(NULL);
|
||||
if (conns)
|
||||
free(conns); // make sure the connections have been deleted first
|
||||
if (config.service_name)
|
||||
free(config.service_name);
|
||||
if (config.regtype)
|
||||
free(config.regtype);
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
if (config.computed_piddir)
|
||||
free(config.computed_piddir);
|
||||
#endif
|
||||
if (ranarray)
|
||||
free((void *)ranarray);
|
||||
if (config.cfg)
|
||||
config_destroy(config.cfg);
|
||||
if (config.appName)
|
||||
@@ -1071,14 +1233,12 @@ void exit_function() {
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
conns = NULL; // no connections active
|
||||
memset((void *)&main_thread_id, 0, sizeof(main_thread_id));
|
||||
memset(&config, 0, sizeof(config)); // also clears all strings, BTW
|
||||
fp_time_at_startup = get_absolute_time_in_fp();
|
||||
fp_time_at_last_debug_message = fp_time_at_startup;
|
||||
// debug(1,"startup");
|
||||
daemon_set_verbosity(LOG_DEBUG);
|
||||
memset(&config, 0, sizeof(config)); // also clears all strings, BTW
|
||||
atexit(exit_function);
|
||||
|
||||
// this is a bit weird, but apparently necessary
|
||||
// this is a bit weird, but necessary -- basename() may modify the argument passed in
|
||||
char *basec = strdup(argv[0]);
|
||||
char *bname = basename(basec);
|
||||
config.appName = strdup(bname);
|
||||
@@ -1086,9 +1246,18 @@ int main(int argc, char **argv) {
|
||||
die("can not allocate memory for the app name!");
|
||||
free(basec);
|
||||
|
||||
// debug(1,"startup");
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
daemon_set_verbosity(LOG_DEBUG);
|
||||
#else
|
||||
setlogmask (LOG_UPTO (LOG_DEBUG));
|
||||
openlog(NULL,0,LOG_DAEMON);
|
||||
#endif
|
||||
atexit(exit_function);
|
||||
|
||||
// set defaults
|
||||
|
||||
// get thje endianness
|
||||
// get the endianness
|
||||
union {
|
||||
uint32_t u32;
|
||||
uint8_t arr[4];
|
||||
@@ -1136,12 +1305,12 @@ int main(int argc, char **argv) {
|
||||
1); // we expect to be able to connect to the output device
|
||||
config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds.
|
||||
config.udp_port_base = 6001;
|
||||
config.udp_port_range = 100;
|
||||
config.udp_port_range = 10;
|
||||
config.output_format = SPS_FORMAT_S16; // default
|
||||
config.output_rate = 44100; // default
|
||||
config.decoders_supported =
|
||||
1 << decoder_hammerton; // David Hammerton's decoder supported by default
|
||||
#ifdef HAVE_APPLE_ALAC
|
||||
#ifdef CONFIG_APPLE_ALAC
|
||||
config.decoders_supported += 1 << decoder_apple_alac;
|
||||
#endif
|
||||
|
||||
@@ -1165,6 +1334,8 @@ int main(int argc, char **argv) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
|
||||
pid_t pid;
|
||||
|
||||
/* Reset signal handlers */
|
||||
@@ -1183,45 +1354,23 @@ int main(int argc, char **argv) {
|
||||
daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]);
|
||||
|
||||
daemon_pid_file_proc = pid_file_proc;
|
||||
|
||||
/* Check if we are called with -D or --disconnectFromOutput parameter */
|
||||
if (argc >= 2 &&
|
||||
((strcmp(argv[1], "-D") == 0) || (strcmp(argv[1], "--disconnectFromOutput") == 0))) {
|
||||
if ((pid = daemon_pid_file_is_running()) >= 0) {
|
||||
if (kill(pid, SIGUSR2) != 0) { // try to send the signal
|
||||
daemon_log(LOG_WARNING,
|
||||
"Failed trying to send disconnectFromOutput command to daemon pid: %d: %s", pid,
|
||||
strerror(errno));
|
||||
}
|
||||
} else {
|
||||
daemon_log(LOG_WARNING,
|
||||
"Can't send a disconnectFromOutput request -- Failed to find daemon: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Check if we are called with -R or --reconnectToOutput parameter */
|
||||
if (argc >= 2 &&
|
||||
((strcmp(argv[1], "-R") == 0) || (strcmp(argv[1], "--reconnectToOutput") == 0))) {
|
||||
if ((pid = daemon_pid_file_is_running()) >= 0) {
|
||||
if (kill(pid, SIGHUP) != 0) { // try to send the signal
|
||||
daemon_log(LOG_WARNING,
|
||||
"Failed trying to send reconnectToOutput command to daemon pid: %d: %s", pid,
|
||||
strerror(errno));
|
||||
}
|
||||
} else {
|
||||
daemon_log(LOG_WARNING, "Can't send a reconnectToOutput request -- Failed to find daemon: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// parse arguments into config -- needed to locate pid_dir
|
||||
int audio_arg = parse_options(argc, argv);
|
||||
|
||||
// mDNS supports maximum of 63-character names (we append 13).
|
||||
if (strlen(config.service_name) > 50) {
|
||||
warn("Supplied name too long (max 50 characters)");
|
||||
config.service_name[50] = '\0'; // truncate it and carry on...
|
||||
}
|
||||
|
||||
/* Check if we are called with -k or --kill parameter */
|
||||
if (argc >= 2 && ((strcmp(argv[1], "-k") == 0) || (strcmp(argv[1], "--kill") == 0))) {
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
int ret;
|
||||
|
||||
/* Kill daemon with SIGTERM */
|
||||
@@ -1231,20 +1380,20 @@ int main(int argc, char **argv) {
|
||||
else
|
||||
daemon_pid_file_remove();
|
||||
return ret < 0 ? 1 : 0;
|
||||
#else
|
||||
syslog(LOG_ERR, "shairport-sync was built without support for the -k or --kill option");
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
/* If we are going to daemonise, check that the daemon is not running already.*/
|
||||
if ((config.daemonise) && ((pid = daemon_pid_file_is_running()) >= 0)) {
|
||||
daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// mDNS supports maximum of 63-character names (we append 13).
|
||||
if (strlen(config.service_name) > 50) {
|
||||
warn("Supplied name too long (max 50 characters)");
|
||||
config.service_name[50] = '\0'; // truncate it and carry on...
|
||||
}
|
||||
|
||||
/* here, daemonise with libdaemon */
|
||||
|
||||
if (config.daemonise) {
|
||||
@@ -1293,10 +1442,11 @@ int main(int argc, char **argv) {
|
||||
/* Close FDs */
|
||||
if (daemon_close_all(-1) < 0) {
|
||||
daemon_log(LOG_ERR, "Failed to close all file descriptors: %s", strerror(errno));
|
||||
|
||||
/* Send the error condition to the parent process */
|
||||
daemon_retval_send(1);
|
||||
goto finish;
|
||||
|
||||
daemon_signal_done();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create the PID file if required */
|
||||
@@ -1307,12 +1457,16 @@ int main(int argc, char **argv) {
|
||||
if ((result != 0) && (result != -EEXIST)) {
|
||||
// error creating or accessing the PID file directory
|
||||
daemon_retval_send(3);
|
||||
goto finish;
|
||||
|
||||
daemon_signal_done();
|
||||
return 0;
|
||||
}
|
||||
if (daemon_pid_file_create() < 0) {
|
||||
daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno));
|
||||
|
||||
daemon_retval_send(2);
|
||||
goto finish;
|
||||
daemon_signal_done();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1322,6 +1476,12 @@ int main(int argc, char **argv) {
|
||||
/* end libdaemon stuff */
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
main_thread_id = pthread_self();
|
||||
if (!main_thread_id)
|
||||
debug(1, "Main thread is set up to be NULL!");
|
||||
|
||||
signal_setup();
|
||||
|
||||
// make sure the program can create files that group and world can read
|
||||
@@ -1334,6 +1494,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
config.output->init(argc - audio_arg, argv + audio_arg);
|
||||
|
||||
pthread_cleanup_push(main_cleanup_handler, NULL);
|
||||
|
||||
// daemon_log(LOG_NOTICE, "startup");
|
||||
|
||||
switch (endianness) {
|
||||
@@ -1374,25 +1536,36 @@ int main(int argc, char **argv) {
|
||||
|
||||
char *version_dbs = get_version_string();
|
||||
if (version_dbs) {
|
||||
debug(1, "Version: \"%s\"", version_dbs);
|
||||
debug(1, "software version: \"%s\"", version_dbs);
|
||||
free(version_dbs);
|
||||
} else {
|
||||
debug(1, "Can't print the version information!");
|
||||
debug(1, "can't print the version information!");
|
||||
}
|
||||
|
||||
/* Print out options */
|
||||
debug(1, "log verbosity is %d.", debuglev);
|
||||
debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off");
|
||||
debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped "
|
||||
"deliberately.",
|
||||
config.diagnostic_drop_packet_fraction);
|
||||
debug(1, "statistics_requester status is %d.", config.statistics_requested);
|
||||
#if CONFIG_LIBDAEMON
|
||||
debug(1, "daemon status is %d.", config.daemonise);
|
||||
debug(1, "deamon pid file path is \"%s\".", pid_file_proc());
|
||||
#endif
|
||||
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);
|
||||
debug(1, "player name is \"%s\".", config.service_name);
|
||||
debug(1, "backend is \"%s\".", config.output_name);
|
||||
debug(1, "on-start action is \"%s\".", config.cmd_start);
|
||||
debug(1, "on-stop action is \"%s\".", config.cmd_stop);
|
||||
debug(1, "run_this_before_play_begins action is \"%s\".", config.cmd_start);
|
||||
debug(1, "run_this_after_play_ends action is \"%s\".", config.cmd_stop);
|
||||
debug(1, "wait-cmd status is %d.", config.cmd_blocking);
|
||||
debug(1, "on-start returns output is %d.", config.cmd_start_returns_output);
|
||||
debug(1, "run_this_before_play_begins may return output is %d.", config.cmd_start_returns_output);
|
||||
debug(1, "run_this_if_an_unfixable_error_is_detected action is \"%s\".", config.cmd_unfixable);
|
||||
debug(1, "run_this_before_entering_active_state action is \"%s\".", config.cmd_active_start);
|
||||
debug(1, "run_this_after_exiting_active_state action is \"%s\".", config.cmd_active_stop);
|
||||
debug(1, "active_state_timeout is %f seconds.", config.active_state_timeout);
|
||||
debug(1, "mdns backend \"%s\".", config.mdns_name);
|
||||
debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency);
|
||||
debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing);
|
||||
@@ -1406,6 +1579,12 @@ int main(int argc, char **argv) {
|
||||
debug(1, "volume_max_db is %d.", config.volume_max_db);
|
||||
else
|
||||
debug(1, "volume_max_db is not set");
|
||||
debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.",
|
||||
config.volume_range_db);
|
||||
debug(
|
||||
1,
|
||||
"volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used first) is %d.",
|
||||
config.volume_range_hw_priority);
|
||||
debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).",
|
||||
config.playback_mode);
|
||||
debug(1, "disable_synchronization is %d.", config.no_sync);
|
||||
@@ -1419,8 +1598,6 @@ int main(int argc, char **argv) {
|
||||
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, "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);
|
||||
debug(1, "decoders_supported field is %d.", config.decoders_supported);
|
||||
debug(1, "use_apple_decoder is %d.", config.use_apple_decoder);
|
||||
@@ -1454,28 +1631,31 @@ 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);
|
||||
debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off");
|
||||
debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped "
|
||||
"deliberately.",
|
||||
config.diagnostic_drop_packet_fraction);
|
||||
|
||||
uint8_t ap_md5[16];
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
MD5_CTX ctx;
|
||||
MD5_Init(&ctx);
|
||||
MD5_Update(&ctx, config.service_name, strlen(config.service_name));
|
||||
MD5_Final(ap_md5, &ctx);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#if MBEDTLS_VERSION_MINOR >= 7
|
||||
mbedtls_md5_context tctx;
|
||||
mbedtls_md5_starts_ret(&tctx);
|
||||
mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
||||
mbedtls_md5_finish_ret(&tctx, ap_md5);
|
||||
#else
|
||||
mbedtls_md5_context tctx;
|
||||
mbedtls_md5_starts(&tctx);
|
||||
mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
||||
mbedtls_md5_finish(&tctx, ap_md5);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
md5_context tctx;
|
||||
md5_starts(&tctx);
|
||||
md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
||||
@@ -1486,36 +1666,44 @@ int main(int argc, char **argv) {
|
||||
metadata_init(); // create the metadata pipe if necessary
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_METADATA_HUB
|
||||
#ifdef CONFIG_METADATA_HUB
|
||||
// debug(1, "Initialising metadata hub");
|
||||
metadata_hub_init();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DACP_CLIENT
|
||||
#ifdef CONFIG_DACP_CLIENT
|
||||
// debug(1, "Requesting DACP Monitor");
|
||||
dacp_monitor_start();
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
|
||||
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
|
||||
// Start up DBUS services after initial settings are all made
|
||||
// debug(1, "Starting up D-Bus services");
|
||||
pthread_create(&dbus_thread, NULL, &dbus_thread_func, NULL);
|
||||
#ifdef HAVE_DBUS
|
||||
#ifdef CONFIG_DBUS_INTERFACE
|
||||
start_dbus_service();
|
||||
#endif
|
||||
#ifdef HAVE_MPRIS
|
||||
#ifdef CONFIG_MPRIS_INTERFACE
|
||||
start_mpris_service();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MQTT
|
||||
if (config.mqtt_enabled) {
|
||||
initialise_mqtt();
|
||||
}
|
||||
|
||||
activity_monitor_start();
|
||||
|
||||
>>>>>>> development
|
||||
// daemon_log(LOG_INFO, "Successful Startup");
|
||||
rtsp_listen_loop();
|
||||
|
||||
// should not reach this...
|
||||
shairport_shutdown();
|
||||
finish:
|
||||
daemon_log(LOG_NOTICE, "Unexpected exit...");
|
||||
daemon_retval_send(255);
|
||||
daemon_pid_file_remove();
|
||||
return 1;
|
||||
// daemon_log(LOG_NOTICE, "Unexpected exit...");
|
||||
// daemon_retval_send(0);
|
||||
// daemon_pid_file_remove();
|
||||
pthread_cleanup_pop(1);
|
||||
debug(1, "Odd exit point");
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
+32
-8
@@ -6,6 +6,8 @@
|
||||
* tinysvcmdns - a tiny MDNS implementation for publishing services
|
||||
* Copyright (C) 2011 Darell Tan
|
||||
* All rights reserved.
|
||||
* Updated many times by Mike Brady (c) 2014 -- 2019
|
||||
* Includes fixes for CVE-12087 and CVE-2017-12130
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -81,6 +83,8 @@ struct name_comp {
|
||||
|
||||
// duplicates a name
|
||||
inline uint8_t *dup_nlabel(const uint8_t *n) {
|
||||
if (n == NULL)
|
||||
return NULL;
|
||||
assert(n[0] <= 63); // prevent mis-use
|
||||
return (uint8_t *)strdup((char *)n);
|
||||
}
|
||||
@@ -126,7 +130,8 @@ char *nlabel_to_str(const uint8_t *name) {
|
||||
size_t buf_len = 256;
|
||||
|
||||
assert(name != NULL);
|
||||
|
||||
if (name == NULL)
|
||||
return NULL;
|
||||
label = labelp = malloc(buf_len);
|
||||
|
||||
if (label) {
|
||||
@@ -186,7 +191,9 @@ uint8_t *create_label(const char *txt) {
|
||||
int len;
|
||||
uint8_t *s;
|
||||
|
||||
assert(txt != NULL);
|
||||
// assert(txt != NULL);
|
||||
if (txt == NULL)
|
||||
return NULL;
|
||||
len = strlen(txt);
|
||||
if (len > 63)
|
||||
return NULL;
|
||||
@@ -416,7 +423,7 @@ int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) {
|
||||
if (*rr_head == NULL) {
|
||||
*rr_head = node;
|
||||
} else {
|
||||
struct rr_list *e = *rr_head, *taile;
|
||||
struct rr_list *e = *rr_head, *taile = NULL;
|
||||
for (; e; e = e->next) {
|
||||
// already in list - don't add
|
||||
if (e->e == rr) {
|
||||
@@ -426,7 +433,10 @@ int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) {
|
||||
if (e->next == NULL)
|
||||
taile = e;
|
||||
}
|
||||
taile->next = node;
|
||||
if (taile)
|
||||
taile->next = node;
|
||||
else
|
||||
DEBUG_PRINTF("taile not given a value.\n");
|
||||
}
|
||||
} else {
|
||||
die("can not allocate memory for \"node\" in tinysvcmdns.");
|
||||
@@ -500,7 +510,7 @@ struct rr_entry *rr_create(uint8_t *name, enum rr_type type) {
|
||||
}
|
||||
|
||||
void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type) {
|
||||
assert(rr_nsec->type = RR_NSEC);
|
||||
assert((rr_nsec->type = RR_NSEC));
|
||||
assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap));
|
||||
|
||||
rr_nsec->data.NSEC.bitmap[type / 8] = 1 << (7 - (type % 8));
|
||||
@@ -675,9 +685,12 @@ static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
|
||||
if (rr)
|
||||
memset(rr, 0, sizeof(struct rr_entry));
|
||||
else
|
||||
die("could not allocate memory for \"rr\" in tinysvcmdns");
|
||||
goto err;
|
||||
|
||||
name = uncompress_nlabel(pkt_buf, pkt_len, off);
|
||||
if (name == NULL)
|
||||
goto err;
|
||||
|
||||
p += label_len(pkt_buf, pkt_len, off);
|
||||
rr->name = name;
|
||||
|
||||
@@ -691,6 +704,10 @@ static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
|
||||
rr_list_append(&pkt->rr_qn, rr);
|
||||
|
||||
return p - (pkt_buf + off);
|
||||
|
||||
err:
|
||||
free(rr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// parse the MDNS RR section
|
||||
@@ -713,10 +730,13 @@ static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
|
||||
if (rr)
|
||||
memset(rr, 0, sizeof(struct rr_entry));
|
||||
else
|
||||
die("could not allocate memory for \"rr (2)\" in tinysvcmdns");
|
||||
goto err;
|
||||
|
||||
name = uncompress_nlabel(pkt_buf, pkt_len, off);
|
||||
p += label_len(pkt_buf, pkt_len, off);
|
||||
if (name == NULL)
|
||||
goto err;
|
||||
|
||||
// parse the MDNS RR section p += label_len(pkt_buf, pkt_len, off);
|
||||
rr->name = name;
|
||||
|
||||
rr->type = mdns_read_u16(p);
|
||||
@@ -819,6 +839,10 @@ static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
|
||||
rr_list_append(&pkt->rr_ans, rr);
|
||||
|
||||
return p - (pkt_buf + off);
|
||||
|
||||
err:
|
||||
free(rr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// parse a MDNS packet into an mdns_pkt struct
|
||||
|
||||
Reference in New Issue
Block a user