diff --git a/.gitignore b/.gitignore
index 9e055931..53812396 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
/INSTALL
/shairport-sync
/shairport-sync.exe
-/*.o
+*.o
/*~
*.xml~
/config.mk
@@ -17,6 +17,7 @@ depcomp
install-sh
missing
stamp-h1
+.dirstamp
.deps
man/Makefile
man/Makefile.in
@@ -26,3 +27,7 @@ scripts/shairport-sync
# Some eclipse project files
.cproject
.project
+
+# macOS stuff
+.DS_Store
+shairport-sync.xcodeproj
diff --git a/FFTConvolver/AudioFFT.cpp b/FFTConvolver/AudioFFT.cpp
new file mode 100755
index 00000000..32b3ddaa
--- /dev/null
+++ b/FFTConvolver/AudioFFT.cpp
@@ -0,0 +1,1018 @@
+// ==================================================================================
+// Copyright (c) 2016 HiFi-LoFi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is furnished
+// to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ==================================================================================
+
+#include "AudioFFT.h"
+
+#include
+#include
+#include
+
+
+#if defined(AUDIOFFT_APPLE_ACCELERATE)
+ #define AUDIOFFT_APPLE_ACCELERATE_USED
+ #include
+ #include
+#elif defined (AUDIOFFT_FFTW3)
+ #define AUDIOFFT_FFTW3_USED
+ #include
+#else
+ #if !defined(AUDIOFFT_OOURA)
+ #define AUDIOFFT_OOURA
+ #endif
+ #define AUDIOFFT_OOURA_USED
+ #include
+#endif
+
+
+namespace audiofft
+{
+
+ namespace details
+ {
+
+ static bool IsPowerOf2(size_t val)
+ {
+ return (val == 1 || (val & (val-1)) == 0);
+ }
+
+
+ template
+ void ConvertBuffer(TypeDest* dest, const TypeSrc* src, size_t len)
+ {
+ for (size_t i=0; i(src[i]);
+ }
+ }
+
+
+ template
+ void ScaleBuffer(TypeDest* dest, const TypeSrc* src, const TypeFactor factor, size_t len)
+ {
+ for (size_t i=0; i(static_cast(src[i]) * factor);
+ }
+ }
+
+
+ // ================================================================
+
+
+#ifdef AUDIOFFT_OOURA_USED
+
+ /**
+ * @internal
+ * @class OouraFFT
+ * @brief FFT implementation based on the great radix-4 routines by Takuya Ooura
+ */
+ class OouraFFT : public AudioFFTImpl
+ {
+ public:
+ OouraFFT() :
+ AudioFFTImpl(),
+ _size(0),
+ _ip(),
+ _w(),
+ _buffer()
+ {
+ }
+
+ virtual void init(size_t size) override
+ {
+ if (_size != size)
+ {
+ _ip.resize(2 + static_cast(std::sqrt(static_cast(size))));
+ _w.resize(size / 2);
+ _buffer.resize(size);
+ _size = size;
+
+ const int size4 = static_cast(_size) / 4;
+ makewt(size4, _ip.data(), _w.data());
+ makect(size4, _ip.data(), _w.data() + size4);
+ }
+ }
+
+ virtual void fft(const float* data, float* re, float* im) override
+ {
+ // Convert into the format as required by the Ooura FFT
+ ConvertBuffer(&_buffer[0], data, _size);
+
+ rdft(static_cast(_size), +1, _buffer.data(), _ip.data(), _w.data());
+
+ // Convert back to split-complex
+ {
+ double* b = &_buffer[0];
+ double* bEnd = b + _size;
+ float *r = re;
+ float *i = im;
+ while (b != bEnd)
+ {
+ *(r++) = static_cast(*(b++));
+ *(i++) = static_cast(-(*(b++)));
+ }
+ }
+ const size_t size2 = _size / 2;
+ re[size2] = -im[0];
+ im[0] = 0.0;
+ im[size2] = 0.0;
+ }
+
+ virtual void ifft(float* data, const float* re, const float* im) override
+ {
+ // Convert into the format as required by the Ooura FFT
+ {
+ double* b = &_buffer[0];
+ double* bEnd = b + _size;
+ const float *r = re;
+ const float *i = im;
+ while (b != bEnd)
+ {
+ *(b++) = static_cast(*(r++));
+ *(b++) = -static_cast(*(i++));
+ }
+ _buffer[1] = re[_size / 2];
+ }
+
+ rdft(static_cast(_size), -1, _buffer.data(), _ip.data(), _w.data());
+
+ // Convert back to split-complex
+ ScaleBuffer(data, &_buffer[0], 2.0 / static_cast(_size), _size);
+ }
+
+ private:
+ size_t _size;
+ std::vector _ip;
+ std::vector _w;
+ std::vector _buffer;
+
+ void rdft(int n, int isgn, double *a, int *ip, double *w)
+ {
+ int nw = ip[0];
+ int nc = ip[1];
+
+ if (isgn >= 0)
+ {
+ if (n > 4)
+ {
+ bitrv2(n, ip + 2, a);
+ cftfsub(n, a, w);
+ rftfsub(n, a, nc, w + nw);
+ }
+ else if (n == 4)
+ {
+ cftfsub(n, a, w);
+ }
+ double xi = a[0] - a[1];
+ a[0] += a[1];
+ a[1] = xi;
+ }
+ else
+ {
+ a[1] = 0.5 * (a[0] - a[1]);
+ a[0] -= a[1];
+ if (n > 4)
+ {
+ rftbsub(n, a, nc, w + nw);
+ bitrv2(n, ip + 2, a);
+ cftbsub(n, a, w);
+ }
+ else if (n == 4)
+ {
+ cftfsub(n, a, w);
+ }
+ }
+ }
+
+
+ /* -------- initializing routines -------- */
+
+ void makewt(int nw, int *ip, double *w)
+ {
+ int j, nwh;
+ double delta, x, y;
+
+ ip[0] = nw;
+ ip[1] = 1;
+ if (nw > 2) {
+ nwh = nw >> 1;
+ delta = atan(1.0) / nwh;
+ w[0] = 1;
+ w[1] = 0;
+ w[nwh] = cos(delta * nwh);
+ w[nwh + 1] = w[nwh];
+ if (nwh > 2) {
+ for (j = 2; j < nwh; j += 2) {
+ x = cos(delta * j);
+ y = sin(delta * j);
+ w[j] = x;
+ w[j + 1] = y;
+ w[nw - j] = y;
+ w[nw - j + 1] = x;
+ }
+ bitrv2(nw, ip + 2, w);
+ }
+ }
+ }
+
+
+ void makect(int nc, int *ip, double *c)
+ {
+ int j, nch;
+ double delta;
+
+ ip[1] = nc;
+ if (nc > 1) {
+ nch = nc >> 1;
+ delta = atan(1.0) / nch;
+ c[0] = cos(delta * nch);
+ c[nch] = 0.5 * c[0];
+ for (j = 1; j < nch; j++) {
+ c[j] = 0.5 * cos(delta * j);
+ c[nc - j] = 0.5 * sin(delta * j);
+ }
+ }
+ }
+
+
+ /* -------- child routines -------- */
+
+
+ void bitrv2(int n, int *ip, double *a)
+ {
+ int j, j1, k, k1, l, m, m2;
+ double xr, xi, yr, yi;
+
+ ip[0] = 0;
+ l = n;
+ m = 1;
+ while ((m << 3) < l) {
+ l >>= 1;
+ for (j = 0; j < m; j++) {
+ ip[m + j] = ip[j] + l;
+ }
+ m <<= 1;
+ }
+ m2 = 2 * m;
+ if ((m << 3) == l) {
+ for (k = 0; k < m; k++) {
+ for (j = 0; j < k; j++) {
+ j1 = 2 * j + ip[k];
+ k1 = 2 * k + ip[j];
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += 2 * m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 -= m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += 2 * m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ }
+ j1 = 2 * k + m2 + ip[k];
+ k1 = j1 + m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ }
+ } else {
+ for (k = 1; k < m; k++) {
+ for (j = 0; j < k; j++) {
+ j1 = 2 * j + ip[k];
+ k1 = 2 * k + ip[j];
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ }
+ }
+ }
+ }
+
+
+ void cftfsub(int n, double *a, double *w)
+ {
+ int j, j1, j2, j3, l;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ l = 2;
+ if (n > 8) {
+ cft1st(n, a, w);
+ l = 8;
+ while ((l << 2) < n) {
+ cftmdl(n, l, a, w);
+ l <<= 2;
+ }
+ }
+ if ((l << 2) == n) {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ a[j2] = x0r - x2r;
+ a[j2 + 1] = x0i - x2i;
+ a[j1] = x1r - x3i;
+ a[j1 + 1] = x1i + x3r;
+ a[j3] = x1r + x3i;
+ a[j3 + 1] = x1i - x3r;
+ }
+ } else {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ x0r = a[j] - a[j1];
+ x0i = a[j + 1] - a[j1 + 1];
+ a[j] += a[j1];
+ a[j + 1] += a[j1 + 1];
+ a[j1] = x0r;
+ a[j1 + 1] = x0i;
+ }
+ }
+ }
+
+
+ void cftbsub(int n, double *a, double *w)
+ {
+ int j, j1, j2, j3, l;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ l = 2;
+ if (n > 8) {
+ cft1st(n, a, w);
+ l = 8;
+ while ((l << 2) < n) {
+ cftmdl(n, l, a, w);
+ l <<= 2;
+ }
+ }
+ if ((l << 2) == n) {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = -a[j + 1] - a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = -a[j + 1] + a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i - x2i;
+ a[j2] = x0r - x2r;
+ a[j2 + 1] = x0i + x2i;
+ a[j1] = x1r - x3i;
+ a[j1 + 1] = x1i - x3r;
+ a[j3] = x1r + x3i;
+ a[j3 + 1] = x1i + x3r;
+ }
+ } else {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ x0r = a[j] - a[j1];
+ x0i = -a[j + 1] + a[j1 + 1];
+ a[j] += a[j1];
+ a[j + 1] = -a[j + 1] - a[j1 + 1];
+ a[j1] = x0r;
+ a[j1 + 1] = x0i;
+ }
+ }
+ }
+
+
+ void cft1st(int n, double *a, double *w)
+ {
+ int j, k1, k2;
+ double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ x0r = a[0] + a[2];
+ x0i = a[1] + a[3];
+ x1r = a[0] - a[2];
+ x1i = a[1] - a[3];
+ x2r = a[4] + a[6];
+ x2i = a[5] + a[7];
+ x3r = a[4] - a[6];
+ x3i = a[5] - a[7];
+ a[0] = x0r + x2r;
+ a[1] = x0i + x2i;
+ a[4] = x0r - x2r;
+ a[5] = x0i - x2i;
+ a[2] = x1r - x3i;
+ a[3] = x1i + x3r;
+ a[6] = x1r + x3i;
+ a[7] = x1i - x3r;
+ wk1r = w[2];
+ x0r = a[8] + a[10];
+ x0i = a[9] + a[11];
+ x1r = a[8] - a[10];
+ x1i = a[9] - a[11];
+ x2r = a[12] + a[14];
+ x2i = a[13] + a[15];
+ x3r = a[12] - a[14];
+ x3i = a[13] - a[15];
+ a[8] = x0r + x2r;
+ a[9] = x0i + x2i;
+ a[12] = x2i - x0i;
+ a[13] = x0r - x2r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[10] = wk1r * (x0r - x0i);
+ a[11] = wk1r * (x0r + x0i);
+ x0r = x3i + x1r;
+ x0i = x3r - x1i;
+ a[14] = wk1r * (x0i - x0r);
+ a[15] = wk1r * (x0i + x0r);
+ k1 = 0;
+ for (j = 16; j < n; j += 16) {
+ k1 += 2;
+ k2 = 2 * k1;
+ wk2r = w[k1];
+ wk2i = w[k1 + 1];
+ wk1r = w[k2];
+ wk1i = w[k2 + 1];
+ wk3r = wk1r - 2 * wk2i * wk1i;
+ wk3i = 2 * wk2i * wk1r - wk1i;
+ x0r = a[j] + a[j + 2];
+ x0i = a[j + 1] + a[j + 3];
+ x1r = a[j] - a[j + 2];
+ x1i = a[j + 1] - a[j + 3];
+ x2r = a[j + 4] + a[j + 6];
+ x2i = a[j + 5] + a[j + 7];
+ x3r = a[j + 4] - a[j + 6];
+ x3i = a[j + 5] - a[j + 7];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j + 4] = wk2r * x0r - wk2i * x0i;
+ a[j + 5] = wk2r * x0i + wk2i * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j + 2] = wk1r * x0r - wk1i * x0i;
+ a[j + 3] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j + 6] = wk3r * x0r - wk3i * x0i;
+ a[j + 7] = wk3r * x0i + wk3i * x0r;
+ wk1r = w[k2 + 2];
+ wk1i = w[k2 + 3];
+ wk3r = wk1r - 2 * wk2r * wk1i;
+ wk3i = 2 * wk2r * wk1r - wk1i;
+ x0r = a[j + 8] + a[j + 10];
+ x0i = a[j + 9] + a[j + 11];
+ x1r = a[j + 8] - a[j + 10];
+ x1i = a[j + 9] - a[j + 11];
+ x2r = a[j + 12] + a[j + 14];
+ x2i = a[j + 13] + a[j + 15];
+ x3r = a[j + 12] - a[j + 14];
+ x3i = a[j + 13] - a[j + 15];
+ a[j + 8] = x0r + x2r;
+ a[j + 9] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j + 12] = -wk2i * x0r - wk2r * x0i;
+ a[j + 13] = -wk2i * x0i + wk2r * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j + 10] = wk1r * x0r - wk1i * x0i;
+ a[j + 11] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j + 14] = wk3r * x0r - wk3i * x0i;
+ a[j + 15] = wk3r * x0i + wk3i * x0r;
+ }
+ }
+
+
+ void cftmdl(int n, int l, double *a, double *w)
+ {
+ int j, j1, j2, j3, k, k1, k2, m, m2;
+ double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ m = l << 2;
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ a[j2] = x0r - x2r;
+ a[j2 + 1] = x0i - x2i;
+ a[j1] = x1r - x3i;
+ a[j1 + 1] = x1i + x3r;
+ a[j3] = x1r + x3i;
+ a[j3 + 1] = x1i - x3r;
+ }
+ wk1r = w[2];
+ for (j = m; j < l + m; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ a[j2] = x2i - x0i;
+ a[j2 + 1] = x0r - x2r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j1] = wk1r * (x0r - x0i);
+ a[j1 + 1] = wk1r * (x0r + x0i);
+ x0r = x3i + x1r;
+ x0i = x3r - x1i;
+ a[j3] = wk1r * (x0i - x0r);
+ a[j3 + 1] = wk1r * (x0i + x0r);
+ }
+ k1 = 0;
+ m2 = 2 * m;
+ for (k = m2; k < n; k += m2) {
+ k1 += 2;
+ k2 = 2 * k1;
+ wk2r = w[k1];
+ wk2i = w[k1 + 1];
+ wk1r = w[k2];
+ wk1i = w[k2 + 1];
+ wk3r = wk1r - 2 * wk2i * wk1i;
+ wk3i = 2 * wk2i * wk1r - wk1i;
+ for (j = k; j < l + k; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j2] = wk2r * x0r - wk2i * x0i;
+ a[j2 + 1] = wk2r * x0i + wk2i * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j1] = wk1r * x0r - wk1i * x0i;
+ a[j1 + 1] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j3] = wk3r * x0r - wk3i * x0i;
+ a[j3 + 1] = wk3r * x0i + wk3i * x0r;
+ }
+ wk1r = w[k2 + 2];
+ wk1i = w[k2 + 3];
+ wk3r = wk1r - 2 * wk2r * wk1i;
+ wk3i = 2 * wk2r * wk1r - wk1i;
+ for (j = k + m; j < l + (k + m); j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j2] = -wk2i * x0r - wk2r * x0i;
+ a[j2 + 1] = -wk2i * x0i + wk2r * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j1] = wk1r * x0r - wk1i * x0i;
+ a[j1 + 1] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j3] = wk3r * x0r - wk3i * x0i;
+ a[j3 + 1] = wk3r * x0i + wk3i * x0r;
+ }
+ }
+ }
+
+
+ void rftfsub(int n, double *a, int nc, double *c)
+ {
+ int j, k, kk, ks, m;
+ double wkr, wki, xr, xi, yr, yi;
+
+ m = n >> 1;
+ ks = 2 * nc / m;
+ kk = 0;
+ for (j = 2; j < m; j += 2) {
+ k = n - j;
+ kk += ks;
+ wkr = 0.5 - c[nc - kk];
+ wki = c[kk];
+ xr = a[j] - a[k];
+ xi = a[j + 1] + a[k + 1];
+ yr = wkr * xr - wki * xi;
+ yi = wkr * xi + wki * xr;
+ a[j] -= yr;
+ a[j + 1] -= yi;
+ a[k] += yr;
+ a[k + 1] -= yi;
+ }
+ }
+
+
+ void rftbsub(int n, double *a, int nc, double *c)
+ {
+ int j, k, kk, ks, m;
+ double wkr, wki, xr, xi, yr, yi;
+
+ a[1] = -a[1];
+ m = n >> 1;
+ ks = 2 * nc / m;
+ kk = 0;
+ for (j = 2; j < m; j += 2) {
+ k = n - j;
+ kk += ks;
+ wkr = 0.5 - c[nc - kk];
+ wki = c[kk];
+ xr = a[j] - a[k];
+ xi = a[j + 1] + a[k + 1];
+ yr = wkr * xr + wki * xi;
+ yi = wkr * xi - wki * xr;
+ a[j] -= yr;
+ a[j + 1] = yi - a[j + 1];
+ a[k] += yr;
+ a[k + 1] = yi - a[k + 1];
+ }
+ a[m + 1] = -a[m + 1];
+ }
+
+ OouraFFT(const OouraFFT&) = delete;
+ OouraFFT& operator=(const OouraFFT&) = delete;
+ };
+
+ std::unique_ptr MakeAudioFFTImpl()
+ {
+ return std::unique_ptr(new OouraFFT());
+ }
+
+
+#endif // AUDIOFFT_OOURA_USED
+
+
+ // ================================================================
+
+
+#ifdef AUDIOFFT_APPLE_ACCELERATE_USED
+
+
+ /**
+ * @internal
+ * @class AppleAccelerateFFT
+ * @brief FFT implementation using the Apple Accelerate framework internally
+ */
+ class AppleAccelerateFFT : public AudioFFTImpl
+ {
+ public:
+ AppleAccelerateFFT() :
+ AudioFFTImpl(),
+ _size(0),
+ _powerOf2(0),
+ _fftSetup(0),
+ _re(),
+ _im()
+ {
+ }
+
+ virtual ~AppleAccelerateFFT()
+ {
+ init(0);
+ }
+
+ virtual void init(size_t size) override
+ {
+ if (_fftSetup)
+ {
+ vDSP_destroy_fftsetup(_fftSetup);
+ _size = 0;
+ _powerOf2 = 0;
+ _fftSetup = 0;
+ _re.clear();
+ _im.clear();
+ }
+
+ if (size > 0)
+ {
+ _size = size;
+ _powerOf2 = 0;
+ while ((1 << _powerOf2) < _size)
+ {
+ ++_powerOf2;
+ }
+ _fftSetup = vDSP_create_fftsetup(_powerOf2, FFT_RADIX2);
+ _re.resize(_size / 2);
+ _im.resize(_size / 2);
+ }
+ }
+
+ virtual void fft(const float* data, float* re, float* im) override
+ {
+ const size_t size2 = _size / 2;
+ DSPSplitComplex splitComplex;
+ splitComplex.realp = re;
+ splitComplex.imagp = im;
+ vDSP_ctoz(reinterpret_cast(data), 2, &splitComplex, 1, size2);
+ vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_FORWARD);
+ const float factor = 0.5f;
+ vDSP_vsmul(re, 1, &factor, re, 1, size2);
+ vDSP_vsmul(im, 1, &factor, im, 1, size2);
+ re[size2] = im[0];
+ im[0] = 0.0f;
+ im[size2] = 0.0f;
+ }
+
+ virtual void ifft(float* data, const float* re, const float* im) override
+ {
+ const size_t size2 = _size / 2;
+ ::memcpy(_re.data(), re, size2 * sizeof(float));
+ ::memcpy(_im.data(), im, size2 * sizeof(float));
+ _im[0] = re[size2];
+ DSPSplitComplex splitComplex;
+ splitComplex.realp = _re.data();
+ splitComplex.imagp = _im.data();
+ vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_INVERSE);
+ vDSP_ztoc(&splitComplex, 1, reinterpret_cast(data), 2, size2);
+ const float factor = 1.0f / static_cast(_size);
+ vDSP_vsmul(data, 1, &factor, data, 1, _size);
+ }
+
+ private:
+ size_t _size;
+ size_t _powerOf2;
+ FFTSetup _fftSetup;
+ std::vector _re;
+ std::vector _im;
+
+ AppleAccelerateFFT(const AppleAccelerateFFT&) = delete;
+ AppleAccelerateFFT& operator=(const AppleAccelerateFFT&) = delete;
+ };
+
+
+ std::unique_ptr MakeAudioFFTImpl()
+ {
+ return std::unique_ptr(new AppleAccelerateFFT());
+ }
+
+
+#endif // AUDIOFFT_APPLE_ACCELERATE_USED
+
+
+ // ================================================================
+
+
+#ifdef AUDIOFFT_FFTW3_USED
+
+
+ /**
+ * @internal
+ * @class FFTW3FFT
+ * @brief FFT implementation using FFTW3 internally (see fftw.org)
+ */
+ class FFTW3FFT : public AudioFFTImpl
+ {
+ public:
+ FFTW3FFT() :
+ AudioFFTImpl(),
+ _size(0),
+ _complexSize(0),
+ _planForward(0),
+ _planBackward(0),
+ _data(0),
+ _re(0),
+ _im(0)
+ {
+ }
+
+ virtual ~FFTW3FFT()
+ {
+ init(0);
+ }
+
+ virtual void init(size_t size) override
+ {
+ if (_size != size)
+ {
+ if (_size > 0)
+ {
+ fftwf_destroy_plan(_planForward);
+ fftwf_destroy_plan(_planBackward);
+ _planForward = 0;
+ _planBackward = 0;
+ _size = 0;
+ _complexSize = 0;
+
+ if (_data)
+ {
+ fftwf_free(_data);
+ _data = 0;
+ }
+
+ if (_re)
+ {
+ fftwf_free(_re);
+ _re = 0;
+ }
+
+ if (_im)
+ {
+ fftwf_free(_im);
+ _im = 0;
+ }
+ }
+
+ if (size > 0)
+ {
+ _size = size;
+ _complexSize = AudioFFT::ComplexSize(_size);
+ const size_t complexSize = AudioFFT::ComplexSize(_size);
+ _data = reinterpret_cast(fftwf_malloc(_size * sizeof(float)));
+ _re = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float)));
+ _im = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float)));
+
+ fftw_iodim dim;
+ dim.n = static_cast(size);
+ dim.is = 1;
+ dim.os = 1;
+ _planForward = fftwf_plan_guru_split_dft_r2c(1, &dim, 0, 0, _data, _re, _im, FFTW_MEASURE);
+ _planBackward = fftwf_plan_guru_split_dft_c2r(1, &dim, 0, 0, _re, _im, _data, FFTW_MEASURE);
+ }
+ }
+ }
+
+ virtual void fft(const float* data, float* re, float* im) override
+ {
+ ::memcpy(_data, data, _size * sizeof(float));
+ fftwf_execute_split_dft_r2c(_planForward, _data, _re, _im);
+ ::memcpy(re, _re, _complexSize * sizeof(float));
+ ::memcpy(im, _im, _complexSize * sizeof(float));
+ }
+
+ void ifft(float* data, const float* re, const float* im)
+ {
+ ::memcpy(_re, re, _complexSize * sizeof(float));
+ ::memcpy(_im, im, _complexSize * sizeof(float));
+ fftwf_execute_split_dft_c2r(_planBackward, _re, _im, _data);
+ ScaleBuffer(data, _data, 1.0f / static_cast(_size), _size);
+ }
+
+ private:
+ size_t _size;
+ size_t _complexSize;
+ fftwf_plan _planForward;
+ fftwf_plan _planBackward;
+ float* _data;
+ float* _re;
+ float* _im;
+
+ FFTW3FFT(const FFTW3FFT&) = delete;
+ FFTW3FFT& operator=(const FFTW3FFT&) = delete;
+ };
+
+
+ std::unique_ptr MakeAudioFFTImpl()
+ {
+ return std::unique_ptr(new FFTW3FFT());
+ }
+
+
+#endif // AUDIOFFT_FFTW3_USED
+
+ } // End of namespace details
+
+
+ // =============================================================
+
+
+ AudioFFT::AudioFFT() :
+ _impl(details::MakeAudioFFTImpl())
+ {
+ }
+
+
+ void AudioFFT::init(size_t size)
+ {
+ assert(details::IsPowerOf2(size));
+ _impl->init(size);
+ }
+
+
+ void AudioFFT::fft(const float* data, float* re, float* im)
+ {
+ _impl->fft(data, re, im);
+ }
+
+
+ void AudioFFT::ifft(float* data, const float* re, const float* im)
+ {
+ _impl->ifft(data, re, im);
+ }
+
+
+ size_t AudioFFT::ComplexSize(size_t size)
+ {
+ return (size / 2) + 1;
+ }
+
+} // End of namespace
diff --git a/FFTConvolver/AudioFFT.h b/FFTConvolver/AudioFFT.h
new file mode 100755
index 00000000..80cd2edc
--- /dev/null
+++ b/FFTConvolver/AudioFFT.h
@@ -0,0 +1,176 @@
+// ==================================================================================
+// Copyright (c) 2016 HiFi-LoFi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is furnished
+// to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ==================================================================================
+
+#ifndef _AUDIOFFT_H
+#define _AUDIOFFT_H
+
+
+/**
+* AudioFFT provides real-to-complex/complex-to-real FFT routines.
+*
+* Features:
+*
+* - Real-complex FFT and complex-real inverse FFT for power-of-2-sized real data.
+*
+* - Uniform interface to different FFT implementations (currently Ooura, FFTW3 and Apple Accelerate).
+*
+* - Complex data is handled in "split-complex" format, i.e. there are separate
+* arrays for the real and imaginary parts which can be useful for SIMD optimizations
+* (split-complex arrays have to be of length (size/2+1) representing bins from DC
+* to Nyquist frequency).
+*
+* - Output is "ready to use" (all scaling etc. is already handled internally).
+*
+* - No allocations/deallocations after the initialization which makes it usable
+* for real-time audio applications (that's what I wrote it for and using it).
+*
+*
+* How to use it in your project:
+*
+* - Add the .h and .cpp file to your project - that's all.
+*
+* - To get extra speed, you can link FFTW3 to your project and define
+* AUDIOFFT_FFTW3 (however, please check whether your project suits the
+* according license).
+*
+* - To get the best speed on Apple platforms, you can link the Apple
+* Accelerate framework to your project and define
+* AUDIOFFT_APPLE_ACCELERATE (however, please check whether your
+* project suits the according license).
+*
+*
+* Remarks:
+*
+* - AudioFFT is not intended to be the fastest FFT, but to be a fast-enough
+* FFT suitable for most audio applications.
+*
+* - AudioFFT uses the quite liberal MIT license.
+*
+*
+* Example usage:
+* @code
+* #include "AudioFFT.h"
+*
+* void Example()
+* {
+* const size_t fftSize = 1024; // Needs to be power of 2!
+*
+* std::vector input(fftSize, 0.0f);
+* std::vector re(audiofft::AudioFFT::ComplexSize(fftSize));
+* std::vector im(audiofft::AudioFFT::ComplexSize(fftSize));
+* std::vector output(fftSize);
+*
+* audiofft::AudioFFT fft;
+* fft.init(1024);
+* fft.fft(input.data(), re.data(), im.data());
+* fft.ifft(output.data(), re.data(), im.data());
+* }
+* @endcode
+*/
+
+
+#include
+#include
+
+
+namespace audiofft
+{
+
+ namespace details
+ {
+
+ class AudioFFTImpl
+ {
+ public:
+ AudioFFTImpl() = default;
+ virtual ~AudioFFTImpl() = default;
+ virtual void init(size_t size) = 0;
+ virtual void fft(const float* data, float* re, float* im) = 0;
+ virtual void ifft(float* data, const float* re, const float* im) = 0;
+
+ private:
+ AudioFFTImpl(const AudioFFTImpl&) = delete;
+ AudioFFTImpl& operator=(const AudioFFTImpl&) = delete;
+ };
+ }
+
+
+ // ======================================================
+
+
+ /**
+ * @class AudioFFT
+ * @brief Performs 1D FFTs
+ */
+ class AudioFFT
+ {
+ public:
+ /**
+ * @brief Constructor
+ */
+ AudioFFT();
+
+ /**
+ * @brief Initializes the FFT object
+ * @param size Size of the real input (must be power 2)
+ */
+ void init(size_t size);
+
+ /**
+ * @brief Performs the forward FFT
+ * @param data The real input data (has to be of the length as specified in init())
+ * @param re The real part of the complex output (has to be of length as returned by ComplexSize())
+ * @param im The imaginary part of the complex output (has to be of length as returned by ComplexSize())
+ */
+ void fft(const float* data, float* re, float* im);
+
+ /**
+ * @brief Performs the inverse FFT
+ * @param data The real output data (has to be of the length as specified in init())
+ * @param re The real part of the complex input (has to be of length as returned by ComplexSize())
+ * @param im The imaginary part of the complex input (has to be of length as returned by ComplexSize())
+ */
+ void ifft(float* data, const float* re, const float* im);
+
+ /**
+ * @brief Calculates the necessary size of the real/imaginary complex arrays
+ * @param size The size of the real data
+ * @return The size of the real/imaginary complex arrays
+ */
+ static size_t ComplexSize(size_t size);
+
+ private:
+ std::unique_ptr _impl;
+
+ AudioFFT(const AudioFFT&) = delete;
+ AudioFFT& operator=(const AudioFFT&) = delete;
+ };
+
+
+ /**
+ * @deprecated
+ * @brief Let's keep an AudioFFTBase type around for now because it has been here already in the 1st version in order to avoid breaking existing code.
+ */
+ typedef AudioFFT AudioFFTBase;
+
+} // End of namespace
+
+#endif // Header guard
diff --git a/FFTConvolver/FFTConvolver.cpp b/FFTConvolver/FFTConvolver.cpp
new file mode 100755
index 00000000..049dbed4
--- /dev/null
+++ b/FFTConvolver/FFTConvolver.cpp
@@ -0,0 +1,202 @@
+// ==================================================================================
+// Copyright (c) 2012 HiFi-LoFi
+//
+// This is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+// ==================================================================================
+
+#include "FFTConvolver.h"
+
+#include
+#include
+
+#if defined (FFTCONVOLVER_USE_SSE)
+ #include
+#endif
+
+
+namespace fftconvolver
+{
+
+FFTConvolver::FFTConvolver() :
+ _blockSize(0),
+ _segSize(0),
+ _segCount(0),
+ _fftComplexSize(0),
+ _segments(),
+ _segmentsIR(),
+ _fftBuffer(),
+ _fft(),
+ _preMultiplied(),
+ _conv(),
+ _overlap(),
+ _current(0),
+ _inputBuffer(),
+ _inputBufferFill(0)
+{
+}
+
+
+FFTConvolver::~FFTConvolver()
+{
+ reset();
+}
+
+
+void FFTConvolver::reset()
+{
+ for (size_t i=0; i<_segCount; ++i)
+ {
+ delete _segments[i];
+ delete _segmentsIR[i];
+ }
+
+ _blockSize = 0;
+ _segSize = 0;
+ _segCount = 0;
+ _fftComplexSize = 0;
+ _segments.clear();
+ _segmentsIR.clear();
+ _fftBuffer.clear();
+ _fft.init(0);
+ _preMultiplied.clear();
+ _conv.clear();
+ _overlap.clear();
+ _current = 0;
+ _inputBuffer.clear();
+ _inputBufferFill = 0;
+}
+
+
+bool FFTConvolver::init(size_t blockSize, const Sample* ir, size_t irLen)
+{
+ reset();
+
+ if (blockSize == 0)
+ {
+ return false;
+ }
+
+ // Ignore zeros at the end of the impulse response because they only waste computation time
+ while (irLen > 0 && ::fabs(ir[irLen-1]) < 0.000001f)
+ {
+ --irLen;
+ }
+
+ if (irLen == 0)
+ {
+ return true;
+ }
+
+ _blockSize = NextPowerOf2(blockSize);
+ _segSize = 2 * _blockSize;
+ _segCount = static_cast(::ceil(static_cast(irLen) / static_cast(_blockSize)));
+ _fftComplexSize = audiofft::AudioFFT::ComplexSize(_segSize);
+
+ // FFT
+ _fft.init(_segSize);
+ _fftBuffer.resize(_segSize);
+
+ // Prepare segments
+ for (size_t i=0; i<_segCount; ++i)
+ {
+ _segments.push_back(new SplitComplex(_fftComplexSize));
+ }
+
+ // Prepare IR
+ for (size_t i=0; i<_segCount; ++i)
+ {
+ SplitComplex* segment = new SplitComplex(_fftComplexSize);
+ const size_t remaining = irLen - (i * _blockSize);
+ const size_t sizeCopy = (remaining >= _blockSize) ? _blockSize : remaining;
+ CopyAndPad(_fftBuffer, &ir[i*_blockSize], sizeCopy);
+ _fft.fft(_fftBuffer.data(), segment->re(), segment->im());
+ _segmentsIR.push_back(segment);
+ }
+
+ // Prepare convolution buffers
+ _preMultiplied.resize(_fftComplexSize);
+ _conv.resize(_fftComplexSize);
+ _overlap.resize(_blockSize);
+
+ // Prepare input buffer
+ _inputBuffer.resize(_blockSize);
+ _inputBufferFill = 0;
+
+ // Reset current position
+ _current = 0;
+
+ return true;
+}
+
+
+void FFTConvolver::process(const Sample* input, Sample* output, size_t len)
+{
+ if (_segCount == 0)
+ {
+ ::memset(output, 0, len * sizeof(Sample));
+ return;
+ }
+
+ size_t processed = 0;
+ while (processed < len)
+ {
+ const bool inputBufferWasEmpty = (_inputBufferFill == 0);
+ const size_t processing = std::min(len-processed, _blockSize-_inputBufferFill);
+ const size_t inputBufferPos = _inputBufferFill;
+ ::memcpy(_inputBuffer.data()+inputBufferPos, input+processed, processing * sizeof(Sample));
+
+ // Forward FFT
+ CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize);
+ _fft.fft(_fftBuffer.data(), _segments[_current]->re(), _segments[_current]->im());
+
+ // Complex multiplication
+ if (inputBufferWasEmpty)
+ {
+ _preMultiplied.setZero();
+ for (size_t i=1; i<_segCount; ++i)
+ {
+ const size_t indexIr = i;
+ const size_t indexAudio = (_current + i) % _segCount;
+ ComplexMultiplyAccumulate(_preMultiplied, *_segmentsIR[indexIr], *_segments[indexAudio]);
+ }
+ }
+ _conv.copyFrom(_preMultiplied);
+ ComplexMultiplyAccumulate(_conv, *_segments[_current], *_segmentsIR[0]);
+
+ // Backward FFT
+ _fft.ifft(_fftBuffer.data(), _conv.re(), _conv.im());
+
+ // Add overlap
+ Sum(output+processed, _fftBuffer.data()+inputBufferPos, _overlap.data()+inputBufferPos, processing);
+
+ // Input buffer full => Next block
+ _inputBufferFill += processing;
+ if (_inputBufferFill == _blockSize)
+ {
+ // Input buffer is empty again now
+ _inputBuffer.setZero();
+ _inputBufferFill = 0;
+
+ // Save the overlap
+ ::memcpy(_overlap.data(), _fftBuffer.data()+_blockSize, _blockSize * sizeof(Sample));
+
+ // Update current segment
+ _current = (_current > 0) ? (_current - 1) : (_segCount - 1);
+ }
+
+ processed += processing;
+ }
+}
+
+} // End of namespace fftconvolver
diff --git a/FFTConvolver/FFTConvolver.h b/FFTConvolver/FFTConvolver.h
new file mode 100755
index 00000000..0c446088
--- /dev/null
+++ b/FFTConvolver/FFTConvolver.h
@@ -0,0 +1,100 @@
+// ==================================================================================
+// Copyright (c) 2012 HiFi-LoFi
+//
+// This is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+// ==================================================================================
+
+#ifndef _FFTCONVOLVER_FFTCONVOLVER_H
+#define _FFTCONVOLVER_FFTCONVOLVER_H
+
+#include "AudioFFT.h"
+#include "Utilities.h"
+
+#include
+
+
+namespace fftconvolver
+{
+
+/**
+* @class FFTConvolver
+* @brief Implementation of a partitioned FFT convolution algorithm with uniform block size
+*
+* Some notes on how to use it:
+*
+* - After initialization with an impulse response, subsequent data portions of
+* arbitrary length can be convolved. The convolver internally can handle
+* this by using appropriate buffering.
+*
+* - The convolver works without "latency" (except for the required
+* processing time, of course), i.e. the output always is the convolved
+* input for each processing call.
+*
+* - The convolver is suitable for real-time processing which means that no
+* "unpredictable" operations like allocations, locking, API calls, etc. are
+* performed during processing (all necessary allocations and preparations take
+* place during initialization).
+*/
+class FFTConvolver
+{
+public:
+ FFTConvolver();
+ virtual ~FFTConvolver();
+
+ /**
+ * @brief Initializes the convolver
+ * @param blockSize Block size internally used by the convolver (partition size)
+ * @param ir The impulse response
+ * @param irLen Length of the impulse response
+ * @return true: Success - false: Failed
+ */
+ bool init(size_t blockSize, const Sample* ir, size_t irLen);
+
+ /**
+ * @brief Convolves the the given input samples and immediately outputs the result
+ * @param input The input samples
+ * @param output The convolution result
+ * @param len Number of input/output samples
+ */
+ void process(const Sample* input, Sample* output, size_t len);
+
+ /**
+ * @brief Resets the convolver and discards the set impulse response
+ */
+ void reset();
+
+private:
+ size_t _blockSize;
+ size_t _segSize;
+ size_t _segCount;
+ size_t _fftComplexSize;
+ std::vector _segments;
+ std::vector _segmentsIR;
+ SampleBuffer _fftBuffer;
+ audiofft::AudioFFT _fft;
+ SplitComplex _preMultiplied;
+ SplitComplex _conv;
+ SampleBuffer _overlap;
+ size_t _current;
+ SampleBuffer _inputBuffer;
+ size_t _inputBufferFill;
+
+ // Prevent uncontrolled usage
+ FFTConvolver(const FFTConvolver&);
+ FFTConvolver& operator=(const FFTConvolver&);
+};
+
+} // End of namespace fftconvolver
+
+#endif // Header guard
diff --git a/FFTConvolver/Utilities.cpp b/FFTConvolver/Utilities.cpp
new file mode 100644
index 00000000..a30ff148
--- /dev/null
+++ b/FFTConvolver/Utilities.cpp
@@ -0,0 +1,113 @@
+// ==================================================================================
+// Copyright (c) 2012 HiFi-LoFi
+//
+// This is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+// ==================================================================================
+
+#include "Utilities.h"
+
+
+namespace fftconvolver
+{
+
+bool SSEEnabled()
+{
+#if defined(FFTCONVOLVER_USE_SSE)
+ return true;
+#else
+ return false;
+#endif
+}
+
+
+void Sum(Sample* FFTCONVOLVER_RESTRICT result,
+ const Sample* FFTCONVOLVER_RESTRICT a,
+ const Sample* FFTCONVOLVER_RESTRICT b,
+ size_t len)
+{
+ const size_t end4 = 4 * (len / 4);
+ for (size_t i=0; i.
+// ==================================================================================
+
+#ifndef _FFTCONVOLVER_UTILITIES_H
+#define _FFTCONVOLVER_UTILITIES_H
+
+#include
+#include
+#include
+#include
+#include
+
+
+namespace fftconvolver
+{
+
+#if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
+ #if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE)
+ #define FFTCONVOLVER_USE_SSE
+ #endif
+#endif
+
+
+#if defined (FFTCONVOLVER_USE_SSE)
+ #include
+#endif
+
+
+#if defined(__GNUC__)
+ #define FFTCONVOLVER_RESTRICT __restrict__
+#else
+ #define FFTCONVOLVER_RESTRICT
+#endif
+
+
+/**
+* @brief Returns whether SSE optimization for the convolver is enabled
+* @return true: Enabled - false: Disabled
+*/
+bool SSEEnabled();
+
+
+/**
+* @class Buffer
+* @brief Simple buffer implementation (uses 16-byte alignment if SSE optimization is enabled)
+*/
+template
+class Buffer
+{
+public:
+ explicit Buffer(size_t initialSize = 0) :
+ _data(0),
+ _size(0)
+ {
+ resize(initialSize);
+ }
+
+ virtual ~Buffer()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ deallocate(_data);
+ _data = 0;
+ _size = 0;
+ }
+
+ void resize(size_t size)
+ {
+ if (_size != size)
+ {
+ clear();
+
+ if (size > 0)
+ {
+ assert(!_data && _size == 0);
+ _data = allocate(size);
+ _size = size;
+ }
+ }
+ setZero();
+ }
+
+ size_t size() const
+ {
+ return _size;
+ }
+
+ void setZero()
+ {
+ ::memset(_data, 0, _size * sizeof(T));
+ }
+
+ void copyFrom(const Buffer& other)
+ {
+ assert(_size == other._size);
+ if (this != &other)
+ {
+ ::memcpy(_data, other._data, _size * sizeof(T));
+ }
+ }
+
+ T& operator[](size_t index)
+ {
+ assert(_data && index < _size);
+ return _data[index];
+ }
+
+ const T& operator[](size_t index) const
+ {
+ assert(_data && index < _size);
+ return _data[index];
+ }
+
+ operator bool() const
+ {
+ return (_data != 0 && _size > 0);
+ }
+
+ T* data()
+ {
+ return _data;
+ }
+
+ const T* data() const
+ {
+ return _data;
+ }
+
+ static void Swap(Buffer& a, Buffer& b)
+ {
+ std::swap(a._data, b._data);
+ std::swap(a._size, b._size);
+ }
+
+private:
+ T* allocate(size_t size)
+ {
+#if defined(FFTCONVOLVER_USE_SSE)
+ return static_cast(_mm_malloc(size * sizeof(T), 16));
+#else
+ return new T[size];
+#endif
+ }
+
+ void deallocate(T* ptr)
+ {
+#if defined(FFTCONVOLVER_USE_SSE)
+ _mm_free(ptr);
+#else
+ delete [] ptr;
+#endif
+ }
+
+ T* _data;
+ size_t _size;
+
+ // Prevent uncontrolled usage
+ Buffer(const Buffer&);
+ Buffer& operator=(const Buffer&);
+};
+
+
+/**
+* @brief Type of one sample
+*/
+typedef float Sample;
+
+
+/**
+* @brief Buffer for samples
+*/
+typedef Buffer SampleBuffer;
+
+
+/**
+* @class SplitComplex
+* @brief Buffer for split-complex representation of FFT results
+*
+* The split-complex representation stores the real and imaginary parts
+* of FFT results in two different memory buffers which is useful e.g. for
+* SIMD optimizations.
+*/
+class SplitComplex
+{
+public:
+ explicit SplitComplex(size_t initialSize = 0) :
+ _size(0),
+ _re(),
+ _im()
+ {
+ resize(initialSize);
+ }
+
+ ~SplitComplex()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ _re.clear();
+ _im.clear();
+ _size = 0;
+ }
+
+ void resize(size_t newSize)
+ {
+ _re.resize(newSize);
+ _im.resize(newSize);
+ _size = newSize;
+ }
+
+ void setZero()
+ {
+ _re.setZero();
+ _im.setZero();
+ }
+
+ void copyFrom(const SplitComplex& other)
+ {
+ _re.copyFrom(other._re);
+ _im.copyFrom(other._im);
+ }
+
+ Sample* re()
+ {
+ return _re.data();
+ }
+
+ const Sample* re() const
+ {
+ return _re.data();
+ }
+
+ Sample* im()
+ {
+ return _im.data();
+ }
+
+ const Sample* im() const
+ {
+ return _im.data();
+ }
+
+ size_t size() const
+ {
+ return _size;
+ }
+
+private:
+ size_t _size;
+ SampleBuffer _re;
+ SampleBuffer _im;
+
+ // Prevent uncontrolled usage
+ SplitComplex(const SplitComplex&);
+ SplitComplex& operator=(const SplitComplex&);
+};
+
+
+/**
+* @brief Returns the next power of 2 of a given number
+* @param val The number
+* @return The next power of 2
+*/
+template
+T NextPowerOf2(const T& val)
+{
+ T nextPowerOf2 = 1;
+ while (nextPowerOf2 < val)
+ {
+ nextPowerOf2 *= 2;
+ }
+ return nextPowerOf2;
+}
+
+
+/**
+* @brief Sums two given sample arrays
+* @param result The result array
+* @param a The 1st array
+* @param b The 2nd array
+* @param len The length of the arrays
+*/
+void Sum(Sample* FFTCONVOLVER_RESTRICT result,
+ const Sample* FFTCONVOLVER_RESTRICT a,
+ const Sample* FFTCONVOLVER_RESTRICT b,
+ size_t len);
+
+
+/**
+* @brief Copies a source array into a destination buffer and pads the destination buffer with zeros
+* @param dest The destination buffer
+* @param src The source array
+* @param srcSize The size of the source array
+*/
+template
+void CopyAndPad(Buffer& dest, const T* src, size_t srcSize)
+{
+ assert(dest.size() >= srcSize);
+ ::memcpy(dest.data(), src, srcSize * sizeof(T));
+ ::memset(dest.data() + srcSize, 0, (dest.size()-srcSize) * sizeof(T));
+}
+
+
+/**
+* @brief Adds the complex product of two split-complex buffers to a result buffer
+* @param result The result buffer
+* @param a The 1st factor of the complex product
+* @param b The 2nd factor of the complex product
+*/
+void ComplexMultiplyAccumulate(SplitComplex& result, const SplitComplex& a, const SplitComplex& b);
+
+
+/**
+* @brief Adds the complex product of two split-complex arrays to a result array
+* @param re The real part of the result buffer
+* @param im The imaginary part of the result buffer
+* @param reA The real part of the 1st factor of the complex product
+* @param imA The imaginary part of the 1st factor of the complex product
+* @param reB The real part of the 2nd factor of the complex product
+* @param imB The imaginary part of the 2nd factor of the complex product
+*/
+void ComplexMultiplyAccumulate(Sample* FFTCONVOLVER_RESTRICT re,
+ Sample* FFTCONVOLVER_RESTRICT im,
+ const Sample* FFTCONVOLVER_RESTRICT reA,
+ const Sample* FFTCONVOLVER_RESTRICT imA,
+ const Sample* FFTCONVOLVER_RESTRICT reB,
+ const Sample* FFTCONVOLVER_RESTRICT imB,
+ const size_t len);
+
+} // End of namespace fftconvolver
+
+#endif // Header guard
diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp
new file mode 100644
index 00000000..4589c8ed
--- /dev/null
+++ b/FFTConvolver/convolver.cpp
@@ -0,0 +1,67 @@
+
+
+#include "convolver.h"
+#include
+#include "FFTConvolver.h"
+#include "Utilities.h"
+extern "C" {
+#include "../common.h"
+}
+
+
+static fftconvolver::FFTConvolver convolver_l;
+static fftconvolver::FFTConvolver convolver_r;
+
+
+void convolver_init(const char* filename, int max_length)
+{
+ SF_INFO info;
+ assert(filename);
+ SNDFILE* file = sf_open(filename, SFM_READ, &info);
+ assert(file);
+
+ if (info.samplerate != 44100)
+ die("Impulse file \"%s\" sample rate is %d Hz. Only 44100 Hz is supported", filename, info.samplerate);
+
+ if (info.channels != 1 && info.channels != 2)
+ die("Impulse file \"%s\" contains %d channels. Only 1 or 2 is supported.", filename, info.channels);
+
+ const size_t size = info.frames > max_length ? max_length : info.frames;
+ float buffer[size*info.channels];
+
+ size_t l = sf_readf_float(file, buffer, size);
+ assert(l == size);
+
+ if (info.channels == 1) {
+ convolver_l.init(352, buffer, size);
+ convolver_r.init(352, buffer, size);
+ } else {
+ // deinterleave
+ float buffer_l[size];
+ float buffer_r[size];
+
+ int i;
+ for (i=0; i/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
if INSTALL_SYSTEMD
+ 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 $(DESTDIR)$(systemdsystemunitdir) ] || mkdir -p $(DESTDIR)$(systemdsystemunitdir)
[ -f $(DESTDIR)$(systemdsystemunitdir)/shairport-sync.service ] || cp scripts/shairport-sync.service $(DESTDIR)$(systemdsystemunitdir)
endif
+if INSTALL_FREEBSD_SERVICE
+ pw showgroup shairport-sync > /dev/null 2>&1 || pw addgroup shairport-sync > /dev/null 2>&1
+ pw showuser shairport-sync > /dev/null 2>&1 || pw adduser shairport-sync > /dev/null 2>&1
+ [ -e /var/run/shairport-sync ] || mkdir -p /var/run/shairport-sync
+ chown shairport-sync:shairport-sync /var/run/shairport-sync
+ [ -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
diff --git a/README.md b/README.md
index f2b56305..168fbb14 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,27 @@
Shairport Sync
=============
-**Welcome to Version 3 of Shairport Sync. This is a big update from Version 2. Please see the Release Notes for more information.**
-
Shairport Sync is an AirPlay audio player – it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player and ForkedDaapd, among others.
Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.)
-Shairport Sync does not support AirPlay video or photo streaming.
+Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming.
This is the stable "master" branch. Changes and updates are incorporated into this branch relatively slowly. To access the development version, where all the latest changes are made first, please switch to the "development" branch.
-**Please note that, between 2.8.4 and 2.8.5, there is a change in the standard ./configure arguments which you should not ignore!** If you are updating Shairport Sync from a version that is 2.8.4 or older, please read the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page very carefully.
-
More Information
----------
-Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to play audio at exactly the right time. It does this by monitoring and controlling the *latency* — the time-gap between when a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC).
+Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to play audio at exactly the right time. It uses the information to monitor and control the *latency* — the time-gap between when a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC).
The latency to be used is specified by the source when it negotiates with Shairport Sync. Most sources set a latency of exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds.
-Timestamps are measured relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. This is done by synchronising the two clocks, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols.
+Timestamps are referenced relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. So, another thing Shairport Sync has to do is to synchronize the source clock and the local clock, and it does this usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols.
To maintain the exact latency required, if an output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”.
Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0.
-Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem).
+Shairport Sync is designed for audio back ends that offer accurate timing and synchronisation information, including `alsa` on Linux and `sndio` on FreeBSD. It must have direct access to the output device, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem).
+
+Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing and synchronsiation information is not as accurate as that of `alsa` or `sndio`, removing or disabling PulseAudio so that Shairport Sync can have direct access to a sound card via `alsa` is often impractical. In that case, the `pa` backend can be used.
For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki.
@@ -35,70 +33,78 @@ What else?
* Bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits.
* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds.
* Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.)
-* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
+* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
* Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync.
* Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available.
Status
------
-Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Wheezy and Jessie, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. It will not work properly — if at all — with a PulseAudio (pseudo-)output device. Using a port of the `alsa` system, Shairport Sync runs rather well on FreeBSD.
+Shairport Sync works on a wide variety of Linux devices and FreeBSD. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Wheezy and Jessie, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input.
+
+Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normallys run in the *user mode* but can be configured to run in *system mode*, though this is not recommended. Shairport Sync can work with it in either mode.
Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card – see the note below on configuring the Raspberry Pi to make best use of it.
-At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi.
+Shairport Sync runs natively on FreeBSD using the `sndio` sound system.
-Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux and Fedora inside VMWare Fusion on a Mac, but synchronisation in inaccurate — possibly because the soundcard is being emulated.
+Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux, Fedora and FreeBSD inside VMWare Fusion on a Mac, but synchronisation in inaccurate — possibly because the sound card is being emulated.
-Shairport Sync will output to `alsa` cards, to standard output and to pipes using appropriate backends. You can try compiling additional backends in as you wish, but it definitely will not work properly with them. Maybe someday...
+In addition, Shairport Sync can output to standard output, pipes, `soundio` and `ao` devices using appropriate backends.
For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution.
-Note: Historically, Shairport Sync has taken its settings from command line arguments. While this is still the case, it does not always work well across distributions. Accordingly, from version 2.4 onwards, Shairport Sync reads settings from the file `/etc/shairport-sync.conf`. Access to new features will only be provided via the settings file.
-
Building And Installing
---------------------
-
Shairport Sync may already be available as a package in your Linux distribution (search for `shairport-sync` – the package named `shairport` is a different program). Packages are available on recent versions of Debian, Ubuntu, Arch, OpenWrt and possibly more. If you wish to build and install the latest version of Shairport Sync on OpenWrt, Arch or Fedora platforms, please follow the appropriate instructions below. Limited support is also available for Mac OS X. Otherwise follow the General Build Instructions. Then, when the program has been installed, refer to the section on Configuring Shairport Sync that follows.
-**Note**
+**Remove Old Versions Of Shairport Sync**
-The following procedures will install the `shairport-sync` application into your system. Before continuing, you should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
-(If the existing installation of `shairport-sync` is where the new copy will be installed into, it will be overwritten; sometimes, however, the installation is to another location, so it is safer, initially, to delete previous versions manually.)
+You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
-**Ubuntu:**
-Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson. A `shairport-sync` installer package is available in Ubuntu 16.04.
+**FreeBSD**
-**Debian:**
-`shairport-sync` is in the Debian archive and is scheduled for release with Debian Stretch (9): https://tracker.debian.org/shairport-sync. A backport for Debian Jessie (8) may be provided given enough demand.
+To build Shairport Sync from sources on FreeBSD please refer to [FREEBSD.md](https://github.com/mikebrady/shairport-sync/blob/master/FREEBSD.md).
-**OpenWrt:**
-There is a Shairport Sync package in OpenWrt `trunk`. Also, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt, including one that builds back to `Barrier Breaker`.
+**Determine The Configuration Needed**
-**Arch Linux:**
-Shairport Sync is available for `x86_64` and `i686` platforms in the Arch Linux Community Repository -- search for `shairport-sync`. See also https://www.archlinux.org/packages/.
+Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary:
+```
+$ pactl info
+Server String: unix:/run/user/1000/pulse/native
+Library Protocol Version: 30
+Server Protocol Version: 30
+Is Local: yes
+Client Index: 9
+Tile Size: 65472
+User Name: mike
+Host Name: ubuntu
+Server Name: pulseaudio
+Server Version: 8.0
+Default Sample Specification: s16le 2ch 44100Hz
+Default Channel Map: front-left,front-right
+Default Sink: alsa_output.pci-0000_02_02.0.analog-stereo
+Default Source: alsa_input.pci-0000_02_02.0.analog-stereo
+Cookie: 96f9:3e8d
+$
+```
+If PulseAudio in not installed, you'll get something like this:
+```
+$ pactl info
+-bash: pactl: command not found
+$
+```
+If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend.
-An Arch Linux installation package, suitable for compilation on any platform, is available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD).
+If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it.
-**Mac OS X:**
-A [HomeBrew](http://brew.sh) package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command `$brew install shairport-sync`. Note that the installation uses the `libao` library and so synchronisation is not available — playback glitches will occur occasionally, when the `ao` system's buffers overflow or underflow.
-
-**Fedora:**
-Please see the guide at [FEDORA.md](https://github.com/mikebrady/shairport-sync/blob/master/FEDORA.md).
-
-**Cygwin:**
-Please see the guide at [CYGWIN.md](https://github.com/mikebrady/shairport-sync/blob/master/CYGWIN.md).
-
-Sincere thanks to all package contributors!
-
-**General Build Instructions**
-The following procedures will install the shairport-sync application into your system. Before continuing, you should check to see if shairport-sync is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
+**Building**
To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions.
The following libraries are required:
* OpenSSL or mbed TLS (PolarSSL is supported but deprecated)
* Avahi
-* ALSA
+* ALSA and/or PulseAudio
* libdaemon
* autoconf
* automake
@@ -116,16 +122,18 @@ Debian, Ubuntu and Raspbian users can get the basics with:
- `apt-get install build-essential git xmltoman` – these may already be installed.
- `apt-get install autoconf automake libtool libdaemon-dev libasound2-dev libpopt-dev libconfig-dev`
+- `apt-get install libasound2-dev` for the ALSA libraries
+- `apt-get install libpulse-dev` for the PulseAudio libraries
- `apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended).
- `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise.
- `apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. (It is suggested you use mbed TLS, where available. It doesn't seem to be in Raspbian at the time of writing, March 2017.)
-- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspbian Jessie; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/development/LIBSOXR.md).
+- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspbian Jessie; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md).
If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information.
**Download Shairport Sync:**
-`$ git clone https://github.com/mikebrady/shairport-sync.git`
+`git clone https://github.com/mikebrady/shairport-sync.git`
Next, `cd` into the shairport-sync directory and execute the following command:
@@ -136,25 +144,31 @@ $ autoreconf -i -f
**Choose the appropriate `--with-*` options:**
-- `--with-alsa` for the ALSA audio back end. This is required.
+(Don't worry -- there's a standard set of configuration options recommended further down.)
+
+- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality.
+- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed.
- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout).
- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe.
+- `--with-soundio` include an optional backend module to enable raw audio to be output through the soundio system.
- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15.
- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL.
- `--with-soxr` for libsoxr-based resampling.
- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location.
- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader.
-- `--with-systemv` to install a System V init script at the `make install` stage. Default is not to to install.
-- `--with-systemd` to install a systemd service description at the `make install` stage. Default is not to to install.
- `--with-configfile` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten.
- `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use.
- `--with-apple-alac` to include the Apple ALAC Decoder.
+- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. Default is not to to install.
+- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. Default is not to to install.
**Determine if it's a `systemd` or a "System V" installation:**
+If you wish to have Shairport Sync start automatically when your systewm reboots, you need to figure out whether the system is using `systemd` or the older System V startup facilities. If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.
+
At the time of writing, there are two widely-used systems for starting programs automatically at startup: `systemd` and "System V" . (There are others, but they are not considered here.) To see if the `systemd` process is running on your system, enter the following command:
-`$ ps aux | grep systemd | grep -v grep`
+`ps aux | grep systemd | grep -v grep`
On a system using `systemd` (this is from a Raspberry Pi running Raspbian Jessie) you'll get many lines containing `systemd`, for example:
```
@@ -179,14 +193,15 @@ A final consideration is the location of the configuration file `shairport-sync.
**Sample `./configure` command with parameters for a typical Linux `systemd` installation:**
-Here is an example, suitable for Linux installations that use `systemd`, such as Ubuntu 16.04 and Raspbian Jessie:
+Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and Raspbian Jessie. It specifies bothe ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot:
-`$ ./configure --sysconfdir=/etc --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd`
+`$ ./configure --sysconfdir=/etc --with-alsa --with-pa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd`
* Omit the `--with-soxr` if the libsoxr library is not available.
* For installation into a System V system, replace the `--with-systemd` with `--with-systemv`.
+* If you intend to use Shairport Sync with PulseAudio in the standard user mode, it can not be a system service, so you should omit both `--with-systemd` and `--with-systemv`.
-**Build the application:**
+**Build and Install the Application:**
Enter:
@@ -194,38 +209,25 @@ Enter:
to build the application.
-**Install into to a `systemd` system**
-To complete the installation, you need to define a `shairport-sync` group and user. This is a security measure – the user and group are relatively unprivileged, and the user does not have login priviliges. The user must be a member of the `audio` group to be able to access the audio hardware. The following commands define the group and user correctly if they do not already exist (note the use of `sudo` – omit this if you already have superuser privileges:
-
-```
-$ getent group shairport-sync &>/dev/null || sudo groupadd -r shairport-sync >/dev/null
-$ getent passwd shairport-sync &> /dev/null || sudo useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync >/dev/null
-```
-
-Next, enter:
+Assuming you have used the `./configure` settings suggested above, you can now install the application:
```
$ sudo make install
```
+The user and group `shairport-sync` will be created if necessary, and a runtime folder will be created at `/var/run/shairport-sync` if you have chosen `--with-systemv`. In addition, a `man` page, a default configuration file and automatic power-on startup script will be installed.
-to install `shairport-sync` along with a `man` page, a default configuration file and a `systemd` configuration file called `shairport-sync.service` to launch it automatically at system startup.
-To enable Shairport Sync to start automatically at system startup, enter:
+**Complete installation into to a `systemd` system**
-`$ sudo systemctl enable shairport-sync`
+If you have chosen the `--with-systemd` configuration option, then, to enable Shairport Sync to start automatically at system startup, enter:
-**Install into a System V system**
-If you are installing onto a System V system, enter:
+`$sudo systemctl enable shairport-sync`
+**Complete installation into a System V system**
+
+If you have chosen the `--with-systemd` configuration option, enter:
```
-$ sudo make install
-```
-
-to install `shairport-sync` along with a `man` page, a default configuration file and a System V startup script to launch it automatically at system startup.
-
-To complete the installation, enter:
-```
-$ sudo update-rc.d shairport-sync defaults 90 10
+$sudo update-rc.d shairport-sync defaults 90 10
```
**Man Page**
@@ -234,15 +236,19 @@ You can view the man page here: http://htmlpreview.github.io/?https://github.com
Configuring Shairport Sync
--------
-There are two logically distinct parts to getting Shairport Sync to run properly on your machine — (1) starting and stopping it and (2) ensuring it has the right settings.
+There are two logically distinct parts to getting Shairport Sync to run properly on your machine: (1) starting and stopping it and (2) ensuring it has the right settings.
**(1) Starting and Stopping:**
Starting and stopping Shairport Sync automatically is taken care of differently in different versions of Linux – see the previous section for an example of installing into a `systemd` or a System V based system.
-**(2) Settings:**
-To get the best from Shairport Sync, you’ll need to (a) give Shairport Sync a service name by which it will be seen in iTunes etc., (b) specify the output device to use and (c) specify the name of the mixer volume control to use to control the output level. To get values for (b) and (c) you might need to explore the ALSA output devices with a program like `alsamixer` or similar.
+If you are using PulseAudio in the standard "user" mode, you can not start Shairport Sync automatically at boot time – it must be started when the user logs in to a GUI session. Various GUI tools exist to enable you to build a Startup Application. However, there does not appear to be a similar tool for terminating the program when you log out. Also, it the GUI switches to another user, the user's PulseAudio session is disconnected from the output device.
-Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf`. When you run `$sudo make install`, a sample configuration file is installed (or updated) at `/etc/shairport-sync.conf.sample`. This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed at `/etc/shairport-sync.conf`, which should work in almost any system with a sound card.
+**(2) Settings:**
+To get the best from Shairport Sync, you’ll need to (a) give Shairport Sync a service name by which it will be seen in iTunes etc. and (b) specify the backend you wish to use - `alsa` for the ALSA backend, or `pa` for the PulseAudio back end. If only one backend is included at compilation, or if the backend is ALSA, there is no need to explicitly specify the backend.
+
+For the ALSA backend you may need to (c) specify the output device to use and (d) specify the name of the mixer volume control to use to control the output level. To get values for (b) and (c) you might need to explore the ALSA output devices with a program like `alsamixer` or similar.
+
+Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card.
Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room`, give the service the password `secret` and used `libsoxr` interpolation, then you should do the following:
@@ -255,7 +261,12 @@ general =
// ... other general settings
};
```
-(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.)
+(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add:
+
+```
+ output_backend = "pa";
+```
+to the `general` stanza.
The `alsa` group is used to specify properties of the output device. The most obvious setting is the name of the output device which you can set using the `output_device` tag.
@@ -273,11 +284,15 @@ alsa =
};
```
+The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel.
+
Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing.
-Please note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the standard `#!/bin/...` first line. (This behaviour may be different from other Shairports.)
+Note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the standard `#!/bin/...` first line. (This behaviour may be different from other Shairports.)
-Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatability, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method.
+Shairport Sync can run a program whenever the volume is set or changed. You specify them using the `general` group settings `run_this_when_volume_changes`. This is to facilitate situations where something has to be done when the volume changes, e.g. adjust an external mixer value. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the command has been completed before continuing. Again, please note that the full path to the program must be specified, and script files will not be executed unless they are marked as executable and have the standard `#!/bin/...` first line.
+
+Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method.
**Raspberry Pi**
The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line:
@@ -297,13 +312,13 @@ As previously mentioned, you can use command line arguments to provide settings
Apart from the following options, all command line options can be replaced by settings in the configuration file. Here is a brief description of command line options that are not replicated by settings in the settings file.
-* The `-c` option allows you to specify the location of the configuration file — default is `/etc/shairport-sync.conf`.
+* The `-c` option allows you to specify the location of the configuration file.
* The `-V` option gives you version information about Shairport Sync and then quits.
* The `-d` option causes Shairport Sync to properly daemonise itself, that is, to run in the background. You may need sudo privileges for this.
* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this.
The System V init script at `/etc/init.d/shairport-sync` has a bare minimum :
-`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file, normally `/etc/shairport-sync.conf`.
+`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file.
Examples
--------
@@ -322,7 +337,7 @@ alsa = {
This gives the service a particular name — "Joe's Stereo" and specifies that audio device hw:0 be used.
-For best results — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the mixer control to be used as the `mixer_control_name`.
+For best results with the `alsa` backend — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the mixer control to be used as the `mixer_control_name`.
Here is an example for for a Raspberry Pi using its internal soundcard — device hw:0 — that drives the headphone jack:
```
@@ -373,7 +388,7 @@ alsa = {
};
```
-For an NSLU2, which has no internal soundcard, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus:
+For an NSLU2, which has no internal sound card, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus:
On an NSLU2, to drive a first generation Griffin iMic:
```
@@ -397,6 +412,16 @@ alsa = {
};
```
+Finally, here is an example of using the PulseAudio backend:
+```
+general = {
+ name = "Zoe's Computer";
+ output_backend = "pa";
+};
+
+```
+
+
Metadata broadcasting over UDP
------------------------------
As an alternative to sending metadata to a pipe, the `socket_address` and `socket_port` tags may be set in the metadata group to cause Shairport Sync to broadcast UDP packets containing the track metadata.
@@ -456,7 +481,7 @@ It's not unusual to have resend requests, late packets and even missing packets
WiFi Issues
---------
-If you are using WiFi, you should ensure that WiFi power management is off. See [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for more details.
+If you are using WiFi, you should ensure that WiFi power management is off. See [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/master/TROUBLESHOOTING.md) for more details.
Troubleshooting
---------------
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index bb19382b..d3a16a22 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -1,3 +1,33 @@
+Version 3.1rc0
+====
+Version 3.1 brings two new backends, optional loudness and convolution filters, improvements in non-synchronised backends, enhancements, stability improvements and bug fixes.
+
+**New Features**
+* A `sndio` backend gives Shairport Sync native fully synchronised output on OpenBSD and FreeBSD, thanks to the work of [Tobias Kortkamp (t6)](https://github.com/t6).
+* A `pa` backend now allows Shairport Sync to provide synchronised output on PulseAudio-equipped systems -- many desktop Linuxes use PulseAudio as their sound manager.
+* Optional loudness and convolution filters can be incorporated in the audio processing chain, thanks to the fantastic work of [yannpom](https://github.com/yannpom).
+* A volume-change program hook `run_this_when_volume_is_set` has been added to the `general` settings stanza to execute an application whenever the volume is changed.
+
+**Pesky Changes You Should Know About**
+* The `audio_backend_buffer_desired_length_in_seconds` and `audio_backend_latency_offset_in_seconds` settings have been moved from individual backend stanzas to the `general` stanza. They now have an effect on every type of backend.
+* If you are using a System V (aka `systemv`) installation, please note that the default location for PID file has moved -- it is now stored at `/var/run/shairport-sync/shairport-sync.pid`. This change is needed to improve security a little and to improve compatability across platforms. If you're not doing anything strange, this should make no difference.
+
+**Enhancements**
+* Resynchronisation, which happens when the synchronisation is incorrect by more than 50 ms by default, should be a lot less intrusive when it occurs – it should now either insert silence or skip frames, as appropriate.
+* The Linux installer has been improved and simplified and a FreeBSD installer introduced.
+* A new setting, `audio_backend_silent_lead_in_time`, allows you to set the duration of the period of silence played (the "silent lead-in") before a play session starts.
+* A new command-line option, `--logOutputLevel`, allows you to output the volume levels to the log whenever they are changed. This may be useful during setup.
+* Improvements have been made to the handling of large items of metadata over UDP.
+* A new command line option, `-j`, demonizes Shairport Sync without creating a PID file.
+* A new `alsa`-only setting, `mute_using_playback_switch`, is available for advanced use.
+* Other minor enhancements.
+
+**Bug Fixes**
+* Stability improvements. More care has been taken (!) to make code thread-safe, resulting in improved stability.
+* Conversion from stereo to mono has been fixed to avoid clipping while preserving full resolution. Thanks to [Robert Jones (RobDeBagel)](https://github.com/RobDeBagel) for bringing this to notice.
+* Short intrusions of audio at the start of a new session from the end of the previous session have been eliminated.
+* Many (many!) miscellaneous bugs fixed.
+
Version 3.0.2
====
**Bug Fixes**
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index 7f7852ac..f4cdbd36 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -46,7 +46,7 @@ Allow network discovery. This setting creates a private type network and enables
**Problem**
-You have installed Shairport Sync successfully, the deamon is running, you can see it from your remote terminal but you are unable to play a song.
+You have installed Shairport Sync successfully, the daemon is running, you can see it from your remote terminal but you are unable to play a song.
**Before you change anything to your configuration**
@@ -72,7 +72,7 @@ sudo ufw allow from 192.168.1.1/16 to any port 6000:6005 proto udp
sudo ufw allow from 192.168.1.1/16 to any port 35000:65535 proto udp
```
-You may have to change the IP adresses range depending on your own local network settings.
+You may have to change the IP addresses range depending on your own local network settings.
You can check UFW config by typing `sudo ufw status` in shell. Please make sure that UFW is active, especially if you have deactivated it previously for testing purpose.
@@ -152,6 +152,6 @@ This sets the default alsa audio device to be the USB DAC via a dmixer plugin (w
This will then be used by default by Shairport-Sync and any other applications using alsa.
-Note that some distributions (such as Volumio 2) don't use an asound.conf file by default, they instead specificy the hardware details directly in '/etc/mpd.conf' files so some more in-depth modification is needed to override this.
+Note that some distributions (such as Volumio 2) don't use an asound.conf file by default, they instead specify the hardware details directly in '/etc/mpd.conf' files so some more in-depth modification is needed to override this.
(Note: not tested by Mike B.)
diff --git a/UPDATING.md b/UPDATING.md
index 481f3c63..ae60ad3b 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -31,10 +31,10 @@ $ autoreconf -fi
```
Please review the release notes to see if any configuration settings have been changed. For instance, in the transitions from version 2 to version 3, the `--with-ssl=polarssl` has been deprecated in favour of `--with-ssl=mbedtls`.
```
-#The following is the standard configuration for a Linux that uses the systemd initialisation system:
+#The following is the standard configuration for a Linux that uses the alsa backend and systemd initialisation system:
$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd --sysconfdir=/etc
#OR
-#The following is the standard configuration for a Linux that uses the older System V initialisation system:
+#The following is the standard configuration for a Linux that uses the alsa backend and the older System V initialisation system:
$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemv --sysconfdir=/etc
$ make
diff --git a/apple_alac.cpp b/apple_alac.cpp
index 0ee30290..6d543b2e 100644
--- a/apple_alac.cpp
+++ b/apple_alac.cpp
@@ -52,4 +52,7 @@ extern "C" int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t buf
return 0;
}
-extern "C" int apple_alac_terminate() { delete (theDecoder); }
+extern "C" int apple_alac_terminate() {
+ delete (theDecoder);
+ return 0;
+}
diff --git a/audio.c b/audio.c
index 915cb802..8fbea259 100644
--- a/audio.c
+++ b/audio.c
@@ -25,6 +25,7 @@
*/
#include "audio.h"
+#include "common.h"
#include "config.h"
#include
#include
@@ -41,6 +42,9 @@ extern audio_output audio_soundio;
#ifdef CONFIG_PULSE
extern audio_output audio_pulse;
#endif
+#ifdef CONFIG_PA
+extern audio_output audio_pa;
+#endif
#ifdef CONFIG_ALSA
extern audio_output audio_alsa;
#endif
@@ -55,14 +59,14 @@ extern audio_output audio_stdout;
#endif
static audio_output *outputs[] = {
-#ifdef CONFIG_SNDIO
- &audio_sndio,
-#endif
#ifdef CONFIG_ALSA
&audio_alsa,
#endif
-#ifdef CONFIG_PULSE
- &audio_pulse,
+#ifdef CONFIG_SNDIO
+ &audio_sndio,
+#endif
+#ifdef CONFIG_PA
+ &audio_pa,
#endif
#ifdef CONFIG_AO
&audio_ao,
@@ -70,14 +74,17 @@ static audio_output *outputs[] = {
#ifdef CONFIG_SOUNDIO
&audio_soundio,
#endif
-#ifdef CONFIG_DUMMY
- &audio_dummy,
-#endif
#ifdef CONFIG_PIPE
&audio_pipe,
#endif
#ifdef CONFIG_STDOUT
&audio_stdout,
+#endif
+#ifdef CONFIG_DUMMY
+ &audio_dummy,
+#endif
+#ifdef CONFIG_PULSE
+ &audio_pulse,
#endif
NULL};
@@ -108,3 +115,79 @@ void audio_ls_outputs(void) {
(*out)->help();
}
}
+
+void parse_general_audio_options(void) {
+ /* this must be called after the output device has been initialised, so that the default values
+ * are set before any options are chosen */
+ int value;
+ double dvalue;
+ if (config.cfg != NULL) {
+
+ /* Get the desired buffer size setting (deprecated). */
+ if (config_lookup_int(config.cfg, "general.audio_backend_buffer_desired_length", &value)) {
+ if ((value < 0) || (value > 66150)) {
+ inform("The setting general.audio_backend_buffer_desired_length is deprecated. "
+ "Use alsa.audio_backend_buffer_desired_length_in_seconds instead.");
+ die("Invalid audio_backend_buffer_desired_length value: \"%d\". It "
+ "should be between 0 and "
+ "66150, default is %d",
+ value, (int)(config.audio_backend_buffer_desired_length * 44100));
+ } else {
+ inform("The setting general.audio_backend_buffer_desired_length is deprecated. "
+ "Use general.audio_backend_buffer_desired_length_in_seconds instead.");
+ config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
+ }
+ }
+
+ /* Get the desired buffer size setting in seconds. */
+ if (config_lookup_float(config.cfg, "general.audio_backend_buffer_desired_length_in_seconds",
+ &dvalue)) {
+ if ((dvalue < 0) || (dvalue > 1.5)) {
+ die("Invalid audio_backend_buffer_desired_length_in_seconds value: \"%f\". It "
+ "should be between 0 and "
+ "1.5, default is %.3f seconds",
+ dvalue, config.audio_backend_buffer_desired_length);
+ } else {
+ config.audio_backend_buffer_desired_length = dvalue;
+ }
+ }
+
+ /* Get the latency offset (deprecated). */
+ if (config_lookup_int(config.cfg, "general.audio_backend_latency_offset", &value)) {
+ if ((value < -66150) || (value > 66150)) {
+ inform("The setting general.audio_backend_latency_offset is deprecated. "
+ "Use general.audio_backend_latency_offset_in_seconds instead.");
+ die("Invalid audio_backend_latency_offset value: \"%d\". It "
+ "should be between -66150 and +66150, default is 0",
+ value);
+ } else {
+ inform("The setting general.audio_backend_latency_offset is deprecated. "
+ "Use general.audio_backend_latency_offset_in_seconds instead.");
+ config.audio_backend_latency_offset = 1.0 * value / 44100;
+ }
+ }
+
+ /* 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)) {
+ die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It "
+ "should be between -1.0 and +1.5, default is 0 seconds",
+ dvalue);
+ } else {
+ config.audio_backend_latency_offset = dvalue;
+ }
+ }
+
+ /* Get the desired length of the silent lead-in. */
+ if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", &dvalue)) {
+ if ((dvalue < 0.0) || (dvalue > 4)) {
+ die("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit setting to use the default value",
+ dvalue);
+ } else {
+ config.audio_backend_silent_lead_in_time = dvalue;
+ }
+ }
+ }
+}
diff --git a/audio.h b/audio.h
index 31fa1069..e0defc55 100644
--- a/audio.h
+++ b/audio.h
@@ -47,5 +47,6 @@ typedef struct {
audio_output *audio_get_output(char *name);
void audio_ls_outputs(void);
+void parse_general_audio_options(void);
#endif //_AUDIO_H
diff --git a/audio_alsa.c b/audio_alsa.c
index e3458cab..f880d1f7 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -44,29 +44,33 @@ static void stop(void);
static void flush(void);
int delay(long *the_delay);
static void volume(double vol);
+void do_volume(double vol);
+void do_mute(int request);
+
static void linear_volume(double vol);
static void parameters(audio_parameters *info);
static void mute(int do_mute);
static double set_volume;
static int output_method_signalled = 0;
-audio_output audio_alsa = {.name = "alsa",
- .help = &help,
- .init = &init,
- .deinit = &deinit,
- .start = &start,
- .stop = &stop,
- .flush = &flush,
- .delay = &delay,
- .play = &play,
- .mute = &mute,
- .volume = &volume,
- .parameters = ¶meters};
+audio_output audio_alsa = {
+ .name = "alsa",
+ .help = &help,
+ .init = &init,
+ .deinit = &deinit,
+ .start = &start,
+ .stop = &stop,
+ .flush = &flush,
+ .delay = &delay,
+ .play = &play,
+ .mute = NULL, // a function will be provided if it can, and is allowed to, do hardware mute
+ .volume = NULL, // a function will be provided if it can do hardware volume
+ .parameters = ¶meters};
static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned int desired_sample_rate;
-static snd_pcm_format_t sample_format;
+static enum sps_format_t sample_format;
static snd_pcm_t *alsa_handle = NULL;
static snd_pcm_hw_params_t *alsa_params = NULL;
@@ -84,6 +88,9 @@ static char *alsa_mix_ctrl = "Master";
static int alsa_mix_index = 0;
static int hardware_mixer = 0;
static int has_softvol = 0;
+static int volume_set_request = 0; // set when an external request is made to set the volume.
+int mute_request_pending = 0; // set when an external request is made to mute or unmute.
+int overriding_mute_state_requested = 0; // 1 = mute; 0 = unmute requested
static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *,
snd_pcm_uframes_t) = snd_pcm_writei;
@@ -103,7 +110,9 @@ static void help(void) {
" *) default option\n");
}
-void open_mixer() {
+void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; }
+
+int open_mixer() {
if (hardware_mixer) {
debug(2, "Open Mixer");
int ret = 0;
@@ -126,9 +135,23 @@ void open_mixer() {
alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
if (!alsa_mix_elem)
die("Failed to find mixer element");
+ return 1;
+ } else {
+ return 0;
}
}
+void close_mixer() {
+ if (alsa_mix_handle) {
+ snd_mixer_close(alsa_mix_handle);
+ alsa_mix_handle = NULL;
+ }
+}
+
+#define RELEASE_ALSA_MUTEX_AND_DIE(X) \
+ pthread_mutex_unlock(&alsa_mutex); \
+ die(X);
+
static int init(int argc, char **argv) {
pthread_mutex_lock(&alsa_mutex);
// debug(2,"audio_alsa init called.");
@@ -136,8 +159,10 @@ static int init(int argc, char **argv) {
int value;
double dvalue;
+ // set up default values first
set_period_size_request = 0;
set_buffer_size_request = 0;
+ config.alsa_use_playback_switch_for_mute = 1;
config.audio_backend_latency_offset = 0;
config.audio_backend_buffer_desired_length = 0.15;
@@ -145,63 +170,11 @@ static int init(int argc, char **argv) {
// get settings from settings file first, allow them to be overridden by
// command line options
+ // do the "general" audio options. Note, these options are in the "general" stanza!
+ parse_general_audio_options();
+
if (config.cfg != NULL) {
- /* Get the desired buffer size setting. */
- if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) {
- if ((value < 0) || (value > 66150)) {
- inform("The setting alsa.audio_backend_buffer_desired_length is deprecated. "
- "Use alsa.audio_backend_buffer_desired_length_in_seconds instead.");
- die("Invalid alsa audio backend buffer desired length \"%d\". It "
- "should be between 0 and "
- "66150, default is 6615",
- value);
- } else {
- inform("The setting alsa.audio_backend_buffer_desired_length is deprecated. "
- "Use alsa.audio_backend_buffer_desired_length_in_seconds instead.");
- config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
- }
- }
-
- /* Get the desired buffer size setting. */
- if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length_in_seconds",
- &dvalue)) {
- if ((dvalue < 0) || (dvalue > 1.5)) {
- die("Invalid alsa audio backend buffer desired time \"%f\". It "
- "should be between 0 and "
- "1.5, default is 0.15 seconds",
- dvalue);
- } else {
- config.audio_backend_buffer_desired_length = dvalue;
- }
- }
-
- /* Get the latency offset. */
- if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) {
- if ((value < -66150) || (value > 66150)) {
- inform("The setting alsa.audio_backend_latency_offset is deprecated. "
- "Use alsa.audio_backend_latency_offset_in_seconds instead.");
- die("Invalid alsa audio backend buffer latency offset \"%d\". It "
- "should be between -66150 and +66150, default is 0",
- value);
- } else {
- inform("The setting alsa.audio_backend_latency_offset is deprecated. "
- "Use alsa.audio_backend_latency_offset_in_seconds instead.");
- config.audio_backend_latency_offset = 1.0 * value / 44100;
- }
- }
-
- /* Get the latency offset. */
- if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset_in_seconds", &dvalue)) {
- if ((dvalue < -1.0) || (dvalue > 1.5)) {
- die("Invalid alsa audio backend buffer latency offset time \"%f\". It "
- "should be between -1.0 and +1.5, default is 0 seconds",
- dvalue);
- } else {
- config.audio_backend_latency_offset = dvalue;
- }
- }
-
/* Get the Output Device Name. */
if (config_lookup_string(config.cfg, "alsa.output_device", &str)) {
alsa_out_dev = (char *)str;
@@ -232,8 +205,23 @@ static int init(int argc, char **argv) {
config.no_sync = 0;
else if (strcasecmp(str, "yes") == 0)
config.no_sync = 1;
- else
+ else {
+ pthread_mutex_unlock(&alsa_mutex);
die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\"");
+ }
+ }
+
+ /* Get the mute_using_playback_switch setting. */
+ if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.alsa_use_playback_switch_for_mute = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.alsa_use_playback_switch_for_mute = 1;
+ else {
+ pthread_mutex_unlock(&alsa_mutex);
+ die("Invalid mute_use_playback_switch option choice \"%s\". It should be \"yes\" or "
+ "\"no\"");
+ }
}
/* Get the output format, using the same names as aplay does*/
@@ -252,11 +240,13 @@ static int init(int argc, char **argv) {
config.output_format = SPS_FORMAT_U8;
else if (strcasecmp(str, "S8") == 0)
config.output_format = SPS_FORMAT_S8;
- else
+ else {
+ pthread_mutex_unlock(&alsa_mutex);
die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", "
"\"S24_3LE\", \"S24_3BE\" or "
"\"S32\"",
str);
+ }
}
/* Get the output rate, which must be a multiple of 44,100*/
@@ -270,6 +260,7 @@ static int init(int argc, char **argv) {
config.output_rate = value;
break;
default:
+ pthread_mutex_unlock(&alsa_mutex);
die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800", value);
}
}
@@ -280,31 +271,37 @@ static int init(int argc, char **argv) {
config.no_mmap = 1;
else if (strcasecmp(str, "yes") == 0)
config.no_mmap = 0;
- else
+ else {
+ pthread_mutex_unlock(&alsa_mutex);
die("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\"");
+ }
}
/* Get the optional period size value */
if (config_lookup_int(config.cfg, "alsa.period_size", &value)) {
set_period_size_request = 1;
debug(1, "Value read for period size is %d.", value);
- if (value < 0)
+ if (value < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("Invalid alsa period size setting \"%d\". It "
"must be greater than 0.",
value);
- else
+ } else {
period_size_requested = value;
+ }
}
/* Get the optional buffer size value */
if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) {
set_buffer_size_request = 1;
debug(1, "Value read for buffer size is %d.", value);
- if (value < 0)
+ if (value < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("Invalid alsa buffer size setting \"%d\". It "
"must be greater than 0.",
value);
- else
+ } else {
buffer_size_requested = value;
+ }
}
}
@@ -337,14 +334,17 @@ static int init(int argc, char **argv) {
break;
default:
help();
+ pthread_mutex_unlock(&alsa_mutex);
die("Invalid audio option -%c specified", opt);
}
}
- if (optind < argc)
+ if (optind < argc) {
+ pthread_mutex_unlock(&alsa_mutex);
die("Invalid audio argument: %s", argv[optind]);
+ }
- debug(1, "Output device name is \"%s\".", alsa_out_dev);
+ debug(1, "alsa output device name is \"%s\".", alsa_out_dev);
if (hardware_mixer) {
@@ -380,10 +380,14 @@ static int init(int argc, char **argv) {
"volume control.",
alsa_mix_ctrl);
- if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0)
+ if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("Cannot open control \"%s\"", alsa_mix_dev);
- if (snd_ctl_elem_id_malloc(&elem_id) < 0)
+ }
+ if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("Cannot allocate memory for control \"%s\"", alsa_mix_dev);
+ }
snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
@@ -411,12 +415,16 @@ static int init(int argc, char **argv) {
*/
}
}
- if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) {
+ if ((config.alsa_use_playback_switch_for_mute == 1) &&
+ (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) {
audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff
- debug(1, "Has mute ability.");
+ // debug(1, "Has mixer and mute ability we will use.");
+ } else {
+ // debug(1, "Has mixer but not using hardware mute.");
}
-
- snd_mixer_close(alsa_mix_handle);
+ close_mixer();
+ } else {
+ // debug(1, "Has no mixer and thus no hardware mute.");
}
alsa_mix_handle = NULL;
@@ -427,12 +435,10 @@ static int init(int argc, char **argv) {
static void deinit(void) {
// debug(2,"audio_alsa deinit called.");
stop();
- if (hardware_mixer && alsa_mix_handle) {
- snd_mixer_close(alsa_mix_handle);
- }
}
int open_alsa_device(void) {
+ // the alsa mutex is already acquired when this is called
const snd_pcm_uframes_t minimal_buffer_headroom =
352 * 2; // we accept this much headroom in the hardware buffer, but we'll
@@ -459,13 +465,13 @@ int open_alsa_device(void) {
ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0);
if (ret < 0)
return (ret);
- // die("Alsa initialization failed: unable to open pcm device: %s.",
- // snd_strerror(ret));
snd_pcm_hw_params_alloca(&alsa_params);
ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
if (ret < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
+ ;
die("audio_alsa: Broken configuration for device \"%s\": no configurations "
"available",
alsa_out_dev);
@@ -491,6 +497,7 @@ int open_alsa_device(void) {
ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, access);
if (ret < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev,
snd_strerror(ret));
}
@@ -517,15 +524,20 @@ int open_alsa_device(void) {
case SPS_FORMAT_S32:
sf = SND_PCM_FORMAT_S32;
break;
+ default:
+ pthread_mutex_unlock(&alsa_mutex);
+ die("Unsupported output format at audio_alsa.c");
}
ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf);
if (ret < 0) {
- die("audio_alsa: Sample format %d not available for device \"%s\": %s", sf, alsa_out_dev,
- snd_strerror(ret));
+ pthread_mutex_unlock(&alsa_mutex);
+ die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format,
+ alsa_out_dev, snd_strerror(ret));
}
ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2);
if (ret < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev,
snd_strerror(ret));
}
@@ -541,6 +553,7 @@ int open_alsa_device(void) {
ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested,
&dir);
if (ret < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("audio_alsa: cannot set period size of %lu: %s", period_size_requested,
snd_strerror(ret));
snd_pcm_uframes_t actual_period_size;
@@ -556,6 +569,7 @@ int open_alsa_device(void) {
debug(1, "Attempting to set the buffer size to %lu", buffer_size_requested);
ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested);
if (ret < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested,
snd_strerror(ret));
snd_pcm_uframes_t actual_buffer_size;
@@ -569,16 +583,19 @@ int open_alsa_device(void) {
ret = snd_pcm_hw_params(alsa_handle, alsa_params);
if (ret < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev,
snd_strerror(ret));
}
if (my_sample_rate != desired_sample_rate) {
+ pthread_mutex_unlock(&alsa_mutex);
die("Can't set the D/A converter to %d.", desired_sample_rate);
}
ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length);
if (ret < 0) {
+ pthread_mutex_unlock(&alsa_mutex);
die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev,
snd_strerror(ret));
}
@@ -606,7 +623,7 @@ int open_alsa_device(void) {
buffer_size);
}
*/
- debug(1, "The alsa buffer is to small (%lu bytes) to accommodate the desired backend buffer "
+ debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend buffer "
"length (%ld) you have chosen.",
actual_buffer_length, config.audio_backend_buffer_desired_length);
}
@@ -802,16 +819,24 @@ static void play(short buf[], int samples) {
if (alsa_handle == NULL) {
pthread_mutex_lock(&alsa_mutex);
ret = open_alsa_device();
- if (hardware_mixer)
- open_mixer();
+ if (ret == 0) {
+ if (audio_alsa.volume)
+ do_volume(set_volume);
+ if (audio_alsa.mute)
+ do_mute(0);
+ }
pthread_mutex_unlock(&alsa_mutex);
- if ((hardware_mixer) && (ret == 0) && (audio_alsa.volume))
- audio_alsa.volume(set_volume);
}
if (ret == 0) {
pthread_mutex_lock(&alsa_mutex);
snd_pcm_sframes_t current_delay = 0;
int err, ignore;
+ if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) {
+ if ((err = snd_pcm_prepare(alsa_handle))) {
+ ignore = snd_pcm_recover(alsa_handle, err, 1);
+ debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err));
+ }
+ }
if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
(snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) {
if (buf == NULL)
@@ -842,27 +867,42 @@ static void flush(void) {
// debug(2,"audio_alsa flush called.");
pthread_mutex_lock(&alsa_mutex);
int derr;
- if (hardware_mixer && alsa_mix_handle) {
- snd_mixer_close(alsa_mix_handle);
- alsa_mix_handle = NULL;
- }
+ do_mute(1);
if (alsa_handle) {
// debug(1,"Dropping frames for flush...");
+ /*
if ((derr = snd_pcm_drop(alsa_handle)))
debug(1, "Error dropping frames: \"%s\".", snd_strerror(derr));
// debug(1,"Dropped frames ok. State is %d.",snd_pcm_state(alsa_handle));
if ((derr = snd_pcm_prepare(alsa_handle)))
debug(1, "Error preparing after flush: \"%s\".", snd_strerror(derr));
// debug(1,"Frames successfully dropped.");
+ */
+
/*
if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED)
debug(1,"Flush returns to SND_PCM_STATE_PREPARED state.");
if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING)
debug(1,"Flush returns to SND_PCM_STATE_RUNNING state.");
*/
+ /*
if (!((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
(snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)))
debug(1, "Flush returning unexpected state -- %d.", snd_pcm_state(alsa_handle));
+ */
+
+ // this is derived from
+ // http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2latency_8c-example.html#a45
+
+ if ((derr = snd_pcm_nonblock(alsa_handle, 0)))
+ debug(1, "Error %d (\"%s\") unblocking output device.", derr, snd_strerror(derr));
+ if ((derr = snd_pcm_drain(alsa_handle)))
+ debug(1, "Error %d (\"%s\") draining output device.", derr, snd_strerror(derr));
+ if ((derr = snd_pcm_nonblock(alsa_handle, 1)))
+ debug(1, "Error %d (\"%s\") reblocking output device.", derr, snd_strerror(derr));
+
+ if ((derr = snd_pcm_hw_free(alsa_handle)))
+ debug(1, "Error %d (\"%s\") freeing output device hardware.", derr, snd_strerror(derr));
// flush also closes the device
snd_pcm_close(alsa_handle);
@@ -886,11 +926,10 @@ static void parameters(audio_parameters *info) {
info->maximum_volume_dB = alsa_mix_maxdb;
}
-static void volume(double vol) {
- pthread_mutex_lock(&alsa_mutex);
- debug(2, "Setting volume db to %f.", vol);
+void do_volume(double vol) { // caller is assumed to have the alsa_mutex when using this function
+ debug(3, "Setting volume db to %f.", vol);
set_volume = vol;
- if (hardware_mixer && alsa_mix_handle) {
+ if (volume_set_request && open_mixer()) {
if (has_softvol) {
if (ctl && elem_id) {
snd_ctl_elem_value_t *value;
@@ -913,13 +952,23 @@ static void volume(double vol) {
debug(1, "Can't set playback volume accurately to %f dB.", vol);
if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0)
if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 1) != 0)
- die("Failed to set playback dB volume");
+ debug(1, "Could not set playback dB volume on the mixer.");
}
}
+ volume_set_request = 0; // any external request that has been made is now satisfied
+ // debug(1,"Alsa volume actually set.");
+ close_mixer();
}
+}
+
+void volume(double vol) {
+ pthread_mutex_lock(&alsa_mutex);
+ volume_set_request = 1; // an external request has been made to set the volume
+ do_volume(vol);
pthread_mutex_unlock(&alsa_mutex);
}
+/*
static void linear_volume(double vol) {
debug(2, "Setting linear volume to %f.", vol);
set_volume = vol;
@@ -931,21 +980,49 @@ static void linear_volume(double vol) {
if (alsa_mix_handle) {
if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0)
die("Failed to set playback volume");
+
}
}
}
+*/
-static void mute(int do_mute) {
+static void mute(int mute_state_requested) {
+ // debug(1,"External Mute Request: %d",mute_state_requested);
pthread_mutex_lock(&alsa_mutex);
- // debug(2,"audio_alsa mute called.");
- if (hardware_mixer && alsa_mix_handle) {
- if (do_mute) {
- // debug(1,"Mute");
- snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0);
- } else {
- // debug(1,"Unmute");
- snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1);
- }
- }
+ mute_request_pending = 1;
+ overriding_mute_state_requested = mute_state_requested;
+ do_mute(mute_state_requested);
pthread_mutex_unlock(&alsa_mutex);
}
+
+void do_mute(int mute_state_requested) {
+
+ // if a mute is requested now, then
+ // if an external mute request is in place, leave everything muted
+ // otherwise, if an external mute request is pending, action it
+ // otherwise, action the do_mute request
+
+ int local_mute_state_requested =
+ overriding_mute_state_requested; // go with whatever was asked by the external "mute" call
+
+ // The mute state requested will be actioned unless mute_request_pending is set
+ // If it is set, then that the pending request will be actioned.
+ // If the hardware isn't there, or we are not allowed to use it, nothing will be done
+ // The caller must have the alsa mutex
+
+ if (config.alsa_use_playback_switch_for_mute == 1) {
+ if (mute_request_pending == 0)
+ local_mute_state_requested = mute_state_requested;
+ if (open_mixer()) {
+ if (local_mute_state_requested) {
+ // debug(1,"Playback Switch mute actually done");
+ snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0);
+ } else if (overriding_mute_state_requested == 0) {
+ // debug(1,"Playback Switch unmute actually done");
+ snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1);
+ }
+ close_mixer();
+ }
+ }
+ mute_request_pending = 0;
+}
diff --git a/audio_ao.c b/audio_ao.c
index 02e1e5af..78fbea3f 100644
--- a/audio_ao.c
+++ b/audio_ao.c
@@ -48,67 +48,16 @@ static int init(int argc, char **argv) {
int driver = ao_default_driver_id();
ao_option *ao_opts = NULL;
+ // set up default values first
+
config.audio_backend_buffer_desired_length = 1.0;
config.audio_backend_latency_offset = 0;
- // get settings from settings file first, allow them to be overridden by command line options
+ // get settings from settings file first, allow them to be overridden by
+ // command line options
- if (config.cfg != NULL) {
- /* Get the desired buffer size setting. */
- if (config_lookup_int(config.cfg, "ao.audio_backend_buffer_desired_length", &value)) {
- if ((value < 0) || (value > 66150)) {
- inform("The setting ao.audio_backend_buffer_desired_length is deprecated. "
- "Use ao.audio_backend_buffer_desired_length_in_seconds instead.");
- die("Invalid ao audio backend buffer desired length \"%d\". It "
- "should be between 0 and "
- "66150, default is 6615",
- value);
- } else {
- inform("The setting ao.audio_backend_buffer_desired_length is deprecated. "
- "Use ao.audio_backend_buffer_desired_length_in_seconds instead.");
- config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
- }
- }
-
- /* Get the desired buffer size setting. */
- if (config_lookup_float(config.cfg, "ao.audio_backend_buffer_desired_length_in_seconds",
- &dvalue)) {
- if ((dvalue < 0) || (dvalue > 1.5)) {
- die("Invalid ao audio backend buffer desired time \"%f\". It "
- "should be between 0 and "
- "1.5, default is 0.15 seconds",
- dvalue);
- } else {
- config.audio_backend_buffer_desired_length = dvalue;
- }
- }
-
- /* Get the latency offset. */
- if (config_lookup_int(config.cfg, "ao.audio_backend_latency_offset", &value)) {
- if ((value < -66150) || (value > 66150)) {
- inform("The setting ao.audio_backend_latency_offset is deprecated. "
- "Use ao.audio_backend_latency_offset_in_seconds instead.");
- die("Invalid ao audio backend buffer latency offset \"%d\". It "
- "should be between -66150 and +66150, default is 0",
- value);
- } else {
- inform("The setting ao.audio_backend_latency_offset is deprecated. "
- "Use ao.audio_backend_latency_offset_in_seconds instead.");
- config.audio_backend_latency_offset = 1.0 * value / 44100;
- }
- }
-
- /* Get the latency offset. */
- if (config_lookup_float(config.cfg, "ao.audio_backend_latency_offset_in_seconds", &dvalue)) {
- if ((dvalue < -1.0) || (dvalue > 1.5)) {
- die("Invalid ao audio backend buffer latency offset time \"%f\". It "
- "should be between -1.0 and +1.5, default is 0 seconds",
- dvalue);
- } else {
- config.audio_backend_latency_offset = dvalue;
- }
- }
- }
+ // do the "general" audio options. Note, these options are in the "general" stanza!
+ parse_general_audio_options();
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
@@ -169,9 +118,7 @@ static void deinit(void) {
ao_shutdown();
}
-static void start(int sample_rate, int sample_format) {
- // this information is not used
-}
+static void start(int sample_rate, int sample_format) {}
static void play(short buf[], int samples) { ao_play(dev, (char *)buf, samples * 4); }
diff --git a/audio_pa.c b/audio_pa.c
new file mode 100644
index 00000000..76a2eb13
--- /dev/null
+++ b/audio_pa.c
@@ -0,0 +1,372 @@
+// Based (distantly, with thanks) on
+// http://stackoverflow.com/questions/29977651/how-can-the-pulseaudio-asynchronous-library-be-used-to-play-raw-pcm-data
+
+#include "audio.h"
+#include "common.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// note -- these are hacked and hardwired into this code.
+#define FORMAT PA_SAMPLE_S16NE
+#define RATE 44100
+
+// Four seconds buffer -- should be plenty
+#define buffer_allocation 44100 * 4 * 2 * 2
+
+static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static struct {
+ char *server;
+ char *sink;
+ char *service_name;
+} pulse_options = {.server = NULL, .sink = NULL, .service_name = NULL};
+
+pa_threaded_mainloop *mainloop;
+pa_mainloop_api *mainloop_api;
+pa_context *context;
+pa_stream *stream;
+char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq;
+size_t audio_size = buffer_allocation;
+size_t audio_occupancy;
+
+void context_state_cb(pa_context *context, void *mainloop);
+void stream_state_cb(pa_stream *s, void *mainloop);
+void stream_success_cb(pa_stream *stream, int success, void *userdata);
+void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata);
+
+static int init(int argc, char **argv) {
+
+ // set up default values first
+ config.audio_backend_buffer_desired_length = 0.35;
+ config.audio_backend_latency_offset = 0;
+
+ // get settings from settings file
+
+ // do the "general" audio options. Note, these options are in the "general" stanza!
+ parse_general_audio_options();
+
+ // now the specific options
+ if (config.cfg != NULL) {
+ const char *str;
+
+ /* Get the Application Name. */
+ if (config_lookup_string(config.cfg, "pa.application_name", &str)) {
+ config.pa_application_name = (char *)str;
+ }
+ }
+
+ // finish collecting settings
+
+ // allocate space for the audio buffer
+ audio_lmb = malloc(audio_size);
+ if (audio_lmb == NULL)
+ die("Can't allocate %d bytes for pulseaudio buffer.", audio_size);
+ audio_toq = audio_eoq = audio_lmb;
+ audio_umb = audio_lmb + audio_size;
+ audio_occupancy = 0;
+
+ // Get a mainloop and its context
+ mainloop = pa_threaded_mainloop_new();
+ assert(mainloop);
+ mainloop_api = pa_threaded_mainloop_get_api(mainloop);
+ if (config.pa_application_name)
+ context = pa_context_new(mainloop_api, config.pa_application_name);
+ else
+ context = pa_context_new(mainloop_api, "Shairport Sync");
+ assert(context);
+
+ // Set a callback so we can wait for the context to be ready
+ pa_context_set_state_callback(context, &context_state_cb, mainloop);
+
+ // Lock the mainloop so that it does not run and crash before the context is ready
+ pa_threaded_mainloop_lock(mainloop);
+
+ // Start the mainloop
+ assert(pa_threaded_mainloop_start(mainloop) == 0);
+ assert(pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) == 0);
+
+ // Wait for the context to be ready
+ for (;;) {
+ pa_context_state_t context_state = pa_context_get_state(context);
+ assert(PA_CONTEXT_IS_GOOD(context_state));
+ if (context_state == PA_CONTEXT_READY)
+ break;
+ pa_threaded_mainloop_wait(mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(mainloop);
+
+ return 0;
+}
+
+static void deinit(void) {
+ // debug(1, "pa deinit start");
+ pa_threaded_mainloop_stop(mainloop);
+ pa_threaded_mainloop_free(mainloop);
+ // debug(1, "pa deinit done");
+}
+
+static void start(int sample_rate, int sample_format) {
+
+ uint32_t buffer_size_in_bytes = (uint32_t)2 * 2 * RATE * 0.1; // hard wired in here
+ // debug(1, "pa_buffer size is %u bytes.", buffer_size_in_bytes);
+
+ pa_threaded_mainloop_lock(mainloop);
+ // Create a playback stream
+ pa_sample_spec sample_specifications;
+ sample_specifications.format = FORMAT;
+ sample_specifications.rate = RATE;
+ sample_specifications.channels = 2;
+
+ pa_channel_map map;
+ pa_channel_map_init_stereo(&map);
+
+ stream = pa_stream_new(context, "Playback", &sample_specifications, &map);
+ pa_stream_set_state_callback(stream, stream_state_cb, mainloop);
+ pa_stream_set_write_callback(stream, stream_write_cb, mainloop);
+ // pa_stream_set_latency_update_callback(stream, stream_latency_cb, mainloop);
+
+ // recommended settings, i.e. server uses sensible values
+ pa_buffer_attr buffer_attr;
+ buffer_attr.maxlength = (uint32_t)-1;
+ buffer_attr.tlength = buffer_size_in_bytes;
+ buffer_attr.prebuf = (uint32_t)0;
+ buffer_attr.minreq = (uint32_t)-1;
+
+ // Settings copied as per the chromium browser source
+ pa_stream_flags_t stream_flags;
+ stream_flags = PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_NOT_MONOTONIC |
+ // PA_STREAM_AUTO_TIMING_UPDATE;
+ PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
+
+ // Connect stream to the default audio output sink
+ assert(pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) == 0);
+
+ // Wait for the stream to be ready
+ for (;;) {
+ pa_stream_state_t stream_state = pa_stream_get_state(stream);
+ assert(PA_STREAM_IS_GOOD(stream_state));
+ if (stream_state == PA_STREAM_READY)
+ break;
+ pa_threaded_mainloop_wait(mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(mainloop);
+}
+
+static void play(short buf[], int samples) {
+ // debug(1,"pa_play of %d samples.",samples);
+ char *bbuf = (char *)buf;
+ // copy the samples into the queue
+ size_t bytes_to_transfer = samples * 2 * 2;
+ size_t space_to_end_of_buffer = audio_umb - audio_eoq;
+ if (space_to_end_of_buffer >= bytes_to_transfer) {
+ memcpy(audio_eoq, bbuf, bytes_to_transfer);
+ audio_occupancy += bytes_to_transfer;
+ pthread_mutex_lock(&buffer_mutex);
+ audio_eoq += bytes_to_transfer;
+ pthread_mutex_unlock(&buffer_mutex);
+ } else {
+ memcpy(audio_eoq, bbuf, space_to_end_of_buffer);
+ bbuf += space_to_end_of_buffer;
+ memcpy(audio_lmb, bbuf, bytes_to_transfer - space_to_end_of_buffer);
+ pthread_mutex_lock(&buffer_mutex);
+ audio_occupancy += bytes_to_transfer;
+ pthread_mutex_unlock(&buffer_mutex);
+ audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer;
+ }
+ if ((audio_occupancy >= 11025 * 2 * 2) && (pa_stream_is_corked(stream))) {
+ // debug(1,"Uncorked");
+ pa_threaded_mainloop_lock(mainloop);
+ pa_stream_cork(stream, 0, stream_success_cb, mainloop);
+ pa_threaded_mainloop_unlock(mainloop);
+ }
+}
+
+int pa_delay(long *the_delay) {
+ long result = 0;
+ int reply = -ENODEV;
+ pa_usec_t latency;
+ int negative;
+ pa_threaded_mainloop_lock(mainloop);
+ int gl = pa_stream_get_latency(stream, &latency, &negative);
+ pa_threaded_mainloop_unlock(mainloop);
+ if (gl == PA_ERR_NODATA) {
+ // debug(1, "No latency data yet.");
+ reply = -ENODEV;
+ } else if (gl != 0) {
+ // debug(1,"Error %d getting latency.",gl);
+ reply = -EIO;
+ } else {
+ result = (audio_occupancy / (2 * 2)) + (latency * 44100) / 1000000;
+ reply = 0;
+ }
+ *the_delay = result;
+ return reply;
+}
+
+void flush(void) {
+ // Cork the stream so it will stop playing
+ pa_threaded_mainloop_lock(mainloop);
+ if (pa_stream_is_corked(stream) == 0) {
+ // debug(1,"Flush and cork for flush.");
+ pa_stream_flush(stream, stream_success_cb, NULL);
+ pa_stream_cork(stream, 1, stream_success_cb, mainloop);
+ }
+ pa_threaded_mainloop_unlock(mainloop);
+ audio_toq = audio_eoq = audio_lmb;
+ audio_umb = audio_lmb + audio_size;
+ audio_occupancy = 0;
+}
+
+static void stop(void) {
+ // Cork the stream so it will stop playing
+ pa_threaded_mainloop_lock(mainloop);
+ if (pa_stream_is_corked(stream) == 0) {
+ // debug(1,"Flush and cork for stop.");
+ pa_stream_flush(stream, stream_success_cb, NULL);
+ pa_stream_cork(stream, 1, stream_success_cb, mainloop);
+ }
+ pa_threaded_mainloop_unlock(mainloop);
+ audio_toq = audio_eoq = audio_lmb;
+ audio_umb = audio_lmb + audio_size;
+ audio_occupancy = 0;
+
+ // debug(1, "finish with stream");
+ pa_stream_disconnect(stream);
+}
+
+static void help(void) { printf(" no settings.\n"); }
+
+audio_output audio_pa = {.name = "pa",
+ .help = &help,
+ .init = &init,
+ .deinit = &deinit,
+ .start = &start,
+ .stop = &stop,
+ .flush = &flush,
+ .delay = &pa_delay,
+ .play = &play,
+ .volume = NULL,
+ .parameters = NULL,
+ .mute = NULL};
+
+void context_state_cb(pa_context *context, void *mainloop) {
+ pa_threaded_mainloop_signal(mainloop, 0);
+}
+
+void stream_state_cb(pa_stream *s, void *mainloop) { pa_threaded_mainloop_signal(mainloop, 0); }
+
+void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata) {
+
+ /*
+ // play with timing information
+ const struct pa_timing_info *ti = pa_stream_get_timing_info(stream);
+ if ((ti == NULL) || (ti->write_index_corrupt)) {
+ debug(2, "Timing info invalid");
+ } else {
+ struct timeval time_now;
+
+ pa_gettimeofday(&time_now);
+
+ uint64_t time_now_fp = ((uint64_t)time_now.tv_sec << 32) +
+ ((uint64_t)time_now.tv_usec << 32) / 1000000; // types okay
+ uint64_t time_of_ti_fp = ((uint64_t)(ti->timestamp.tv_sec) << 32) +
+ ((uint64_t)(ti->timestamp.tv_usec) << 32) / 1000000; // types okay
+
+ if (time_now_fp >= time_of_ti_fp) {
+ uint64_t estimate_age = ((time_now_fp - time_of_ti_fp) * 1000000) >> 32;
+ uint64_t bytes_in_buffer = ti->write_index - ti->read_index;
+ pa_usec_t microseconds_to_write_buffer = (bytes_in_buffer * 1000000) / (44100 * 2 * 2);
+ pa_usec_t ea = (pa_usec_t)estimate_age;
+ pa_usec_t pa_latency = ti->sink_usec + ti->transport_usec + microseconds_to_write_buffer;
+ pa_usec_t estimated_latency = pa_latency - estimate_age;
+ // debug(1,"Estimated latency is %d microseconds.",estimated_latency);
+
+ // } else {
+ // debug(1, "Time now is earlier than time of timing information");
+ }
+ }
+ */
+ int bytes_to_transfer = requested_bytes;
+ int bytes_transferred = 0;
+ uint8_t *buffer = NULL;
+
+ while ((bytes_to_transfer > 0) && (audio_occupancy > 0)) {
+ size_t bytes_we_can_transfer = bytes_to_transfer;
+ if (audio_occupancy < bytes_we_can_transfer) {
+ // debug(1, "Underflow? We have %d bytes but we are asked for %d bytes", audio_occupancy,
+ // bytes_we_can_transfer);
+ pa_stream_cork(stream, 1, stream_success_cb, mainloop);
+ // debug(1, "Corked");
+ bytes_we_can_transfer = audio_occupancy;
+ }
+
+ // bytes we can transfer will never be greater than the bytes available
+
+ pa_stream_begin_write(stream, (void **)&buffer, &bytes_we_can_transfer);
+ if (bytes_we_can_transfer <= (audio_umb - audio_toq)) {
+ // the bytes are all in a row in the audo buffer
+ memcpy(buffer, audio_toq, bytes_we_can_transfer);
+ audio_toq += bytes_we_can_transfer;
+ // lock
+ pthread_mutex_lock(&buffer_mutex);
+ audio_occupancy -= bytes_we_can_transfer;
+ pthread_mutex_unlock(&buffer_mutex);
+ // unlock
+ pa_stream_write(stream, buffer, bytes_we_can_transfer, NULL, 0LL, PA_SEEK_RELATIVE);
+ bytes_transferred += bytes_we_can_transfer;
+ } else {
+ // the bytes are in two places in the audio buffer
+ size_t first_portion_to_write = audio_umb - audio_toq;
+ if (first_portion_to_write != 0)
+ memcpy(buffer, audio_toq, first_portion_to_write);
+ char *new_buffer = buffer + first_portion_to_write;
+ memcpy(new_buffer, audio_lmb, bytes_we_can_transfer - first_portion_to_write);
+ pa_stream_write(stream, buffer, bytes_we_can_transfer, NULL, 0LL, PA_SEEK_RELATIVE);
+ bytes_transferred += bytes_we_can_transfer;
+ audio_toq = audio_lmb + bytes_we_can_transfer - first_portion_to_write;
+ // lock
+ pthread_mutex_lock(&buffer_mutex);
+ audio_occupancy -= bytes_we_can_transfer;
+ pthread_mutex_unlock(&buffer_mutex);
+ // unlock
+ }
+ bytes_to_transfer -= bytes_we_can_transfer;
+ // debug(1,"audio_toq is %llx",audio_toq);
+ }
+
+ // debug(1,"<< 0) {
+ uint8_t *buffer = NULL;
+ size_t bytes_to_fill = 44100;
+ size_t i;
+
+ if (bytes_to_fill > bytes_remaining)
+ bytes_to_fill = bytes_remaining;
+
+ pa_stream_begin_write(stream, (void **)&buffer, &bytes_to_fill);
+
+ for (i = 0; i < bytes_to_fill; i += 2) {
+ buffer[i] = (i % 100) * 40 / 100 + 44;
+ buffer[i + 1] = (i % 100) * 40 / 100 + 44;
+ }
+
+ pa_stream_write(stream, buffer, bytes_to_fill, NULL, 0LL, PA_SEEK_RELATIVE);
+
+ bytes_remaining -= bytes_to_fill;
+ }
+}
+
+void stream_success_cb(pa_stream *stream, int success, void *userdata) { return; }
diff --git a/audio_pipe.c b/audio_pipe.c
index 22cac672..131244c1 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -42,20 +42,35 @@
static int fd = -1;
char *pipename = NULL;
+int warned = 0;
static void start(int sample_rate, int sample_format) {
// this will leave fd as -1 if a reader hasn't been attached
fd = open(pipename, O_WRONLY | O_NONBLOCK);
+ if ((fd < -1) && (warned == 0)) {
+ warn("Error %d opening the pipe named \"%s\".", errno, pipename);
+ warned = 1;
+ }
}
static void play(short buf[], int samples) {
// if the file is not open, try to open it.
+ char errorstring[1024];
if (fd == -1) {
fd = open(pipename, O_WRONLY | O_NONBLOCK);
}
// if it's got a reader, write to it.
- if (fd != -1) {
- int ignore = non_blocking_write(fd, buf, samples * 4);
+ if (fd > 0) {
+ int rc = non_blocking_write(fd, buf, samples * 4);
+ if ((rc < 0) && (warned == 0)) {
+ strerror_r(errno, (char *)errorstring, 1024);
+ warn("Error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
+ warned = 1;
+ }
+ } else if ((fd == -1) && (warned == 0)) {
+ strerror_r(errno, (char *)errorstring, 1024);
+ warn("Error %d opening the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
+ warned = 1;
}
}
@@ -71,9 +86,14 @@ static int init(int argc, char **argv) {
int value;
double dvalue;
+ // set up default values first
+
config.audio_backend_buffer_desired_length = 1.0;
config.audio_backend_latency_offset = 0;
+ // do the "general" audio options. Note, these options are in the "general" stanza!
+ parse_general_audio_options();
+
if (config.cfg != NULL) {
/* Get the Output Pipename. */
const char *str;
@@ -83,62 +103,8 @@ static int init(int argc, char **argv) {
if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0))
die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead.");
-
- /* Get the desired buffer size setting. */
- if (config_lookup_int(config.cfg, "pipe.audio_backend_buffer_desired_length", &value)) {
- if ((value < 0) || (value > 66150)) {
- inform("The setting pipe.audio_backend_buffer_desired_length is deprecated. "
- "Use pipe.audio_backend_buffer_desired_length_in_seconds instead.");
- die("Invalid pipe audio backend buffer desired length \"%d\". It "
- "should be between 0 and "
- "66150, default is 6615",
- value);
- } else {
- inform("The setting pipe.audio_backend_buffer_desired_length is deprecated. "
- "Use pipe.audio_backend_buffer_desired_length_in_seconds instead.");
- config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
- }
- }
-
- /* Get the desired buffer size setting. */
- if (config_lookup_float(config.cfg, "pipe.audio_backend_buffer_desired_length_in_seconds",
- &dvalue)) {
- if ((dvalue < 0) || (dvalue > 1.5)) {
- die("Invalid pipe audio backend buffer desired time \"%f\". It "
- "should be between 0 and "
- "1.5, default is 0.15 seconds",
- dvalue);
- } else {
- config.audio_backend_buffer_desired_length = dvalue;
- }
- }
-
- /* Get the latency offset. */
- if (config_lookup_int(config.cfg, "pipe.audio_backend_latency_offset", &value)) {
- if ((value < -66150) || (value > 66150)) {
- inform("The setting pipe.audio_backend_latency_offset is deprecated. "
- "Use pipe.audio_backend_latency_offset_in_seconds instead.");
- die("Invalid pipe audio backend buffer latency offset \"%d\". It "
- "should be between -66150 and +66150, default is 0",
- value);
- } else {
- inform("The setting pipe.audio_backend_latency_offset is deprecated. "
- "Use pipe.audio_backend_latency_offset_in_seconds instead.");
- config.audio_backend_latency_offset = 1.0 * value / 44100;
- }
- }
-
- /* Get the latency offset. */
- if (config_lookup_float(config.cfg, "pipe.audio_backend_latency_offset_in_seconds", &dvalue)) {
- if ((dvalue < -1.0) || (dvalue > 1.5)) {
- die("Invalid pipe audio backend buffer latency offset time \"%f\". It "
- "should be between -1.0 and +1.5, default is 0 seconds",
- dvalue);
- } else {
- config.audio_backend_latency_offset = dvalue;
- }
- }
}
+
if ((pipename == NULL) && (argc != 1))
die("bad or missing argument(s) to pipe");
diff --git a/audio_pulse.c b/audio_pulse.c
index f6fe7adf..96d7c320 100644
--- a/audio_pulse.c
+++ b/audio_pulse.c
@@ -110,9 +110,7 @@ static void deinit(void) {
pa_dev = NULL;
}
-static void start(int sample_rate, int sample_format) {
-// unused information
-}
+static void start(int sample_rate, int sample_format) {}
static void play(short buf[], int samples) {
if (pa_simple_write(pa_dev, (char *)buf, (size_t)samples * 4, &pa_error) < 0) {
diff --git a/audio_sndio.c b/audio_sndio.c
index e8f14807..8d2ba7d2 100644
--- a/audio_sndio.c
+++ b/audio_sndio.c
@@ -1,9 +1,10 @@
/*
- * sndio output driver. This file is part of Shairport.
+ * sndio output driver. This file is part of Shairport Sync.
* Copyright (c) 2013 Dimitri Sokolyuk
+ * Copyright (c) 2017 Tobias Kortkamp
*
* Modifications for audio synchronisation
- * and related work, copyright (c) Mike Brady 2014
+ * and related work, copyright (c) Mike Brady 2014 -- 2017
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
@@ -20,58 +21,23 @@
*/
#include "audio.h"
+#include "common.h"
+#include
#include
#include
+#include
#include
-static struct sio_hdl *sio;
-static struct sio_par par;
-
-static int init(int argc, char **argv) {
- sio = sio_open(SIO_DEVANY, SIO_PLAY, 0);
- if (!sio)
- die("sndio: cannot connect to sound server");
-
- sio_initpar(&par);
-
- par.bits = 16;
- par.rate = 44100;
- par.pchan = 2;
- par.le = SIO_LE_NATIVE;
- par.sig = 1;
-
- if (!sio_setpar(sio, &par))
- die("sndio: failed to set audio parameters");
- if (!sio_getpar(sio, &par))
- die("sndio: failed to get audio parameters");
-
- config.audio_backend_buffer_desired_length = 44100; // one second.
- config.audio_backend_latency_offset = 0;
-
- return 0;
-}
-
-static void deinit(void) { sio_close(sio); }
-
-static void start(int sample_rate) {
- sio_start(sio);
-}
-
-static void play(short buf[], int samples) {
- sio_write(sio, (char *)buf, samples * par.bps * par.pchan);
-}
-
-static void stop(void) { sio_stop(sio); }
-
-static void help(void) {
- printf(" There are no options for sndio audio.\n");
- printf(" Use AUDIODEVICE environment variable.\n");
-}
-
-static void volume(double vol) {
- unsigned int v = vol * SIO_MAXVOL;
- sio_setvol(sio, v);
-}
+static void help(void);
+static int init(int, char **);
+static void onmove_cb(void *, int);
+static void deinit(void);
+static void start(int, int);
+static void play(short[], int);
+static void stop(void);
+static void onmove_cb(void *, int);
+static int delay(long *);
+static void flush(void);
audio_output audio_sndio = {.name = "sndio",
.help = &help,
@@ -79,9 +45,227 @@ audio_output audio_sndio = {.name = "sndio",
.deinit = &deinit,
.start = &start,
.stop = &stop,
- .flush = NULL,
- .delay = NULL,
+ .flush = &flush,
+ .delay = &delay,
.play = &play,
- .volume = &volume,
+ .volume = NULL,
.parameters = NULL,
.mute = NULL};
+
+static pthread_mutex_t sndio_mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct sio_hdl *hdl;
+static int framesize;
+static size_t played;
+static size_t written;
+int64_t time_of_last_onmove_cb;
+int at_least_one_onmove_cb_seen;
+struct sio_par par;
+
+struct sndio_formats {
+ const char *name;
+ enum sps_format_t fmt;
+
+ 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 void help() { printf(" -d output-device set the output device [default*|...]\n"); }
+
+static int init(int argc, char **argv) {
+ int i, found, opt, round, rate, bufsz;
+ const char *devname, *tmp;
+
+ // set up default values first
+
+ sio_initpar(&par);
+ par.rate = 44100;
+ par.pchan = 2;
+ par.bits = 16;
+ par.bps = SIO_BPS(par.bits);
+ par.le = 1;
+ par.sig = 1;
+ devname = SIO_DEVANY;
+
+ config.audio_backend_buffer_desired_length = 1.0;
+ config.audio_backend_latency_offset = 0;
+
+ // get settings from settings file
+
+ // do the "general" audio options. Note, these options are in the "general" stanza!
+ parse_general_audio_options();
+
+ // get the specific settings
+
+ if (config.cfg != NULL) {
+ if (!config_lookup_string(config.cfg, "sndio.device", &devname))
+ devname = SIO_DEVANY;
+ if (config_lookup_int(config.cfg, "sndio.rate", &rate)) {
+ if (rate % 44100 == 0 && rate >= 44100 && rate <= 352800) {
+ par.rate = rate;
+ } else {
+ die("sndio: output rate must be a multiple of 44100 and 44100 <= rate <= "
+ "352800");
+ }
+ }
+ if (config_lookup_int(config.cfg, "sndio.bufsz", &bufsz)) {
+ if (bufsz > 0) {
+ par.appbufsz = bufsz;
+ } else {
+ die("sndio: bufsz must be > 0");
+ }
+ }
+ if (config_lookup_int(config.cfg, "sndio.round", &round)) {
+ if (round > 0) {
+ par.round = round;
+ } else {
+ die("sndio: round must be > 0");
+ }
+ }
+ if (config_lookup_string(config.cfg, "sndio.format", &tmp)) {
+ for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
+ if (strcasecmp(formats[i].name, tmp) == 0) {
+ config.output_format = formats[i].fmt;
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ die("Invalid output format \"%s\". Should be one of: S8, U8, S16, S24, "
+ "S24_3LE, S24_3BE, S32",
+ tmp);
+ }
+ }
+ optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
+ argv--; // so we shift the arguments to satisfy getopt()
+ argc++;
+ while ((opt = getopt(argc, argv, "d:")) > 0) {
+ switch (opt) {
+ case 'd':
+ devname = optarg;
+ break;
+ default:
+ help();
+ die("Invalid audio option -%c specified", opt);
+ }
+ }
+ if (optind < argc)
+ die("Invalid audio argument: %s", argv[optind]);
+ pthread_mutex_lock(&sndio_mutex);
+ debug(1, "Output device name is \"%s\".", devname);
+ hdl = sio_open(devname, SIO_PLAY, 0);
+ if (!hdl)
+ die("sndio: cannot open audio device");
+
+ 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) {
+ par.bits = formats[i].bits;
+ par.bps = formats[i].bps;
+ par.sig = formats[i].sig;
+ par.le = formats[i].le;
+ 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) {
+ config.output_format = formats[i].fmt;
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ die("sndio: failed to negotiate audio parameters");
+
+ framesize = par.bps * par.pchan;
+ config.output_rate = par.rate;
+ config.audio_backend_buffer_desired_length = 1.0 * par.bufsz / par.rate;
+ config.audio_backend_latency_offset = 0;
+
+ sio_onmove(hdl, onmove_cb, NULL);
+
+ pthread_mutex_unlock(&sndio_mutex);
+ return 0;
+}
+
+static void deinit() {
+ pthread_mutex_lock(&sndio_mutex);
+ sio_close(hdl);
+ pthread_mutex_unlock(&sndio_mutex);
+}
+
+static void start(int sample_rate, int sample_format) {
+ pthread_mutex_lock(&sndio_mutex);
+ if (!sio_start(hdl))
+ die("sndio: unable to start");
+ written = played = 0;
+ time_of_last_onmove_cb = 0;
+ at_least_one_onmove_cb_seen = 0;
+ pthread_mutex_unlock(&sndio_mutex);
+}
+
+static void play(short buf[], int frames) {
+ if (frames > 0) {
+ pthread_mutex_lock(&sndio_mutex);
+ written += sio_write(hdl, buf, frames * framesize);
+ pthread_mutex_unlock(&sndio_mutex);
+ }
+}
+
+static void stop() {
+ pthread_mutex_lock(&sndio_mutex);
+ if (!sio_stop(hdl))
+ die("sndio: unable to stop");
+ written = played = 0;
+ pthread_mutex_unlock(&sndio_mutex);
+}
+
+static void onmove_cb(void *arg, int delta) {
+ pthread_mutex_lock(&sndio_mutex);
+ time_of_last_onmove_cb = get_absolute_time_in_fp();
+ at_least_one_onmove_cb_seen = 1;
+ played += delta;
+ pthread_mutex_unlock(&sndio_mutex);
+}
+
+static int delay(long *_delay) {
+ pthread_mutex_lock(&sndio_mutex);
+ size_t estimated_extra_frames_output = 0;
+ if (at_least_one_onmove_cb_seen) { // when output starts, the onmove_cb callback will be made
+ // calculate the difference in time between now and when the last callback occoured,
+ // and use it to estimate the frames that would have been output
+ uint64_t time_difference = get_absolute_time_in_fp() - time_of_last_onmove_cb;
+ uint64_t frame_difference = time_difference * par.rate;
+ uint64_t frame_difference_big_integer = frame_difference >> 32;
+ estimated_extra_frames_output = frame_difference_big_integer;
+ // debug(1,"Frames played to last cb: %d, estimated to current time:
+ // %d.",played,estimated_extra_frames_output);
+ }
+ *_delay = (written / framesize) - (played + estimated_extra_frames_output);
+ pthread_mutex_unlock(&sndio_mutex);
+ return 0;
+}
+
+static void flush() {
+ pthread_mutex_lock(&sndio_mutex);
+ if (!sio_stop(hdl) || !sio_start(hdl))
+ die("sndio: unable to flush");
+ written = played = 0;
+ pthread_mutex_unlock(&sndio_mutex);
+}
diff --git a/audio_soundio.c b/audio_soundio.c
index 17f09562..6960fa35 100644
--- a/audio_soundio.c
+++ b/audio_soundio.c
@@ -84,7 +84,16 @@ static void underflow_callback(struct SoundIoOutStream *outstream) {
static int init(int argc, char **argv) {
int err;
+
config.audio_backend_buffer_desired_length = 2.0;
+ config.audio_backend_latency_offset = 0;
+
+ // get settings from settings file
+
+ // do the "general" audio options. Note, these options are in the "general" stanza!
+ parse_general_audio_options();
+
+ // get the specific settings
soundio = soundio_create();
if (!soundio) {
diff --git a/audio_stdout.c b/audio_stdout.c
index 2e79f76b..a97df679 100644
--- a/audio_stdout.c
+++ b/audio_stdout.c
@@ -40,77 +40,29 @@ static int fd = -1;
static void start(int sample_rate, int sample_format) { fd = STDOUT_FILENO; }
-static void play(short buf[], int samples) { int ignore = write(fd, buf, samples * 4); }
+static void play(short buf[], int samples) {
+ char errorstring[1024];
+ int warned = 0;
+ int rc = write(fd, buf, samples * 4);
+ if ((rc < 0) && (warned == 0)) {
+ strerror_r(errno, (char *)errorstring, 1024);
+ warn("Error %d writing to stdout: \"%s\".", errno, errorstring);
+ warned = 1;
+ }
+}
static void stop(void) {
// don't close stdout
}
static int init(int argc, char **argv) {
- const char *str;
- int value;
- double dvalue;
-
+ // set up default values first
config.audio_backend_buffer_desired_length = 1.0;
config.audio_backend_latency_offset = 0;
- if (config.cfg != NULL) {
- /* Get the desired buffer size setting. */
- if (config_lookup_int(config.cfg, "stdout.audio_backend_buffer_desired_length", &value)) {
- if ((value < 0) || (value > 66150)) {
- inform("The setting audio_backend_buffer_desired_length is deprecated. "
- "Use audio_backend_buffer_desired_length_in_seconds instead.");
- die("Invalid stdout audio backend buffer desired length \"%d\". It "
- "should be between 0 and "
- "66150, default is 6615",
- value);
- } else {
- inform("The stdout.setting audio_backend_buffer_desired_length is deprecated. "
- "Use stdout.audio_backend_buffer_desired_length_in_seconds instead.");
- config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
- }
- }
-
- /* Get the desired buffer size setting. */
- if (config_lookup_float(config.cfg, "stdout.audio_backend_buffer_desired_length_in_seconds",
- &dvalue)) {
- if ((dvalue < 0) || (dvalue > 1.5)) {
- die("Invalid stdout audio backend buffer desired time \"%f\". It "
- "should be between 0 and "
- "1.5, default is 0.15 seconds",
- dvalue);
- } else {
- config.audio_backend_buffer_desired_length = dvalue;
- }
- }
-
- /* Get the latency offset. */
- if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", &value)) {
- if ((value < -66150) || (value > 66150)) {
- inform("The setting stdout.audio_backend_latency_offset is deprecated. "
- "Use stdout.audio_backend_latency_offset_in_seconds instead.");
- die("Invalid stdout audio backend buffer latency offset \"%d\". It "
- "should be between -66150 and +66150, default is 0",
- value);
- } else {
- inform("The setting stdout.audio_backend_latency_offset is deprecated. "
- "Use stdout.audio_backend_latency_offset_in_seconds instead.");
- config.audio_backend_latency_offset = 1.0 * value / 44100;
- }
- }
-
- /* Get the latency offset. */
- if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset_in_seconds",
- &dvalue)) {
- if ((dvalue < -1.0) || (dvalue > 1.5)) {
- die("Invalid stdout audio backend buffer latency offset time \"%f\". It "
- "should be between -1.0 and +1.5, default is 0 seconds",
- dvalue);
- } else {
- config.audio_backend_latency_offset = dvalue;
- }
- }
- }
+ // get settings from settings file
+ // do the "general" audio options. Note, these options are in the "general" stanza!
+ parse_general_audio_options();
return 0;
}
diff --git a/common.c b/common.c
index 53d7715e..8f615be4 100644
--- a/common.c
+++ b/common.c
@@ -32,6 +32,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -79,6 +80,10 @@
#include
+#ifdef CONFIG_ALSA
+void set_alsa_out_dev(char *);
+#endif
+
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
static volatile int requested_connection_state_to_output = 1;
@@ -91,7 +96,7 @@ int get_requested_connection_state_to_output() { return requested_connection_sta
void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; }
-void die(char *format, ...) {
+void die(const char *format, ...) {
char s[1024];
s[0] = 0;
va_list args;
@@ -103,7 +108,7 @@ void die(char *format, ...) {
exit(1);
}
-void warn(char *format, ...) {
+void warn(const char *format, ...) {
char s[1024];
s[0] = 0;
va_list args;
@@ -113,7 +118,7 @@ void warn(char *format, ...) {
daemon_log(LOG_WARNING, "%s", s);
}
-void debug(int level, char *format, ...) {
+void debug(int level, const char *format, ...) {
if (level > debuglev)
return;
char s[1024];
@@ -125,7 +130,7 @@ void debug(int level, char *format, ...) {
daemon_log(LOG_DEBUG, "%s", s);
}
-void inform(char *format, ...) {
+void inform(const char *format, ...) {
char s[1024];
s[0] = 0;
va_list args;
@@ -454,13 +459,78 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
}
#endif
-void command_start(void) {
- if (config.cmd_start) {
+void command_set_volume(double volume) {
+ if (config.cmd_set_volume) {
/*Spawn a child to run the program.*/
pid_t pid = fork();
+ if (pid == 0) { /* child process */
+ size_t command_buffer_size = strlen(config.cmd_set_volume) + 32;
+ char *command_buffer = (char *)malloc(command_buffer_size);
+ if (command_buffer == NULL) {
+ inform("Couldn't allocate memory for set_volume argument string");
+ } else {
+ memset(command_buffer, 0, command_buffer_size);
+ sprintf(command_buffer, "%s%f", config.cmd_set_volume, volume);
+ // debug(1,"command_buffer is \"%s\".",command_buffer);
+ int argC;
+ char **argV;
+ // debug(1,"set_volume command found.");
+ if (poptParseArgvString(command_buffer, &argC, (const char ***)&argV) != 0) {
+ // note that argV should be free()'d after use, but we expect this fork to exit
+ // eventually.
+ warn("Can't decipher on-set-volume command arguments \"%s\".", command_buffer);
+ free(argV);
+ free(command_buffer);
+ } else {
+ free(command_buffer);
+ // debug(1,"Executing on-set-volume command %s with %d arguments.",argV[0],argC);
+ execv(argV[0], argV);
+ warn("Execution of on-set-volume command \"%s\" failed to start", config.cmd_set_volume);
+ // debug(1, "Error executing on-set-volume command %s", config.cmd_set_volume);
+ exit(127); /* only if execv fails */
+ }
+ }
+
+ } else {
+ if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
+ process to finish */
+ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
+ if (rc != pid) {
+ warn("Execution of on-set-volume command returned an error.");
+ debug(1, "on-set-volume command %s finished with error %d", config.cmd_set_volume, errno);
+ }
+ }
+ // debug(1,"Continue after on-set-volume command");
+ }
+ }
+}
+
+void command_start(void) {
+ if (config.cmd_start) {
+ pid_t pid;
+ int pipes[2];
+
+ if (config.cmd_start_returns_output && pipe(pipes) != 0) {
+ warn("Unable to allocate pipe for popen of start command.");
+ debug(1, "pipe finished with error %d", errno);
+ return;
+ }
+ /*Spawn a child to run the program.*/
+ pid = fork();
if (pid == 0) { /* child process */
int argC;
char **argV;
+
+ if (config.cmd_start_returns_output) {
+ close(pipes[0]);
+ if (dup2(pipes[1], 1) < 0) {
+ warn("Unable to reopen pipe as stdout for popen of start command");
+ debug(1, "dup2 finished with error %d", errno);
+ close(pipes[1]);
+ return;
+ }
+ }
+
// debug(1,"on-start command found.");
if (poptParseArgvString(config.cmd_start, &argC, (const char ***)&argV) !=
0) // note that argV should be free()'d after use, but we expect this fork to exit
@@ -474,13 +544,28 @@ void command_start(void) {
exit(127); /* only if execv fails */
}
} else {
- if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
+ if (config.cmd_blocking || config.cmd_start_returns_output) { /* pid!=0 means parent process
+ and if blocking is true, wait for
process to finish */
- pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
+ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
if (rc != pid) {
warn("Execution of on-start command returned an error.");
debug(1, "on-start command %s finished with error %d", config.cmd_start, errno);
}
+ if (config.cmd_start_returns_output) {
+ static char buffer[256];
+ int len;
+ close(pipes[1]);
+ len = read(pipes[0], buffer, 255);
+ close(pipes[0]);
+ buffer[len] = '\0';
+ if (buffer[len - 1] == '\n')
+ buffer[len - 1] = '\0'; // strip trailing newlines
+ debug(1, "received '%s' as the device to use from the on-start command", buffer);
+#ifdef CONFIG_ALSA
+ set_alsa_out_dev(buffer);
+#endif
+ }
}
// debug(1,"Continue after on-start command");
}
@@ -647,9 +732,10 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
// debug(1, "non-blocking write error waiting for pipe to become ready for writing...");
} else if (rc == 0) {
// warn("non-blocking write timeout waiting for pipe to become ready for writing");
- rc = -2;
+ rc = -1;
+ errno = -ETIMEDOUT;
} else { // rc > 0, implying it might be ready
- size_t bytes_written = write(fd, ibuf, bytes_remaining);
+ ssize_t bytes_written = write(fd, ibuf, bytes_remaining);
if (bytes_written == -1) {
// debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno));
rc = -1;
@@ -754,11 +840,13 @@ void ranarrayinit() {
uint64_t ranarrayval() {
uint64_t v = ranarray[ranarraynext];
- ranarraynext = (ranarraynext++) % ranarraylength;
+ ranarraynext++;
+ ranarraynext = ranarraynext % ranarraylength;
+ return v;
}
void r64arrayinit() { ranarrayinit(); }
uint64_t ranarray64u() { return (ranarrayval()); }
-int64_t ranarray64i() { return (ranarrayval(&rx) >> 1); }
+int64_t ranarray64i() { return (ranarrayval() >> 1); }
diff --git a/common.h b/common.h
index 3a1cab58..40bf594d 100644
--- a/common.h
+++ b/common.h
@@ -39,8 +39,8 @@ enum endian_type {
} endian_type;
enum stuffing_type {
- ST_basic = 0,
- ST_soxr,
+ ST_basic = 0, // straight deletion or insertion of a frame in a 352-frame packet
+ ST_soxr, // use libsoxr to make a 352 frame packet one frame longer or shorter
} type;
enum playback_mode_type {
@@ -56,7 +56,8 @@ enum decoders_supported_type {
decoder_apple_alac,
} decoders_supported_type;
-// the following enum must _exactly match the snd_pcm_format_t definition in the alsa pcm.h file.
+// the following enum is for the formats recognised -- currently only S16LE is recognised for input,
+// so these are output only for the present
enum sps_format_t {
SPS_FORMAT_UNKNOWN = 0,
@@ -74,6 +75,11 @@ 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.
+#endif
#ifdef CONFIG_METADATA
int metadata_enabled;
char *metadata_pipename;
@@ -110,10 +116,13 @@ typedef struct {
int64_t AirPlayLatency; // supplied with --AirPlayLatency option
int64_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option
int daemonise;
+ int daemonise_store_pid; // don't try to save a PID file
+ char *piddir;
+ int logOutputLevel; // log output level
int statistics_requested, use_negotiated_latencies;
enum playback_mode_type playback_mode;
- char *cmd_start, *cmd_stop;
- int cmd_blocking;
+ char *cmd_start, *cmd_stop, *cmd_set_volume;
+ 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;
@@ -131,10 +140,23 @@ typedef struct {
// audio backend buffer -- the DAC buffer for ALSA
double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any
// fixed latency there might be in the audio path
+ 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.
enum sps_format_t output_format;
int output_rate;
+
+#ifdef CONFIG_CONVOLUTION
+ int convolution;
+ const char *convolution_ir_file;
+ float convolution_gain;
+ int convolution_max_length;
+#endif
+
+ int loudness;
+ float loudness_reference_volume_db;
+ int alsa_use_playback_switch_for_mute;
+
} shairport_cfg;
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
@@ -160,11 +182,11 @@ void r64arrayinit();
uint64_t ranarray64u();
int64_t ranarray64i();
-int debuglev;
-void die(char *format, ...);
-void warn(char *format, ...);
-void inform(char *format, ...);
-void debug(int level, char *format, ...);
+extern int debuglev;
+void die(const char *format, ...);
+void warn(const char *format, ...);
+void inform(const char *format, ...);
+void debug(int level, const char *format, ...);
uint8_t *base64_dec(char *input, int *outlen);
char *base64_enc(uint8_t *input, int length);
@@ -189,13 +211,9 @@ uint32_t uatoi(const char *nptr);
shairport_cfg config;
config_t config_file_stuff;
-int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative
-int64_t session_corrections;
-int64_t play_segment_reference_frame;
-uint64_t play_segment_reference_frame_remote_time;
-
void command_start(void);
void command_stop(void);
+void command_set_volume(double volume);
void shairport_shutdown();
// void shairport_startup_complete(void);
diff --git a/configure.ac b/configure.ac
index 45dcd570..8a949377 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,21 +2,19 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.0.2], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.1rc0], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
# Derived from the Avahi configure.ac file
-#
-#
# Specifying the OS type, defaulting to linux.
#
-AC_ARG_WITH(os_type, AS_HELP_STRING([--with-os-type=OSType],[Specify the distribution to target: One of linux freebsd or darwin]))
-if test "z$with_os_type" = "z"; then
- with_os_type="linux"
+AC_ARG_WITH(os, AS_HELP_STRING([--with-os=OSType],[Specify the distribution to target: One of linux freebsd or darwin]))
+if test "z$with_os" = "z"; then
+ with_os="linux"
fi
-with_os_type=`echo ${with_os_type} | tr '[[:upper:]]' '[[:lower:]]' `
+with_os=`echo ${with_os} | tr '[[:upper:]]' '[[:lower:]]' `
# Checks for programs.
AC_PROG_CC
@@ -26,10 +24,12 @@ AC_PROG_INSTALL
PKG_PROG_PKG_CONFIG([0.9.0])
# Checks for libraries.
-if test "x${with_os_type}" = xlinux -o "x${with_os_type}" = xfreebsd ; then
+if test "x${with_os}" = xlinux -o "x${with_os}" = xfreebsd ; then
AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed))
fi
+AM_CONDITIONAL([BUILD_FOR_FREEBSD], [test "x${with_os}" = xfreebsd ])
+
##### Some build systems are not fully using pkg-config, so we can use the flag ${with_pkg_config} on a case-by-case basis
##### to control how to deal with them
@@ -78,9 +78,13 @@ AM_CONDITIONAL([INSTALL_SYSTEMV], [test "x$with_systemv" = "xyes"])
# Check to see if we should include the systemd stuff to define it as a service
AC_ARG_WITH([systemd],
-[ --with-systemd = install a systemd service description file during a make install], , )
+[ --with-systemd = install a systemd startup script during a make install], , )
AM_CONDITIONAL([INSTALL_SYSTEMD], [test "x$with_systemd" = "xyes"])
+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"])
+
# Add the libconfig package
if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
@@ -111,6 +115,19 @@ AC_ARG_WITH(apple-alac, [ --with-apple-alac = include support for the Apple ALA
fi ])
AM_CONDITIONAL([USE_APPLE_ALAC], [test "x$HAS_APPLE_ALAC" = "x1"])
+# Look for firstconfdir flag
+AC_ARG_WITH(firstconfdir, [ --with-firstconfdir= Specify a pathname to a directory where a configuration file will be searched for before looking in sysconfdir.], [
+ AC_MSG_CHECKING(--with-firstconfdir argument)
+ if test "x${with_firstconfdir}" = x -o "x${with_firstconfdir}" = xyes ; then
+ AC_MSG_RESULT(not found)
+ AC_MSG_ERROR(when you use the --with-firstconfdir directive you must specify a pathname to a directory where a configuration file will be searched for before looking in sysconfdir.)
+ fi
+ AC_MSG_RESULT(${with_firstconfdir})
+ HAS_FIRSTCONFDIR=1
+ AC_SUBST(FIRST_CONF_DIR,["${with_firstconfdir}"])
+ AC_DEFINE([USE_FIRSTCONFDIR],1,[Look for a configuration file here before looking in the sysconfdir])], )
+AM_CONDITIONAL([USE_FIRSTCONFDIR], [test "x$HAS_FIRSTCONFDIR" = "x1"])
+
# Look for piddir flag
AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [
AC_MSG_CHECKING(--with-piddir argument)
@@ -206,8 +223,8 @@ AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)],
AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"])
# Look for SNDIO flag
-AC_ARG_WITH(sndio, [ --with-sndio = choose SNDIO API support (FreeBSD) -- probably broken], [
- AC_MSG_RESULT(>>Including a SNDIO back end -- N.B. this is probably broken!)
+AC_ARG_WITH(sndio, [ --with-sndio = choose SNDIO API support], [
+ AC_MSG_RESULT(>>Including a SNDIO back end)
HAS_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!))], )
@@ -230,14 +247,38 @@ AC_ARG_WITH(soundio, [ --with-soundio = choose soundio API support.], [
AM_CONDITIONAL([USE_SOUNDIO], [test "x$HAS_SOUNDIO" = "x1"])
# Look for pulseaudio flag
-AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose PulseAudio API support. N.B. no synchronisation -- so underflow or overflow is inevitable!], [
- AC_MSG_RESULT(>>Including a PulseAudio back end. N.B. no synchronisation -- so underflow or overflow is inevitable!)
+AC_ARG_WITH(pa, [ --with-pa = choose PulseAudio support.], [
+ AC_MSG_RESULT(>>Including a PulseAudio back end.)
+ HAS_PA=1
+ AC_DEFINE([CONFIG_PA], 1, [Needed by the compiler.])
+ if test "x${with_pkg_config}" = xyes ; then
+ PKG_CHECK_MODULES(
+ [PULSEAUDIO], [libpulse >= 0.9.2],
+ [LIBS="${PULSEAUDIO_LIBS} ${LIBS}"],[AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library!)])
+ else
+ 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"])
+
+# Look for old pulseaudio flag
+AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose PulseAudio API support. Note that this backend is deprecated. Use --with-pa for an updated PulseAudio backend!], [
+ AC_MSG_RESULT(>>Including an outdated PulseAudio back end. Note that this backend is deprecated. Use --with-pa for an updated PulseAudio backend!)
HAS_PULSE=1
AC_DEFINE([CONFIG_PULSE], 1, [Needed by the compiler.])
AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the pulse-simple library -- possibly in the libpulse-dev package!))
AC_CHECK_LIB([pulse], [pa_stream_peek], , AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library.))], )
AM_CONDITIONAL([USE_PULSE], [test "x$HAS_PULSE" = "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
+ AM_INIT_AUTOMAKE([subdir-objects])
+ AC_DEFINE([CONFIG_CONVOLUTION], 1, [Needed by the compiler.])
+ AC_CHECK_LIB([sndfile], [sf_open], , AC_MSG_ERROR(Convolution support requires the sndfile library!))], )
+AM_CONDITIONAL([USE_CONVOLUTION], [test "x$HAS_CONVOLUTION" = "x1"])
+
# Look for dns_sd flag
AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [
AC_MSG_RESULT(>>Including dns_sd for mDNS support)
@@ -295,8 +336,6 @@ AC_TYPE_UINT8_T
AC_FUNC_ALLOCA
AC_FUNC_ERROR_AT_LINE
AC_FUNC_FORK
-AC_FUNC_MALLOC
-AC_FUNC_REALLOC
AC_CHECK_FUNCS([atexit clock_gettime gethostname inet_ntoa memchr memmove memset mkfifo pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul])
AC_CONFIG_FILES([Makefile man/Makefile scripts/shairport-sync.service])
diff --git a/loudness.c b/loudness.c
new file mode 100644
index 00000000..b4dbc3c3
--- /dev/null
+++ b/loudness.c
@@ -0,0 +1,50 @@
+#include "loudness.h"
+#include "common.h"
+#include
+
+loudness_processor loudness_r;
+loudness_processor loudness_l;
+
+void _loudness_set_volume(loudness_processor *p, float volume) {
+ float gain = -(volume - config.loudness_reference_volume_db) * 0.5;
+ if (gain < 0)
+ gain = 0;
+
+ float Fc = 10.0;
+ float Q = 0.5;
+
+ // Formula from http://www.earlevel.com/main/2011/01/02/biquad-formulas/
+ float Fs = 44100.0;
+
+ float K = tan(M_PI * Fc / Fs);
+ float V = pow(10.0, gain / 20.0);
+
+ float norm = 1 / (1 + 1 / Q * K + K * K);
+ p->a0 = (1 + V / Q * K + K * K) * norm;
+ p->a1 = 2 * (K * K - 1) * norm;
+ p->a2 = (1 - V / Q * K + K * K) * norm;
+ p->b1 = p->a1;
+ p->b2 = (1 - 1 / Q * K + K * K) * norm;
+}
+
+float loudness_process(loudness_processor *p, float i0) {
+ float o0 = p->a0 * i0 + p->a1 * p->i1 + p->a2 * p->i2 - p->b1 * p->o1 - p->b2 * p->o2;
+
+ p->o2 = p->o1;
+ p->o1 = o0;
+
+ p->i2 = p->i1;
+ p->i1 = i0;
+
+ return o0;
+}
+
+void loudness_set_volume(float volume) {
+ float gain = -(volume - config.loudness_reference_volume_db) * 0.5;
+ if (gain < 0)
+ gain = 0;
+
+ inform("Volume: %.1f dB - Loudness gain @10Hz: %.1f dB", volume, gain);
+ _loudness_set_volume(&loudness_l, volume);
+ _loudness_set_volume(&loudness_r, volume);
+}
diff --git a/loudness.h b/loudness.h
new file mode 100644
index 00000000..1e20c9b8
--- /dev/null
+++ b/loudness.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+
+typedef struct {
+ float a0, a1, a2, b1, b2;
+ float i1, i2, o1, o2;
+} loudness_processor;
+
+extern loudness_processor loudness_r;
+extern loudness_processor loudness_l;
+
+void loudness_set_volume(float volume);
+float loudness_process(loudness_processor *p, float sample);
diff --git a/man/shairport-sync.7 b/man/shairport-sync.7
index c616ed68..0da7ba01 100644
--- a/man/shairport-sync.7
+++ b/man/shairport-sync.7
@@ -2,7 +2,7 @@
.SH NAME
shairport-sync \- Synchronised Audio Player for iTunes / AirPlay
.SH SYNOPSIS
-\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--get-cover-art]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB
+\fBshairport-sync [-djvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--get-cover-art]\fB [--logOutputLevel]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB
shairport-sync -D\fB
@@ -15,7 +15,7 @@ shairport-sync -R\fB
shairport-sync -V\fB
\f1
.SH DESCRIPTION
-shairport-sync plays audio streamed from iTunes or from an AirPlay device to an Advanced Linux Sound Architecture (ALSA) compatible audio output device.
+shairport-sync plays audio streamed from iTunes or from an AirPlay device to an ALSA compatible audio output device (available on Linux and FreeBSD) , to a "sndio" output device (available on OpenBSD, FreeBSD and Linux) or to a PulseAudio output stream (available on Linux).
A feature of shairport-sync is that the audio is played synchronously. This means that if many devices are playing the same stream at the same time, all the outputs will stay in step with one another. This allows multiple devices to play the same source without getting out of phase with one another, enabling, for example, simultaneous multi-room operation.
@@ -27,7 +27,7 @@ You should use the configuration file for setting up shairport-sync. This file i
(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command \fI$ shairport-sync -V\f1. One of the items in the output string is the value of the \fBsysconfdir\f1, i.e. the System Configuration Directory.)
-Settings are organised into groups, for example, there is a "general" group of standard settings, and there is an "alsa" group with settings that pertain to the ALSA back end. Here is an example of a typical configuration file:
+Within the configuraton file, settings are organised into groups, for example, there is a "general" group of standard settings, and there is an "alsa" group with settings that pertain to the ALSA back end. Here is an example of a typical configuration file:
\fBgeneral = {\f1
@@ -37,6 +37,8 @@ Settings are organised into groups, for example, there is a "general" group of s
\fBpassword = "secret";\f1
+\fBoutput_backend = "alsa";\f1
+
\fB};\f1
\fB\f1
@@ -68,28 +70,28 @@ The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \
The default is "%H", which is replaced by the hostname with the first letter capitalised.
.TP
\fBpassword=\f1\fI"password"\f1\fB;\f1
-Require the password \fIpassword\f1 to connect to the service. If you leave this setting commented out, no password is needed.
+Require the password \fIpassword\f1 to connect to the service. If you leave this setting commented out, no password is needed.
.TP
\fBinterpolation=\f1\fI"mode"\f1\fB;\f1
Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly in synchrony with the player. The default mode, "basic", is normally almost completely inaudible. The alternative mode, "soxr", is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled.
.TP
\fBstatistics=\f1\fI"setting"\f1\fB;\f1
-Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
+Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
.TP
\fBmdns_backend=\f1\fI"backend"\f1\fB;\f1
-shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS \fIbackend\f1. The default is "avahi". Perform the command \fBshairport-sync -h\f1 to get a list of available mDNS modules.
+shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS \fIbackend\f1. The default is "avahi". Perform the command \fBshairport-sync -h\f1 to get a list of available mDNS modules.
.TP
\fBoutput_backend=\f1\fI"backend"\f1\fB;\f1
-shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. The default is "alsa". Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends. Only the alsa backend supports synchronisation.
+shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends -- the default is the first on this list. Only the "alsa", "sndio" and "pa" backends support synchronisation.
.TP
\fBport=\f1\fIportnumber\f1\fB;\f1
-Use this to specify the \fIportnumber\f1 shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.
+Use this to specify the \fIportnumber\f1 shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.
.TP
\fBudp_port_base=\f1\fIportnumber\f1\fB;\f1
-When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting \fIportnumber\f1 for these three ports. It will pick the first three unused ports starting from \fIportnumber\f1. The default is port 6001.
+When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting \fIportnumber\f1 for these three ports. It will pick the first three unused ports starting from \fIportnumber\f1. The default is port 6001.
.TP
\fBudp_port_range=\f1\fIrange\f1\fB;\f1
-Use this in conjunction with the prevous setting to specify the \fIrange\f1 of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.
+Use this in conjunction with the prevous setting to specify the \fIrange\f1 of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.
.TP
\fBdrift_tolerance_in_seconds=\f1\fIseconds\f1\fB;\f1
Allow playback to drift up to \fIseconds\f1 out of exact synchronization before attempting to correct it. The default is 0.002 seconds, i.e. 2 milliseconds. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections. This setting replaces the deprecated \fBdrift\f1 setting.
@@ -98,13 +100,13 @@ Allow playback to drift up to \fIseconds\f1 out of exact synchronization before
Resynchronise if timings differ by more than \fIthreshold\f1 seconds. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 0.050 seconds, i.e. 50 milliseconds. Specify 0.0 to disable resynchronisation. This setting replaces the deprecated \fBresync_threshold\f1 setting.
.TP
\fBlog_verbosity=\f1\fI0\f1\fB;\f1
-Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
+Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
.TP
\fBignore_volume_control=\f1\fI"choice"\f1\fB;\f1
-Set this \fIchoice\f1 to \fI"yes"\f1 if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is \fI"no"\f1.
+Set this \fIchoice\f1 to \fI"yes"\f1 if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is \fI"no"\f1.
.TP
\fBvolume_max_db=\f1\fIdBvalue\f1\fB;\f1
-Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB.
+Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB.
.TP
\fBvolume_range_db=\f1\fIdBvalue\f1\fB;\f1
Use this \fIdBvalue\f1 to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume.
@@ -118,98 +120,119 @@ As a third example, you can actually extend the range provided by a mixer. Many
If you omit this setting, the native range of the mixer is used.
.TP
\fBregtype=\f1\fI"regTypeString"\f1\fB;\f1
-Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is \fI"_raop._tcp"\f1.
+Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is \fI"_raop._tcp"\f1.
.TP
\fBplayback_mode=\f1\fI"mode"\f1\fB;\f1
-The \fImode\f1 can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
+The \fImode\f1 can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
.TP
\fBinterface=\f1\fI"name"\f1\fB;\f1
-Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
+Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
.TP
\fBalac_decoder=\f1\fI"decodername"\f1\fB;\f1
-This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work.
+This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work.
+.TP
+\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1
+Set this \fIoffset_in_seconds\f1 to compensate for a fixed delay in the audio back end. For example, if the output device delays by 100 ms, set this to -0.1.
+.TP
+\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIlength_in_seconds\f1\fB;\f1
+Use this \fIlength_in_seconds\f1 to set the desired length of the queue of audio frames in the backend's output buffer.
+
+The default is 0.15 seconds for the ALSA backend, 0.35 seconds for the PA backend and one second for all other backends.
+
+If this value is set too small, underflow may occur on low-powered machines. If set too large, the response times to the volume control may become excessive, or it may exceed the backend's buffer size. It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata.
+.TP
+\fBaudio_backend_silent_lead_in_time=\f1\fIlead_in_time_in_seconds\f1\fB;\f1
+This is an advanced setting. Use the \fIlead_in_time_in_seconds\f1 to set the desired length of the period of silence (a "silent lead-in") played before a play session begins.
+
+The purpose of this silent lead-in is to give the backend sufficient time to prepare for operation and to make an estimate (and, importantly, to correct the estimate) of the exact time at which to begin playing audio to achieve initial synchronisation. The value can be from 0.0 up to a maximum of either 4.0 seconds. The actual duration will be close to the setting but can not exceed the latency set by the client, usually 2 seconds or a little more.
+
+If the value chosen is too short for synchronised backends such as the ALSA, sndio or PA backends, then audio will not be synchronised correctly at the start of play. The default is to have a silent lead-in of approximately the same time as the latency set by the client.
+.TP
+\fBrun_this_when_volume_is_set=\f1\fI"/full/path/to/application/and/args"\f1\fB;\f1
+Here you can specify a program and its arguments that will be run when the volume is set or changed. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate.
+
+The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument. AirPlay volume goes from 0.0 to -30.0 and -144.0 means "mute".
.TP
\fB"ALSA" SETTINGS\f1
These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as \fBalsamixer\f1 or \fBaplay\f1 to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the \fBalsa\f1 group settings:
.TP
\fBoutput_device=\f1\fI"output_device"\f1\fB;\f1
-Use the output device called \fIoutput_device\f1. The default is the device called \fI"default"\f1.
-.TP
+Use the output device called \fIoutput_device\f1. The default is the device called \fI"default"\f1.
.TP
\fBmixer_control_name=\f1\fI"name"\f1\fB;\f1
-Specify the \fIname\f1 of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. If you do not specify a mixer control name, shairport-sync will adjust the volume in software. \fBmixer_type=\f1\fI"mixer_type"\f1\fB;\f1
-This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the \fBmixer_control_name\f1 setting -- if you specify a mixer name with the \fBmixer_control_name\f1 setting, it is assumed that the mixer is implemented in hardware.
+Specify the \fIname\f1 of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. If you do not specify a mixer control name, shairport-sync will adjust the volume in software.
.TP
\fBmixer_device=\f1\fI"mixer_device"\f1\fB;\f1
-By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
-.TP
-\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset\f1\fB;\f1
-Set this \fIoffset\f1, in seconds, to compensate for a fixed delay in the audio back end. For example, if the output device delays by 100 ms, set this to -0.1.
-.TP
-\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIlength\f1\fB;\f1
-Use this to set the desired length, in seconds, of the queue of audio frames in the output device's hardware output buffer. The default is 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying, or it may exceed the hardware buffer size. It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata.
+By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
.TP
\fBoutput_rate=\f1\fIframe rate\f1\fB;\f1
Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available.
.TP
\fBoutput_format=\f1\fI"format"\f1\fB;\f1
-Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify. "S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16". If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
+Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify.
+
+"S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16".
+
+If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
.TP
\fBdisable_synchronization=\f1\fI"no"\f1\fB;\f1
This is an advanced setting and is for debugging only. Set to \fI"yes"\f1 to disable synchronization. Default is \fI"no"\f1. If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to audio buffer overflow or underflow.
.TP
\fBperiod_size=\f1\fInumber\f1\fB;\f1
-Use this optional advanced setting to set the alsa period size near to this value.
+Use this optional advanced setting to set the alsa period size near to this value.
.TP
\fBbuffer_size=\f1\fInumber\f1\fB;\f1
-Use this optional advanced setting to set the alsa buffer size near to this value.
+Use this optional advanced setting to set the alsa buffer size near to this value.
.TP
\fBuse_mmap_if_available=\f1\fI"yes"\f1\fB;\f1
-Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is \fI"yes"\f1.
+Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is \fI"yes"\f1.
+.TP
+\fBmute_using_playback_switch=\f1\fI"yes"\f1\fB;\f1
+This is an advanced setting and the default is \fI"yes"\f1, which means that hardware mute will be implemented using a feature called a 'playback switch', where one is available; set it to \fI"no"\f1 to prevent the playback switch being used.
+
+The motivation for this is to avoid using the alsa function call "snd_mixer_selem_set_playback_switch_all", which is incorrectly implemented on certain soundcards, including the emulated card in VMWare Fusion 8.5.
+.TP
+\fB"SNDIO" SETTINGS\f1
+These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.
+.TP
+\fBdevice=\f1\fI"snd/0"\f1\fB;\f1
+Use this optional setting to specify the name of the output device, e.g. \fI"snd/0"\f1. The default is to use the SNDIO system's default.
+.TP
+\fBrate=\f1\fI44100\f1\fB;\f1
+Use this optional setting to specify the output rate in frames per second. Valid rates are 44100, 88200, 176400 or 352800. The output device must have the capability to accept data at the specified rate. The default is 44100.
+.TP
+\fBformat=\f1\fI"S16"\f1\fB;\f1
+Use this optional setting to specify the output format. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify.
+
+"S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16".
+
+Since the SNDIO backend does not use a hardware mixer for volume control, dither will be introduced into the output if it is less than full volume. Thus, (unless you are ignoring the volume control setting), consider using 32- or 24-bit output if your device is capable of it, to get the lowest possible levels of dither.
+
+Please note that 32- or 24-bit has not been extensively tested on SNDIO.
+.TP
+\fBround=\f1\fIvalue\f1\fB;\f1
+Use this optional advanced setting to specify the period size of the SNDIO channel. If omitted, a SNDIO system default value will be used.
+.TP
+\fBbufsiz=\f1\fIvalue\f1\fB;\f1
+Use this optional advanced setting to specify the buffer size of the SNDIO channel. If omitted, a SNDIO system default value will be used.
+.TP
+\fB"PA" SETTINGS\f1
+These settings are for the new PulseAudio backend.
+.TP
+\fBapplication_name=\f1\fI"Shairport Sync"\f1\fB;\f1
+Use this to set the name to appear in the Sounds "Applications" tab when Shairport Sync is active. The default is the name "Shairport Sync".
.TP
\fB"PIPE" SETTINGS\f1
These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, interleaved stereo.
-
-Use the \fIname\f1 setting to set the name and location of the pipe.
-
-There are two further settings affecting timing that might be useful if the pipe reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size.
-
-These are the settings available within the \fBpipe\f1 group:
.TP
\fBname=\f1\fI"/path/to/pipe"\f1\fB;\f1
-Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it.
-.TP
-\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1
-Packets of audio frames are written to the pipe synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
-.TP
-\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1
-Use this setting, in seconds, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 1 second. Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 1.0 seconds.
+Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it.
.TP
\fB"STDOUT" SETTINGS\f1
-These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format, usually PCM 16 bit little endian, 44,100 samples per second, interleaved stereo.
-
-There are two settings affecting timing that might be useful if the stdout reader is, for example, a program to play an audio stream such as \fBaplay\f1. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size.
-
-These are the settings available within the \fBstdout\f1 group:
-.TP
-\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1
-Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
-.TP
-\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1
-Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 1 second. Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 1.0 seconds.
+There are no settings for the STDOUT backend.
.TP
\fB"AO" SETTINGS\f1
-These settings are for the AO backend, used for the libao audio library.
-
-There are two settings affecting timing. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size.
-
-These are the settings available within the \fBao\f1 group:
-.TP
-\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1
-Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
-.TP
-\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1
-Use this setting, in seconds, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the libao system. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 1 second. Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 1.0 seconds.
+There are no configuration file settings for the AO backend.
.TP
\fB"METADATA" SETTINGS\f1
shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It sends the metadata to a pipe, by default \fI/tmp/shairport-sync-metadata\f1. To process metadata, shairport-sync must have been compiled with metadata support included. You can check that this is so by running the command \fB$ shairport-sync -V\f1; the identification string will contain the word \fBmetadata\f1.
@@ -219,40 +242,40 @@ Please note that different sources provide different levels of metadata. Some pr
The \fBmetadata\f1 group of settings allow you to enable metadata handling and to control certain aspects of it:
.TP
\fBenabled=\f1\fI"choice"\f1\fB;\f1
-Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it, along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".
+Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it, along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".
.TP
\fBinclude_cover_art=\f1\fI"choice"\f1\fB;\f1
-Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe. You must also enable metadata (see above). One reason for not including cover art is that the images can sometimes be very large and may delay transmission of subsequent metadata through the pipe. The default is "no".
+Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe. You must also enable metadata (see above). One reason for not including cover art is that the images can sometimes be very large and may delay transmission of subsequent metadata through the pipe. The default is "no".
.TP
\fBpipe_name=\f1\fI"filepathname"\f1\fB;\f1
-Specify the absolute path name of the pipe through which metadata should be sent The default is \fI/tmp/shairport-sync-metadata\f1.
+Specify the absolute path name of the pipe through which metadata should be sent The default is \fI/tmp/shairport-sync-metadata\f1.
.TP
\fBsocket_address=\f1\fI"hostnameOrIP"\f1\fB;\f1
-If \fIhostnameOrIP\f1 is set to a host name or and IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. Additionally, \fIsocket-port\f1 must be non-zero and \fIenabled\f1 must be set to "yes".
+If \fIhostnameOrIP\f1 is set to a host name or and IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. Additionally, \fIsocket-port\f1 must be non-zero and \fIenabled\f1 must be set to "yes".
.TP
\fBsocket_port=\f1\fIport\f1\fB;\f1
-If \fBsocket_address\f1 is set, use \fIport\f1 to specify the port to send UDP packets to. Must not be zero.
+If \fBsocket_address\f1 is set, use \fIport\f1 to specify the port to send UDP packets to. Must not be zero.
.TP
\fBsocket_msglength=\f1\fI65000\f1\fB;\f1
-The maximum packet size for any UDP metadata. This must be between 500 or 65000. The default is 500.
+The maximum packet size for any UDP metadata. This must be between 500 or 65000. The default is 500.
.TP
\fB"SESSIONCONTROL" SETTINGS\f1
shairport-sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends.
.TP
\fBrun_this_before_play_begins=\f1\fI"/path/to/application and args"\f1\fB;\f1
-Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate.
+Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate.
.TP
\fBrun_this_after_play_ends=\f1\fI"/path/to/application and args"\f1\fB;\f1
-Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate.
+Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate.
.TP
\fBwait_for_completion=\f1\fI"choice"\f1\fB;\f1
-Set \fIchoice\f1 to "yes" to make shairport-sync wait until the programs specified in the \fBrun_this_before_play_begins\f1 and \fBrun_this_after_play_ends\f1 have completed execution before continuing. The default is "no".
+Set \fIchoice\f1 to "yes" to make shairport-sync wait until the programs specified in the \fBrun_this_before_play_begins\f1, \fBrun_this_after_play_ends\f1 and \fBrun_this_when_volume_is_set\f1 have completed execution before continuing. The default is "no".
.TP
\fBallow_session_interruption=\f1\fI"choice"\f1\fB;\f1
-If \fBchoice\f1 is set to "yes", then another source will be able to interrupt an existing play session and start a new one. When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal.
+If \fBchoice\f1 is set to "yes", then another source will be able to interrupt an existing play session and start a new one. When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal.
.TP
\fBsession_timeout=\f1\fIseconds\f1\fB;\f1
-If a play session has been established and the source disappears without warning (such as a device going out of range of a network) then wait for \fIseconds\f1 seconds before ending the session. Once the session has terminated, other devices can use it. The default is 120 seconds.
+If a play session has been established and the source disappears without warning (such as a device going out of range of a network) then wait for \fIseconds\f1 seconds before ending the session. Once the session has terminated, other devices can use it. The default is 120 seconds.
.SH OPTIONS
This section is about the command-line options available in shairport-sync.
@@ -265,7 +288,9 @@ There are two kinds of command-line options for shairport-sync: regular \fBprogr
These command-line options are used by shairport-sync itself.
.TP
\fB-a \f1\fIservice name\f1\fB | --name=\f1\fIservice name\f1
-Use this \fIservice name\f1 to identify this player in iTunes, etc. The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
+Use this \fIservice name\f1 to identify this player in iTunes, etc.
+
+The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
The default is "%H", which is replaced by the hostname with the first letter capitalised.
.TP
@@ -283,7 +308,7 @@ Disconnect the shairport-sync daemon from the output device and exit. (Requires
Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
.TP
\fB-d | --daemon\f1
-Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time.
+Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time. See also the \fB-j\f1 option.
.TP
\fB-E \f1\fIprogram\f1\fB | --on-stop=\f1\fIprogram\f1
Execute \fIprogram\f1 when playback has ended. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have \fI#!/bin/sh\f1 (or whatever is appropriate) in the headline.
@@ -298,9 +323,15 @@ Please note that cover art data may be very large, and may place too great a bur
\fB-h | --help\f1
Print brief help message and exit.
.TP
+\fB-j\f1
+Instruct shairport-sync to demonise itself. Unlike the \fB-d\f1 option, it will not write a Process ID (PID) to a file -- it will just (hence the "j") demonise itself.
+.TP
\fB-k | --kill\f1
Kill the shairport-sync daemon and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option).
.TP
+\fB--logOutputLevel\f1
+Use this to log the volume level when the volume is changed. It may be useful if you are trying to determine a suitable value for the maximum volume level. Not available as a configuration file setting.
+.TP
\fB-L | --latency=\f1\fIlatency\f1
Use this to set the \fIdefault latency\f1, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the \fIdefault latency\f1 is 88,200 frames, where there are 44,100 frames to the second.
@@ -313,7 +344,7 @@ Listen for metadata coming from the source and send it, along with metadata from
Force the use of the specified mDNS backend to advertise the player on the network. The default is to try all mDNS backends until one works.
.TP
\fB-o \f1\fIoutputbackend\f1\fB | --output=\f1\fIoutputbackend\f1
-Force the use of the specified output backend to play the audio. The default is to try the first one. (This is not used at present.)
+Force the use of the specified output backend to play the audio. The default is to try the first one.
.TP
\fB-p \f1\fIport\f1\fB | --port=\f1\fIport\f1
Listen for play requests on \fIport\f1. The default is to use port 5000.
diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml
index db1ce722..c59d8988 100644
--- a/man/shairport-sync.7.xml
+++ b/man/shairport-sync.7.xml
@@ -35,13 +35,14 @@
- shairport-sync [-dvw]
+ shairport-sync [-djvw][-a name][-A latency][-B command][-c configurationfile][-E command][--get-cover-art]
+ [--logOutputLevel][-L latency][-m backend][--meta-dir=directory]
@@ -63,7 +64,7 @@
shairport-sync plays audio streamed from iTunes or from an AirPlay
- device to an Advanced Linux Sound Architecture (ALSA) compatible audio output device.
+ device to an ALSA compatible audio output device (available on Linux and FreeBSD) , to a "sndio" output device (available on OpenBSD, FreeBSD and Linux) or to a PulseAudio output stream (available on Linux).
A feature of shairport-sync is that the audio is played synchronously.
This means that if many devices are playing the same stream at the same
@@ -87,7 +88,7 @@
i.e. the System Configuration Directory.)
-
Settings are organised into groups, for example, there is a "general" group of
+
Within the configuraton file, settings are organised into groups, for example, there is a "general" group of
standard settings, and there is an "alsa" group with settings that pertain to the ALSA
back end. Here is an example of a typical configuration file:
@@ -95,6 +96,7 @@
name = "Mike's Boombox";
interpolation = "soxr";
password = "secret";
+
output_backend = "alsa";
};
alsa = {
@@ -132,11 +134,11 @@
+
+
+
+
-
+
These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system.
(By the way, you can use tools such as alsamixer or aplay to discover what devices are available.)
@@ -255,159 +288,115 @@
-
mixer_type="mixer_type";
- This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting
- -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware.
+ If you do not specify a mixer control name, shairport-sync will adjust the volume in software.
-
-
-
+
+
+
+
These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.
+
+
+
+
+
+
+
+
+
These settings are for the new PulseAudio backend.
+
+
These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second,
interleaved stereo.
-
Use the name setting to set the name and location of the pipe.
-
There are two further settings affecting timing that might be useful if the pipe reader is, for example,
- a program to play an audio stream such as aplay. The audio_backend_latency_offset_in_seconds affects precisely
- when the first audio packet is sent
- and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.
-
These are the settings available within the pipe group:
-
-
-
-
-
-
These settings are for the STDOUT backend, used to route audio to standard output ("stdout").
- The audio is in raw CD audio format, usually PCM 16 bit little endian, 44,100 samples per second, interleaved stereo.
-
There are two settings affecting timing that might be useful if the stdout reader is, for example,
- a program to play an audio stream such as aplay. The audio_backend_latency_offset_in_seconds affects precisely when the first audio packet is sent
- and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.
-
These are the settings available within the stdout group:
-
-
-
-
+
There are no settings for the STDOUT backend.
-
These settings are for the AO backend, used for the libao audio library.
-
There are two settings affecting timing. The audio_backend_latency_offset_in_seconds affects precisely when the first audio packet is sent
- and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.
-
These are the settings available within the ao group:
+
There are no configuration file settings for the AO backend.
-
-
-
-
shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level,
@@ -420,33 +409,33 @@
shairport-sync plays audio streamed from iTunes or from an AirPlay
- device to an Advanced Linux Sound Architecture (ALSA) compatible audio output device.
+ device to an ALSA compatible audio output device (available on Linux and FreeBSD) , to a "sndio" output device (available on OpenBSD, FreeBSD and Linux) or to a PulseAudio output stream (available on Linux).
A feature of shairport-sync is that the audio is played synchronously.
This means that if many devices are playing the same stream at the same
@@ -68,7 +69,7 @@
i.e. the System Configuration Directory.)
-
Settings are organised into groups, for example, there is a "general" group of
+
Within the configuraton file, settings are organised into groups, for example, there is a "general" group of
standard settings, and there is an "alsa" group with settings that pertain to the ALSA
back end. Here is an example of a typical configuration file:
@@ -76,6 +77,7 @@
name = "Mike's Boombox";
interpolation = "soxr";
password = "secret";
+
output_backend = "alsa";
};
alsa = {
@@ -113,11 +115,11 @@
password="password";
- Require the password password to connect to the service. If you leave this setting commented out, no password is needed.
+
Require the password password to connect to the service. If you leave this setting commented out, no password is needed.
interpolation="mode";
- Interpolate, or "stuff", the audio stream using the mode. Interpolation here refers to the
+
Interpolate, or "stuff", the audio stream using the mode. Interpolation here refers to the
process of adding or removing frames of audio to or from the
stream sent to the output device to keep it exactly in synchrony
with the player.
@@ -126,67 +128,67 @@
requires much more processing power. For this mode, support for
libsoxr, the SoX Resampler Library, must be selected when
shairport-sync is compiled.
-
+
statistics="setting";
- Use this setting to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
+
Use this setting to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
mdns_backend="backend";
- shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS backend. The default is "avahi". Perform the command shairport-sync -h to get a list of available mDNS modules.
+
shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS backend. The default is "avahi". Perform the command shairport-sync -h to get a list of available mDNS modules.
output_backend="backend";
- shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio backend. The default is "alsa". Perform the command shairport-sync -h to get a list of available audio backends. Only the alsa backend supports synchronisation.
+
shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio backend. Perform the command shairport-sync -h to get a list of available audio backends -- the default is the first on this list. Only the "alsa", "sndio" and "pa" backends support synchronisation.
port=portnumber;
- Use this to specify the portnumber shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.
+
Use this to specify the portnumber shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.
udp_port_base=portnumber;
- When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting portnumber for these three ports. It will pick the first three unused ports starting from portnumber. The default is port 6001.
+
When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting portnumber for these three ports. It will pick the first three unused ports starting from portnumber. The default is port 6001.
udp_port_range=range;
- Use this in conjunction with the prevous setting to specify the range of ports that can be checked for availability. Only three ports are needed.
- The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.
+
Use this in conjunction with the prevous setting to specify the range of ports that can be checked for availability. Only three ports are needed.
+ The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.
drift_tolerance_in_seconds=seconds;
- Allow playback to drift up to seconds out of exact synchronization before attempting to correct it.
+
Allow playback to drift up to seconds out of exact synchronization before attempting to correct it.
The default is 0.002 seconds, i.e. 2 milliseconds. The smaller the tolerance, the more likely it is that overcorrection will occur.
Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the statistics setting to
monitor correction levels. Corrections should not greatly exceed net corrections. This setting replaces the deprecated drift setting.
-
+
resync_threshold_in_seconds=threshold;
- Resynchronise if timings differ by more than threshold seconds.
+
Resynchronise if timings differ by more than threshold seconds.
If the output timing differs from the source timing by more than
the threshold, output will be muted and a full resynchronisation
will occur. The default threshold is 0.050 seconds, i.e. 50
milliseconds. Specify 0.0 to disable resynchronisation.
This setting replaces the deprecated resync_threshold setting.
-
+
log_verbosity=0;
- Use this to specify how much debugging information should be output or logged. The value 0 means no debug information, 3 means most debug information. The default is 0.
+
Use this to specify how much debugging information should be output or logged. The value 0 means no debug information, 3 means most debug information. The default is 0.
ignore_volume_control="choice";
- Set this choice to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to.
- This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is "no".
+
Set this choice to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to.
+ This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is "no".
volume_max_db=dBvalue;
-
Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB.
-
+
Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB.
+
@@ -209,25 +211,56 @@
regtype="regTypeString";
- Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
+
Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
playback_mode="mode";
- The mode can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
+
The mode can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
interface="name";
- Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
+
Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
alac_decoder="decodername";
- This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work.
+
This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work.
Set this offset_in_seconds to compensate for a fixed delay in the audio back end.
+ For example, if the output device delays by 100 ms, set this to -0.1.
Use this length_in_seconds to set the desired length of the queue of audio frames in the backend's output buffer.
+
The default is 0.15 seconds for the ALSA backend, 0.35 seconds for the PA backend and one second for all other backends.
+
If this value is set too small, underflow may occur on low-powered machines.
+ If set too large, the response times to the volume control may become excessive, or it may exceed the backend's buffer size.
+ It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata.
This is an advanced setting. Use the lead_in_time_in_seconds to set the desired length of the period of silence
+ (a "silent lead-in") played before a play session begins.
+
The purpose of this silent lead-in is to give the backend sufficient time to prepare for operation and to make an estimate
+ (and, importantly, to correct the estimate) of the exact time at which to begin playing audio to achieve initial synchronisation.
+ The value can be from 0.0 up to a maximum of either 4.0 seconds. The actual duration will be close to the setting but can not exceed the latency set by the client,
+ usually 2 seconds or a little more.
+
If the value chosen is too short for synchronised backends such as the ALSA, sndio or PA backends, then audio will not be synchronised correctly at the start of play.
+ The default is to have a silent lead-in of approximately the same time as the latency set by the client.
Here you can specify a program and its arguments that will be run when the volume is set or changed. Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line must begin with the standard #!/bin/... as appropriate.
+
The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument.
+ AirPlay volume goes from 0.0 to -30.0 and -144.0 means "mute".
-
+
"ALSA" SETTINGS
These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system.
(By the way, you can use tools such as alsamixer or aplay to discover what devices are available.)
@@ -236,159 +269,115 @@
output_device="output_device";
- Use the output device called output_device. The default is the device called "default".
-
+
Use the output device called output_device. The default is the device called "default".
mixer_control_name="name";
- Specify the name of the mixer control to be used by shairport-sync to control the volume.
+
Specify the name of the mixer control to be used by shairport-sync to control the volume.
The mixer control must be on the mixer device, which by default is the output device.
- If you do not specify a mixer control name, shairport-sync will adjust the volume in software.
-
-
mixer_type="mixer_type";
- This setting is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the mixer_control_name setting
- -- if you specify a mixer name with the mixer_control_name setting, it is assumed that the mixer is implemented in hardware.
+ If you do not specify a mixer control name, shairport-sync will adjust the volume in software.
mixer_device="mixer_device";
- By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
-
-
-
audio_backend_latency_offset_in_seconds=offset;
- Set this offset, in seconds, to compensate for a fixed delay in the audio back end.
- For example, if the output device delays by 100 ms, set this to -0.1.
-
-
-
- Use this to set the desired length, in seconds, of the queue of audio frames in the output device's hardware output buffer.
- The default is 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines.
- If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying,
- or it may exceed the hardware buffer size.
- It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata.
+
By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
output_rate=frame rate;
- Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available.
-
+
Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available.
+
output_format="format";
- Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify. "S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16". If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
-
+
Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify.
"S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16".
If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
+
disable_synchronization="no";
- This is an advanced setting and is for debugging only. Set to "yes" to disable synchronization. Default is "no".
+
This is an advanced setting and is for debugging only. Set to "yes" to disable synchronization. Default is "no".
If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to
audio buffer overflow or underflow.
-
+
period_size=number;
- Use this optional advanced setting to set the alsa period size near to this value.
+
Use this optional advanced setting to set the alsa period size near to this value.
buffer_size=number;
- Use this optional advanced setting to set the alsa buffer size near to this value.
+
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 this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes".
-
+
+
+
mute_using_playback_switch="yes";
+
+
This is an advanced setting and the default is "yes", which means that hardware mute will be implemented using a feature called a 'playback switch', where one is available;
+ set it to "no" to prevent the playback switch being used.
+
The motivation for this is to avoid using the alsa function call
+ "snd_mixer_selem_set_playback_switch_all", which is incorrectly implemented on certain soundcards, including the emulated card in VMWare Fusion 8.5.
+
+
+
"SNDIO" SETTINGS
+
These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.
+
+
device="snd/0";
+
Use this optional setting to specify the name of the output device, e.g. "snd/0". The default is to use the SNDIO system's default.
+
+
+
rate=44100;
+
Use this optional setting to specify the output rate in frames per second. Valid rates are 44100, 88200, 176400 or 352800.
+ The output device must have the capability to accept data at the specified rate. The default is 44100.
+
+
+
format="S16";
+
Use this optional setting to specify the output format. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32".
+ The device must have the capability to accept the format you specify.
"S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian.
+ Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16".
+ Since the SNDIO backend does not use a hardware mixer for volume control, dither will be introduced into the output if it is less than full volume.
+ Thus, (unless you are ignoring the volume control setting),
+ consider using 32- or 24-bit output if your device is capable of it, to get the lowest possible levels of dither.
+
Please note that 32- or 24-bit has not been extensively tested on SNDIO.
+
+
+
round=value;
+
Use this optional advanced setting to specify the period size of the SNDIO channel. If omitted, a SNDIO system default value will be used.
+
+
+
bufsiz=value;
+
Use this optional advanced setting to specify the buffer size of the SNDIO channel. If omitted, a SNDIO system default value will be used.
+
+
+
+
"PA" SETTINGS
+
These settings are for the new PulseAudio backend.
+
+
application_name="Shairport Sync";
+
Use this to set the name to appear in the Sounds "Applications" tab when Shairport Sync is active. The default is the name "Shairport Sync".
+
+
"PIPE" SETTINGS
These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second,
interleaved stereo.
-
Use the name setting to set the name and location of the pipe.
-
There are two further settings affecting timing that might be useful if the pipe reader is, for example,
- a program to play an audio stream such as aplay. The audio_backend_latency_offset_in_seconds affects precisely
- when the first audio packet is sent
- and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.
-
These are the settings available within the pipe group:
-
name="/path/to/pipe";
- Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up
+
Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up
and will be closed upon shutdown.
Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached.
- The sender will wait for up to five seconds for a packet to be written before discarding it.
-
-
-
-
-
- Packets of audio frames are written to the pipe synchronously -- that is, they are written at exactly the time they should be played.
- You can offset the time of initial audio output relative to its nominal time using this setting.
- For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
-
-
-
-
-
- Use this setting, in seconds, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to the pipe.
- For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length_in_seconds setting of 1.0,
- send subsequent packets of audio a second before they are due to be played, they will be buffered in the pipe reader's buffer, giving it a nominal buffer size of 1 second.
- Note that if the pipe reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow --
- shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 1.0 seconds.
-
+ The sender will wait for up to five seconds for a packet to be written before discarding it.
"STDOUT" SETTINGS
-
These settings are for the STDOUT backend, used to route audio to standard output ("stdout").
- The audio is in raw CD audio format, usually PCM 16 bit little endian, 44,100 samples per second, interleaved stereo.
-
There are two settings affecting timing that might be useful if the stdout reader is, for example,
- a program to play an audio stream such as aplay. The audio_backend_latency_offset_in_seconds affects precisely when the first audio packet is sent
- and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.
-
These are the settings available within the stdout group:
-
- Packets of audio frames are written to stdout synchronously -- that is, they are written at exactly the time they should be played.
- You can offset the time of initial audio output relative to its nominal time using this setting.
- For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
-
-
-
-
-
- Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout.
- For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length_in_seconds setting of 1.0,
- send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 1 second.
- Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow --
- shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 1.0 seconds.
-
-
+
There are no settings for the STDOUT backend.
"AO" SETTINGS
-
These settings are for the AO backend, used for the libao audio library.
-
There are two settings affecting timing. The audio_backend_latency_offset_in_seconds affects precisely when the first audio packet is sent
- and the audio_backend_buffer_desired_length_in_seconds setting affects the nominal output buffer size.
-
These are the settings available within the ao group:
+
There are no configuration file settings for the AO backend.
-
- Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played.
- You can offset the time of initial audio output relative to its nominal time using this setting.
- For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
-
-
-
-
-
- Use this setting, in seconds, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of
- audio frames are sent to the libao system.
- For example, if you send the first packet of audio exactly when it is due and, using a audio_backend_buffer_desired_length_in_seconds setting of 1.0,
- send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 1 second.
- Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow --
- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 1.0 seconds.
-
-
"METADATA" SETTINGS
shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level,
@@ -401,33 +390,33 @@
enabled="choice";
- Set the choice to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it,
- along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".
+
Set the choice to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it,
+ along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".
include_cover_art="choice";
- Set the choice to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe.
+
Set the choice to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe.
You must also enable metadata (see above).
One reason for not including cover art is that the images can sometimes be very large and may delay transmission of subsequent metadata through the pipe.
- The default is "no".
+ The default is "no".
pipe_name="filepathname";
- Specify the absolute path name of the pipe through which metadata should be sent The default is /tmp/shairport-sync-metadata.
+
Specify the absolute path name of the pipe through which metadata should be sent The default is /tmp/shairport-sync-metadata.
socket_address="hostnameOrIP";
- If hostnameOrIP is set to a host name or and IP address, UDP packets containing metadata will be sent to this address.
- May be a multicast address. Additionally, socket-port must be non-zero and enabled must be set to "yes".
+
If hostnameOrIP is set to a host name or and IP address, UDP packets containing metadata will be sent to this address.
+ May be a multicast address. Additionally, socket-port must be non-zero and enabled must be set to "yes".
socket_port=port;
- If socket_address is set, use port to specify the port to send UDP packets to. Must not be zero.
+
If socket_address is set, use port to specify the port to send UDP packets to. Must not be zero.
socket_msglength=65000;
- The maximum packet size for any UDP metadata. This must be between 500 or 65000. The default is 500.
+
The maximum packet size for any UDP metadata. This must be between 500 or 65000. The default is 500.
"SESSIONCONTROL" SETTINGS
@@ -436,29 +425,29 @@
run_this_before_play_begins="/path/to/application and args";
- Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line must begin with the standard #!/bin/... as appropriate.
+
Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line must begin with the standard #!/bin/... as appropriate.
run_this_after_play_ends="/path/to/application and args";
- Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line must begin with the standard #!/bin/... as appropriate.
+
Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line must begin with the standard #!/bin/... as appropriate.
wait_for_completion="choice";
- Set choice to "yes" to make shairport-sync wait until the programs specified in the run_this_before_play_begins
- and run_this_after_play_ends have completed execution before continuing. The default is "no".
+
Set choice to "yes" to make shairport-sync wait until the programs specified in the run_this_before_play_begins,
+ run_this_after_play_ends and run_this_when_volume_is_set have completed execution before continuing. The default is "no".
allow_session_interruption="choice";
- If choice is set to "yes", then another source will be able to interrupt an existing play session and start a new one.
- When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal.
+
If choice is set to "yes", then another source will be able to interrupt an existing play session and start a new one.
+ When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal.
session_timeout=seconds;
- If a play session has been established and the source disappears without warning (such as a device going out of range of a network)
+
If a play session has been established and the source disappears without warning (such as a device going out of range of a network)
then wait for seconds seconds before ending the session. Once the session has terminated, other devices can use it.
- The default is 120 seconds.
+ The default is 120 seconds.
@@ -490,7 +479,7 @@
-a service name | --name=service name
- Use this service name to identify this player in iTunes, etc.
+ Use this service name to identify this player in iTunes, etc.
The following substitutions are allowed:
%h for the computer's hostname,
@@ -498,7 +487,7 @@
%v for the shairport-sync version number, e.g. "3.0.1" and
%V for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
The default is "%H", which is replaced by the hostname with the first letter capitalised.
-
+
@@ -538,9 +527,9 @@
Instruct shairport-sync to demonise itself. It will write its
Process ID (PID) to a file, usually at
- /var/run/shairport-sync.pid, which is used by the
+ /var/run/shairport-sync/shairport-sync.pid, which is used by the
-k, -D and -R options to locate
- the daemon at a later time.
+ the daemon at a later time. See also the -j option.
@@ -575,6 +564,14 @@
+
-j
+
+ Instruct shairport-sync to demonise itself. Unlike the -d option, it will not write a
+ Process ID (PID) to a file -- it will just (hence the "j") demonise itself.
+
+
+
+
-k | --kill
Kill the shairport-sync daemon and exit. (Requires that the daemon has
@@ -583,6 +580,15 @@
+
--logOutputLevel
+
+ Use this to log the volume level when the volume is changed. It may be useful if you are trying to
+ determine a suitable value for the maximum volume level. Not available as a configuration file setting.
+
+
+
+
+
-L | --latency=latency
Use this to set the default latency, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the default latency is 88,200 frames, where there are 44,100
@@ -616,8 +622,7 @@
-o outputbackend | --output=outputbackend
Force the use of the specified output backend to play the audio.
- The default is to try the first one. (This is not used at
- present.)
+ The default is to try the first one.
diff --git a/mdns.h b/mdns.h
index 7aa9dbb2..49bae008 100644
--- a/mdns.h
+++ b/mdns.h
@@ -24,7 +24,7 @@ typedef struct {
// "sr=44100", "vn=3", "txtvers=1", config.password ? "pw=true" : "pw=false"
#define MDNS_RECORD_WITH_METADATA \
- "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", \
+ "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", \
METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", \
"ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false"
@@ -35,7 +35,7 @@ typedef struct {
// "txtvers=1", config.password ? "pw=true" : "pw=false"
#define MDNS_RECORD_WITHOUT_METADATA \
- "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \
+ "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \
"sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", \
config.password ? "pw=true" : "pw=false"
diff --git a/mdns_avahi.c b/mdns_avahi.c
index 31c02bea..f8e75d17 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -179,7 +179,7 @@ static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
- debug(2, "avahi: service '%s' group is not yet commited.", name);
+ debug(2, "avahi: service '%s' group is not yet committed.", name);
break;
case AVAHI_ENTRY_GROUP_REGISTERING:
diff --git a/player.c b/player.c
index b7ca2dd7..7b8b419b 100644
--- a/player.c
+++ b/player.c
@@ -65,6 +65,10 @@
#include
#endif
+#ifdef CONFIG_CONVOLUTION
+#include
+#endif
+
#include "common.h"
#include "player.h"
#include "rtp.h"
@@ -76,95 +80,19 @@
#include "apple_alac.h"
#endif
-// parameters from the source
-static unsigned char *aesiv;
-#ifdef HAVE_LIBSSL
-static AES_KEY aes;
-#endif
-static int input_rate, input_bit_depth, input_num_channels, max_frames_per_packet;
-
-static uint32_t timestamp_epoch, last_timestamp,
- maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2
- // or 1.
-
-static int input_bytes_per_frame = 4;
-static int output_bytes_per_frame;
-static int output_sample_ratio;
-static int output_rate;
-static int64_t previous_random_number = 0;
-
-// The maximum frame size change there can be is +/- 1;
-static int max_frame_size_change;
-// #define FRAME_BYTES(max_frames_per_packet) (4 * max_frames_per_packet)
-// maximal resampling shift - conservative
-//#define OUTFRAME_BYTES(max_frames_per_packet) (4 * (max_frames_per_packet + 3))
-
-#ifdef HAVE_LIBMBEDTLS
-static mbedtls_aes_context dctx;
-#endif
-
-#ifdef HAVE_LIBPOLARSSL
-static aes_context dctx;
-#endif
-
-// static pthread_t player_thread = NULL;
-static int please_stop;
-static int encrypted; // Normally the audio is encrypted, but it may not be
-
-static int
- connection_state_to_output; // if true, then play incoming stuff; if false drop everything
-
-static alac_file *decoder_info;
-
-// debug variables
-static int late_packet_message_sent;
-static uint64_t packet_count = 0;
-static int32_t last_seqno_read;
-static int decoder_in_use = 0;
-
-// interthread variables
-static int fix_volume = 0x10000;
-static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER;
+#include "loudness.h"
// default buffer size
// needs to be a power of 2 because of the way BUFIDX(seqno) works
-#define BUFFER_FRAMES 512
+//#define BUFFER_FRAMES 512
#define MAX_PACKET 2048
// DAC buffer occupancy stuff
#define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 600
-typedef struct audio_buffer_entry { // decoded audio packets
- int ready;
- int64_t timestamp;
- seq_t sequence_number;
- signed short *data;
- int length; // the length of the decoded data
-} abuf_t;
-static abuf_t audio_buffer[BUFFER_FRAMES];
+// static abuf_t audio_buffer[BUFFER_FRAMES];
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
-// mutex-protected variables
-static seq_t ab_read, ab_write;
-static int ab_buffering = 1, ab_synced = 0;
-static int64_t first_packet_timestamp = 0;
-static int flush_requested = 0;
-static int64_t flush_rtp_timestamp;
-static uint64_t time_of_last_audio_packet;
-static int shutdown_requested;
-
-// mutexes and condition variables
-static pthread_mutex_t ab_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t flush_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t flowcontrol;
-
-static int64_t first_packet_time_to_play, time_since_play_started; // nanoseconds
-
-static audio_parameters audio_information;
-
-// stats
-static uint64_t missing_packets, late_packets, too_late_packets, resend_requests;
-
// make timestamps and seqnos definitely monotonic
// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33
@@ -174,42 +102,42 @@ static uint64_t missing_packets, late_packets, too_late_packets, resend_requests
// over 50,000 years.
// also, it won't reach zero until then, if ever, so we can safely say that a null monotonic
// timestamp can mean something special
-int64_t monotonic_timestamp(uint32_t timestamp) {
+int64_t monotonic_timestamp(uint32_t timestamp, rtsp_conn_info *conn) {
int64_t previous_value;
int64_t return_value;
- if (timestamp_epoch == 0) {
- if (timestamp > maximum_timestamp_interval)
- timestamp_epoch = 1;
+ if (conn->timestamp_epoch == 0) {
+ if (timestamp > conn->maximum_timestamp_interval)
+ conn->timestamp_epoch = 1;
else
- timestamp_epoch = 2;
- previous_value = timestamp_epoch;
+ conn->timestamp_epoch = 2;
+ previous_value = conn->timestamp_epoch;
previous_value <<= 32;
previous_value += timestamp;
} else {
- previous_value = timestamp_epoch;
+ previous_value = conn->timestamp_epoch;
previous_value <<= 32;
- previous_value += last_timestamp;
- if (timestamp < last_timestamp) {
+ previous_value += conn->last_timestamp;
+ if (timestamp < conn->last_timestamp) {
// the incoming timestamp is less than the last one.
// if the difference is more than a minute, assume it's really from the next epoch
- if ((last_timestamp - timestamp) > maximum_timestamp_interval)
- timestamp_epoch++;
+ if ((conn->last_timestamp - timestamp) > conn->maximum_timestamp_interval)
+ conn->timestamp_epoch++;
} else {
// the incoming timestamp is greater than the last one.
// if the difference is more than a minute, assume it's really from the previous epoch
- if ((timestamp - last_timestamp) > maximum_timestamp_interval)
- timestamp_epoch--;
+ if ((timestamp - conn->last_timestamp) > conn->maximum_timestamp_interval)
+ conn->timestamp_epoch--;
}
}
- last_timestamp = timestamp;
- return_value = timestamp_epoch;
+ conn->last_timestamp = timestamp;
+ return_value = conn->timestamp_epoch;
return_value <<= 32;
return_value += timestamp;
if (previous_value > return_value) {
- if ((previous_value - return_value) > maximum_timestamp_interval)
+ if ((previous_value - return_value) > conn->maximum_timestamp_interval)
debug(1, "interval between successive rtptimes greater than allowed!");
} else {
- if ((return_value - previous_value) > maximum_timestamp_interval)
+ if ((return_value - previous_value) > conn->maximum_timestamp_interval)
debug(1, "interval between successive rtptimes greater than allowed!");
}
if (return_value < 0)
@@ -217,18 +145,15 @@ int64_t monotonic_timestamp(uint32_t timestamp) {
return return_value;
}
-// add an epoch to the seq_no. Uses the accompanying timestamp to determine the correct epoch
-uint64_t monotonic_seqno(uint16_t seq_no) {}
-
-static void ab_resync(void) {
+static void ab_resync(rtsp_conn_info *conn) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++) {
- audio_buffer[i].ready = 0;
- audio_buffer[i].sequence_number = 0;
+ conn->audio_buffer[i].ready = 0;
+ conn->audio_buffer[i].sequence_number = 0;
}
- ab_synced = 0;
- last_seqno_read = -1;
- ab_buffering = 1;
+ conn->ab_synced = 0;
+ conn->last_seqno_read = -1;
+ conn->ab_buffering = 1;
}
// the sequence number is a 16-bit unsigned number which wraps pretty often
@@ -251,9 +176,9 @@ static inline seq_t PREDECESSOR(seq_t x) {
}
// anything with ORDINATE in it must be proctected by the ab_mutex
-static inline int32_t ORDINATE(seq_t x) {
- int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay
- int32_t q = ab_read; // int32_t from seq_t, i.e. uint16_t, so okay
+static inline int32_t ORDINATE(seq_t x, seq_t base) {
+ int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay
+ int32_t q = base; // int32_t from seq_t, i.e. uint16_t, so okay
int32_t t = (p + 0x10000 - q) & 0xffff;
// we definitely will get a positive number in t at this point, but it might be a
// positive alias of a negative number, i.e. x might actually be "before" ab_read
@@ -267,15 +192,15 @@ static inline int32_t ORDINATE(seq_t x) {
}
// wrapped number between two seq_t.
-int32_t seq_diff(seq_t a, seq_t b) {
- int32_t diff = ORDINATE(b) - ORDINATE(a);
+int32_t seq_diff(seq_t a, seq_t b, seq_t base) {
+ int32_t diff = ORDINATE(b, base) - ORDINATE(a, base);
return diff;
}
// the sequence numbers will wrap pretty often.
// this returns true if the second arg is after the first
-static inline int seq_order(seq_t a, seq_t b) {
- int32_t d = ORDINATE(b) - ORDINATE(a);
+static inline int seq_order(seq_t a, seq_t b, seq_t base) {
+ int32_t d = ORDINATE(b, base) - ORDINATE(a, base);
return d > 0;
}
@@ -305,7 +230,7 @@ static inline int seq32_order(uint32_t a, uint32_t b) {
return (C & 0x80000000) == 0;
}
-static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) {
+static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_conn_info *conn) {
// parameters: where the decoded stuff goes, its length in samples,
// the incoming packet, the length of the incoming packet in bytes
// destlen should contain the allowed max number of samples on entry
@@ -318,78 +243,78 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len) {
unsigned char packet[MAX_PACKET];
unsigned char packetp[MAX_PACKET];
assert(len <= MAX_PACKET);
- int reply = 0; // everything okay
- int outsize = input_bytes_per_frame * (*destlen); // the size the output should be, in bytes
+ int reply = 0; // everything okay
+ int outsize = conn->input_bytes_per_frame * (*destlen); // the size the output should be, in bytes
int toutsize = outsize;
- if (encrypted) {
+ if (conn->stream.encrypted) {
unsigned char iv[16];
int aeslen = len & ~0xf;
- memcpy(iv, aesiv, sizeof(iv));
+ memcpy(iv, conn->stream.aesiv, sizeof(iv));
#ifdef HAVE_LIBMBEDTLS
- mbedtls_aes_crypt_cbc(&dctx, MBEDTLS_AES_DECRYPT, aeslen, iv, buf, packet);
+ mbedtls_aes_crypt_cbc(&conn->dctx, MBEDTLS_AES_DECRYPT, aeslen, iv, buf, packet);
#endif
#ifdef HAVE_LIBPOLARSSL
- aes_crypt_cbc(&dctx, AES_DECRYPT, aeslen, iv, buf, packet);
+ aes_crypt_cbc(&conn->dctx, AES_DECRYPT, aeslen, iv, buf, packet);
#endif
#ifdef HAVE_LIBSSL
- AES_cbc_encrypt(buf, packet, aeslen, &aes, iv, AES_DECRYPT);
+ AES_cbc_encrypt(buf, packet, aeslen, &conn->aes, iv, AES_DECRYPT);
#endif
memcpy(packet + aeslen, buf + aeslen, len - aeslen);
#ifdef HAVE_APPLE_ALAC
if (config.use_apple_decoder) {
- if (decoder_in_use != 1 << decoder_apple_alac) {
+ if (conn->decoder_in_use != 1 << decoder_apple_alac) {
debug(1, "Apple ALAC Decoder used on encrypted audio.");
- decoder_in_use = 1 << decoder_apple_alac;
+ conn->decoder_in_use = 1 << decoder_apple_alac;
}
apple_alac_decode_frame(packet, len, (unsigned char *)dest, &outsize);
outsize = outsize * 4; // bring the size to bytes
} else
#endif
{
- if (decoder_in_use != 1 << decoder_hammerton) {
+ if (conn->decoder_in_use != 1 << decoder_hammerton) {
debug(1, "Hammerton Decoder used on encrypted audio.");
- decoder_in_use = 1 << decoder_hammerton;
+ conn->decoder_in_use = 1 << decoder_hammerton;
}
- alac_decode_frame(decoder_info, packet, (unsigned char *)dest, &outsize);
+ alac_decode_frame(conn->decoder_info, packet, (unsigned char *)dest, &outsize);
}
} else {
// not encrypted
#ifdef HAVE_APPLE_ALAC
if (config.use_apple_decoder) {
- if (decoder_in_use != 1 << decoder_apple_alac) {
+ if (conn->decoder_in_use != 1 << decoder_apple_alac) {
debug(1, "Apple ALAC Decoder used on unencrypted audio.");
- decoder_in_use = 1 << decoder_apple_alac;
+ conn->decoder_in_use = 1 << decoder_apple_alac;
}
apple_alac_decode_frame(buf, len, (unsigned char *)dest, &outsize);
outsize = outsize * 4; // bring the size to bytes
} else
#endif
{
- if (decoder_in_use != 1 << decoder_hammerton) {
+ if (conn->decoder_in_use != 1 << decoder_hammerton) {
debug(1, "Hammerton Decoder used on unencrypted audio.");
- decoder_in_use = 1 << decoder_hammerton;
+ conn->decoder_in_use = 1 << decoder_hammerton;
}
- alac_decode_frame(decoder_info, buf, dest, &outsize);
+ alac_decode_frame(conn->decoder_info, buf, dest, &outsize);
}
}
if (outsize > toutsize) {
debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- "
"truncated, but buffer overflow possible! Encrypted = %d.",
- outsize, toutsize, encrypted);
+ outsize, toutsize, conn->stream.encrypted);
reply = -1; // output packet is the wrong size
}
- *destlen = outsize / input_bytes_per_frame;
- if ((outsize % input_bytes_per_frame) != 0)
+ *destlen = outsize / conn->input_bytes_per_frame;
+ if ((outsize % conn->input_bytes_per_frame) != 0)
debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) "
"and the audio frame size (%d).",
- *destlen, outsize, input_bytes_per_frame);
+ *destlen, outsize, conn->input_bytes_per_frame);
return reply;
}
-static int init_decoder(int32_t fmtp[12]) {
+static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) {
// This is a guess, but the format of the fmtp looks identical to the format of an
// ALACSpecificCOnfig
@@ -470,22 +395,22 @@ static int init_decoder(int32_t fmtp[12]) {
alac_file *alac;
- max_frames_per_packet = fmtp[1]; // number of audio frames per packet.
+ conn->max_frames_per_packet = fmtp[1]; // number of audio frames per packet.
- input_rate = fmtp[11];
- input_num_channels = fmtp[7];
- input_bit_depth = fmtp[3];
+ conn->input_rate = fmtp[11];
+ conn->input_num_channels = fmtp[7];
+ conn->input_bit_depth = fmtp[3];
- input_bytes_per_frame = input_num_channels * ((input_bit_depth + 7) / 8);
+ conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8);
- alac = alac_create(input_bit_depth, input_num_channels);
+ alac = alac_create(conn->input_bit_depth, conn->input_num_channels);
if (!alac)
return 1;
- decoder_info = alac;
+ conn->decoder_info = alac;
- alac->setinfo_max_samples_per_frame = max_frames_per_packet;
+ alac->setinfo_max_samples_per_frame = conn->max_frames_per_packet;
alac->setinfo_7a = fmtp[2];
- alac->setinfo_sample_size = input_bit_depth;
+ alac->setinfo_sample_size = conn->input_bit_depth;
alac->setinfo_rice_historymult = fmtp[4];
alac->setinfo_rice_initialhistory = fmtp[5];
alac->setinfo_rice_kmodifier = fmtp[6];
@@ -503,107 +428,101 @@ static int init_decoder(int32_t fmtp[12]) {
return 0;
}
-static void terminate_decoders(void) {
- alac_free(decoder_info);
+static void terminate_decoders(rtsp_conn_info *conn) {
+ alac_free(conn->decoder_info);
#ifdef HAVE_APPLE_ALAC
apple_alac_terminate();
#endif
}
-static void init_buffer(void) {
+static void init_buffer(rtsp_conn_info *conn) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++)
- audio_buffer[i].data =
- malloc(input_bytes_per_frame * (max_frames_per_packet + max_frame_size_change));
- ab_resync();
+ conn->audio_buffer[i].data = malloc(
+ conn->input_bytes_per_frame * (conn->max_frames_per_packet + conn->max_frame_size_change));
+ ab_resync(conn);
}
-static void free_buffer(void) {
+static void free_buffer(rtsp_conn_info *conn) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++)
- free(audio_buffer[i].data);
+ free(conn->audio_buffer[i].data);
}
-void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) {
+void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len,
+ rtsp_conn_info *conn) {
// all timestamps are done at the output rate
- int64_t ltimestamp = timestamp * output_sample_ratio;
+ int64_t ltimestamp = timestamp * conn->output_sample_ratio;
// ignore a request to flush that has been made before the first packet...
- if (packet_count == 0) {
- pthread_mutex_lock(&flush_mutex);
- flush_requested = 0;
- flush_rtp_timestamp = 0;
- pthread_mutex_unlock(&flush_mutex);
+ if (conn->packet_count == 0) {
+ pthread_mutex_lock(&conn->flush_mutex);
+ conn->flush_requested = 0;
+ conn->flush_rtp_timestamp = 0;
+ pthread_mutex_unlock(&conn->flush_mutex);
}
- pthread_mutex_lock(&ab_mutex);
- packet_count++;
- time_of_last_audio_packet = get_absolute_time_in_fp();
- if (connection_state_to_output) { // if we are supposed to be processing these packets
+ pthread_mutex_lock(&conn->ab_mutex);
+ conn->packet_count++;
+ conn->time_of_last_audio_packet = get_absolute_time_in_fp();
+ if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
// if (flush_rtp_timestamp != 0)
// debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp);
- if ((flush_rtp_timestamp != 0) && (ltimestamp <= flush_rtp_timestamp)) {
+ if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) {
debug(3,
"Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to "
"timestamp: %lld.",
- seqno, ltimestamp, flush_rtp_timestamp);
+ seqno, ltimestamp, conn->flush_rtp_timestamp);
} else {
- if ((flush_rtp_timestamp != 0x0) &&
- (ltimestamp > flush_rtp_timestamp)) // if we have gone past the flush boundary time
- flush_rtp_timestamp = 0x0;
+ if ((conn->flush_rtp_timestamp != 0x0) &&
+ (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time
+ conn->flush_rtp_timestamp = 0x0;
abuf_t *abuf = 0;
- if (!ab_synced) {
+ if (!conn->ab_synced) {
debug(2, "syncing to seqno %u.", seqno);
- ab_write = seqno;
- ab_read = seqno;
- ab_synced = 1;
+ conn->ab_write = seqno;
+ conn->ab_read = seqno;
+ conn->ab_synced = 1;
}
- if (ab_write == seqno) { // expected packet
- abuf = audio_buffer + BUFIDX(seqno);
- ab_write = SUCCESSOR(seqno);
- } else if (seq_order(ab_write, seqno)) { // newer than expected
+ if (conn->ab_write == seqno) { // expected packet
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ conn->ab_write = SUCCESSOR(seqno);
+ } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
// if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8)
// debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and seqno:
// %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno);
- int32_t gap = seq_diff(ab_write, seqno);
+ int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read);
if (gap <= 0)
debug(1, "Unexpected gap size: %d.", gap);
int i;
for (i = 0; i < gap; i++) {
- abuf = audio_buffer + BUFIDX(seq_sum(ab_write, i));
+ abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
abuf->ready = 0; // to be sure, to be sure
abuf->timestamp = 0;
abuf->sequence_number = 0;
}
// debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write);
- abuf = audio_buffer + BUFIDX(seqno);
+ abuf = conn->audio_buffer + BUFIDX(seqno);
// rtp_request_resend(ab_write, gap);
// resend_requests++;
- ab_write = SUCCESSOR(seqno);
- } else if (seq_order(ab_read, seqno)) { // late but not yet played
- late_packets++;
- abuf = audio_buffer + BUFIDX(seqno);
+ conn->ab_write = SUCCESSOR(seqno);
+ } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played
+ conn->late_packets++;
+ abuf = conn->audio_buffer + BUFIDX(seqno);
} else { // too late.
- too_late_packets++;
- /*
- if (!late_packet_message_sent) {
- debug(1, "too-late packet received: %u; ab_read: %u; ab_write: %u.", seqno, ab_read,
- ab_write);
- late_packet_message_sent=1;
- }
- */
+ conn->too_late_packets++;
}
// pthread_mutex_unlock(&ab_mutex);
if (abuf) {
- int datalen = max_frames_per_packet;
- if (alac_decode(abuf->data, &datalen, data, len) == 0) {
+ int datalen = conn->max_frames_per_packet;
+ if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) {
abuf->ready = 1;
abuf->length = datalen;
abuf->timestamp = ltimestamp;
@@ -618,11 +537,11 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len) {
// pthread_mutex_lock(&ab_mutex);
}
- int rc = pthread_cond_signal(&flowcontrol);
+ int rc = pthread_cond_signal(&conn->flowcontrol);
if (rc)
debug(1, "Error signalling flowcontrol.");
}
- pthread_mutex_unlock(&ab_mutex);
+ pthread_mutex_unlock(&conn->ab_mutex);
}
int32_t rand_in_range(int32_t exclusive_range_limit) {
@@ -636,13 +555,20 @@ int32_t rand_in_range(int32_t exclusive_range_limit) {
}
static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume,
- int dither) {
+ int dither, rtsp_conn_info *conn) {
int64_t hyper_sample = sample;
int result;
- int64_t hyper_volume = (int64_t)volume << 16;
- hyper_sample = hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to
- // dither it down to its
- // target resolution
+
+ if (config.loudness) {
+ hyper_sample <<=
+ 32; // Do not apply volume as it has already been done with the Loudness DSP filter
+ } else {
+ int64_t hyper_volume = (int64_t)volume << 16;
+ hyper_sample =
+ hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to
+ // dither it down to its
+ // target resolution
+ }
// next, do dither, if necessary
if (dither) {
@@ -676,6 +602,8 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t
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.");
}
dither_mask -= 1;
// int64_t r = r64i();
@@ -683,8 +611,8 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t
// calculating them on the fly. Should be easier on low-powered
// processors
- int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask);
- previous_random_number = r;
+ int64_t tpdf = (r & dither_mask) - (conn->previous_random_number & dither_mask);
+ conn->previous_random_number = r;
// add dither, allowing for clipping
if (tpdf >= 0) {
if (INT64_MAX - tpdf >= hyper_sample)
@@ -750,48 +678,15 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t
*op = hyper_sample;
result = 1;
break;
+ case SPS_FORMAT_UNKNOWN:
+ die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples");
}
*outp += result;
}
-static inline short dithered_vol(short sample) {
- long out;
-
- out = (long)sample * fix_volume;
- if (fix_volume < 0x10000) {
-
- // add a TPDF dither -- see
- // http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf
- // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25
-
- // I think, for a 32 --> 16 bits, the range of
- // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from
- // -32768 to +32767
-
- // See the original paper at
- // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf
- // by Lipshitz, Wannamaker and Vanderkooy, 1992.
-
- long tpdf = rand_in_range(65536 + 1) - rand_in_range(65536 + 1);
- // Check there's no clipping -- if there is,
- if (tpdf >= 0) {
- if (LONG_MAX - tpdf >= out)
- out += tpdf;
- else
- out = LONG_MAX;
- } else {
- if (LONG_MIN - tpdf <= out)
- out += tpdf;
- else
- out = LONG_MIN;
- }
- }
- return out >> 16;
-}
-
// get the next frame, when available. return 0 if underrun/stream reset.
-static abuf_t *buffer_get_frame(void) {
+static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
int16_t buf_fill;
uint64_t local_time_now;
// struct timespec tn;
@@ -800,7 +695,7 @@ static abuf_t *buffer_get_frame(void) {
abuf_t *curframe;
int notified_buffer_empty = 0; // diagnostic only
- pthread_mutex_lock(&ab_mutex);
+ pthread_mutex_lock(&conn->ab_mutex);
int wait;
long dac_delay = 0; // long because alsa returns a long
do {
@@ -812,54 +707,55 @@ static abuf_t *buffer_get_frame(void) {
// config.timeout of zero means don't check..., but iTunes may be confused by a long gap
// followed by a resumption...
- if ((time_of_last_audio_packet != 0) && (shutdown_requested == 0) &&
+ if ((conn->time_of_last_audio_packet != 0) && (conn->shutdown_requested == 0) &&
(config.dont_check_timeout == 0)) {
uint64_t ct = config.timeout; // go from int to 64-bit int
- if ((local_time_now > time_of_last_audio_packet) &&
- (local_time_now - time_of_last_audio_packet >= ct << 32)) {
+ if ((local_time_now > conn->time_of_last_audio_packet) &&
+ (local_time_now - conn->time_of_last_audio_packet >= ct << 32)) {
debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\"");
rtsp_request_shutdown_stream();
- shutdown_requested = 1;
+ conn->shutdown_requested = 1;
}
}
int rco = get_requested_connection_state_to_output();
- if (connection_state_to_output != rco) {
- connection_state_to_output = rco;
+ if (conn->connection_state_to_output != rco) {
+ conn->connection_state_to_output = rco;
// change happening
- if (connection_state_to_output == 0) { // going off
- pthread_mutex_lock(&flush_mutex);
- flush_requested = 1;
- pthread_mutex_unlock(&flush_mutex);
+ if (conn->connection_state_to_output == 0) { // going off
+ pthread_mutex_lock(&conn->flush_mutex);
+ conn->flush_requested = 1;
+ pthread_mutex_unlock(&conn->flush_mutex);
}
}
- pthread_mutex_lock(&flush_mutex);
- if (flush_requested == 1) {
+ pthread_mutex_lock(&conn->flush_mutex);
+ if (conn->flush_requested == 1) {
if (config.output->flush)
config.output->flush();
- ab_resync();
- first_packet_timestamp = 0;
- first_packet_time_to_play = 0;
- time_since_play_started = 0;
- flush_requested = 0;
+ ab_resync(conn);
+ conn->first_packet_timestamp = 0;
+ conn->first_packet_time_to_play = 0;
+ conn->time_since_play_started = 0;
+ conn->flush_requested = 0;
}
- pthread_mutex_unlock(&flush_mutex);
+ pthread_mutex_unlock(&conn->flush_mutex);
uint32_t flush_limit = 0;
- if (ab_synced) {
+ if (conn->ab_synced) {
do {
- curframe = audio_buffer + BUFIDX(ab_read);
- if ((ab_read != ab_write) && (curframe->ready)) { // it could be synced and empty, under
- // exceptional circumstances, with the
- // frame unused, thus apparently ready
+ curframe = conn->audio_buffer + BUFIDX(conn->ab_read);
+ if ((conn->ab_read != conn->ab_write) &&
+ (curframe->ready)) { // it could be synced and empty, under
+ // exceptional circumstances, with the
+ // frame unused, thus apparently ready
- if (curframe->sequence_number != ab_read) {
+ if (curframe->sequence_number != conn->ab_read) {
// some kind of sync problem has occurred.
- if (BUFIDX(curframe->sequence_number) == BUFIDX(ab_read)) {
+ if (BUFIDX(curframe->sequence_number) == BUFIDX(conn->ab_read)) {
// it looks like some kind of aliasing has happened
- if (seq_order(ab_read, curframe->sequence_number)) {
- ab_read = curframe->sequence_number;
+ if (seq_order(conn->ab_read, curframe->sequence_number, conn->ab_read)) {
+ conn->ab_read = curframe->sequence_number;
debug(1, "Aliasing of buffer index -- reset.");
}
} else {
@@ -867,42 +763,45 @@ static abuf_t *buffer_get_frame(void) {
}
}
- if ((flush_rtp_timestamp != 0) && (curframe->timestamp <= flush_rtp_timestamp)) {
+ if ((conn->flush_rtp_timestamp != 0) &&
+ (curframe->timestamp <= conn->flush_rtp_timestamp)) {
debug(1, "Dropping flushed packet seqno %u, timestamp %lld", curframe->sequence_number,
curframe->timestamp);
curframe->ready = 0;
flush_limit++;
- ab_read = SUCCESSOR(ab_read);
+ conn->ab_read = SUCCESSOR(conn->ab_read);
}
- if (curframe->timestamp > flush_rtp_timestamp)
- flush_rtp_timestamp = 0;
+ if (curframe->timestamp > conn->flush_rtp_timestamp)
+ conn->flush_rtp_timestamp = 0;
}
- } while ((flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0));
+ } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0));
if (flush_limit == 8820) {
debug(1, "Flush hit the 8820 frame limit!");
flush_limit = 0;
}
- curframe = audio_buffer + BUFIDX(ab_read);
+ curframe = conn->audio_buffer + BUFIDX(conn->ab_read);
if (curframe->ready) {
notified_buffer_empty = 0; // at least one buffer now -- diagnostic only.
- if (ab_buffering) { // if we are getting packets but not yet forwarding them to the player
+ if (conn->ab_buffering) { // if we are getting packets but not yet forwarding them to the
+ // player
int have_sent_prefiller_silence; // set true when we have sent some silent frames to the
// DAC
int64_t reference_timestamp;
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
- &remote_reference_timestamp_time);
- reference_timestamp *= output_sample_ratio;
- if (first_packet_timestamp == 0) { // if this is the very first packet
+ &remote_reference_timestamp_time, conn);
+ reference_timestamp *= conn->output_sample_ratio;
+ if (conn->first_packet_timestamp == 0) { // if this is the very first packet
// debug(1,"First frame seen, time %u, with %d
// frames...",curframe->timestamp,seq_diff(ab_read, ab_write));
if (reference_timestamp) { // if we have a reference time
// debug(1,"First frame seen with timestamp...");
- first_packet_timestamp = curframe->timestamp; // we will keep buffering until we are
- // supposed to start playing this
+ conn->first_packet_timestamp =
+ curframe->timestamp; // we will keep buffering until we are
+ // supposed to start playing this
have_sent_prefiller_silence = 0;
// Here, calculate when we should start playing. We need to know when to allow the
@@ -928,135 +827,201 @@ static abuf_t *buffer_get_frame(void) {
// if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e.
// -4410 frames.
- debug(1, "Output sample ratio is %d", output_sample_ratio);
+ // debug(1, "Output sample ratio is %d", conn->output_sample_ratio);
- int64_t delta = (first_packet_timestamp - reference_timestamp) +
- config.latency * output_sample_ratio +
- config.audio_backend_latency_offset * config.output_rate;
+ int64_t delta = (conn->first_packet_timestamp - reference_timestamp) +
+ config.latency * conn->output_sample_ratio +
+ (int64_t)(config.audio_backend_latency_offset * config.output_rate);
if (delta >= 0) {
int64_t delta_fp_sec =
(delta << 32) / config.output_rate; // int64_t which is positive
- first_packet_time_to_play = reference_timestamp_time + delta_fp_sec;
+ conn->first_packet_time_to_play = reference_timestamp_time + delta_fp_sec;
} else {
int64_t abs_delta = -delta;
int64_t delta_fp_sec =
(abs_delta << 32) / config.output_rate; // int64_t which is positive
- first_packet_time_to_play = reference_timestamp_time - delta_fp_sec;
+ conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec;
}
- if (local_time_now >= first_packet_time_to_play) {
+ if (local_time_now >= conn->first_packet_time_to_play) {
debug(
1,
"First packet is late! It should have played before now. Flushing 0.1 seconds");
- player_flush(first_packet_timestamp + 4410 * output_sample_ratio);
+ player_flush(conn->first_packet_timestamp + 4410 * conn->output_sample_ratio, conn);
}
}
}
- if (first_packet_time_to_play != 0) {
- // recalculate first_packet_time_to_play -- the latency might change
- int64_t delta = (first_packet_timestamp - reference_timestamp) +
- config.latency * output_sample_ratio +
- config.audio_backend_latency_offset * config.output_rate;
+ if (conn->first_packet_time_to_play != 0) {
+ // recalculate conn->first_packet_time_to_play -- the latency might change
+ int64_t delta = (conn->first_packet_timestamp - reference_timestamp) +
+ config.latency * conn->output_sample_ratio +
+ (int64_t)(config.audio_backend_latency_offset * config.output_rate);
if (delta >= 0) {
int64_t delta_fp_sec =
(delta << 32) / config.output_rate; // int64_t which is positive
- first_packet_time_to_play = reference_timestamp_time + delta_fp_sec;
+ conn->first_packet_time_to_play = reference_timestamp_time + delta_fp_sec;
} else {
int64_t abs_delta = -delta;
int64_t delta_fp_sec =
(abs_delta << 32) / config.output_rate; // int64_t which is positive
- first_packet_time_to_play = reference_timestamp_time - delta_fp_sec;
+ conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec;
}
- int64_t max_dac_delay = config.output_rate / 10;
+ // now, the size of the initial silence must be affected by the lead-in time.
+ // it must be somewhat less than the lead-in time so that dynamic adjustments can be
+ // made
+ // to compensate for delays due to paging, etc.
+ // The suggestion is that it should be at least 100 ms less than the lead-in time.
+
+ int64_t max_dac_delay = config.output_rate / 10; // so the lead-in time must be greater
+ // than this, say 0.2 sec, to allow for
+ // dynamic adjustment
int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC
- if (local_time_now >= first_packet_time_to_play) {
+ if (local_time_now >= conn->first_packet_time_to_play) {
+ // debug(1,"Gone past starting time");
+ have_sent_prefiller_silence = 1;
+ conn->ab_buffering = 0;
+
// we've gone past the time...
// debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp
// of %llx and dac_delay of %d and %d packets;
- // flush.",(((tn-first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read,
+ // flush.",(((tn-conn->first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read,
// ab_write));
- if (config.output->flush)
- config.output->flush();
- ab_resync();
- first_packet_timestamp = 0;
- first_packet_time_to_play = 0;
- time_since_play_started = 0;
+ /*
+ if (config.output->flush)
+ config.output->flush();
+ ab_resync(conn);
+ conn->first_packet_timestamp = 0;
+ conn->first_packet_time_to_play = 0;
+ conn->time_since_play_started = 0;
+ */
} else {
- // first_packet_time_to_play is definitely later than local_time_now
- if ((config.output->delay) && (have_sent_prefiller_silence != 0)) {
- int resp = config.output->delay(&dac_delay);
- if (resp != 0) {
- debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp);
- dac_delay = 0;
- }
- } else
- dac_delay = 0;
- int64_t gross_frame_gap =
- ((first_packet_time_to_play - local_time_now) * config.output_rate) >> 32;
- int64_t exact_frame_gap = gross_frame_gap - dac_delay;
- if (exact_frame_gap < 0) {
- // we've gone past the time...
- // debug(1,"Run a bit past the exact start time by %lld frames, with time now of
- // %llx, fpttp of %llx and dac_delay of %d and %d packets;
- // flush.",-exact_frame_gap,tn,first_packet_time_to_play,dac_delay,seq_diff(ab_read,
- // ab_write));
- if (config.output->flush)
- config.output->flush();
- ab_resync();
- first_packet_timestamp = 0;
- first_packet_time_to_play = 0;
- } else {
- int64_t fs = filler_size;
- if (fs > (max_dac_delay - dac_delay))
- fs = max_dac_delay - dac_delay;
- if (fs < 0) {
- debug(2, "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",
- max_dac_delay, dac_delay);
- fs = 0;
- }
- if ((exact_frame_gap <= fs) || (exact_frame_gap <= max_frames_per_packet * 2)) {
- fs = exact_frame_gap;
- // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d,
- // with %d packets, ab_read is %04x, ab_write is
- // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read,
- // ab_write),ab_read,ab_write);
- ab_buffering = 0;
- }
- signed short *silence;
- // if (fs==0)
- // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and
- // dac_delay of %lld.",gross_frame_gap,dac_delay);
- // the fs (number of frames of silence to play) can be zero in the DAC doesn't start
- // ouotputting frames for a while -- it could get loaded up but not start responding
- // for many milliseconds.
- if (fs != 0) {
- silence = malloc(output_bytes_per_frame * fs);
- if (silence == NULL)
- debug(1, "Failed to allocate %d byte silence buffer.", fs);
- else {
- memset(silence, 0, output_bytes_per_frame * fs);
- // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d,
- // with %d packets.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read, ab_write));
- config.output->play(silence, fs);
- free(silence);
- have_sent_prefiller_silence = 1;
+ // do some calculations
+ int64_t lead_time = conn->first_packet_time_to_play - local_time_now;
+ int64_t lead_in_time =
+ (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)0x100000000);
+ // debug(1,"Lead time is %llx at fpttp
+ // %llx.",lead_time,conn->first_packet_time_to_play);
+ // an audio_backend_silent_lead_in_time of less than zero means start filling ASAP
+ if ((lead_in_time < 0) || (lead_time <= lead_in_time)) {
+ // debug(1,"Checking");
+ if (config.output->delay) {
+ // conn->first_packet_time_to_play is definitely later than local_time_now
+ if (have_sent_prefiller_silence != 0) {
+ int resp = config.output->delay(&dac_delay);
+
+ if (resp != 0) {
+ debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp);
+ dac_delay = 0;
+ }
+ } else {
+ dac_delay = 0;
}
+ int64_t gross_frame_gap =
+ ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >>
+ 32;
+ int64_t exact_frame_gap = gross_frame_gap - dac_delay;
+ if (exact_frame_gap < 0) {
+ // we've gone past the time...
+ // debug(1,"Run past time.");
+ // debug(1,"Run a bit past the exact start time by %lld frames, with time now of
+ // %llx, fpttp of %llx and dac_delay of %d and %d packets;
+ // flush.",-exact_frame_gap,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read,
+ // ab_write));
+ if (config.output->flush)
+ config.output->flush();
+ ab_resync(conn);
+ conn->first_packet_timestamp = 0;
+ conn->first_packet_time_to_play = 0;
+ } else {
+ int64_t fs = filler_size;
+ if (fs > (max_dac_delay - dac_delay))
+ fs = max_dac_delay - dac_delay;
+ if (fs < 0) {
+ debug(2,
+ "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",
+ max_dac_delay, dac_delay);
+ fs = 0;
+ }
+ if ((exact_frame_gap <= fs) ||
+ (exact_frame_gap <= conn->max_frames_per_packet * 2)) {
+ fs = exact_frame_gap;
+ // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is
+ // %d,
+ // with %d packets, ab_read is %04x, ab_write is
+ // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read,
+ // ab_write),ab_read,ab_write);
+ conn->ab_buffering = 0;
+ }
+ signed short *silence;
+ // if (fs==0)
+ // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and
+ // dac_delay of %lld.",gross_frame_gap,dac_delay);
+ // the fs (number of frames of silence to play) can be zero in the DAC doesn't
+ // start
+ // ouotputting frames for a while -- it could get loaded up but not start
+ // responding
+ // for many milliseconds.
+ if (fs > 0) {
+ silence = malloc(conn->output_bytes_per_frame * fs);
+ if (silence == NULL)
+ debug(1, "Failed to allocate %d byte silence buffer.", fs);
+ else {
+ memset(silence, 0, conn->output_bytes_per_frame * fs);
+ // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d
+ // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read,
+ // conn->ab_write, conn->ab_read));
+ config.output->play(silence, fs);
+ free(silence);
+ }
+ }
+ have_sent_prefiller_silence =
+ 1; // even if we haven't sent silence because it's zero frames long...
+ }
+ } else {
+ // no delay function on back end -- just send the prefiller silence
+ // debug(1,"Back end has no delay function.");
+ // send the appropriate prefiller here...
+
+ signed short *silence;
+ if (lead_time != 0) {
+ int64_t frame_gap = (lead_time * config.output_rate) >> 32;
+ // debug(1,"%d frames needed.",frame_gap);
+ while (frame_gap > 0) {
+ size_t fs = config.output_rate / 10;
+ if (fs > frame_gap)
+ fs = frame_gap;
+
+ silence = malloc(conn->output_bytes_per_frame * fs);
+ if (silence == NULL)
+ debug(1, "Failed to allocate %d frame silence buffer.", fs);
+ else {
+ // debug(1, "Outputting %d frames of silence.", fs);
+ memset(silence, 0, conn->output_bytes_per_frame * fs);
+ config.output->play(silence, fs);
+ free(silence);
+ }
+ frame_gap -= fs;
+ }
+ }
+ have_sent_prefiller_silence = 1;
+ conn->ab_buffering = 0;
}
}
}
}
- if (ab_buffering == 0) {
+ if (conn->ab_buffering == 0) {
// not the time of the playing of the first frame
uint64_t reference_timestamp_time; // don't need this...
- get_reference_timestamp_stuff(&play_segment_reference_frame, &reference_timestamp_time,
- &play_segment_reference_frame_remote_time);
- play_segment_reference_frame *= output_sample_ratio;
+ get_reference_timestamp_stuff(&conn->play_segment_reference_frame,
+ &reference_timestamp_time,
+ &conn->play_segment_reference_frame_remote_time, conn);
+ conn->play_segment_reference_frame *= conn->output_sample_ratio;
#ifdef CONFIG_METADATA
send_ssnc_metadata('prsm', NULL, 0,
0); // "resume", but don't wait if the queue is locked
@@ -1079,20 +1044,20 @@ static abuf_t *buffer_get_frame(void) {
// Note: the last three items are expressed in frames and must be converted to time.
int do_wait = 0; // don't wait unless we can really prove we must
- if ((ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) {
+ if ((conn->ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) {
do_wait =
1; // if the current frame exists and is ready, then wait unless it's time to let it go...
int64_t reference_timestamp;
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
- &remote_reference_timestamp_time); // all types okay
- reference_timestamp *= output_sample_ratio;
+ &remote_reference_timestamp_time, conn); // all types okay
+ reference_timestamp *= conn->output_sample_ratio;
if (reference_timestamp) { // if we have a reference time
int64_t packet_timestamp = curframe->timestamp; // types okay
int64_t delta = packet_timestamp - reference_timestamp;
int64_t offset =
- config.latency * output_sample_ratio +
- config.audio_backend_latency_offset * config.output_rate -
+ config.latency * conn->output_sample_ratio +
+ (int64_t)(config.audio_backend_latency_offset * config.output_rate) -
config.audio_backend_buffer_desired_length *
config.output_rate; // all arguments are int32_t, so expression promotion okay
int64_t net_offset = delta + offset; // okay
@@ -1116,20 +1081,21 @@ static abuf_t *buffer_get_frame(void) {
}
}
if (do_wait == 0)
- if ((ab_synced != 0) && (ab_read == ab_write)) { // the buffer is empty!
+ if ((conn->ab_synced != 0) && (conn->ab_read == conn->ab_write)) { // the buffer is empty!
if (notified_buffer_empty == 0) {
debug(1, "Buffers exhausted.");
notified_buffer_empty = 1;
}
do_wait = 1;
}
- wait = (ab_buffering || (do_wait != 0) || (!ab_synced)) && (!please_stop);
+ wait = (conn->ab_buffering || (do_wait != 0) || (!conn->ab_synced)) &&
+ (!conn->player_thread_please_stop);
if (wait) {
uint64_t time_to_wait_for_wakeup_fp =
- ((uint64_t)1 << 32) / input_rate; // this is time period of one frame
- time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets
- time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time
+ ((uint64_t)1 << 32) / conn->input_rate; // this is time period of one frame
+ time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets
+ time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN
uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp;
@@ -1139,8 +1105,7 @@ static abuf_t *buffer_get_frame(void) {
struct timespec time_of_wakeup;
time_of_wakeup.tv_sec = sec;
time_of_wakeup.tv_nsec = nsec;
-
- pthread_cond_timedwait(&flowcontrol, &ab_mutex, &time_of_wakeup);
+ pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup);
// int rc = pthread_cond_timedwait(&flowcontrol,&ab_mutex,&time_of_wakeup);
// if (rc!=0)
// debug(1,"pthread_cond_timedwait returned error code %d.",rc);
@@ -1151,41 +1116,41 @@ static abuf_t *buffer_get_frame(void) {
struct timespec time_to_wait;
time_to_wait.tv_sec = sec;
time_to_wait.tv_nsec = nsec;
- pthread_cond_timedwait_relative_np(&flowcontrol, &ab_mutex, &time_to_wait);
+ pthread_cond_timedwait_relative_np(&conn->flowcontrol, &conn->ab_mutex, &time_to_wait);
#endif
}
} while (wait);
- if (please_stop) {
- pthread_mutex_unlock(&ab_mutex);
+ if (conn->player_thread_please_stop) {
+ pthread_mutex_unlock(&conn->ab_mutex);
return 0;
}
- seq_t read = ab_read;
+ seq_t read = conn->ab_read;
// check if t+8, t+16, t+32, t+64, t+128, ... (buffer_start_fill / 2)
// packets have arrived... last-chance resend
- if (!ab_buffering) {
- for (i = 8; i < (seq_diff(ab_read, ab_write) / 2); i = (i * 2)) {
- seq_t next = seq_sum(ab_read, i);
- abuf = audio_buffer + BUFIDX(next);
+ if (!conn->ab_buffering) {
+ for (i = 8; i < (seq_diff(conn->ab_read, conn->ab_write, conn->ab_read) / 2); i = (i * 2)) {
+ seq_t next = seq_sum(conn->ab_read, i);
+ abuf = conn->audio_buffer + BUFIDX(next);
if (!abuf->ready) {
- rtp_request_resend(next, 1);
+ rtp_request_resend(next, 1, conn);
// debug(1,"Resend %u.",next);
- resend_requests++;
+ conn->resend_requests++;
}
}
}
if (!curframe->ready) {
// debug(1, "Supplying a silent frame for frame %u", read);
- missing_packets++;
- curframe->timestamp = 0;
+ conn->missing_packets++;
+ curframe->timestamp = 0; // indicate a silent frame should be substituted
}
curframe->ready = 0;
- ab_read = SUCCESSOR(ab_read);
- pthread_mutex_unlock(&ab_mutex);
+ conn->ab_read = SUCCESSOR(conn->ab_read);
+ pthread_mutex_unlock(&conn->ab_mutex);
return curframe;
}
@@ -1212,13 +1177,13 @@ static inline int32_t mean_32(int32_t a, int32_t b) {
// this takes an array of signed 32-bit integers and (a) removes or inserts a frame as specified in
// stuff,
// (b) multiplies each sample by the fixedvolume (a 16-bit quantity)
-// (c) dithers the result to the output size 32/24/16 bits
+// (c) dithers the result to the output size 32/24/16/8 bits
// (d) outputs the result in the approprate format
// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32
// stuff: 1 means add 1; 0 means do nothing; -1 means remove 1
static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format,
- char *outptr, int stuff, int dither) {
+ char *outptr, int stuff, int dither, rtsp_conn_info *conn) {
int tstuff = stuff;
char *l_outptr = outptr;
if ((stuff > 1) || (stuff < -1) || (length < 100)) {
@@ -1233,17 +1198,19 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l
stuffsamp =
(rand() % (length - 2)) + 1; // ensure there's always a sample before and after the item
- pthread_mutex_lock(&vol_mutex);
+ pthread_mutex_lock(&conn->vol_mutex);
for (i = 0; i < stuffsamp; i++) { // the whole frame, if no stuffing
- process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither);
- process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither);
+ process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
};
if (tstuff) {
if (tstuff == 1) {
// debug(3, "+++++++++");
// interpolate one sample
- process_sample(mean_32(inptr[-2], inptr[0]), &l_outptr, l_output_format, fix_volume, dither);
- process_sample(mean_32(inptr[-1], inptr[1]), &l_outptr, l_output_format, fix_volume, dither);
+ process_sample(mean_32(inptr[-2], inptr[0]), &l_outptr, l_output_format, conn->fix_volume,
+ dither, conn);
+ process_sample(mean_32(inptr[-1], inptr[1]), &l_outptr, l_output_format, conn->fix_volume,
+ dither, conn);
} else if (stuff == -1) {
// debug(3, "---------");
inptr++;
@@ -1257,60 +1224,12 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l
remainder = remainder + tstuff; // don't run over the correct end of the output buffer
for (i = stuffsamp; i < remainder; i++) {
- process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither);
- process_sample(*inptr++, &l_outptr, l_output_format, fix_volume, dither);
+ process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
}
}
- pthread_mutex_unlock(&vol_mutex);
-
- return length + tstuff;
-}
-
-static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff) {
- int tstuff = stuff;
- if ((stuff > 1) || (stuff < -1) || (length < 100)) {
- // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100.");
- tstuff = 0; // if any of these conditions hold, don't stuff anything/
- }
- int i;
- int stuffsamp = length;
- if (tstuff)
- // stuffsamp = rand() % (length - 1);
- stuffsamp =
- (rand() % (length - 2)) + 1; // ensure there's always a sample before and after the item
-
- pthread_mutex_lock(&vol_mutex);
- for (i = 0; i < stuffsamp; i++) { // the whole frame, if no stuffing
- *outptr++ = dithered_vol(*inptr++);
- *outptr++ = dithered_vol(*inptr++);
- };
- if (tstuff) {
- if (tstuff == 1) {
- // debug(3, "+++++++++");
- // interpolate one sample
- //*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1);
- //*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1);
- *outptr++ = dithered_vol(shortmean(inptr[-2], inptr[0]));
- *outptr++ = dithered_vol(shortmean(inptr[-1], inptr[1]));
- } else if (stuff == -1) {
- // debug(3, "---------");
- inptr++;
- inptr++;
- }
-
- // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the
- // rest.
- int remainder = length;
- if (tstuff < 0)
- remainder = remainder + tstuff; // don't run over the correct end of the output buffer
-
- for (i = stuffsamp; i < remainder; i++) {
- *outptr++ = dithered_vol(*inptr++);
- *outptr++ = dithered_vol(*inptr++);
- }
- }
- pthread_mutex_unlock(&vol_mutex);
-
+ pthread_mutex_unlock(&conn->vol_mutex);
+ conn->amountStuffed = tstuff;
return length + tstuff;
}
@@ -1320,13 +1239,13 @@ static int stuff_buffer_basic(short *inptr, int length, short *outptr, int stuff
// resample the array to have one more or one less frame, as specified in
// stuff,
// (b) multiplies each sample by the fixedvolume (a 16-bit quantity)
-// (c) dithers the result to the output size 32/24/16 bits
+// (c) dithers the result to the output size 32/24/16/8 bits
// (d) outputs the result in the approprate format
// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32
static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
enum sps_format_t l_output_format, char *outptr, int stuff,
- int dither) {
+ int dither, rtsp_conn_info *conn) {
if (scratchBuffer == NULL) {
die("soxr scratchBuffer not initialised.");
}
@@ -1383,12 +1302,8 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng
ip = scratchBuffer;
char *l_outptr = outptr;
for (i = 0; i < length + tstuff; i++) {
- process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither);
- process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither);
- //*op = dithered_vol(*op);
- // op++;
- //*op = dithered_vol(*op);
- // op++;
+ process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
};
} else { // the whole frame, if no stuffing
@@ -1399,87 +1314,11 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng
int i;
for (i = 0; i < length; i++) {
- process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither);
- process_sample(*ip++, &l_outptr, l_output_format, fix_volume, dither);
- //*op++ = dithered_vol(*ip++);
- //*op++ = dithered_vol(*ip++);
+ process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
};
}
- return length + tstuff;
-}
-// stuff: 1 means add 1; 0 means do nothing; -1 means remove 1
-static int stuff_buffer_soxr(short *inptr, int length, short *outptr, int stuff) {
- int tstuff = stuff;
- if ((stuff > 1) || (stuff < -1) || (length < 100)) {
- // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100.");
- tstuff = 0; // if any of these conditions hold, don't stuff anything/
- }
- int i;
- short *ip, *op;
- ip = inptr;
- op = outptr;
-
- if (tstuff) {
- // debug(1,"Stuff %d.",stuff);
- soxr_io_spec_t io_spec;
- io_spec.itype = SOXR_INT16_I;
- io_spec.otype = SOXR_INT16_I;
- io_spec.scale = 1.0; // this seems to crash if not = 1.0
- io_spec.e = NULL;
- io_spec.flags = 0;
-
- size_t odone;
-
- soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */
- inptr, length, NULL, /* Input. */
- outptr, length + tstuff, &odone, /* Output. */
- &io_spec, /* Input, output and transfer spec. */
- NULL, NULL); /* Default configuration.*/
-
- if (error)
- die("soxr error: %s\n", "error: %s\n", soxr_strerror(error));
-
- if (odone > length + 1)
- die("odone = %d!\n", odone);
-
- const int gpm = 5;
-
- // keep the first (dpm) samples, to mitigate the Gibbs phenomenon
- for (i = 0; i < gpm; i++) {
- *op++ = *ip++;
- *op++ = *ip++;
- }
-
- // keep the last (dpm) samples, to mitigate the Gibbs phenomenon
- op = outptr + (length + tstuff - gpm) * sizeof(short);
- ip = inptr + (length - gpm) * sizeof(short);
- for (i = 0; i < gpm; i++) {
- *op++ = *ip++;
- *op++ = *ip++;
- }
-
- // finally, adjust the volume, if necessary
- if (fix_volume != 65536.0) {
- // pthread_mutex_lock(&vol_mutex);
- op = outptr;
- for (i = 0; i < length + tstuff; i++) {
- *op = dithered_vol(*op);
- op++;
- *op = dithered_vol(*op);
- op++;
- };
- // pthread_mutex_unlock(&vol_mutex);
- }
-
- } else { // the whole frame, if no stuffing
-
- // pthread_mutex_lock(&vol_mutex);
- for (i = 0; i < length; i++) {
- *op++ = dithered_vol(*ip++);
- *op++ = dithered_vol(*ip++);
- };
- // pthread_mutex_unlock(&vol_mutex);
- }
+ conn->amountStuffed = tstuff;
return length + tstuff;
}
#endif
@@ -1489,55 +1328,119 @@ typedef struct stats { // statistics for running averages
} stats_t;
static void *player_thread_func(void *arg) {
- struct inter_threads_record itr;
- itr.please_stop = 0;
- timestamp_epoch = 0; // indicate that the next timestamp will be the first one.
- maximum_timestamp_interval = input_rate * 60; // actually there shouldn't be more than about 13v
- // seconds of a gap between successive rtptimes, at
- // worst
- output_sample_ratio = config.output_rate / input_rate;
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- debug(1, "Output sample ratio is %d.", output_sample_ratio);
+ conn->please_stop = 0;
+ conn->packet_count = 0;
+ conn->previous_random_number = 0;
+ conn->input_bytes_per_frame = 4;
+ conn->player_thread_please_stop = 0;
+ conn->decoder_in_use = 0;
+ conn->ab_buffering = 1;
+ conn->ab_synced = 0;
+ conn->first_packet_timestamp = 0;
+ conn->flush_requested = 0;
+ conn->fix_volume = 0x10000;
- max_frame_size_change = 1 * output_sample_ratio; // we add or subtract one frame at the nominal
- // rate, multiply it by the frame ratio.
+ int rc = pthread_mutex_init(&conn->ab_mutex, NULL);
+ if (rc)
+ debug(1, "Error initialising ab_mutex.");
+ rc = pthread_mutex_init(&conn->flush_mutex, NULL);
+ if (rc)
+ debug(1, "Error initialising flush_mutex.");
+ rc = pthread_mutex_init(&conn->vol_mutex, NULL);
+ if (rc)
+ debug(1, "Error initialising vol_mutex.");
+// set the flowcontrol condition variable to wait on a monotonic clock
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN
+ pthread_condattr_t attr;
+ pthread_condattr_init(&attr);
+ pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
+ rc = pthread_cond_init(&conn->flowcontrol, &attr);
+#endif
+#ifdef COMPILE_FOR_OSX
+ rc = pthread_cond_init(&conn->flowcontrol, NULL);
+#endif
+ if (rc)
+ debug(1, "Error initialising flowcontrol condition variable.");
+ config.output->start(config.output_rate, config.output_format);
+
+ init_decoder((int32_t *)&conn->stream.fmtp,
+ conn); // this sets up incoming rate, bit depth, channels
+ // must be after decoder init
+ init_buffer(conn);
+
+ if (conn->stream.encrypted) {
+#ifdef HAVE_LIBMBEDTLS
+ memset(&conn->dctx, 0, sizeof(mbedtls_aes_context));
+ mbedtls_aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128);
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+ memset(&conn->dctx, 0, sizeof(aes_context));
+ aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128);
+#endif
+
+#ifdef HAVE_LIBSSL
+ AES_set_decrypt_key(conn->stream.aeskey, 128, &conn->aes);
+#endif
+ }
+
+ conn->timestamp_epoch = 0; // indicate that the next timestamp will be the first one.
+ conn->maximum_timestamp_interval =
+ conn->input_rate * 60; // actually there shouldn't be more than about 13v
+ // seconds of a gap between successive rtptimes, at
+ // worst
+
+ conn->output_sample_ratio = config.output_rate / conn->input_rate;
+
+ // debug(1, "Output sample ratio is %d.", conn->output_sample_ratio);
+
+ conn->max_frame_size_change =
+ 500 * conn->output_sample_ratio; // we add or subtract one frame at the nominal
+ // rate, multiply it by the frame ratio.
+ // but, on some occasions, more than one frame could be added
- output_bytes_per_frame = 4;
switch (config.output_format) {
case SPS_FORMAT_S24_3LE:
case SPS_FORMAT_S24_3BE:
- output_bytes_per_frame = 6;
+ conn->output_bytes_per_frame = 6;
break;
case SPS_FORMAT_S24:
- output_bytes_per_frame = 8;
+ conn->output_bytes_per_frame = 8;
break;
case SPS_FORMAT_S32:
- output_bytes_per_frame = 8;
+ conn->output_bytes_per_frame = 8;
break;
+ default:
+ conn->output_bytes_per_frame = 4;
}
- debug(1, "Output frame bytes is %d.", output_bytes_per_frame);
+ debug(1, "Output frame bytes is %d.", conn->output_bytes_per_frame);
// create and start the timing, control and audio receiver threads
pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
- pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&itr);
- pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)&itr);
- pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)&itr);
+ pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn);
+ pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn);
+ pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn);
- session_corrections = 0;
- play_segment_reference_frame = 0; // zero signals that we are not in a play segment
+ conn->session_corrections = 0;
+ conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment
// check that there are enough buffers to accommodate the desired latency and the latency offset
- int maximum_latency = config.latency + config.audio_backend_latency_offset * input_rate;
+ int maximum_latency =
+ config.latency + (int)(config.audio_backend_latency_offset * config.output_rate);
if ((maximum_latency + (352 - 1)) / 352 + 10 > BUFFER_FRAMES)
die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame "
"packets may be accommodated.",
maximum_latency, BUFFER_FRAMES);
- connection_state_to_output = get_requested_connection_state_to_output();
+ conn->connection_state_to_output = get_requested_connection_state_to_output();
// this is about half a minute
-#define trend_interval 3758
+//#define trend_interval 3758
+#define trend_interval 1003
+
stats_t statistics[trend_interval];
int number_of_statistics, oldest_statistic, newest_statistic;
int at_least_one_frame_seen = 0;
@@ -1550,13 +1453,15 @@ static void *player_thread_func(void *arg) {
time_t playstart = time(NULL);
- buffer_occupancy = 0;
+ conn->buffer_occupancy = 0;
int play_samples;
int64_t current_delay;
int play_number = 0;
- time_of_last_audio_packet = 0;
- shutdown_requested = 0;
+ conn->play_number_after_flush = 0;
+ // int last_timestamp = 0; // for debugging only
+ conn->time_of_last_audio_packet = 0;
+ conn->shutdown_requested = 0;
number_of_statistics = oldest_statistic = newest_statistic = 0;
tsum_of_sync_errors = tsum_of_corrections = tsum_of_insertions_and_deletions = tsum_of_drifts = 0;
@@ -1593,60 +1498,62 @@ static void *player_thread_func(void *arg) {
case SPS_FORMAT_S32:
output_bit_depth = 32;
break;
+ case SPS_FORMAT_UNKNOWN:
+ die("Unknown format choosing output bit depth");
}
debug(1, "Output bit depth is %d.", output_bit_depth);
- if (input_bit_depth > output_bit_depth) {
+ if (conn->input_bit_depth > output_bit_depth) {
debug(1, "Dithering will be enabled because the input bit depth is greater than the output bit "
"depth");
}
- if (fix_volume != 0x10000) {
- debug(1, "Dithering will be enabled the output volume is being altered in software");
+ if (conn->fix_volume != 0x10000) {
+ debug(1, "Dithering will be enabled becasuse the output volume is being altered in software");
}
- // if we are changing any of the parameters of the input, like sample rate or sample depth, then
- // we
- // need an intermediate "transition" buffer
+ // we need an intermediate "transition" buffer
- if (1) {
- // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) {
- // debug(1,"Define tbuf of length
- // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change));
- tbuf = malloc(sizeof(int32_t) * 2 *
- (max_frames_per_packet * output_sample_ratio + max_frame_size_change));
- if (tbuf == NULL)
- debug(1, "Failed to allocate memory for the transition buffer.");
- sbuf = 0;
- if (config.packet_stuffing == ST_soxr) { // needed for stuffing
- sbuf = malloc(sizeof(int32_t) * 2 *
- (max_frames_per_packet * output_sample_ratio + max_frame_size_change));
- if (sbuf == NULL)
- debug(1, "Failed to allocate memory for the transition buffer.");
- }
- } else {
- tbuf = 0;
+ // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) {
+ // debug(1,"Define tbuf of length
+ // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change));
+ tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio +
+ conn->max_frame_size_change));
+ if (tbuf == NULL)
+ die("Failed to allocate memory for the transition buffer.");
+ sbuf = 0;
+ if (config.packet_stuffing == ST_soxr) { // needed for stuffing
+ sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio +
+ conn->max_frame_size_change));
+ if (sbuf == NULL)
+ debug(1, "Failed to allocate memory for the sbuf buffer.");
}
-
// We might need an output buffer and a buffer of silence.
// The size of these dependents on the number of frames, the size of each frame and the maximum
// size change
- outbuf = malloc(output_bytes_per_frame *
- (max_frames_per_packet * output_sample_ratio + max_frame_size_change));
+ outbuf = malloc(
+ conn->output_bytes_per_frame *
+ (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change));
if (outbuf == NULL)
- debug(1, "Failed to allocate memory for an output buffer.");
- silence = malloc(output_bytes_per_frame * max_frames_per_packet * output_sample_ratio);
+ die("Failed to allocate memory for an output buffer.");
+ silence = malloc(conn->output_bytes_per_frame * conn->max_frames_per_packet *
+ conn->output_sample_ratio);
if (silence == NULL)
- debug(1, "Failed to allocate memory for a silence buffer.");
- memset(silence, 0, output_bytes_per_frame * max_frames_per_packet * output_sample_ratio);
- late_packet_message_sent = 0;
- first_packet_timestamp = 0;
- missing_packets = late_packets = too_late_packets = resend_requests = 0;
- flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to be used
- // as a null operand, so we'll use it like that too
+ die("Failed to allocate memory for a silence buffer.");
+ memset(silence, 0,
+ conn->output_bytes_per_frame * conn->max_frames_per_packet * conn->output_sample_ratio);
+ conn->first_packet_timestamp = 0;
+ conn->missing_packets = conn->late_packets = conn->too_late_packets = conn->resend_requests = 0;
+ conn->flush_rtp_timestamp =
+ 0; // it seems this number has a special significance -- it seems to be used
+ // as a null operand, so we'll use it like that too
int sync_error_out_of_bounds =
0; // number of times in a row that there's been a serious sync error
+ conn->framesProcessedInThisEpoch = 0;
+ conn->framesGeneratedInThisEpoch = 0;
+ conn->correctionsRequestedInThisEpoch = 0;
+
if (config.statistics_requested) {
if ((config.output->delay)) {
if (config.no_sync == 0) {
@@ -1685,108 +1592,101 @@ static void *player_thread_func(void *arg) {
}
uint64_t tens_of_seconds = 0;
- while (!please_stop) {
- abuf_t *inframe = buffer_get_frame();
+ while (!conn->player_thread_please_stop) {
+ abuf_t *inframe = buffer_get_frame(conn);
if (inframe) {
inbuf = inframe->data;
inbuflength = inframe->length;
if (inbuf) {
play_number++;
- // if it's a supplied silent frame, let us know...
+ conn->play_number_after_flush++;
if (inframe->timestamp == 0) {
// debug(1,"Player has a supplied silent frame.");
- last_seqno_read =
- (SUCCESSOR(last_seqno_read) & 0xffff); // manage the packet out of sequence minder
- config.output->play(silence, max_frames_per_packet * output_sample_ratio);
+ conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
+ 0xffff); // manage the packet out of sequence minder
+ config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
+ } else if (conn->play_number_after_flush < 10) {
+ /*
+ int64_t difference = 0;
+ if (last_timestamp)
+ difference = inframe->timestamp - last_timestamp;
+ last_timestamp = inframe->timestamp;
+ debug(1, "Play number %d, monotonic timestamp %llx, difference
+ %lld.",conn->play_number_after_flush,inframe->timestamp,difference);
+ */
+ config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
} else {
-
int enable_dither = 0;
- if ((fix_volume != 0x10000) || (input_bit_depth > output_bit_depth))
+ if ((conn->fix_volume != 0x10000) || (conn->input_bit_depth > output_bit_depth) ||
+ (config.playback_mode == ST_mono))
enable_dither = 1;
// here, let's transform the frame of data, if necessary
- if (tbuf != NULL) { // this will be null if no changes are needed
- switch (input_bit_depth) {
- case 16: {
- int i, j;
- int16_t ls, rs;
- int16_t *inps = inbuf;
- int16_t *outps = tbuf;
- int32_t *outpl = (int32_t *)tbuf;
- for (i = 0; i < inbuflength; i++) {
- ls = *inps++;
- rs = *inps++;
+ switch (conn->input_bit_depth) {
+ case 16: {
+ int i, j;
+ int16_t ls, rs;
+ int32_t ll, rl;
+ int16_t *inps = inbuf;
+ int16_t *outps = tbuf;
+ int32_t *outpl = (int32_t *)tbuf;
+ for (i = 0; i < inbuflength; i++) {
+ ls = *inps++;
+ rs = *inps++;
- // here, do the mode stuff -- mono / reverse stereo / leftonly / rightonly
+ // here, do the mode stuff -- mono / reverse stereo / leftonly / rightonly
+ // also, raise the 16-bit samples to 32 bits.
- switch (config.playback_mode) {
- case ST_mono: {
- int both = ls + rs;
- // Note -- this is assuming 16 bit signed.
- if (both > INT16_MAX) {
- both = INT16_MAX;
- } else if (both < INT16_MIN) {
- both = INT16_MIN;
- }
- int16_t sboth = (int16_t)both;
- ls = sboth;
- rs = sboth;
- } break;
- case ST_reverse_stereo: {
- int16_t t = ls;
- ls = rs;
- rs = t;
- } break;
- case ST_left_only:
- rs = ls;
- break;
- case ST_right_only:
- ls = rs;
- break;
- }
-
- // here, replicate the samples if you're upsampling
-
- for (j = 0; j < output_sample_ratio; j++) {
- // raise the 16-bit sample to 32 bits.
- int32_t t = ls << 16;
- *outpl++ = t;
- int32_t u = rs << 16;
- *outpl++ = u;
-
- /*
- switch (output_bit_depth) {
- case 16:
- *outps++=ls;
- *outps++=rs;
- break;
- case 24: {
- int32_t t = ls<<8;
- *outpl++=t;
- int32_t u = rs<<8;
- *outpl++=u;
- } break;
- case 32: {
- int32_t t = ls<<16;
- *outpl++=t;
- int32_t u = rs<<16;
- *outpl++=u;
- } break;
- }
- */
- }
+ switch (config.playback_mode) {
+ case ST_mono: {
+ int32_t both = ls + rs;
+ both = both << (16 - 1); // keep all 17 bits of the sum of the 16bit left and right
+ // -- the 17th bit will influence dithering later
+ ll = both;
+ rl = both;
+ } break;
+ case ST_reverse_stereo: {
+ ll = rs;
+ rl = ls;
+ ll = ll << 16;
+ rl = rl << 16;
+ } break;
+ case ST_left_only:
+ rl = ls;
+ ll = ls;
+ ll = ll << 16;
+ rl = rl << 16;
+ break;
+ case ST_right_only:
+ ll = rs;
+ rl = rs;
+ ll = ll << 16;
+ rl = rl << 16;
+ break;
+ case ST_stereo:
+ ll = ls;
+ rl = rs;
+ ll = ll << 16;
+ rl = rl << 16;
+ break; // nothing extra to do
}
- } break;
- default:
- die("Shairport Sync only supports 16 bit input");
+ // here, replicate the samples if you're upsampling
+
+ for (j = 0; j < conn->output_sample_ratio; j++) {
+ *outpl++ = ll;
+ *outpl++ = rl;
+ }
}
- // inbuf = tbuf;
- inbuflength *= output_sample_ratio;
+ } break;
+ default:
+ die("Shairport Sync only supports 16 bit input");
}
+ inbuflength *= conn->output_sample_ratio;
+
// We have a frame of data. We need to see if we want to add or remove a frame from it to
// keep in sync.
// So we calculate the timing error for the first frame in the DAC.
@@ -1799,8 +1699,8 @@ static void *player_thread_func(void *arg) {
int64_t reference_timestamp;
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
- &remote_reference_timestamp_time); // types okay
- reference_timestamp *= output_sample_ratio;
+ &remote_reference_timestamp_time, conn); // types okay
+ reference_timestamp *= conn->output_sample_ratio;
int64_t rt, nt;
rt = reference_timestamp; // uint32_t to int64_t
nt = inframe->timestamp; // uint32_t to int64_t
@@ -1836,28 +1736,29 @@ static void *player_thread_func(void *arg) {
int amount_to_stuff = 0;
// check sequencing
- if (last_seqno_read == -1)
- last_seqno_read =
+ if (conn->last_seqno_read == -1)
+ conn->last_seqno_read =
inframe->sequence_number; // int32_t from seq_t, i.e. uint16_t, so okay.
else {
- last_seqno_read =
- SUCCESSOR(last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay.
+ conn->last_seqno_read =
+ SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay.
if (inframe->sequence_number !=
- last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay
+ conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay
debug(1, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u "
"and ab_write: %u.",
- last_seqno_read, inframe->sequence_number, ab_read, ab_write);
- last_seqno_read = inframe->sequence_number; // reset warning...
+ conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write);
+ conn->last_seqno_read = inframe->sequence_number; // reset warning...
}
}
- buffer_occupancy = seq_diff(ab_read, ab_write); // int32_t from int32
+ conn->buffer_occupancy =
+ seq_diff(conn->ab_read, conn->ab_write, conn->ab_read); // int32_t from int32
- if (buffer_occupancy < minimum_buffer_occupancy)
- minimum_buffer_occupancy = buffer_occupancy;
+ if (conn->buffer_occupancy < minimum_buffer_occupancy)
+ minimum_buffer_occupancy = conn->buffer_occupancy;
- if (buffer_occupancy > maximum_buffer_occupancy)
- maximum_buffer_occupancy = buffer_occupancy;
+ if (conn->buffer_occupancy > maximum_buffer_occupancy)
+ maximum_buffer_occupancy = conn->buffer_occupancy;
// here, we want to check (a) if we are meant to do synchronisation,
// (b) if we have a delay procedure, (c) if we can get the delay.
@@ -1873,7 +1774,7 @@ static void *player_thread_func(void *arg) {
current_delay = l_delay;
if (resp == 0) { // no error
if (current_delay < 0) {
- debug(1, "Underrun of %d frames reported, but ignored.", current_delay);
+ debug(1, "Underrun of %lld frames reported, but ignored.", current_delay);
current_delay =
0; // could get a negative value if there was underrun, but ignore it.
}
@@ -1892,167 +1793,217 @@ static void *player_thread_func(void *arg) {
int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t
// This is the timing error for the next audio frame in the DAC.
+
+ // if positive, it means that the packet will be late -- the delay is longer than
+ // requested
+ // if negative, the packet will be early -- the delay is less than expected.
+
sync_error =
- delay -
- config.latency * output_sample_ratio; // int64_t from int64_t - int32_t, so okay
-
- // if (llabs(sync_error)>352*512)
- // debug(1,"Very large sync error: %lld",sync_error);
-
- // before we finally commit to this frame, check its sequencing and timing
-
- // require a certain error before bothering to fix it...
- if (sync_error > config.tolerance * config.output_rate) { // int64_t > int, okay
- amount_to_stuff = -1;
- }
- if (sync_error < -config.tolerance * config.output_rate) {
- amount_to_stuff = 1;
- }
-
- // only allow stuffing if there is enough time to do it -- check DAC buffer...
- if (current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) {
- // debug(1,"DAC buffer too short to allow stuffing.");
- amount_to_stuff = 0;
- }
-
- // try to keep the corrections definitely below 1 in 1000 audio frames
-
- // calculate the time elapsed since the play session started.
-
- if (amount_to_stuff) {
- if ((local_time_now) && (first_packet_time_to_play) &&
- (local_time_now >= first_packet_time_to_play)) {
-
- int64_t tp = (local_time_now - first_packet_time_to_play) >>
- 32; // seconds int64_t from uint64_t which is always positive, so ok
-
- if (tp < 5)
- amount_to_stuff = 0; // wait at least five seconds
- else if (tp < 30) {
- if ((random() % 1000) >
- 352) // keep it to about 1:1000 for the first thirty seconds
- amount_to_stuff = 0;
- }
- }
- }
-
- if (config.no_sync != 0)
- amount_to_stuff = 0; // no stuffing if it's been disabled
-
-#ifdef HAVE_LIBSOXR
- switch (config.packet_stuffing) {
- case ST_basic:
- // if (amount_to_stuff) debug(1,"Basic stuff...");
- if (tbuf) {
- play_samples =
- stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format,
- outbuf, amount_to_stuff, enable_dither);
- } else
- play_samples =
- stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff);
- break;
- case ST_soxr:
- // if (amount_to_stuff) debug(1,"Soxr stuff...");
- if (tbuf)
- play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, (int32_t *)sbuf, inbuflength,
- config.output_format, outbuf, amount_to_stuff,
- enable_dither);
- else
- play_samples =
- stuff_buffer_soxr(inbuf, inbuflength, (short *)outbuf, amount_to_stuff);
- break;
- }
-#else
- // if (amount_to_stuff) debug(1,"Standard stuff...");
- if (tbuf)
- play_samples =
- stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format, outbuf,
- amount_to_stuff, enable_dither);
- else
- play_samples =
- stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf, amount_to_stuff);
-#endif
-
- /*
- {
- int co;
- int is_silent=1;
- short *p = outbuf;
- for (co=0;coplay((short *)outbuf, play_samples); // remove the (short*)!
- }
-
- // check for loss of sync
- // timestamp of zero means an inserted silent frame in place of a missing frame
+ delay - (config.latency * conn->output_sample_ratio +
+ (int64_t)(config.audio_backend_latency_offset *
+ config.output_rate)); // int64_t from int64_t - int32_t, so okay
// not too sure if abs() is implemented for int64_t, so we'll do it manually
int64_t abs_sync_error = sync_error;
if (abs_sync_error < 0)
abs_sync_error = -abs_sync_error;
- if ((config.no_sync == 0) && (inframe->timestamp != 0) && (!please_stop) &&
- (config.resyncthreshold > 0.0) &&
+ if ((config.no_sync == 0) && (inframe->timestamp != 0) &&
+ (!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) &&
(abs_sync_error > config.resyncthreshold * config.output_rate)) {
sync_error_out_of_bounds++;
- // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld;
- // timestamp: %llx, time now
- // %llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now);
- if (sync_error_out_of_bounds > 3) {
- debug(1, "Lost sync with source for %d consecutive packets -- flushing and "
- "resyncing. Error: %lld.",
- sync_error_out_of_bounds, sync_error);
- sync_error_out_of_bounds = 0;
- player_flush(nt);
- }
} else {
sync_error_out_of_bounds = 0;
}
- } else {
- // if there is no delay procedure, or it's not working or not allowed, there can be no
- // synchronising
- if (fix_volume == 0x10000)
+ if (sync_error_out_of_bounds > 3) {
+ // debug(1, "New lost sync with source for %d consecutive packets -- flushing and "
+ // "resyncing. Error: %lld.",
+ // sync_error_out_of_bounds, sync_error);
+ sync_error_out_of_bounds = 0;
- if (inbuf == NULL)
- debug(1, "NULL inbuf to play -- skipping it.");
- else {
- if (inbuflength == 0)
- debug(1, "empty frame to play -- skipping it (3).");
- else
- config.output->play(inbuf, inbuflength);
+ size_t filler_length =
+ config.resyncthreshold * config.output_rate; // number of samples
+ if ((sync_error > 0) && (sync_error > filler_length)) {
+ // debug(1, "Large positive sync error: %lld. Dropping the frame.", sync_error);
+ } else if ((sync_error < 0) && ((-sync_error) > filler_length)) {
+ // debug(1, "Large negative sync error: %lld. Inserting silence.", sync_error);
+ char *long_silence = malloc(conn->output_bytes_per_frame * (-sync_error));
+ if (long_silence == NULL)
+ die("Failed to allocate memory for a long_silence buffer.");
+ memset(long_silence, 0, conn->output_bytes_per_frame * (-sync_error));
+ config.output->play((short *)long_silence, (-sync_error));
+ free(long_silence);
}
- else {
- if (tbuf)
- play_samples = stuff_buffer_basic_32(
- (int32_t *)tbuf, inbuflength, config.output_format, outbuf, 0, enable_dither);
- else
- play_samples = stuff_buffer_basic(inbuf, inbuflength, (short *)outbuf,
- 0); // no stuffing, but volume adjustment
+ } else {
+
+ // before we finally commit to this frame, check its sequencing and timing
+
+ // require a certain error before bothering to fix it...
+ if (sync_error > config.tolerance * config.output_rate) { // int64_t > int, okay
+ amount_to_stuff = -1;
+ }
+ if (sync_error < -config.tolerance * config.output_rate) {
+ amount_to_stuff = 1;
+ }
+
+ // only allow stuffing if there is enough time to do it -- check DAC buffer...
+ if (current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) {
+ // debug(2,"DAC buffer too short(at %lld frames) to allow stuffing.",current_delay);
+ amount_to_stuff = 0;
+ }
+
+ // try to keep the corrections definitely below 1 in 1000 audio frames
+
+ // calculate the time elapsed since the play session started.
+
+ if (amount_to_stuff) {
+ if ((local_time_now) && (conn->first_packet_time_to_play) &&
+ (local_time_now >= conn->first_packet_time_to_play)) {
+
+ int64_t tp = (local_time_now - conn->first_packet_time_to_play) >>
+ 32; // seconds int64_t from uint64_t which is always positive, so ok
+
+ if (tp < 5)
+ amount_to_stuff = 0; // wait at least five seconds
+ /*
+ else if (tp < 30) {
+ if ((random() % 1000) >
+ 352) // keep it to about 1:1000 for the first thirty seconds
+ amount_to_stuff = 0;
+ }
+ */
+ }
+ }
+
+ if (config.no_sync != 0)
+ amount_to_stuff = 0; // no stuffing if it's been disabled
+
+ // Apply DSP here
+ if (config.loudness
+#ifdef CONFIG_CONVOLUTION
+ || config.convolution
+#endif
+ ) {
+ int32_t *tbuf32 = (int32_t *)tbuf;
+ float fbuf_l[inbuflength];
+ float fbuf_r[inbuflength];
+
+ // Deinterleave, and convert to float
+ int i;
+ for (i = 0; i < inbuflength; ++i) {
+ fbuf_l[i] = tbuf32[2 * i];
+ fbuf_r[i] = tbuf32[2 * i + 1];
+ }
+
+#ifdef CONFIG_CONVOLUTION
+ // Apply convolution
+ if (config.convolution) {
+ convolver_process_l(fbuf_l, inbuflength);
+ convolver_process_r(fbuf_r, inbuflength);
+
+ float gain = pow(10.0, config.convolution_gain / 20.0);
+ for (i = 0; i < inbuflength; ++i) {
+ fbuf_l[i] *= gain;
+ fbuf_r[i] *= gain;
+ }
+ }
+#endif
+
+ if (config.loudness) {
+ // Apply volume and loudness
+ // Volume must be applied here because the loudness filter will increase the
+ // signal level and it would saturate the int32_t otherwise
+ float gain = conn->fix_volume / 65536.0f;
+ float gain_db = 20 * log10(gain);
+ // debug(1, "Applying soft volume dB: %f k: %f", gain_db, gain);
+
+ for (i = 0; i < inbuflength; ++i) {
+ fbuf_l[i] = loudness_process(&loudness_l, fbuf_l[i] * gain);
+ fbuf_r[i] = loudness_process(&loudness_r, fbuf_r[i] * gain);
+ }
+ }
+
+ // Interleave and convert back to int32_t
+ for (i = 0; i < inbuflength; ++i) {
+ tbuf32[2 * i] = fbuf_l[i];
+ tbuf32[2 * i + 1] = fbuf_r[i];
+ }
+ }
+
+ switch (config.packet_stuffing) {
+ case ST_basic:
+ // if (amount_to_stuff) debug(1,"Basic stuff...");
+ play_samples =
+ stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format,
+ outbuf, amount_to_stuff, enable_dither, conn);
+ break;
+ case ST_soxr:
+#ifdef HAVE_LIBSOXR
+ // if (amount_to_stuff) debug(1,"Soxr stuff...");
+ play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, (int32_t *)sbuf, inbuflength,
+ config.output_format, outbuf, amount_to_stuff,
+ enable_dither, conn);
+#endif
+ break;
+ }
+
+ /*
+ {
+ int co;
+ int is_silent=1;
+ short *p = outbuf;
+ for (co=0;coplay((short *)outbuf, play_samples); // remove the (short*)!
}
+
+ // check for loss of sync
+ // timestamp of zero means an inserted silent frame in place of a missing frame
+ /*
+ if ((config.no_sync == 0) && (inframe->timestamp != 0) &&
+ (!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) &&
+ (abs_sync_error > config.resyncthreshold * config.output_rate)) {
+ sync_error_out_of_bounds++;
+ // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld;
+ // timestamp: %llx, time now
+ //
+ %llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now);
+ if (sync_error_out_of_bounds > 3) {
+ debug(1, "Lost sync with source for %d consecutive packets -- flushing and "
+ "resyncing. Error: %lld.",
+ sync_error_out_of_bounds, sync_error);
+ sync_error_out_of_bounds = 0;
+ player_flush(nt, conn);
+ }
+ } else {
+ sync_error_out_of_bounds = 0;
+ }
+ */
}
+ } else {
+ // if there is no delay procedure, or it's not working or not allowed, there can be no
+ // synchronising
+ play_samples = stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format,
+ outbuf, 0, enable_dither, conn);
+ if (outbuf == NULL)
+ debug(1, "NULL outbuf to play -- skipping it.");
+ else
+ config.output->play((short *)outbuf, play_samples); // remove the (short*)!
}
// mark the frame as finished
@@ -2083,7 +2034,7 @@ static void *player_thread_func(void *arg) {
}
statistics[newest_statistic].sync_error = sync_error;
- statistics[newest_statistic].correction = amount_to_stuff;
+ statistics[newest_statistic].correction = conn->amountStuffed;
if (number_of_statistics == 0)
statistics[newest_statistic].drift = 0;
@@ -2092,17 +2043,17 @@ static void *player_thread_func(void *arg) {
sync_error - previous_sync_error - previous_correction;
previous_sync_error = sync_error;
- previous_correction = amount_to_stuff;
+ previous_correction = conn->amountStuffed;
tsum_of_sync_errors += sync_error;
tsum_of_drifts += statistics[newest_statistic].drift;
- if (amount_to_stuff > 0) {
- tsum_of_insertions_and_deletions += amount_to_stuff;
+ if (conn->amountStuffed > 0) {
+ tsum_of_insertions_and_deletions += conn->amountStuffed;
} else {
- tsum_of_insertions_and_deletions -= amount_to_stuff;
+ tsum_of_insertions_and_deletions -= conn->amountStuffed;
}
- tsum_of_corrections += amount_to_stuff;
- session_corrections += amount_to_stuff;
+ tsum_of_corrections += conn->amountStuffed;
+ conn->session_corrections += conn->amountStuffed;
newest_statistic = (newest_statistic + 1) % trend_interval;
number_of_statistics++;
@@ -2121,53 +2072,59 @@ static void *player_thread_func(void *arg) {
if (at_least_one_frame_seen) {
if ((config.output->delay)) {
if (config.no_sync == 0) {
- inform("%*.1f," /* Sync error in milliseconds */
- "%*.1f," /* net correction in ppm */
- "%*.1f," /* corrections in ppm */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*lli," /* min DAC queue size */
- "%*d," /* min buffer occupancy */
- "%*d", /* max buffer occupancy */
- 10,
- 1000 * moving_average_sync_error / config.output_rate, 10,
- moving_average_correction * 1000000 / (352 * output_sample_ratio), 10,
- moving_average_insertions_plus_deletions * 1000000 /
- (352 * output_sample_ratio),
- 12, play_number, 7, missing_packets, 7, late_packets, 7, too_late_packets,
- 7, resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy,
- 5, maximum_buffer_occupancy);
+ inform(
+ " %*.1f," /* Sync error in milliseconds */
+ "%*.1f," /* net correction in ppm */
+ "%*.1f," /* corrections in ppm */
+ "%*d," /* total packets */
+ "%*llu," /* missing packets */
+ "%*llu," /* late packets */
+ "%*llu," /* too late packets */
+ "%*llu," /* resend requests */
+ "%*lli," /* min DAC queue size */
+ "%*d," /* min buffer occupancy */
+ "%*d", /* max buffer occupancy */
+ 9, /* should be 10, but there's an explicit space at the start to ensure
+ alignment */
+ 1000 * moving_average_sync_error / config.output_rate,
+ 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio),
+ 10, moving_average_insertions_plus_deletions * 1000000 /
+ (352 * conn->output_sample_ratio),
+ 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
+ conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size,
+ 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy);
} else {
- inform("%*.1f," /* Sync error in milliseconds */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*lli," /* min DAC queue size */
- "%*d," /* min buffer occupancy */
- "%*d", /* max buffer occupancy */
- 10,
- 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7,
- missing_packets, 7, late_packets, 7, too_late_packets, 7, resend_requests,
- 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5,
+ inform(" %*.1f," /* Sync error in milliseconds */
+ "%*d," /* total packets */
+ "%*llu," /* missing packets */
+ "%*llu," /* late packets */
+ "%*llu," /* too late packets */
+ "%*llu," /* resend requests */
+ "%*lli," /* min DAC queue size */
+ "%*d," /* min buffer occupancy */
+ "%*d", /* max buffer occupancy */
+ 9, /* should be 10, but there's an explicit space at the start to ensure
+ alignment */
+ 1000 * moving_average_sync_error / config.output_rate,
+ 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
+ conn->too_late_packets, 7, conn->resend_requests, 7,
+ minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5,
maximum_buffer_occupancy);
}
} else {
- inform("%*.1f," /* Sync error in milliseconds */
- "%*d," /* total packets */
- "%*llu," /* missing packets */
- "%*llu," /* late packets */
- "%*llu," /* too late packets */
- "%*llu," /* resend requests */
- "%*d," /* min buffer occupancy */
- "%*d", /* max buffer occupancy */
- 10,
- 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7,
- missing_packets, 7, late_packets, 7, too_late_packets, 7, resend_requests, 5,
+ inform(" %*.1f," /* Sync error in milliseconds */
+ "%*d," /* total packets */
+ "%*llu," /* missing packets */
+ "%*llu," /* late packets */
+ "%*llu," /* too late packets */
+ "%*llu," /* resend requests */
+ "%*d," /* min buffer occupancy */
+ "%*d", /* max buffer occupancy */
+ 9, /* should be 10, but there's an explicit space at the start to ensure
+ alignment */
+ 1000 * moving_average_sync_error / config.output_rate,
+ 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
+ conn->too_late_packets, 7, conn->resend_requests, 5,
minimum_buffer_occupancy, 5, maximum_buffer_occupancy);
}
} else {
@@ -2196,14 +2153,8 @@ static void *player_thread_func(void *arg) {
config.output->stop();
usleep(100000); // allow this time to (?) allow the alsa subsystem to finish cleaning up after
// itself. 50 ms seems too short
- free(outbuf);
- free(silence);
- if (tbuf)
- free(tbuf);
- if (sbuf)
- free(sbuf);
debug(1, "Shut down audio, control and timing threads");
- itr.please_stop = 1;
+ conn->please_stop = 1;
pthread_kill(rtp_audio_thread, SIGUSR1);
pthread_kill(rtp_control_thread, SIGUSR1);
pthread_kill(rtp_timing_thread, SIGUSR1);
@@ -2213,12 +2164,35 @@ static void *player_thread_func(void *arg) {
debug(1, "audio thread joined");
pthread_join(rtp_control_thread, NULL);
debug(1, "control thread joined");
+ free_buffer(conn);
+ terminate_decoders(conn);
+ // remove flow control and mutexes
+ rc = pthread_cond_destroy(&conn->flowcontrol);
+ if (rc)
+ debug(1, "Error destroying flowcontrol condition variable.");
+ rc = pthread_mutex_destroy(&conn->flush_mutex);
+ if (rc)
+ debug(1, "Error destroying flush_mutex variable.");
+ rc = pthread_mutex_destroy(&conn->ab_mutex);
+ if (rc)
+ debug(1, "Error destroying ab_mutex variable.");
+ rc = pthread_mutex_destroy(&conn->vol_mutex);
+ if (rc)
+ debug(1, "Error destroying vol_mutex variable.");
debug(1, "Player thread exit");
+ if (outbuf)
+ free(outbuf);
+ if (silence)
+ free(silence);
+ if (tbuf)
+ free(tbuf);
+ if (sbuf)
+ free(sbuf);
return 0;
}
// takes the volume as specified by the airplay protocol
-void player_volume(double airplay_volume) {
+void player_volume(double airplay_volume, rtsp_conn_info *conn) {
// The volume ranges -144.0 (mute) or -30 -- 0. See
// http://git.zx2c4.com/Airtunes2/about/#setting-volume
@@ -2250,10 +2224,13 @@ void player_volume(double airplay_volume) {
// Thus, we ask our vol2attn function for an appropriate dB between -96.3 and 0 dB and translate
// it back to a number.
+ command_set_volume(airplay_volume);
+
int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db,
max_db; // hw_range_db is a flag; if 0 means no mixer
if (config.output->parameters) {
+ audio_parameters audio_information;
// have a hardware mixer
config.output->parameters(&audio_information);
hw_max_db = audio_information.maximum_volume_dB;
@@ -2267,11 +2244,14 @@ void player_volume(double airplay_volume) {
int32_t sw_min_db = -9630;
int32_t sw_max_db = 0;
int32_t sw_range_db = sw_max_db - sw_min_db;
- int32_t desired_range_db = 0; // this is used as a flag; if 0 means no desired range
+ int32_t desired_range_db = 0; // this is also used as a flag; if 0 means no desired range
if (config.volume_range_db)
desired_range_db = (int32_t)trunc(config.volume_range_db * 100);
+ // This is wrong, I think -- it doesn't work properly if the volume range is composite and you
+ // want to set a maximum value which should affect the hardware mixer.
+
if (config.volume_max_db_set) {
if (hw_range_db) {
if (((config.volume_max_db * 100) < hw_max_db) &&
@@ -2305,6 +2285,7 @@ void player_volume(double airplay_volume) {
max_db = hw_max_db;
min_db = max_db - desired_range_db;
} else {
+ // we have a hardware mixer and a desired range greater than the mixer's range.
if ((hw_range_db + sw_range_db) < desired_range_db) {
inform("The volume attenuation range %f is greater than can be accommodated by the "
"hardware and software -- set to %f.",
@@ -2352,18 +2333,33 @@ void player_volume(double airplay_volume) {
double scaled_attenuation = hw_min_db + sw_min_db;
// now, we can map the input to the desired output volume
- if (airplay_volume == -144.0) {
+ if ((airplay_volume == -144.0) && (config.ignore_volume_control == 0)) {
// do a mute
// needed even with hardware mute, as when sound is unmuted it might otherwise be very loud.
hardware_attenuation = hw_min_db;
- software_attenuation = sw_min_db;
- if (config.output->mute)
+ if (config.output->mute) {
+ // allow the audio material to reach the mixer, but mute the mixer
+ // it the mute is removed externally, the material with be there
+ software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and
+ // the max is +40, this will be -36
+ // (all by 100, of course)
+ // debug(1,"Mute, with hardware mute and software_attenuation set to
+ // %d.",software_attenuation);
config.output->mute(1); // use real mute if it's there
-
+ } else {
+ if (config.output->volume == NULL) { // if there is also no hardware volume control
+ software_attenuation = sw_min_db; // set any software output to zero too
+ // debug(1,"Mute, with no hardware mute and software_attenuation set to
+ // %d.",software_attenuation);
+ }
+ }
} else {
if (config.output->mute)
config.output->mute(0); // unmute mute if it's there
- scaled_attenuation = vol2attn(airplay_volume, max_db, min_db);
+ if (config.ignore_volume_control == 1)
+ scaled_attenuation = max_db;
+ else
+ scaled_attenuation = vol2attn(airplay_volume, max_db, min_db);
if (hw_range_db) {
// if there is a hardware mixer
if (scaled_attenuation <= hw_max_db) {
@@ -2394,106 +2390,77 @@ void player_volume(double airplay_volume) {
// debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of
// %f",software_attenuation,temp_fix_volume,airplay_volume);
- pthread_mutex_lock(&vol_mutex);
- fix_volume = temp_fix_volume;
- pthread_mutex_unlock(&vol_mutex);
+ pthread_mutex_lock(&conn->vol_mutex);
+ conn->fix_volume = temp_fix_volume;
+ pthread_mutex_unlock(&conn->vol_mutex);
+
+ if (config.loudness)
+ loudness_set_volume(software_attenuation / 100);
+
+ if (config.logOutputLevel) {
+ inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
+ }
#ifdef CONFIG_METADATA
char *dv = malloc(128); // will be freed in the metadata thread
if (dv) {
memset(dv, 0, 128);
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
- min_db / 100.0, max_db / 100.0);
+ if (config.ignore_volume_control == 1)
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
+ else
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
+ min_db / 100.0, max_db / 100.0);
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
}
#endif
}
-void player_flush(int64_t timestamp) {
+void player_flush(int64_t timestamp, rtsp_conn_info *conn) {
debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp);
- pthread_mutex_lock(&flush_mutex);
- flush_requested = 1;
+ pthread_mutex_lock(&conn->flush_mutex);
+ conn->flush_requested = 1;
// if (timestamp!=0)
- flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this
- pthread_mutex_unlock(&flush_mutex);
- play_segment_reference_frame = 0;
+ conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this
+ pthread_mutex_unlock(&conn->flush_mutex);
+ conn->play_segment_reference_frame = 0;
+ conn->play_number_after_flush = 0;
#ifdef CONFIG_METADATA
send_ssnc_metadata('pfls', NULL, 0, 1);
#endif
}
-int player_play(stream_cfg *stream, pthread_t *player_thread) {
+int player_play(pthread_t *player_thread, rtsp_conn_info *conn) {
+
+ // need to use conn in place of streram below. Need to put the stream as a parameter to he
// if (*player_thread!=NULL)
// die("Trying to create a second player thread for this RTSP session");
- packet_count = 0;
- encrypted = stream->encrypted;
if (config.buffer_start_fill > BUFFER_FRAMES)
die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill,
BUFFER_FRAMES);
- if (encrypted) {
-#ifdef HAVE_LIBMBEDTLS
- memset(&dctx, 0, sizeof(mbedtls_aes_context));
- mbedtls_aes_setkey_dec(&dctx, stream->aeskey, 128);
-#endif
-
-#ifdef HAVE_LIBPOLARSSL
- memset(&dctx, 0, sizeof(aes_context));
- aes_setkey_dec(&dctx, stream->aeskey, 128);
-#endif
-
-#ifdef HAVE_LIBSSL
- AES_set_decrypt_key(stream->aeskey, 128, &aes);
-#endif
- aesiv = stream->aesiv;
- }
- init_decoder(stream->fmtp); // this sets up incoming rate, bit depth, channels
- // must be after decoder init
- init_buffer();
- please_stop = 0;
command_start();
#ifdef CONFIG_METADATA
send_ssnc_metadata('pbeg', NULL, 0, 1);
#endif
-
-// set the flowcontrol condition variable to wait on a monotonic clock
-#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN
- pthread_condattr_t attr;
- pthread_condattr_init(&attr);
- pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
- int rc = pthread_cond_init(&flowcontrol, &attr);
-#endif
-#ifdef COMPILE_FOR_OSX
- int rc = pthread_cond_init(&flowcontrol, NULL);
-#endif
- if (rc)
- debug(1, "Error initialising condition variable.");
- config.output->start(config.output_rate, config.output_format);
size_t size = (PTHREAD_STACK_MIN + 256 * 1024);
pthread_attr_t tattr;
pthread_attr_init(&tattr);
- rc = pthread_attr_setstacksize(&tattr, size);
+ int rc = pthread_attr_setstacksize(&tattr, size);
if (rc)
debug(1, "Error setting stack size for player_thread: %s", strerror(errno));
- pthread_create(player_thread, &tattr, player_thread_func, NULL);
+ pthread_create(player_thread, &tattr, player_thread_func, (void *)conn);
pthread_attr_destroy(&tattr);
return 0;
}
-void player_stop(pthread_t *player_thread) {
+void player_stop(pthread_t *player_thread, rtsp_conn_info *conn) {
// if (*thread==NULL)
// debug(1,"Trying to stop a non-existent player thread");
// else {
- please_stop = 1;
- pthread_cond_signal(&flowcontrol); // tell it to give up
+ conn->player_thread_please_stop = 1;
+ pthread_cond_signal(&conn->flowcontrol); // tell it to give up
pthread_join(*player_thread, NULL);
#ifdef CONFIG_METADATA
send_ssnc_metadata('pend', NULL, 0, 1);
#endif
command_stop();
- free_buffer();
- terminate_decoders();
- int rc = pthread_cond_destroy(&flowcontrol);
- if (rc)
- debug(1, "Error destroying condition variable.");
- // }
}
diff --git a/player.h b/player.h
index eba97e8e..d69792c6 100644
--- a/player.h
+++ b/player.h
@@ -1,35 +1,178 @@
#ifndef _PLAYER_H
#define _PLAYER_H
+#include
+
+#include "config.h"
+
+#ifdef HAVE_LIBMBEDTLS
+#include
+#include
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+#include
+#include
+#endif
+
+#ifdef HAVE_LIBSSL
+#include
+#endif
+
+#include "alac.h"
#include "audio.h"
+#define time_ping_history 8
+
+typedef struct time_ping_record {
+ uint64_t local_to_remote_difference;
+ uint64_t dispersion;
+ uint64_t local_time;
+ uint64_t remote_time;
+} time_ping_record;
+
+typedef uint16_t seq_t;
+
+typedef struct audio_buffer_entry { // decoded audio packets
+ int ready;
+ int64_t timestamp;
+ seq_t sequence_number;
+ signed short *data;
+ int length; // the length of the decoded data
+} abuf_t;
+
+// default buffer size
+// needs to be a power of 2 because of the way BUFIDX(seqno) works
+#define BUFFER_FRAMES 512
+
typedef struct {
int encrypted;
uint8_t aesiv[16], aeskey[16];
int32_t fmtp[12];
} stream_cfg;
-typedef uint16_t seq_t;
+typedef struct {
+ int fd;
+ int authorized; // set if a password is required and has been supplied
+ stream_cfg stream;
+ SOCKADDR remote, local;
+ int stop;
+ int running;
+ pthread_t thread;
+ pthread_t player_thread;
-// wrapped number between two seq_t.
-int32_t seq_diff(seq_t a, seq_t b);
+ abuf_t audio_buffer[BUFFER_FRAMES];
+ int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate;
+ int input_bytes_per_frame, output_bytes_per_frame, output_sample_ratio;
+ int max_frame_size_change;
+ int64_t previous_random_number;
+ alac_file *decoder_info;
+ uint32_t please_stop;
+ uint64_t packet_count;
+ int shutdown_requested;
+ int connection_state_to_output;
+ int player_thread_please_stop;
+ int64_t first_packet_time_to_play, time_since_play_started; // nanoseconds
+ // stats
+ uint64_t missing_packets, late_packets, too_late_packets, resend_requests;
+ int decoder_in_use;
+ // debug variables
+ int32_t last_seqno_read;
+ // mutexes and condition variables
+ pthread_cond_t flowcontrol;
+ pthread_mutex_t ab_mutex, flush_mutex;
+ pthread_mutex_t vol_mutex;
+ int fix_volume;
+ uint32_t timestamp_epoch, last_timestamp,
+ maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2
+ // or 1.
+ int ab_buffering, ab_synced;
+ int64_t first_packet_timestamp;
+ int flush_requested;
+ int64_t flush_rtp_timestamp;
+ uint64_t time_of_last_audio_packet;
+ seq_t ab_read, ab_write;
-int player_play(stream_cfg *cfg, pthread_t *thread);
-void player_stop(pthread_t *thread);
+#ifdef HAVE_LIBMBEDTLS
+ mbedtls_aes_context dctx;
+#endif
-void player_volume(double f);
-void player_flush(int64_t timestamp);
+#ifdef HAVE_LIBPOLARSSL
+ aes_context dctx;
+#endif
-void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len);
+#ifdef HAVE_LIBSSL
+ AES_KEY aes;
+#endif
-int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic
- // timestamp guaranteed to start between 2^32 2^33
- // frames and continue up to 2^64 frames
+ int amountStuffed;
+
+ int32_t framesProcessedInThisEpoch;
+ int32_t framesGeneratedInThisEpoch;
+ int32_t correctionsRequestedInThisEpoch;
+ int64_t syncErrorsInThisEpoch;
+
+ // RTP stuff
+ // only one RTP session can be active at a time.
+ int rtp_running;
+
+ char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
+ char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it
+ // could be one of many, so we need to know it
+ uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope
+ short connection_ip_family; // AF_INET / AF_INET6
+ uint32_t client_active_remote; // used when you want to control the client...
+
+ SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client
+ SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client
+ int audio_socket; // our local [server] audio socket
+ int control_socket; // our local [server] control socket
+ int timing_socket; // local timing socket
+
+ int64_t reference_timestamp;
+ uint64_t reference_timestamp_time;
+ uint64_t remote_reference_timestamp_time;
+
+ // debug variables
+ int request_sent;
+
+ uint8_t time_ping_count;
+ struct time_ping_record time_pings[time_ping_history];
+
+ uint64_t departure_time; // dangerous -- this assumes that there will never be two timing
+ // request in flight at the same time
+
+ pthread_mutex_t reference_time_mutex;
+
+ uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks
+
+ int timing_sender_stop; // for asking the timing-sending thread to stop
+ int last_stuff_request;
+
+ int64_t play_segment_reference_frame;
+ uint64_t play_segment_reference_frame_remote_time;
+
+ int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative
+ int64_t session_corrections;
+
+ int play_number_after_flush;
+
+} rtsp_conn_info;
+
+int player_play(pthread_t *thread, rtsp_conn_info *conn);
+void player_stop(pthread_t *thread, rtsp_conn_info *conn);
+
+void player_volume(double f, rtsp_conn_info *conn);
+void player_flush(int64_t timestamp, rtsp_conn_info *conn);
+void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len,
+ rtsp_conn_info *conn);
+
+int64_t monotonic_timestamp(uint32_t timestamp,
+ rtsp_conn_info *conn); // add an epoch to the timestamp. The monotonic
+// timestamp guaranteed to start between 2^32 2^33
+// frames and continue up to 2^64 frames
// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds.
// assumes, without checking, that successive timestamps in a series always span an interval of less
// than one minute.
-uint64_t monotonic_seqno(uint16_t seq_no); // add an epoch to the seq_no. Uses the accompanying
- // timstamp to detemine the correct epoch
-
#endif //_PLAYER_H
diff --git a/rtp.c b/rtp.c
index dcaf28d4..dfd15b95 100644
--- a/rtp.c
+++ b/rtp.c
@@ -42,63 +42,28 @@
#include "player.h"
#include "rtp.h"
-/*
-// this does not compile properly with OpenWrt Barrier Breaker...
-#if defined(__linux__)
-#include
-#endif
-*/
-typedef struct time_ping_record {
- uint64_t local_to_remote_difference;
- uint64_t dispersion;
- uint64_t local_time;
- uint64_t remote_time;
-} time_ping_record;
+void rtp_initialise(rtsp_conn_info *conn) {
-// only one RTP session can be active at a time.
-static int running = 0;
+ conn->rtp_running = 0;
+ // initialise the timer mutex
+ int rc = pthread_mutex_init(&conn->reference_time_mutex, NULL);
+ if (rc)
+ debug(1, "Error initialising reference_time_mutex.");
+}
-static char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
-static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it
- // could be one of many, so we need to know it
-static uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope
-static short connection_ip_family; // AF_INET / AF_INET6
-static uint32_t client_active_remote; // used when you want to control the client...
+void rtp_terminate(rtsp_conn_info *conn) {
-static SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client
-static SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client
-static int audio_socket; // our local [server] audio socket
-static int control_socket; // our local [server] control socket
-static int timing_socket; // local timing socket
-// static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
-
-static int64_t reference_timestamp;
-static uint64_t reference_timestamp_time;
-static uint64_t remote_reference_timestamp_time;
-
-// debug variables
-static int request_sent;
-
-#define time_ping_history 8
-#define time_ping_fudge_factor 100000
-
-static uint8_t time_ping_count;
-struct time_ping_record time_pings[time_ping_history];
-
-// static struct timespec dtt; // dangerous -- this assumes that there will never be two timing
-// request in flight at the same time
-static uint64_t departure_time; // dangerous -- this assumes that there will never be two timing
- // request in flight at the same time
-
-static pthread_mutex_t reference_time_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-uint64_t static local_to_remote_time_difference; // used to switch between local and remote clocks
+ // destroy the timer mutex
+ int rc = pthread_mutex_destroy(&conn->reference_time_mutex);
+ if (rc)
+ debug(1, "Error destroying reference_time_mutex variable.");
+}
void *rtp_audio_receiver(void *arg) {
debug(2, "Audio receiver -- Server RTP thread starting.");
// we inherit the signal mask (SIGUSR1)
- struct inter_threads_record *itr = arg;
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
int32_t last_seqno = -1;
uint8_t packet[2048], *pktp;
@@ -114,8 +79,8 @@ void *rtp_audio_receiver(void *arg) {
float stat_M2 = 0.0;
ssize_t nread;
- while (itr->please_stop == 0) {
- nread = recv(audio_socket, packet, sizeof(packet), 0);
+ while (conn->please_stop == 0) {
+ nread = recv(conn->audio_socket, packet, sizeof(packet), 0);
uint64_t local_time_now_fp = get_absolute_time_in_fp();
if (time_of_previous_packet_fp) {
@@ -129,7 +94,7 @@ void *rtp_audio_receiver(void *arg) {
stat_mean += stat_delta / stat_n;
stat_M2 += stat_delta * (time_interval_us - stat_mean);
if (stat_n % 2500 == 0) {
- debug(2, "Packet reception interval stats: mean, standard deviation and max for the last "
+ debug(3, "Packet reception interval stats: mean, standard deviation and max for the last "
"2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",
stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us);
stat_n = 0;
@@ -164,7 +129,7 @@ void *rtp_audio_receiver(void *arg) {
// debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno);
last_seqno = seqno; // reset warning...
}
- int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)));
+ int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)), conn);
// if (packet[1]&0x10)
// debug(1,"Audio packet Extension bit set.");
@@ -174,7 +139,7 @@ void *rtp_audio_receiver(void *arg) {
// check if packet contains enough content to be reasonable
if (plen >= 16) {
- player_put_packet(seqno, timestamp, pktp, plen);
+ player_put_packet(seqno, timestamp, pktp, plen, conn);
continue;
}
if (type == 0x56 && seqno == 0) {
@@ -188,7 +153,7 @@ void *rtp_audio_receiver(void *arg) {
}
debug(1, "Audio receiver -- Server RTP thread interrupted. terminating.");
- close(audio_socket);
+ close(conn->audio_socket);
return NULL;
}
@@ -197,16 +162,17 @@ void *rtp_control_receiver(void *arg) {
// we inherit the signal mask (SIGUSR1)
debug(2, "Control receiver -- Server RTP thread starting.");
- struct inter_threads_record *itr = arg;
- reference_timestamp = 0; // nothing valid received yet
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
+
+ conn->reference_timestamp = 0; // nothing valid received yet
uint8_t packet[2048], *pktp;
struct timespec tn;
uint64_t remote_time_of_sync, local_time_now, remote_time_now;
int64_t sync_rtp_timestamp, rtp_timestamp_less_latency;
ssize_t nread;
- while (itr->please_stop == 0) {
- nread = recv(control_socket, packet, sizeof(packet), 0);
+ while (conn->please_stop == 0) {
+ nread = recv(conn->control_socket, packet, sizeof(packet), 0);
local_time_now = get_absolute_time_in_fp();
// clock_gettime(CLOCK_MONOTONIC,&tn);
// local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000;
@@ -227,15 +193,15 @@ void *rtp_control_receiver(void *arg) {
*obfp=0;
debug(1,"Sync Packet Received: \"%s\"",obf);
*/
- if (local_to_remote_time_difference) { // need a time packet to be interchanged first...
+ if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged first...
remote_time_of_sync = (uint64_t)ntohl(*((uint32_t *)&packet[8])) << 32;
remote_time_of_sync += ntohl(*((uint32_t *)&packet[12]));
// debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync);
- rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4])));
- sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])));
+ rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4])), conn);
+ sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])), conn);
if (config.use_negotiated_latencies) {
int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + 11025;
@@ -252,11 +218,12 @@ void *rtp_control_receiver(void *arg) {
// it's as if the first sync after a flush or resume is the timing of the next packet
// after the one whose RTP is given. Weird.
}
- pthread_mutex_lock(&reference_time_mutex);
- remote_reference_timestamp_time = remote_time_of_sync;
- reference_timestamp_time = remote_time_of_sync - local_to_remote_time_difference;
- reference_timestamp = sync_rtp_timestamp;
- pthread_mutex_unlock(&reference_time_mutex);
+ pthread_mutex_lock(&conn->reference_time_mutex);
+ conn->remote_reference_timestamp_time = remote_time_of_sync;
+ conn->reference_timestamp_time =
+ remote_time_of_sync - conn->local_to_remote_time_difference;
+ conn->reference_timestamp = sync_rtp_timestamp;
+ pthread_mutex_unlock(&conn->reference_time_mutex);
// debug(1,"New Reference timestamp and timestamp time...");
// get estimated remote time now
// remote_time_now = local_time_now + local_to_remote_time_difference;
@@ -274,14 +241,14 @@ void *rtp_control_receiver(void *arg) {
plen -= 4;
seq_t seqno = ntohs(*(unsigned short *)(pktp + 2));
- int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)));
+ int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)), conn);
pktp += 12;
plen -= 12;
// check if packet contains enough content to be reasonable
if (plen >= 16) {
- player_put_packet(seqno, timestamp, pktp, plen);
+ player_put_packet(seqno, timestamp, pktp, plen, conn);
continue;
} else {
debug(1, "Too-short retransmitted audio packet received in control port, ignored.");
@@ -291,14 +258,14 @@ void *rtp_control_receiver(void *arg) {
}
debug(1, "Control RTP thread interrupted. terminating.");
- close(control_socket);
+ close(conn->control_socket);
return NULL;
}
void *rtp_timing_sender(void *arg) {
debug(2, "Timing sender thread starting.");
- int *stop = arg; // the parameter points to this request to stop thing
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
struct timing_request {
char leader;
char type;
@@ -316,13 +283,13 @@ void *rtp_timing_sender(void *arg) {
req.filler = 0;
req.seqno = htons(7);
- time_ping_count = 0;
+ conn->time_ping_count = 0;
// we inherit the signal mask (SIGUSR1)
- while (*stop == 0) {
+ while (conn->timing_sender_stop == 0) {
// debug(1,"Send a timing request");
- if (!running)
+ if (!conn->rtp_running)
die("rtp_timing_sender called without active stream!");
// debug(1, "Requesting ntp timestamp exchange.");
@@ -331,15 +298,15 @@ void *rtp_timing_sender(void *arg) {
req.origin = req.receive = req.transmit = 0;
// clock_gettime(CLOCK_MONOTONIC,&dtt);
- departure_time = get_absolute_time_in_fp();
+ conn->departure_time = get_absolute_time_in_fp();
socklen_t msgsize = sizeof(struct sockaddr_in);
#ifdef AF_INET6
- if (rtp_client_timing_socket.SAFAMILY == AF_INET6) {
+ if (conn->rtp_client_timing_socket.SAFAMILY == AF_INET6) {
msgsize = sizeof(struct sockaddr_in6);
}
#endif
- if (sendto(timing_socket, &req, sizeof(req), 0, (struct sockaddr *)&rtp_client_timing_socket,
- msgsize) == -1) {
+ if (sendto(conn->timing_socket, &req, sizeof(req), 0,
+ (struct sockaddr *)&conn->rtp_client_timing_socket, msgsize) == -1) {
perror("Error sendto-ing to timing socket");
}
request_number++;
@@ -355,14 +322,13 @@ void *rtp_timing_sender(void *arg) {
void *rtp_timing_receiver(void *arg) {
debug(2, "Timing receiver -- Server RTP thread starting.");
// we inherit the signal mask (SIGUSR1)
-
- struct inter_threads_record *itr = arg;
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
uint8_t packet[2048], *pktp;
ssize_t nread;
- int request_stop = 0;
+ conn->timing_sender_stop = 0;
pthread_t timer_requester;
- pthread_create(&timer_requester, NULL, &rtp_timing_sender, (void *)&request_stop);
+ pthread_create(&timer_requester, NULL, &rtp_timing_sender, arg);
// struct timespec att;
uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time, transit_time,
processing_time;
@@ -374,8 +340,8 @@ void *rtp_timing_receiver(void *arg) {
uint64_t first_local_to_remote_time_difference = 0;
uint64_t first_local_to_remote_time_difference_time;
uint64_t l2rtd = 0;
- while (itr->please_stop == 0) {
- nread = recv(timing_socket, packet, sizeof(packet), 0);
+ while (conn->please_stop == 0) {
+ nread = recv(conn->timing_socket, packet, sizeof(packet), 0);
arrival_time = get_absolute_time_in_fp();
// clock_gettime(CLOCK_MONOTONIC,&att);
@@ -400,7 +366,7 @@ void *rtp_timing_receiver(void *arg) {
// arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000;
// departure_time = ((uint64_t)dtt.tv_sec<<32)+((uint64_t)dtt.tv_nsec<<32)/1000000000;
- return_time = arrival_time - departure_time;
+ return_time = arrival_time - conn->departure_time;
// uint64_t rtus = (return_time*1000000)>>32; debug(1,"Time ping turnaround time: %lld
// us.",rtus);
@@ -424,45 +390,47 @@ void *rtp_timing_receiver(void *arg) {
uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2;
- unsigned int cc;
+ unsigned int cc, chosen;
for (cc = time_ping_history - 1; cc > 0; cc--) {
- time_pings[cc] = time_pings[cc - 1];
- time_pings[cc].dispersion = (time_pings[cc].dispersion * 133) /
- 100; // make the dispersions 'age' by this rational factor
+ conn->time_pings[cc] = conn->time_pings[cc - 1];
+ conn->time_pings[cc].dispersion = (conn->time_pings[cc].dispersion * 110) /
+ 100; // make the dispersions 'age' by this rational factor
}
// these are for diagnostics only -- not used
- time_pings[0].local_time = arrival_time;
- time_pings[0].remote_time = distant_transmit_time;
+ conn->time_pings[0].local_time = arrival_time;
+ conn->time_pings[0].remote_time = distant_transmit_time;
- time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time;
- time_pings[0].dispersion = return_time;
- if (time_ping_count < time_ping_history)
- time_ping_count++;
+ conn->time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time;
+ conn->time_pings[0].dispersion = return_time;
+ if (conn->time_ping_count < time_ping_history)
+ conn->time_ping_count++;
uint64_t local_time_chosen = arrival_time;
;
uint64_t remote_time_chosen = distant_transmit_time;
// now pick the timestamp with the lowest dispersion
- uint64_t l2rtd = time_pings[0].local_to_remote_difference;
- uint64_t tld = time_pings[0].dispersion;
- for (cc = 1; cc < time_ping_count; cc++)
- if (time_pings[cc].dispersion < tld) {
- l2rtd = time_pings[cc].local_to_remote_difference;
- tld = time_pings[cc].dispersion;
- local_time_chosen = time_pings[cc].local_time;
- remote_time_chosen = time_pings[cc].remote_time;
+ uint64_t l2rtd = conn->time_pings[0].local_to_remote_difference;
+ uint64_t tld = conn->time_pings[0].dispersion;
+ chosen = 0;
+ for (cc = 1; cc < conn->time_ping_count; cc++)
+ if (conn->time_pings[cc].dispersion < tld) {
+ l2rtd = conn->time_pings[cc].local_to_remote_difference;
+ chosen = cc;
+ tld = conn->time_pings[cc].dispersion;
+ local_time_chosen = conn->time_pings[cc].local_time;
+ remote_time_chosen = conn->time_pings[cc].remote_time;
}
int64_t ji;
- if (time_ping_count > 1) {
- if (l2rtd > local_to_remote_time_difference) {
+ if (conn->time_ping_count > 1) {
+ if (l2rtd > conn->local_to_remote_time_difference) {
local_to_remote_time_jitters =
- local_to_remote_time_jitters + l2rtd - local_to_remote_time_difference;
- ji = l2rtd - local_to_remote_time_difference;
+ local_to_remote_time_jitters + l2rtd - conn->local_to_remote_time_difference;
+ ji = l2rtd - conn->local_to_remote_time_difference;
} else {
local_to_remote_time_jitters =
- local_to_remote_time_jitters + local_to_remote_time_difference - l2rtd;
- ji = -(local_to_remote_time_difference - l2rtd);
+ local_to_remote_time_jitters + conn->local_to_remote_time_difference - l2rtd;
+ ji = -(conn->local_to_remote_time_difference - l2rtd);
}
local_to_remote_time_jitters_count += 1;
}
@@ -470,13 +438,14 @@ void *rtp_timing_receiver(void *arg) {
// int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time difference
// with dispersion of %lld us with delta of %lld us",rtus,ji);
- local_to_remote_time_difference = l2rtd;
+ conn->local_to_remote_time_difference = l2rtd;
if (first_local_to_remote_time_difference == 0) {
- first_local_to_remote_time_difference = local_to_remote_time_difference;
+ first_local_to_remote_time_difference = conn->local_to_remote_time_difference;
first_local_to_remote_time_difference_time = get_absolute_time_in_fp();
}
int64_t clock_drift, clock_drift_in_usec;
+ double clock_drift_ppm = 0.0;
if (first_local_time == 0) {
first_local_time = local_time_chosen;
first_remote_time = remote_time_chosen;
@@ -489,29 +458,31 @@ void *rtp_timing_receiver(void *arg) {
clock_drift = remote_time_change - local_time_change;
else
clock_drift = -(local_time_change - remote_time_change);
+ if (clock_drift >= 0)
+ clock_drift_in_usec = (clock_drift * 1000000) >> 32;
+ else
+ clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32);
+ clock_drift_ppm = (1.0 * clock_drift_in_usec) / (local_time_change >> 32);
}
- if (clock_drift >= 0)
- clock_drift_in_usec = (clock_drift * 1000000) >> 32;
- else
- clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32);
int64_t source_drift_usec;
- if (play_segment_reference_frame != 0) {
+ 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);
+ &remote_reference_timestamp_time, conn);
uint64_t frame_difference = 0;
- if (reference_timestamp >= play_segment_reference_frame)
- frame_difference = (uint64_t)reference_timestamp - (uint64_t)play_segment_reference_frame;
- else // rollover
+ if (reference_timestamp >= conn->play_segment_reference_frame)
frame_difference =
- (uint64_t)reference_timestamp + 0x100000000 - (uint64_t)play_segment_reference_frame;
+ (uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame;
+ else // rollover
+ frame_difference = (uint64_t)reference_timestamp + 0x100000000 -
+ (uint64_t)conn->play_segment_reference_frame;
uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference << 32) / 44100);
uint64_t frame_time_difference_actual =
remote_reference_timestamp_time -
- play_segment_reference_frame_remote_time; // this is all done by reference to the
- // sources' system clock
+ 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);
@@ -533,18 +504,15 @@ void *rtp_timing_receiver(void *arg) {
// config.output->delay(¤t_delay);
//}
// Useful for troubleshooting:
- // clock_drift between source and local clock -- +ve means source is faster
- // session_corrections -- the amount of correction done, in microseconds. +ve means frames
- // added
- // current_delay = delay in DAC buffer in frames
- // source_drift_usec = how much faster (+ve) or slower the source DAC is running relative
- // to the source clock
- // buffer_occupancy = the number of buffers occupied. Crude, but should show no long term
- // trend if source and device are in sync.
- // return_time = the time from soliciting a timing packet to getting it back. It should be
- // short ( < 5 ms) and pretty consistent.
- // debug(1, "%lld\t%lld\t%ld\t%lld\t%u\t%llu",
- // clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32);
+ // debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec %10.1lld\treturn_time_in_usec
+ // %10.1llu",
+ // clock_drift_ppm,
+ // chosen,
+ //(session_corrections*1000000)/44100,
+ // current_delay,
+ // source_drift_usec,
+ // buffer_occupancy,
+ //(return_time*1000000)>>32);
} else {
debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread);
@@ -552,13 +520,14 @@ void *rtp_timing_receiver(void *arg) {
}
debug(1, "Timing thread interrupted. terminating.");
- request_stop = 1;
+ conn->timing_sender_stop = 1;
void *retval;
pthread_kill(timer_requester, SIGUSR1);
+ debug(1, "Wait for timer requester to exit.");
pthread_join(timer_requester, &retval);
debug(1, "Closed and terminated timer requester thread.");
debug(1, "Timing RTP thread terminated.");
- close(timing_socket);
+ close(conn->timing_socket);
return NULL;
}
@@ -593,13 +562,14 @@ static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_
#endif
} while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) &&
- (desired_port++ < config.udp_port_base + config.udp_port_range));
+ (++desired_port < config.udp_port_base + config.udp_port_range));
// debug(1,"UDP port chosen: %d.",desired_port);
if (ret < 0) {
close(local_socket);
- die("error: could not bind a UDP port!");
+ die("error: could not bind a UDP port! Check the udp_port_range is large enough (>= 10) or "
+ "check for restrictive firewall settings or a bad router!");
}
int sport;
@@ -622,18 +592,18 @@ static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_
}
void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote,
- int *lsport, int *lcport, int *ltport) {
+ int *lsport, int *lcport, int *ltport, rtsp_conn_info *conn) {
// this gets the local and remote ip numbers (and ports used for the TCD stuff)
// we use the local stuff to specify the address we are coming from and
// we use the remote stuff to specify where we're goint to
- if (running)
+ if (conn->rtp_running)
die("rtp_setup called with active stream!");
debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport);
- client_active_remote = active_remote;
+ conn->client_active_remote = active_remote;
// print out what we know about the client
void *client_addr, *self_addr;
@@ -641,20 +611,21 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t
char client_port_str[64];
char self_addr_str[64];
- connection_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client
+ conn->connection_ip_family =
+ remote->SAFAMILY; // keep information about the kind of ip of the client
#ifdef AF_INET6
- if (connection_ip_family == AF_INET6) {
+ if (conn->connection_ip_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote;
client_addr = &(sa6->sin6_addr);
client_port = ntohs(sa6->sin6_port);
sa6 = (struct sockaddr_in6 *)local;
self_addr = &(sa6->sin6_addr);
self_port = ntohs(sa6->sin6_port);
- self_scope_id = sa6->sin6_scope_id;
+ conn->self_scope_id = sa6->sin6_scope_id;
}
#endif
- if (connection_ip_family == AF_INET) {
+ if (conn->connection_ip_family == AF_INET) {
struct sockaddr_in *sa4 = (struct sockaddr_in *)remote;
client_addr = &(sa4->sin_addr);
client_port = ntohs(sa4->sin_port);
@@ -663,96 +634,102 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t
self_port = ntohs(sa4->sin_port);
}
- inet_ntop(connection_ip_family, client_addr, client_ip_string, sizeof(client_ip_string));
- inet_ntop(connection_ip_family, self_addr, self_ip_string, sizeof(self_ip_string));
+ inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string,
+ sizeof(conn->client_ip_string));
+ inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string,
+ sizeof(conn->self_ip_string));
- debug(1, "Set up play connection from %s to self at %s.", client_ip_string, self_ip_string);
+ debug(1, "Set up play connection from %s to self at %s.", conn->client_ip_string,
+ conn->self_ip_string);
// set up a the record of the remote's control socket
struct addrinfo hints;
struct addrinfo *servinfo;
- memset(&rtp_client_control_socket, 0, sizeof(rtp_client_control_socket));
+ memset(&conn->rtp_client_control_socket, 0, sizeof(conn->rtp_client_control_socket));
memset(&hints, 0, sizeof hints);
- hints.ai_family = connection_ip_family;
+ hints.ai_family = conn->connection_ip_family;
hints.ai_socktype = SOCK_DGRAM;
char portstr[20];
snprintf(portstr, 20, "%d", cport);
- if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0)
+ if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0)
die("Can't get address of client's control port");
#ifdef AF_INET6
if (servinfo->ai_family == AF_INET6) {
- memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
+ memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
// ensure the scope id matches that of remote. this is needed for link-local addresses.
- struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&rtp_client_control_socket;
- sa6->sin6_scope_id = self_scope_id;
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_control_socket;
+ sa6->sin6_scope_id = conn->self_scope_id;
} else
#endif
- memcpy(&rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
+ memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
freeaddrinfo(servinfo);
// set up a the record of the remote's timing socket
- memset(&rtp_client_timing_socket, 0, sizeof(rtp_client_timing_socket));
+ memset(&conn->rtp_client_timing_socket, 0, sizeof(conn->rtp_client_timing_socket));
memset(&hints, 0, sizeof hints);
- hints.ai_family = connection_ip_family;
+ hints.ai_family = conn->connection_ip_family;
hints.ai_socktype = SOCK_DGRAM;
snprintf(portstr, 20, "%d", tport);
- if (getaddrinfo(client_ip_string, portstr, &hints, &servinfo) != 0)
+ if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0)
die("Can't get address of client's timing port");
#ifdef AF_INET6
if (servinfo->ai_family == AF_INET6) {
- memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
+ memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
// ensure the scope id matches that of remote. this is needed for link-local addresses.
- struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&rtp_client_timing_socket;
- sa6->sin6_scope_id = self_scope_id;
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_timing_socket;
+ sa6->sin6_scope_id = conn->self_scope_id;
} else
#endif
- memcpy(&rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
+ memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
freeaddrinfo(servinfo);
// now, we open three sockets -- one for the audio stream, one for the timing and one for the
// control
- *lsport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &audio_socket);
- *lcport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &control_socket);
- *ltport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &timing_socket);
+ *lsport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
+ &conn->audio_socket);
+ *lcport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
+ &conn->control_socket);
+ *ltport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
+ &conn->timing_socket);
debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport,
*ltport);
- reference_timestamp = 0;
+ conn->reference_timestamp = 0;
// pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
// pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
// pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
- running = 1;
- request_sent = 0;
+ conn->rtp_running = 1;
+ conn->request_sent = 0;
}
void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
- uint64_t *remote_timestamp_time) {
+ uint64_t *remote_timestamp_time, rtsp_conn_info *conn) {
// types okay
- pthread_mutex_lock(&reference_time_mutex);
- *timestamp = reference_timestamp;
- *timestamp_time = reference_timestamp_time;
- *remote_timestamp_time = remote_reference_timestamp_time;
- pthread_mutex_unlock(&reference_time_mutex);
+ pthread_mutex_lock(&conn->reference_time_mutex);
+ *timestamp = conn->reference_timestamp;
+ *timestamp_time = conn->reference_timestamp_time;
+ *remote_timestamp_time = conn->remote_reference_timestamp_time;
+ pthread_mutex_unlock(&conn->reference_time_mutex);
}
-void clear_reference_timestamp(void) {
- pthread_mutex_lock(&reference_time_mutex);
- reference_timestamp = 0;
- reference_timestamp_time = 0;
- pthread_mutex_unlock(&reference_time_mutex);
+void clear_reference_timestamp(rtsp_conn_info *conn) {
+ pthread_mutex_lock(&conn->reference_time_mutex);
+ conn->reference_timestamp = 0;
+ conn->reference_timestamp_time = 0;
+ pthread_mutex_unlock(&conn->reference_time_mutex);
}
-void rtp_shutdown(void) {
- if (!running)
+void rtp_shutdown(rtsp_conn_info *conn) {
+ if (!conn->rtp_running)
debug(1, "rtp_shutdown called without active stream!");
debug(2, "shutting down RTP thread");
- clear_reference_timestamp();
+ clear_reference_timestamp(conn);
// debug(1,"Shut down audio, control and timing threads");
// usleep(3000000); // hack
// pthread_kill(rtp_audio_thread, SIGUSR1);
@@ -761,11 +738,11 @@ void rtp_shutdown(void) {
// pthread_join(rtp_audio_thread, &retval);
// pthread_join(rtp_control_thread, &retval);
// pthread_join(rtp_timing_thread, &retval);
- running = 0;
+ conn->rtp_running = 0;
}
-void rtp_request_resend(seq_t first, uint32_t count) {
- if (running) {
+void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
+ if (conn->rtp_running) {
// if (!request_sent) {
debug(3, "requesting resend of %d packets starting at %u.", count, first);
// request_sent = 1;
@@ -779,12 +756,12 @@ void rtp_request_resend(seq_t first, uint32_t count) {
*(unsigned short *)(req + 6) = htons(count); // count
socklen_t msgsize = sizeof(struct sockaddr_in);
#ifdef AF_INET6
- if (rtp_client_control_socket.SAFAMILY == AF_INET6) {
+ if (conn->rtp_client_control_socket.SAFAMILY == AF_INET6) {
msgsize = sizeof(struct sockaddr_in6);
}
#endif
- if (sendto(audio_socket, req, sizeof(req), 0, (struct sockaddr *)&rtp_client_control_socket,
- msgsize) == -1) {
+ if (sendto(conn->audio_socket, req, sizeof(req), 0,
+ (struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) {
perror("Error sendto-ing to audio socket");
}
} else {
@@ -795,9 +772,9 @@ void rtp_request_resend(seq_t first, uint32_t count) {
}
}
-void rtp_request_client_pause() {
- if (running) {
- if (client_active_remote == 0) {
+void rtp_request_client_pause(rtsp_conn_info *conn) {
+ if (conn->rtp_running) {
+ if (conn->client_active_remote == 0) {
debug(1, "Can't request a client pause: no valid active remote.");
} else {
// debug(1,"Send a client pause request to %s:3689 with active remote
@@ -814,7 +791,7 @@ void rtp_request_client_pause() {
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
- getaddrinfo(client_ip_string, "3689", &hints, &res);
+ getaddrinfo(conn->client_ip_string, "3689", &hints, &res);
// make a socket:
@@ -834,7 +811,7 @@ void rtp_request_client_pause() {
sprintf(message,
"GET /ctrl-int/1/pause HTTP/1.1\r\nHost: %s:3689\r\nActive-Remote: %u\r\n\r\n",
- client_ip_string, client_active_remote);
+ conn->client_ip_string, conn->client_active_remote);
// debug(1,"Sending this message: \"%s\".",message);
// Send some data
diff --git a/rtp.h b/rtp.h
index f4c8ab0b..e0ebefa9 100644
--- a/rtp.h
+++ b/rtp.h
@@ -5,7 +5,8 @@
#include "player.h"
-typedef struct inter_threads_record { uint32_t please_stop; } inter_threads_record;
+void rtp_initialise(rtsp_conn_info *conn);
+void rtp_terminate(rtsp_conn_info *conn);
void *rtp_audio_receiver(void *arg);
void *rtp_control_receiver(void *arg);
@@ -13,14 +14,14 @@ void *rtp_timing_receiver(void *arg);
void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int controlport, int timingport,
uint32_t active_remote, int *local_server_port, int *local_control_port,
- int *local_timing_port);
-void rtp_shutdown(void);
-void rtp_request_resend(seq_t first, uint32_t count);
-void rtp_request_client_pause(void); // ask the client to pause
+ int *local_timing_port, rtsp_conn_info *conn);
+void rtp_shutdown(rtsp_conn_info *conn);
+void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn);
+void rtp_request_client_pause(rtsp_conn_info *conn); // ask the client to pause
void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
- uint64_t *remote_timestamp_time);
-void clear_reference_timestamp(void);
+ uint64_t *remote_timestamp_time, rtsp_conn_info *conn);
+void clear_reference_timestamp(rtsp_conn_info *conn);
uint64_t static local_to_remote_time_jitters;
uint64_t static local_to_remote_time_jitters_count;
diff --git a/rtsp.c b/rtsp.c
index 59594019..c4c2dd72 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -69,6 +69,8 @@
#define INETx_ADDRSTRLEN INET_ADDRSTRLEN
#endif
+#define METADATA_SNDBUF (4 * 1024 * 1024)
+
enum rtsp_read_request_response {
rtsp_read_request_response_ok,
rtsp_read_request_response_shutdown_requested,
@@ -90,17 +92,6 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
// static int please_shutdown = 0;
// static pthread_t playing_thread = 0;
-typedef struct {
- int fd;
- int authorized; // set is a password is required and has been supplied
- stream_cfg stream;
- SOCKADDR remote, local;
- int stop;
- int running;
- pthread_t thread;
- pthread_t player_thread;
-} rtsp_conn_info;
-
static rtsp_conn_info *playing_conn =
NULL; // the data structure representing the connection that has the player.
static rtsp_conn_info **conns = NULL;
@@ -296,9 +287,10 @@ static void cleanup_threads(void) {
// debug(2, "culling threads.");
for (i = 0; i < nconns;) {
if (conns[i]->running == 0) {
+ debug(1, "found a non-running thread");
pthread_join(conns[i]->thread, &retval);
free(conns[i]);
- debug(3, "one thread joined...");
+ debug(1, "one thread joined...");
nconns--;
if (nconns)
conns[i] = conns[nconns];
@@ -313,7 +305,7 @@ static void cleanup_threads(void) {
void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread) {
int i;
- debug(2, "asking playing threads to stop");
+ debug(1, "asking playing threads to stop");
for (i = 0; i < nconns; i++) {
if (((except_this_thread == 0) || (pthread_equal(conns[i]->thread, except_this_thread) == 0)) &&
(conns[i]->running != 0)) {
@@ -654,7 +646,7 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1
rtptime--;
// debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime);
- player_flush(rtptime);
+ player_flush(rtptime, conn);
}
}
}
@@ -673,6 +665,7 @@ static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
"it's sending a response to teardown anyway");
resp->respcode = 200;
msg_add_header(resp, "Connection", "close");
+ debug(2, "TEARDOWN asking connection to stop");
conn->stop = 1;
}
@@ -680,7 +673,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
if (!rtsp_playing())
debug(1, "This RTSP conversation thread doesn't think it's playing, but "
"it's sending a response to flush anyway");
- char *p;
+ char *p = NULL;
uint32_t rtptime = 0;
char *hdr = msg_get_header(req, "RTP-Info");
@@ -694,8 +687,14 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1
}
}
- // debug(1,"RTSP Flush Requested: %u.",rtptime);
- player_flush(rtptime);
+// debug(1,"RTSP Flush Requested: %u.",rtptime);
+#ifdef CONFIG_METADATA
+ if (p)
+ send_metadata('ssnc', 'flsr', p, strlen(p), req, 1);
+ else
+ send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
+#endif
+ player_flush(rtptime, conn);
resp->respcode = 200;
}
@@ -800,7 +799,8 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
tport = atoi(p);
// rtsp_take_player();
- rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port);
+ rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, <port,
+ conn);
if (!lsport)
goto error;
char *q;
@@ -819,7 +819,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
strcat(hdr, q); // should unsplice the timing port entry
}
- player_play(&conn->stream, &conn->player_thread); // the thread better be 0
+ player_play(&conn->player_thread, conn); // the thread better be 0
char *resphdr = alloca(200);
*resphdr = 0;
@@ -857,20 +857,8 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r
if (!strncmp(cp, "volume: ", 8)) {
float volume = atof(cp + 8);
- if (config.ignore_volume_control == 0) {
- debug(2, "volume: %f\n", volume);
- player_volume(volume);
- }
-#ifdef CONFIG_METADATA
- else { // if ignore volume is on...
- char *dv = malloc(128); // will be freed in the metadata thread
- if (dv) {
- memset(dv, 0, 128);
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", volume, 0.0, 0.0, 0.0);
- send_ssnc_metadata('pvol', dv, strlen(dv), 1);
- }
- }
-#endif
+ // debug(1, "AirPlay request to set volume to: %f\n", volume);
+ player_volume(volume, conn);
} else
#ifdef CONFIG_METADATA
if (!strncmp(cp, "progress: ", 10)) {
@@ -890,8 +878,7 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r
#ifdef CONFIG_METADATA
// Metadata is not used by shairport-sync.
// Instead we send all metadata to a fifo pipe, so that other apps can listen to
-// the pipe and use
-// the metadata.
+// the pipe and use the metadata.
// We use two 4-character codes to identify each piece of data and we send the
// data itself, if any,
@@ -957,6 +944,11 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *r
// `clip` -- the payload is the IP number of the client, i.e. the sender of audio.
// Can be an IPv4 or an IPv6 number.
+// A special sub-protocol is used for sending large data items over UDP
+// If the payload exceeded 4 MB, it is chunked using the following format:
+// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
+// Notice that the number of items is different to the standard
+
// including a simple base64 encoder to minimise malloc/free activity
// From Stack Overflow, with thanks:
@@ -1034,6 +1026,8 @@ void metadata_create(void) {
if (metadata_sock < 0) {
debug(1, "Could not open metadata socket");
} else {
+ int buffer_size = METADATA_SNDBUF;
+ setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr));
metadata_sockaddr.sin_family = AF_INET;
metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr);
@@ -1094,6 +1088,47 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
memcpy(ptr, data, length);
sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr,
sizeof(metadata_sockaddr));
+ } else if (metadata_sock >= 0) {
+ // send metadata in numbered chunks using the protocol:
+ // ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data)
+
+ uint32_t chunk_ix = 0;
+ uint32_t chunk_total = length / (config.metadata_sockmsglength - 24);
+ if (chunk_total * (config.metadata_sockmsglength - 24) < length) {
+ chunk_total++;
+ }
+ uint32_t remaining = length;
+ uint32_t v;
+ char *data_crsr = data;
+ do {
+ char *ptr = metadata_sockmsg;
+ memcpy(ptr, "ssncchnk", 8);
+ ptr += 8;
+ v = htonl(chunk_ix);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(chunk_total);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(type);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(code);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ uint32_t datalen = remaining;
+ if (datalen > config.metadata_sockmsglength - 24) {
+ datalen = config.metadata_sockmsglength - 24;
+ }
+ memcpy(ptr, data_crsr, datalen);
+ data_crsr += datalen;
+ sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0,
+ (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr));
+ chunk_ix++;
+ remaining -= datalen;
+ if (remaining == 0)
+ break;
+ } while (1);
}
// readers may go away and come back
@@ -1296,7 +1331,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m
// not all items have RTP-time stuff in them, which is okay
if (!strncmp(ct, "application/x-dmap-tagged", 25)) {
- debug(2, "received metadata tags in SET_PARAMETER request.");
+ debug(3, "received metadata tags in SET_PARAMETER request.");
if (p == NULL)
debug(1, "Missing RTP-Time info for metadata");
if (p)
@@ -1364,17 +1399,25 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
if (config.allow_session_interruption == 1) {
// some other thread has the player ... ask it to relinquish the thread
if (playing_conn) {
- playing_conn->stop = 1;
- memory_barrier();
- pthread_kill(playing_conn->thread, SIGUSR1);
+ debug(1, "Playing connection asked to stop");
+ if (playing_conn == conn) {
+ debug(1, "ANNOUNCE asking to stop itself.");
+ } else {
+ playing_conn->stop = 1;
+ memory_barrier();
+ pthread_kill(playing_conn->thread, SIGUSR1);
+ usleep(
+ 1000000); // here, it is possible for other connections to come in and nab the player.
+ }
} else {
die("Non existent the_playing_conn with play_lock enabled.");
}
- usleep(1000000); // here, it is possible for other connections to come in and nab the player.
debug(1, "Try to get the player now");
// pthread_mutex_lock(&play_lock);
if (pthread_mutex_trylock(&play_lock) == 0)
have_the_player = 1;
+ else
+ debug(1, "ANNOUNCE failed to get the player");
}
}
@@ -1452,6 +1495,9 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
int i;
for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t"));
+ // here we should check the sanity ot the fmtp values
+ // for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
+ // debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]);
char *hdr = msg_get_header(req, "X-Apple-Client-Name");
if (hdr) {
@@ -1717,6 +1763,8 @@ static void *rtsp_conversation_thread_func(void *pconn) {
rtsp_conn_info *conn = pconn;
+ rtp_initialise(conn);
+
rtsp_message *req, *resp;
char *hdr, *auth_nonce = NULL;
@@ -1764,8 +1812,8 @@ static void *rtsp_conversation_thread_func(void *pconn) {
debug(1, "Closing down RTSP conversation thread...");
if (rtsp_playing()) {
- player_stop(&conn->player_thread); // might be less noisy doing this first
- rtp_shutdown();
+ player_stop(&conn->player_thread, conn); // might be less noisy doing this first
+ rtp_shutdown(conn);
// usleep(400000); // let an angel pass...
pthread_mutex_unlock(&play_lock);
}
@@ -1780,6 +1828,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
// debug(1, "This RTSP conversation thread doesn't think it's playing for a "
// "close RTSP connection.");
// }
+ rtp_terminate(conn);
debug(2, "RTSP conversation thread terminated.");
// please_shutdown = 0;
return NULL;
@@ -1925,6 +1974,8 @@ void rtsp_listen_loop(void) {
continue;
rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info));
+ if (conn == 0)
+ die("Couldn't allocate memory for an rtsp_conn_info record.");
memset(conn, 0, sizeof(rtsp_conn_info));
socklen_t slen = sizeof(conn->remote);
@@ -1982,17 +2033,16 @@ void rtsp_listen_loop(void) {
} else {
debug(1, "Error figuring out Shairport Sync's own IP number.");
}
-
- usleep(500000);
- pthread_t rtsp_conversation_thread;
- ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn);
+ // usleep(500000);
+ // pthread_t rtsp_conversation_thread;
+ // conn->thread = rtsp_conversation_thread;
+ // conn->stop = 0; // record's memory has been zeroed
+ // conn->authorized = 0; // record's memory has been zeroed
+ conn->running = 1;
+ ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func,
+ conn); // also acts as a memory barrier
if (ret)
die("Failed to create RTSP receiver thread!");
-
- conn->thread = rtsp_conversation_thread;
- conn->stop = 0;
- conn->authorized = 0;
- conn->running = 1;
track_thread(conn);
}
}
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index d19e85f3..1d664365 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -18,21 +18,60 @@ 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 (only three are 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.
// statistics = "no"; // set to "yes" to print statistics in the log
// drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it
// resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it
// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose.
+
// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to.
// volume_range_db = 60 ; // use this advanced setting to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range.
// volume_max_db = 0.0 ; // use this advanced setting, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use.
// The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer).
// Leave it commented out to use mixer's maximum volume.
+// 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".
+
// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose
// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple.
// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically.
+
+// 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_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.
+};
+
+
+dsp =
+{
+
+//////////////////////////////////////////
+// This convolution filter can be used to apply almost any correction to the audio signal, like frequency and phase correction.
+// For example you could measure (with a good microphone and a sweep-sine) the frequency response of your speakers + room,
+// and apply a correction to get a flat response curve.
+//////////////////////////////////////////
+//
+// convolution = "yes"; // Activate the convolution filter.
+// convolution_ir_file = "impulse.wav"; // Impulse Response file to be convolved to the audio stream
+// convolution_gain = -4.0; // Static gain applied to prevent clipping during the convolution process
+// convolution_max_length = 44100; // Truncate the input file to this length in order to save CPU.
+
+
+//////////////////////////////////////////
+// This loudness filter is used to compensate for human ear non linearity.
+// When the volume decreases, our ears loose more sentisitivity in the low range frequencies than in the mid range ones.
+// This filter aims at compensating for this loss, applying a variable gain to low frequencies depending on the volume.
+// More info can be found here: https://en.wikipedia.org/wiki/Equal-loudness_contour
+// For this filter to work properly, you should disable (or set to a fix value) all other volume control and only let shairport-sync control your volume.
+// The setting "loudness_reference_volume_db" should be set at the volume reported by shairport-sync when listening to music at a normal listening volume.
+//////////////////////////////////////////
+//
+// loudness = "yes"; // Activate the filter
+// loudness_reference_volume_db = -20.0; // Above this level the filter will have no effect anymore. Below this level it will gradually boost the low frequencies.
+
};
// How to deal with metadata, including artwork
@@ -59,7 +98,7 @@ sessioncontrol =
// Back End Settings
-// These are parameters for the "alsa" audio back end, the only back end that supports synchronised audio.
+// These are parameters for the "alsa" audio back end.
alsa =
{
// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
@@ -67,36 +106,38 @@ alsa =
// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this.
// output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability.
// output_format = "S16"; // can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
-// audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
-// audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying.
// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no".
// period_size = ; // Use this optional advanced setting to set the alsa period size near to this value
// buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value
// use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes"
+// mute_using_playback_switch = "yes"; // Use this optional advanced setting to control whether the snd_mixer_selem_set_playback_switch_all call can be used for muting. Default is yes.
};
-// These are parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done.
+// Parameters for the "sndio" audio back end. All are optional.
+sndio =
+{
+// device = "snd/0"; // optional setting to set the name of the output device. Default is the sndio system default.
+// rate = 44100; // optional setting which can be 44100, 88200, 176400 or 352800, but the device must have the capability. Default is 44100.
+// format = "S16"; // optional setting which can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
+// round = ; // advanced optional setting to set the period size near to this value
+// bufsz = ; // advanced optional setting to set the buffer size near to this value
+};
+
+// Parameters for the "pa" PulseAudio backend.
+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 "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
-// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
-// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length.
};
-// These are parameters for the "stdout" audio back end, a back end that directs raw CD-style audio output to stdout. No interpolation is done.
-stdout =
-{
-// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
-// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length.
-};
+// These are no configuration file parameters for the "stdout" audio back end. No interpolation is done.
-// These are parameters for the "ao" audio back end. No interpolation is done.
-ao =
-{
-// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
-// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length.
-};
+// These are 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.
-
diff --git a/scripts/shairport-sync.freebsd b/scripts/shairport-sync.freebsd
new file mode 100644
index 00000000..9088bd82
--- /dev/null
+++ b/scripts/shairport-sync.freebsd
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+
+# PROVIDE: shairport_sync
+# REQUIRE: FILESYSTEMS DAEMON hostname avahi_daemon
+
+. /etc/rc.subr
+
+name="shairport_sync"
+rcvar="shairport_sync_enable"
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+
+shairport_sync_start()
+{
+ checkyesno shairport_sync_enable && echo "Starting shairport-sync." && \
+ su -m shairport-sync -c "/usr/local/bin/shairport-sync -d"
+}
+
+shairport_sync_stop()
+{
+ checkyesno shairport_sync_enable && echo "Stopping shairport-sync." && \
+ /usr/local/bin/shairport-sync -k
+}
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/shairport.c b/shairport.c
index c34a54a7..53d0fdcd 100644
--- a/shairport.c
+++ b/shairport.c
@@ -66,6 +66,10 @@
#include
#include
+#ifdef CONFIG_CONVOLUTION
+#include
+#endif
+
static int shutting_down = 0;
static char *appName = NULL;
char configuration_file_path[4096 + 1];
@@ -141,6 +145,9 @@ char *get_version_string() {
#ifdef CONFIG_AO
strcat(version_string, "-ao");
#endif
+#ifdef CONFIG_PA
+ strcat(version_string, "-pa");
+#endif
#ifdef CONFIG_PULSE
strcat(version_string, "-pulse");
#endif
@@ -159,6 +166,9 @@ char *get_version_string() {
#ifdef HAVE_LIBSOXR
strcat(version_string, "-soxr");
#endif
+#ifdef CONFIG_CONVOLUTION
+ strcat(version_string, "-convolution");
+#endif
#ifdef CONFIG_METADATA
strcat(version_string, "-metadata");
#endif
@@ -185,6 +195,7 @@ void usage(char *progname) {
printf("Options:\n");
printf(" -h, --help show this help.\n");
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");
@@ -193,7 +204,7 @@ void usage(char *progname) {
"/etc/shairport-sync.conf.\n");
printf("\n");
- printf("The following general options are for backward compatability. These and all new options "
+ printf("The following general options are for backward compatibility. These and all new options "
"have settings in the configuration file, by default /etc/shairport-sync.conf:\n");
printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n");
printf(" -p, --port=PORT set RTSP listening port.\n");
@@ -243,6 +254,8 @@ void usage(char *progname) {
"88) before trying to correct it.\n");
printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a "
"password.\n");
+ printf(" --logOutputLevel log the output level setting -- useful for setting maximum "
+ "volume.\n");
#ifdef CONFIG_METADATA
printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. "
"--metadata-pipename=/tmp/shairport-sync-metadata.\n");
@@ -265,14 +278,18 @@ 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 */
+ int daemonisewith = 0;
+ int daemonisewithout = 0;
struct poptOption optionsTable[] = {
{"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL},
{"disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL},
{"reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL},
{"kill", 'k', POPT_ARG_NONE, NULL, 0, NULL},
- {"daemon", 'd', POPT_ARG_NONE, &config.daemonise, 0, NULL},
+ {"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL},
+ {"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL},
{"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL},
{"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL},
+ {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL},
{"version", 'V', POPT_ARG_NONE, NULL, 0, NULL},
{"port", 'p', POPT_ARG_INT, &config.port, 0, NULL},
{"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL},
@@ -346,15 +363,25 @@ int parse_options(int argc, char **argv) {
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
}
+ if ((daemonisewith) && (daemonisewithout))
+ die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected "
+ "both!");
+ if ((daemonisewith) || (daemonisewithout)) {
+ config.daemonise = 1;
+ if (daemonisewith)
+ config.daemonise_store_pid = 1;
+ };
+
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
+ config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set
config_setting_t *setting;
const char *str = 0;
int value = 0;
double dvalue = 0.0;
- debug(1, "Looking for the configuration file \"%s\".", config.configfile);
+ // debug(1, "Looking for the configuration file \"%s\".", config.configfile);
config_init(&config_file_stuff);
@@ -371,17 +398,41 @@ int parse_options(int argc, char **argv) {
if (config_lookup_string(config.cfg, "general.name", &str)) {
raw_service_name = (char *)str;
}
-
+ int daemonisewithout = 0;
+ int daemonisewith = 0;
/* Get the Daemonize setting. */
- if (config_lookup_string(config.cfg, "general.daemonize", &str)) {
+ if (config_lookup_string(config.cfg, "sessioncontrol.daemonize_with_pid_file", &str)) {
if (strcasecmp(str, "no") == 0)
- config.daemonise = 0;
+ daemonisewith = 0;
else if (strcasecmp(str, "yes") == 0)
- config.daemonise = 1;
+ daemonisewith = 1;
else
- die("Invalid daemonize option choice \"%s\". It should be \"yes\" or \"no\"");
+ die("Invalid daemonize_with_pid_file option choice \"%s\". It should be \"yes\" or "
+ "\"no\"");
}
+ /* Get the Just_Daemonize setting. */
+ 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\"");
+ }
+ if ((daemonisewith) && (daemonisewithout))
+ die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have "
+ "selected both!");
+ if ((daemonisewith) || (daemonisewithout)) {
+ config.daemonise = 1;
+ if (daemonisewith)
+ config.daemonise_store_pid = 1;
+ }
+ /* Get the directory path for the pid file created when the program is daemonised. */
+ if (config_lookup_string(config.cfg, "sessioncontrol.daemon_pid_dir", &str))
+ config.piddir = (char *)str;
+
/* Get the mdns_backend setting. */
if (config_lookup_string(config.cfg, "general.mdns_backend", &str))
config.mdns_name = (char *)str;
@@ -428,7 +479,7 @@ int parse_options(int argc, char **argv) {
else if (strcasecmp(str, "soxr") == 0)
config.packet_stuffing = ST_soxr;
else
- die("Invalid interpolation option choice \"%s\". It should be \"basic\" or \"soxr\"");
+ die("Invalid interpolation option choice. It should be \"basic\" or \"soxr\"");
}
/* Get the statistics setting. */
@@ -485,11 +536,15 @@ int parse_options(int argc, char **argv) {
/* Get the optional volume_max_db setting. */
if (config_lookup_float(config.cfg, "general.volume_max_db", &dvalue)) {
- debug(1, "Max volume setting of %f dB", dvalue);
+ // debug(1, "Max volume setting of %f dB", dvalue);
config.volume_max_db = dvalue;
config.volume_max_db_set = 1;
}
+ if (config_lookup_string(config.cfg, "general.run_this_when_volume_is_set", &str)) {
+ config.cmd_set_volume = (char *)str;
+ }
+
/* Get the playback_mode setting */
if (config_lookup_string(config.cfg, "general.playback_mode", &str)) {
if (strcasecmp(str, "stereo") == 0)
@@ -645,6 +700,18 @@ int parse_options(int argc, char **argv) {
"\"yes\" or \"no\"");
}
+ if (config_lookup_string(config.cfg, "sessioncontrol.before_play_begins_returns_output",
+ &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.cmd_start_returns_output = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.cmd_start_returns_output = 1;
+ else
+ die("Invalid session control before_play_begins_returns_output option choice \"%s\". It "
+ "should be "
+ "\"yes\" or \"no\"");
+ }
+
if (config_lookup_string(config.cfg, "sessioncontrol.allow_session_interruption", &str)) {
config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0
if (strcasecmp(str, "no") == 0)
@@ -662,6 +729,64 @@ int parse_options(int argc, char **argv) {
config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0
}
+#ifdef CONFIG_CONVOLUTION
+
+ if (config_lookup_string(config.cfg, "dsp.convolution", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.convolution = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.convolution = 1;
+ else
+ die("Invalid dsp.convolution. It should be \"yes\" or \"no\"");
+ }
+
+ if (config_lookup_float(config.cfg, "dsp.convolution_gain", &dvalue)) {
+ config.convolution_gain = dvalue;
+ if (dvalue > 10 || dvalue < -50)
+ die("Invalid value \"%f\" for dsp.convolution_gain. It should be between -50 and +10 dB",
+ dvalue);
+ }
+
+ config.convolution_max_length = 8192;
+ if (config_lookup_int(config.cfg, "dsp.convolution_max_length", &value)) {
+ config.convolution_max_length = value;
+
+ if (value < 1 || value > 200000)
+ die("dsp.convolution_max_length must be within 1 and 200000");
+ }
+
+ if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) {
+ config.convolution_ir_file = str;
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ }
+
+ if (config.convolution && config.convolution_ir_file == NULL) {
+ die("Convolution enabled but no convolution_ir_file provided");
+ }
+#endif
+
+ if (config_lookup_string(config.cfg, "dsp.loudness", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.loudness = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.loudness = 1;
+ else
+ die("Invalid dsp.convolution. It should be \"yes\" or \"no\"");
+ }
+
+ config.loudness_reference_volume_db = -20;
+ if (config_lookup_float(config.cfg, "dsp.loudness_reference_volume_db", &dvalue)) {
+ config.loudness_reference_volume_db = dvalue;
+ if (dvalue > 0 || dvalue < -100)
+ die("Invalid value \"%f\" for dsp.loudness_reference_volume_db. It should be between "
+ "-100 and 0",
+ dvalue);
+ }
+
+ if (config.loudness == 1 && config_lookup_string(config.cfg, "alsa.mixer_control_name", &str))
+ die("Loudness activated but hardware volume is active. You must remove "
+ "\"alsa.mixer_control_name\" to use the loudness filter.");
+
} else {
if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO)
debug(1, "Error reading configuration file \"%s\": \"%s\".",
@@ -809,21 +934,21 @@ void shairport_startup_complete(void) {
}
}
-#ifdef USE_CUSTOM_PID_DIR
-
const char *pid_file_proc(void) {
-#ifdef HAVE_ASPRINTF
- static char *fn = NULL;
- asprintf(&fn, "%s/%s.pid", PIDDIR, daemon_pid_file_ident ? daemon_pid_file_ident : "unknown");
+#ifdef USE_CUSTOM_PID_DIR
+ char *use_this_pid_dir = PIDDIR;
#else
- static char fn[8192];
- snprintf(fn, sizeof(fn), "%s/%s.pid", PIDDIR,
+ char *use_this_pid_dir = "/var/run/shairport-sync";
+#endif
+ // debug(1,"config.piddir \"%s\".",config.piddir);
+ if (config.piddir)
+ use_this_pid_dir = config.piddir;
+ char fn[8192];
+ snprintf(fn, sizeof(fn), "%s/%s.pid", use_this_pid_dir,
daemon_pid_file_ident ? daemon_pid_file_ident : "unknown");
-#endif
-
- return fn;
+ // debug(1,"fn \"%s\".",fn);
+ return strdup(fn);
}
-#endif
void exit_function() {
if (config.cfg)
@@ -866,6 +991,8 @@ int main(int argc, char **argv) {
die("Can not recognise the endianness of the processor.");
strcpy(configuration_file_path, SYSCONFDIR);
+ // strcat(configuration_file_path, "/shairport-sync"); // thinking about adding a special
+ // shairport-sync directory
strcat(configuration_file_path, "/");
strcat(configuration_file_path, appName);
strcat(configuration_file_path, ".conf");
@@ -941,11 +1068,10 @@ int main(int argc, char **argv) {
return 1;
}
-#if USE_CUSTOM_PID_DIR
- debug(1, "Locating custom pid dir at \"%s\"", PIDDIR);
- /* Point to a function to help locate where the PID file will go */
+ // Point to a function to help locate where the PID file will go
+ // We always use this function because the default location
+ // is unsatisfactory. By default we want to use /var/run/shairport-sync/.
daemon_pid_file_proc = pid_file_proc;
-#endif
/* Set indentification string for the daemon for both syslog and PID file */
daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]);
@@ -983,6 +1109,9 @@ int main(int argc, char **argv) {
exit(1);
}
+ // parse arguments into config -- needed to locate pid_dir
+ int audio_arg = parse_options(argc, argv);
+
/* Check if we are called with -k or --kill parameter */
if (argc >= 2 && ((strcmp(argv[1], "-k") == 0) || (strcmp(argv[1], "--kill") == 0))) {
int ret;
@@ -996,15 +1125,12 @@ int main(int argc, char **argv) {
return ret < 0 ? 1 : 0;
}
- /* Check that the daemon is not running twice at the same time */
- if ((pid = daemon_pid_file_is_running()) >= 0) {
+ /* 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;
}
- // parse arguments into config
- 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)");
@@ -1052,11 +1178,13 @@ int main(int argc, char **argv) {
goto finish;
}
- /* Create the PID file */
- if (daemon_pid_file_create() < 0) {
- daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno));
- daemon_retval_send(2);
- goto finish;
+ /* Create the PID file if required */
+ if (config.daemonise_store_pid) {
+ if (daemon_pid_file_create() < 0) {
+ daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno));
+ daemon_retval_send(2);
+ goto finish;
+ }
}
/* Send OK to parent process */
@@ -1159,17 +1287,18 @@ int main(int argc, char **argv) {
}
/* Print out options */
-
debug(1, "statistics_requester status is %d.", config.statistics_requested);
debug(1, "daemon status is %d.", config.daemonise);
+ debug(1, "deamon pid file is \"%s\".", pid_file_proc());
debug(1, "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, "Shairport Sync player name is \"%s\".", config.service_name);
- debug(1, "Audio Output name is \"%s\".", config.output_name);
+ 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, "wait-cmd status is %d.", config.cmd_blocking);
+ debug(1, "on-start returns output is %d.", config.cmd_start_returns_output);
debug(1, "mdns backend \"%s\".", config.mdns_name);
debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency);
debug(2, "AirPlayLatency is %d.", config.AirPlayLatency);
@@ -1197,11 +1326,14 @@ int main(int argc, char **argv) {
debug(1, "audio backend desired buffer length is %f seconds.",
config.audio_backend_buffer_desired_length);
debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset);
+ debug(1, "audio backend silence lead-in time is %f seconds. A value -1.0 means use the default.",
+ config.audio_backend_silent_lead_in_time);
debug(1, "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);
+ debug(1, "alsa_use_playback_switch_for_mute is %d.", config.alsa_use_playback_switch_for_mute);
if (config.interface)
debug(1, "mdns service interface \"%s\" requested.", config.interface);
else
@@ -1215,7 +1347,7 @@ int main(int argc, char **argv) {
debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile);
}
#ifdef CONFIG_METADATA
- debug(1, "metdata enabled is %d.", config.metadata_enabled);
+ debug(1, "metadata enabled is %d.", config.metadata_enabled);
debug(1, "metadata pipename is \"%s\".", config.metadata_pipename);
debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr,
config.metadata_sockport);
@@ -1223,6 +1355,15 @@ int main(int argc, char **argv) {
debug(1, "get-coverart is %d.", config.get_coverart);
#endif
+#ifdef CONFIG_CONVOLUTION
+ debug(1, "convolution is %d.", config.convolution);
+ debug(1, "convolution IR file is \"%s\"", config.convolution_ir_file);
+ debug(1, "convolution max length %d", config.convolution_max_length);
+ debug(1, "convolution gain is %f", config.convolution_gain);
+#endif
+ debug(1, "loudness is %d.", config.loudness);
+ debug(1, "loudness reference level is %f", config.loudness_reference_volume_db);
+
uint8_t ap_md5[16];
#ifdef HAVE_LIBSSL