From f30dd7aee69a2dc2f4bef9496995212503c45c80 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 18 May 2026 09:57:12 +0100 Subject: [PATCH] =?UTF-8?q?Enhancements=20=20=20Enable=20the=20AirPlay=202?= =?UTF-8?q?=20build=20to=20operate=20an=20optional=20classic-AirPlay-only?= =?UTF-8?q?=20service=20or=20to=20gracefully=20degrade=20to=20classic=20Ai?= =?UTF-8?q?rPlay,=20as=20follows:=20=20=201.=20Add=20a=20new=20command-lin?= =?UTF-8?q?e=20option=20'-=E2=80=94service-type=3D'=20and=20an=20equ?= =?UTF-8?q?ivalent=20configuration=20entry=20'service=5Ftype=20=3D=20""'=20in=20the=20'general'=20section=20of=20the=20configuratio?= =?UTF-8?q?n=20file,=20=20=20=20=20=20where=20=20can=20be=20"auto",?= =?UTF-8?q?=20"classic"=20or=20"airplay2":=20=20=20=20=20=20=201.=20"auto"?= =?UTF-8?q?=20(default)=20means=20that=20the=20service=20will=20be=20AirPl?= =?UTF-8?q?ay=202=20if=20NQPTP=20is=20running.=20If=20NQPTP=20is=20not=20r?= =?UTF-8?q?unning,=20classic=20AirPlay=20service=20will=20be=20provided=20?= =?UTF-8?q?instead,=20and=20in=20that=20case,=20=20=20=20=20=20=20"(Classi?= =?UTF-8?q?c)"=20will=20be=20appended=20to=20the=20default=20AirPlay=20ser?= =?UTF-8?q?vice=20name=20visible=20to=20AirPlay=20clients=20like=20Apple?= =?UTF-8?q?=20Music,=20for=20example=20"RaspberryPi3B=20(Classic)".=20=20?= =?UTF-8?q?=20=20=20=20=202.=20"classic"=20means=20the=20service=20will=20?= =?UTF-8?q?be=20classic=20AirPlay=20(aka=20AirPlay=201).=20=20=20=20=20=20?= =?UTF-8?q?=203.=20"airplay2"=20means=20the=20service=20will=20be=20the=20?= =?UTF-8?q?modern=20AirPlay=202.=20In=20this=20case,=20as=20distinct=20fro?= =?UTF-8?q?m=20"auto",=20if=20NQPTP=20is=20not=20running,=20Shairport=20Sy?= =?UTF-8?q?nc=20will=20log=20an=20error=20and=20terminate.=20=20=202.=20In?= =?UTF-8?q?=20the=20systemd=20service=20file,=20NQPTP=20is=20now=20a=20"Wa?= =?UTF-8?q?nt"=20rather=20than=20a=20"Require".=20If=20it's=20present,=20t?= =?UTF-8?q?hen=20it=20will=20be=20launched=20before=20Shairport=20Sync.=20?= =?UTF-8?q?If=20it's=20absent,=20Shairport=20Sync=20will=20launch=20anyway?= =?UTF-8?q?.=20=20=203.=20Improve=20the=20delivery=20of=20input=20format?= =?UTF-8?q?=20changes=20and=20emit=20'sdsc'=20metadata=20when=20changes=20?= =?UTF-8?q?occur.=20=20=204.=20Emit=20new=20format=20information=20in=20th?= =?UTF-8?q?e=20log=20if=20statistics=20is=20enabled.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker Changes 1. Support for linux/arm/v6 has been dropped, as Docker is no longer supported. 2. NQPTP is not started in the AirPlay 2 Docker image if '--service-type=classic' or '--service-type=airplay1' is in the command line options at the end of the docker run command. The purpose is to ensure that ports 319 and 320 are left alone when the AirPlay 2 image is set to provide Classic service only. Note that setting the configuration file 'service_type' to 'classic' will not prevent NQPTP from starting up -- you must use the command line option. 3. A new "dev" target has been added. It is a large image containing the custom-built FFmpeg library, NQPTP, Avahi and D-Bus along with the Shairport Sync source and all necessary development tools. When started, Avahi, D-Bus and NQPTP are all installed and running. The bash shell has also been added and is entered. Stability Improvements Reorganise session preemption to fully terminate the existing session before starting a new one. Don't delay closing the event port to wait for it to be closed at the client end. Add a safe_socket_close() function to ensure sockets are fully closed. Use -1 to designate closed rather than 0, to prevent attempts to reclose sockets, causing mayhem. Re-order FFmpeg decommissioning during teardown. --- .github/workflows/docker-vars.yaml | 2 +- ADVANCED TOPICS/PulseAudioAndPipeWire.md | 30 +- FFTConvolver/AudioFFT.cpp | 1600 ++++++++++------------ FFTConvolver/AudioFFT.h | 248 ++-- FFTConvolver/ConvolverThreadPool.cpp | 7 +- FFTConvolver/ConvolverThreadPool.h | 2 +- FFTConvolver/FFTConvolver.cpp | 138 +- FFTConvolver/FFTConvolver.h | 97 +- FFTConvolver/Utilities.cpp | 78 +- FFTConvolver/Utilities.h | 300 ++-- FFTConvolver/convolver.cpp | 22 +- FFTConvolver/convolver.h | 20 +- Makefile.am | 14 +- alac.c | 16 +- ap2_buffered_audio_processor.c | 123 +- ap2_event_message_handler.c | 17 +- ap2_event_message_handler.h | 5 +- ap2_event_receiver.c | 13 +- audio_alsa.c | 20 +- audio_pa.c | 3 +- audio_pipe.c | 7 +- bonjour_strings.c | 118 +- common.c | 146 +- common.h | 34 +- configure.ac | 7 +- dacp.c | 23 +- dbus-service.c | 56 +- definitions.h | 1 - docker/Dockerfile | 27 + docker/devrun.sh | 36 + docker/run.sh | 18 +- man/shairport-sync.1.xml | 38 +- mdns_avahi.c | 7 +- metadata/core.c | 37 +- metadata/core.h | 3 +- metadata/hub.c | 19 +- metadata/hub.h | 6 +- metadata/multicast.c | 37 +- metadata/multicast.h | 7 +- metadata/pc_queue.c | 3 +- metadata/pipe.c | 29 +- metadata/pipe.h | 7 +- mqtt.c | 45 +- mqtt.h | 5 +- player.c | 225 +-- player.h | 3 +- ptp-utilities.c | 3 +- rtp.c | 27 +- rtsp.c | 636 +++++---- rtsp.h | 21 +- scripts/shairport-sync.conf | 7 +- scripts/shairport-sync.service.in | 1 + shairport.c | 676 ++++----- tinysvcmdns.c | 13 +- utilities/buffered_read.c | 15 +- utilities/buffered_read.h | 10 +- utilities/debug.c | 110 +- utilities/debug.h | 17 +- utilities/generate_device_uuid.c | 23 +- utilities/generate_random_uuid.c | 7 +- utilities/mod23.c | 9 +- utilities/mod23.h | 7 +- utilities/network_utilities.c | 47 +- utilities/network_utilities.h | 10 +- utilities/rtsp_message_utilities.c | 53 +- utilities/rtsp_message_utilities.h | 10 +- utilities/string_utilities.c | 194 +++ utilities/string_utilities.h | 8 + utilities/structured_buffer.c | 10 +- 69 files changed, 2897 insertions(+), 2716 deletions(-) mode change 100755 => 100644 FFTConvolver/AudioFFT.cpp mode change 100755 => 100644 FFTConvolver/AudioFFT.h mode change 100755 => 100644 FFTConvolver/FFTConvolver.cpp mode change 100755 => 100644 FFTConvolver/FFTConvolver.h create mode 100755 docker/devrun.sh create mode 100644 utilities/string_utilities.c create mode 100644 utilities/string_utilities.h diff --git a/.github/workflows/docker-vars.yaml b/.github/workflows/docker-vars.yaml index c9f9f8fb..614f70ef 100644 --- a/.github/workflows/docker-vars.yaml +++ b/.github/workflows/docker-vars.yaml @@ -23,7 +23,7 @@ jobs: outputs: nqptp_branch: ${{ env.NQPTP_BRANCH }} push_docker_image: ${{ env.PUSH_DOCKER_IMAGE }} - docker_platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 + docker_platforms: linux/386,linux/amd64,linux/arm/v7,linux/arm64 steps: - name: Push docker image if this is a tag. if: github.ref_type == 'tag' diff --git a/ADVANCED TOPICS/PulseAudioAndPipeWire.md b/ADVANCED TOPICS/PulseAudioAndPipeWire.md index 9ba50812..e0c99e68 100644 --- a/ADVANCED TOPICS/PulseAudioAndPipeWire.md +++ b/ADVANCED TOPICS/PulseAudioAndPipeWire.md @@ -1,6 +1,9 @@ # Working with PulseAudio or PipeWire Many Linux systems, especially desktop Linuxes with a GUI, have [PipeWire](https://pipewire.org) or [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) installed as [sound servers](https://en.wikipedia.org/wiki/Sound_server). -PipeWire and PulseAudio are widely used and have the great advantage of being easily able to mix audio from multiple sources. The slight downside is that audio may be further processed (e.g. transcoded) on its way to the output device. +PipeWire and PulseAudio are widely used and have the great advantage of being easily able to mix audio from multiple sources. + +However, the main thing to remember about PipeWire and PulseAudio sound servers is they only become available when a user logs in -- that is, they are set up as _user services_. +Shairport Sync relies on them and therefore it must also be set up as a user service. Shairport Sync can not be set up as a system service because the PipeWire or PulseAudio services are not available when system services are launched just after system startup. To use PipeWire or PulseAudio-based systems, Shairport Sync must be set up as a user service. @@ -10,26 +13,5 @@ To use PipeWire or PulseAudio-based systems, Shairport Sync must be set up as a 3. To check if PipeWire support is built into Shairport Sync, check that the string `PipeWire` is included in the version string. (Enter `$ shairport-sync -V` to get the version string.) Similarly, the version string will include `PulseAudio` if the PulseAudio backend is built in. 4. Remember to specify which backend Shairport Sync should use in the configuration file or on the command line. -## Automatic Startup of Shairport Sync - -The main thing to remember about PipeWire and PulseAudio sound servers is that the services they offer only become available when a user logs in -- that is, they are set up as _user services_. -Shairport Sync relies on them, so it must also be set up as a user service; it can not be set up as a system service because the PipeWire or PulseAudio services needed by Shairport Sync are not available when system services are launched just after system startup. - -### Starting Shairport Sync as a User Service -To make Shairport Sync start as a user service, (assuming you built and installed Shairport Sync using the BUILD.md guide), ensure you are logged in as the appropriate user and enter: -``` -$ systemctl --user enable shairport-sync -``` -Make sure it is _not enabled_ as a system service: -``` -# systemctl disable shairport-sync -``` -and then reboot. - -#### Problems with User Service -Shairport Sync will function perfectly well as a user service, but there are a number of things to bear in mind: - -1. The AirPlay service will only be available when the user is logged in. When the user logs out, Shairport Sync will terminate and the AirPlay service will disappear. -2. If the Linux system is a desktop Linux with a GUI, audio will be sent to the default output only when the user is logged in through the GUI. - -Automatic user login may help address these problems. +## Starting Shairport Sync as a User Service +Please refer to [this](https://github.com/mikebrady/shairport-sync/blob/development/BUILD.md#5-enable-and-start-service) section to discover how to start Shairport Sync as a user service. diff --git a/FFTConvolver/AudioFFT.cpp b/FFTConvolver/AudioFFT.cpp old mode 100755 new mode 100644 index 4f013d05..768e8e0f --- a/FFTConvolver/AudioFFT.cpp +++ b/FFTConvolver/AudioFFT.cpp @@ -25,317 +25,258 @@ #include #include - #if defined(AUDIOFFT_APPLE_ACCELERATE) - #define AUDIOFFT_APPLE_ACCELERATE_USED - #include - #include -#elif defined (AUDIOFFT_FFTW3) - #define AUDIOFFT_FFTW3_USED - #include +#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 +#if !defined(AUDIOFFT_OOURA) +#define AUDIOFFT_OOURA +#endif +#define AUDIOFFT_OOURA_USED +#include #endif +namespace audiofft { -namespace audiofft -{ +namespace detail { - namespace detail - { +class AudioFFTImpl { +public: + AudioFFTImpl() = default; + AudioFFTImpl(const AudioFFTImpl &) = delete; + AudioFFTImpl &operator=(const AudioFFTImpl &) = delete; + 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; +}; - class AudioFFTImpl - { - public: - AudioFFTImpl() = default; - AudioFFTImpl(const AudioFFTImpl&) = delete; - AudioFFTImpl& operator=(const AudioFFTImpl&) = delete; - 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; - }; +constexpr 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 < len; ++i) { + dest[i] = static_cast(src[i]); + } +} - constexpr bool IsPowerOf2(size_t val) - { - return (val == 1 || (val & (val-1)) == 0); - } +template +void ScaleBuffer(TypeDest *dest, const TypeSrc *src, const TypeFactor factor, size_t len) { + for (size_t i = 0; i < len; ++i) { + dest[i] = static_cast(static_cast(src[i]) * factor); + } +} +} // End of namespace detail - 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); - } - } - - } // End of namespace detail - - - // ================================================================ - +// ================================================================ #ifdef AUDIOFFT_OOURA_USED - /** - * @internal - * @class OouraFFT - * @brief FFT implementation based on the great radix-4 routines by Takuya Ooura - */ - class OouraFFT : public detail::AudioFFTImpl - { - public: - OouraFFT() : - detail::AudioFFTImpl(), - _size(0), - _ip(), - _w(), - _buffer() - { +/** + * @internal + * @class OouraFFT + * @brief FFT implementation based on the great radix-4 routines by Takuya Ooura + */ +class OouraFFT : public detail::AudioFFTImpl { +public: + OouraFFT() : detail::AudioFFTImpl(), _size(0), _ip(), _w(), _buffer() {} + + OouraFFT(const OouraFFT &) = delete; + OouraFFT &operator=(const OouraFFT &) = delete; + + 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); } + } - OouraFFT(const OouraFFT&) = delete; - OouraFFT& operator=(const OouraFFT&) = delete; + virtual void fft(const float *data, float *re, float *im) override { + // Convert into the format as required by the Ooura FFT + detail::ConvertBuffer(_buffer.data(), data, _size); - virtual void init(size_t size) override + rdft(static_cast(_size), +1, _buffer.data(), _ip.data(), _w.data()); + + // Convert back to split-complex { - 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); + double *b = _buffer.data(); + 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 fft(const float* data, float* re, float* im) override + virtual void ifft(float *data, const float *re, const float *im) override { + // Convert into the format as required by the Ooura FFT { - // Convert into the format as required by the Ooura FFT - detail::ConvertBuffer(_buffer.data(), data, _size); - - rdft(static_cast(_size), +1, _buffer.data(), _ip.data(), _w.data()); - - // Convert back to split-complex - { - double* b = _buffer.data(); - double* bEnd = b + _size; - float *r = re; - float *i = im; - while (b != bEnd) - { - *(r++) = static_cast(*(b++)); - *(i++) = static_cast(-(*(b++))); - } + double *b = _buffer.data(); + double *bEnd = b + _size; + const float *r = re; + const float *i = im; + while (b != bEnd) { + *(b++) = static_cast(*(r++)); + *(b++) = -static_cast(*(i++)); } - const size_t size2 = _size / 2; - re[size2] = -im[0]; - im[0] = 0.0; - im[size2] = 0.0; + _buffer[1] = re[_size / 2]; } - 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.data(); - 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 + detail::ScaleBuffer(data, _buffer.data(), 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); } - - rdft(static_cast(_size), -1, _buffer.data(), _ip.data(), _w.data()); - - // Convert back to split-complex - detail::ScaleBuffer(data, _buffer.data(), 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); - } + 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 -------- */ - /* -------- initializing routines -------- */ + void makewt(int nw, int *ip, double *w) { + int j, nwh; + double delta, x, y; - 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); + 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; - 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); - } + 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 -------- */ - /* -------- child routines -------- */ + void bitrv2(int n, int *ip, double *a) { + int j, j1, k, k1, l, m, m2; + double xr, xi, yr, yi; - - 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; + 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; } - 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; + 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]; @@ -345,247 +286,59 @@ namespace audiofft 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; - } + 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; - 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; - } + l = 2; + if (n > 8) { + cft1st(n, a, w); + l = 8; + while ((l << 2) < n) { + cftmdl(n, l, a, w); + l <<= 2; } } - - - 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; + if ((l << 2) == n) { for (j = 0; j < l; j += 2) { j1 = j + l; j2 = j1 + l; @@ -607,8 +360,235 @@ namespace audiofft a[j3] = x1r + x3i; a[j3 + 1] = x1i - x3r; } - wk1r = w[2]; - for (j = m; j < l + m; j += 2) { + } 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; @@ -622,420 +602,308 @@ namespace audiofft 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 -= 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 - x0i); - a[j1 + 1] = wk1r * (x0r + x0i); - x0r = x3i + x1r; - x0i = x3r - x1i; - a[j3] = wk1r * (x0i - x0r); - a[j3 + 1] = wk1r * (x0i + x0r); + 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; } - 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; - } + 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; - 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; - } + 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; - 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]; + 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]; } - }; - - - /** - * @internal - * @brief Concrete FFT implementation - */ - typedef OouraFFT AudioFFTImplementation; + a[m + 1] = -a[m + 1]; + } +}; +/** + * @internal + * @brief Concrete FFT implementation + */ +typedef OouraFFT AudioFFTImplementation; #endif // AUDIOFFT_OOURA_USED - - // ================================================================ - +// ================================================================ #ifdef AUDIOFFT_APPLE_ACCELERATE_USED +/** + * @internal + * @class AppleAccelerateFFT + * @brief FFT implementation using the Apple Accelerate framework internally + */ +class AppleAccelerateFFT : public detail::AudioFFTImpl { +public: + AppleAccelerateFFT() + : detail::AudioFFTImpl(), _size(0), _powerOf2(0), _fftSetup(0), _re(), _im() {} - /** - * @internal - * @class AppleAccelerateFFT - * @brief FFT implementation using the Apple Accelerate framework internally - */ - class AppleAccelerateFFT : public detail::AudioFFTImpl - { - public: - AppleAccelerateFFT() : - detail::AudioFFTImpl(), - _size(0), - _powerOf2(0), - _fftSetup(0), - _re(), - _im() - { + AppleAccelerateFFT(const AppleAccelerateFFT &) = delete; + AppleAccelerateFFT &operator=(const AppleAccelerateFFT &) = delete; + + 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(); } - AppleAccelerateFFT(const AppleAccelerateFFT&) = delete; - AppleAccelerateFFT& operator=(const AppleAccelerateFFT&) = delete; - - 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); + 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 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); - } + 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; - }; - - - /** - * @internal - * @brief Concrete FFT implementation - */ - typedef AppleAccelerateFFT AudioFFTImplementation; +private: + size_t _size; + size_t _powerOf2; + FFTSetup _fftSetup; + std::vector _re; + std::vector _im; +}; +/** + * @internal + * @brief Concrete FFT implementation + */ +typedef AppleAccelerateFFT AudioFFTImplementation; #endif // AUDIOFFT_APPLE_ACCELERATE_USED - - // ================================================================ - +// ================================================================ #ifdef AUDIOFFT_FFTW3_USED +/** + * @internal + * @class FFTW3FFT + * @brief FFT implementation using FFTW3 internally (see fftw.org) + */ +class FFTW3FFT : public detail::AudioFFTImpl { +public: + FFTW3FFT() + : detail::AudioFFTImpl(), _size(0), _complexSize(0), _planForward(0), _planBackward(0), + _data(0), _re(0), _im(0) {} - /** - * @internal - * @class FFTW3FFT - * @brief FFT implementation using FFTW3 internally (see fftw.org) - */ - class FFTW3FFT : public detail::AudioFFTImpl - { - public: - FFTW3FFT() : - detail::AudioFFTImpl(), - _size(0), - _complexSize(0), - _planForward(0), - _planBackward(0), - _data(0), - _re(0), - _im(0) - { - } + FFTW3FFT(const FFTW3FFT &) = delete; + FFTW3FFT &operator=(const FFTW3FFT &) = delete; - FFTW3FFT(const FFTW3FFT&) = delete; - FFTW3FFT& operator=(const FFTW3FFT&) = delete; + virtual ~FFTW3FFT() { init(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; - 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 (_data) { + fftwf_free(_data); + _data = 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))); - fftwf_set_timelimit(0.01); - 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); + 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))); + fftwf_set_timelimit(0.01); + 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)); - } + 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)); + } - virtual void ifft(float* data, const float* re, const float* im) override - { - ::memcpy(_re, re, _complexSize * sizeof(float)); - ::memcpy(_im, im, _complexSize * sizeof(float)); - fftwf_execute_split_dft_c2r(_planBackward, _re, _im, _data); - detail::ScaleBuffer(data, _data, 1.0f / static_cast(_size), _size); - } + virtual void ifft(float *data, const float *re, const float *im) override { + ::memcpy(_re, re, _complexSize * sizeof(float)); + ::memcpy(_im, im, _complexSize * sizeof(float)); + fftwf_execute_split_dft_c2r(_planBackward, _re, _im, _data); + detail::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; - }; - - - /** - * @internal - * @brief Concrete FFT implementation - */ - typedef FFTW3FFT AudioFFTImplementation; +private: + size_t _size; + size_t _complexSize; + fftwf_plan _planForward; + fftwf_plan _planBackward; + float *_data; + float *_re; + float *_im; +}; +/** + * @internal + * @brief Concrete FFT implementation + */ +typedef FFTW3FFT AudioFFTImplementation; #endif // AUDIOFFT_FFTW3_USED +// ============================================================= - // ============================================================= +AudioFFT::AudioFFT() : _impl(new AudioFFTImplementation()) {} +AudioFFT::~AudioFFT() {} - AudioFFT::AudioFFT() : - _impl(new AudioFFTImplementation()) - { - } +void AudioFFT::init(size_t size) { + assert(detail::IsPowerOf2(size)); + _impl->init(size); +} +void AudioFFT::fft(const float *data, float *re, float *im) { _impl->fft(data, re, im); } - AudioFFT::~AudioFFT() - { - } +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; } - void AudioFFT::init(size_t size) - { - assert(detail::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 +} // namespace audiofft diff --git a/FFTConvolver/AudioFFT.h b/FFTConvolver/AudioFFT.h old mode 100755 new mode 100644 index dadf32e6..04bc7d07 --- a/FFTConvolver/AudioFFT.h +++ b/FFTConvolver/AudioFFT.h @@ -22,147 +22,143 @@ #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 -*/ - + * 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 audiofft -{ +namespace detail { +class AudioFFTImpl; +} - namespace detail - { - class AudioFFTImpl; - } +// ============================================================= +/** + * @class AudioFFT + * @brief Performs 1D FFTs + */ +class AudioFFT { +public: + /** + * @brief Constructor + */ + AudioFFT(); - // ============================================================= - + AudioFFT(const AudioFFT &) = delete; + AudioFFT &operator=(const AudioFFT &) = delete; /** - * @class AudioFFT - * @brief Performs 1D FFTs + * @brief Destructor */ - class AudioFFT - { - public: - /** - * @brief Constructor - */ - AudioFFT(); - - AudioFFT(const AudioFFT&) = delete; - AudioFFT& operator=(const AudioFFT&) = delete; - - /** - * @brief Destructor - */ - ~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(); /** - * @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. + * @brief Initializes the FFT object + * @param size Size of the real input (must be power 2) */ - typedef AudioFFT AudioFFTBase; + void init(size_t size); -} // End of namespace + /** + * @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; +}; + +/** + * @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; + +} // namespace audiofft #endif // Header guard diff --git a/FFTConvolver/ConvolverThreadPool.cpp b/FFTConvolver/ConvolverThreadPool.cpp index ef969572..f41befd1 100644 --- a/FFTConvolver/ConvolverThreadPool.cpp +++ b/FFTConvolver/ConvolverThreadPool.cpp @@ -140,8 +140,11 @@ void ConvolverThreadPool::clearState(size_t convolverId) { if (convolverId < _convolvers.size()) { waitForAll(); } else { - debug(1, "assert(convolverId < _convolvers.size()) failed, with convolverId: %u and _convolvers.size(): %u.", convolverId, _convolvers.size()); - } + debug(1, + "assert(convolverId < _convolvers.size()) failed, with convolverId: %u and " + "_convolvers.size(): %u.", + convolverId, _convolvers.size()); + } } /* this is the old version diff --git a/FFTConvolver/ConvolverThreadPool.h b/FFTConvolver/ConvolverThreadPool.h index a690b34f..fc49daa1 100644 --- a/FFTConvolver/ConvolverThreadPool.h +++ b/FFTConvolver/ConvolverThreadPool.h @@ -61,7 +61,7 @@ public: // Get the number of threads size_t getNumThreads() const { return _threads.size(); } - + void shutdown(); private: diff --git a/FFTConvolver/FFTConvolver.cpp b/FFTConvolver/FFTConvolver.cpp old mode 100755 new mode 100644 index 693b5b0f..d79620eb --- a/FFTConvolver/FFTConvolver.cpp +++ b/FFTConvolver/FFTConvolver.cpp @@ -20,47 +20,25 @@ #include #include -#if defined (FFTCONVOLVER_USE_SSE) - #include +#if defined(FFTCONVOLVER_USE_SSE) +#include #endif +namespace fftconvolver { -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() : - _blockSize(0), - _segSize(0), - _segCount(0), - _fftComplexSize(0), - _segments(), - _segmentsIR(), - _fftBuffer(), - _fft(), - _preMultiplied(), - _conv(), - _overlap(), - _current(0), - _inputBuffer(), - _inputBufferFill(0) -{ -} +FFTConvolver::~FFTConvolver() { reset(); } - -FFTConvolver::~FFTConvolver() -{ - reset(); -} - - -void FFTConvolver::reset() -{ - for (size_t i=0; i<_segCount; ++i) - { +void FFTConvolver::reset() { + for (size_t i = 0; i < _segCount; ++i) { delete _segments[i]; delete _segmentsIR[i]; } - + _blockSize = 0; _segSize = 0; _segCount = 0; @@ -77,115 +55,101 @@ void FFTConvolver::reset() _inputBufferFill = 0; } -void FFTConvolver::clearState() -{ - if (_segCount == 0) - { +void FFTConvolver::clearState() { + if (_segCount == 0) { return; // Not initialized } - + _inputBuffer.setZero(); _inputBufferFill = 0; _overlap.setZero(); - - for (size_t i = 0; i < _segCount; ++i) - { + + for (size_t i = 0; i < _segCount; ++i) { _segments[i]->setZero(); } - + _preMultiplied.setZero(); _conv.setZero(); _current = 0; } - -bool FFTConvolver::init(size_t blockSize, const Sample* ir, size_t irLen) -{ + +bool FFTConvolver::init(size_t blockSize, const Sample *ir, size_t irLen) { reset(); - if (blockSize == 0) - { + 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) - { + while (irLen > 0 && ::fabs(ir[irLen - 1]) < 0.000001f) { --irLen; } - if (irLen == 0) - { + if (irLen == 0) { return true; } - + _blockSize = NextPowerOf2(blockSize); _segSize = 2 * _blockSize; - _segCount = static_cast(::ceil(static_cast(irLen) / static_cast(_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)); + 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); + 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); + CopyAndPad(_fftBuffer, &ir[i * _blockSize], sizeCopy); _fft.fft(_fftBuffer.data(), segment->re(), segment->im()); _segmentsIR.push_back(segment); } - - // Prepare convolution buffers + + // 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) - { +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) - { + while (processed < len) { const bool inputBufferWasEmpty = (_inputBufferFill == 0); - const size_t processing = std::min(len-processed, _blockSize-_inputBufferFill); + const size_t processing = std::min(len - processed, _blockSize - _inputBufferFill); const size_t inputBufferPos = _inputBufferFill; - ::memcpy(_inputBuffer.data()+inputBufferPos, input+processed, processing * sizeof(Sample)); + ::memcpy(_inputBuffer.data() + inputBufferPos, input + processed, processing * sizeof(Sample)); // Forward FFT - CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize); + CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize); _fft.fft(_fftBuffer.data(), _segments[_current]->re(), _segments[_current]->im()); // Complex multiplication - if (inputBufferWasEmpty) - { + if (inputBufferWasEmpty) { _preMultiplied.setZero(); - for (size_t i=1; i<_segCount; ++i) - { + 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]); @@ -198,18 +162,18 @@ void FFTConvolver::process(const Sample* input, Sample* output, size_t len) _fft.ifft(_fftBuffer.data(), _conv.re(), _conv.im()); // Add overlap - Sum(output+processed, _fftBuffer.data()+inputBufferPos, _overlap.data()+inputBufferPos, processing); + Sum(output + processed, _fftBuffer.data() + inputBufferPos, _overlap.data() + inputBufferPos, + processing); // Input buffer full => Next block _inputBufferFill += processing; - if (_inputBufferFill == _blockSize) - { + 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)); + ::memcpy(_overlap.data(), _fftBuffer.data() + _blockSize, _blockSize * sizeof(Sample)); // Update current segment _current = (_current > 0) ? (_current - 1) : (_segCount - 1); @@ -218,5 +182,5 @@ void FFTConvolver::process(const Sample* input, Sample* output, size_t len) processed += processing; } } - + } // End of namespace fftconvolver diff --git a/FFTConvolver/FFTConvolver.h b/FFTConvolver/FFTConvolver.h old mode 100755 new mode 100644 index 125fd12b..a6ed4aba --- a/FFTConvolver/FFTConvolver.h +++ b/FFTConvolver/FFTConvolver.h @@ -27,61 +27,58 @@ #include - -namespace fftconvolver -{ +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 -{ + * @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(); + 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 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 Resets the convolver and discards the set impulse response - */ + * @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(); - + /** - * @brief Clears audio history - */ - + * @brief Clears audio history + */ + void clearState(); private: @@ -89,8 +86,8 @@ private: size_t _segSize; size_t _segCount; size_t _fftComplexSize; - std::vector _segments; - std::vector _segmentsIR; + std::vector _segments; + std::vector _segmentsIR; SampleBuffer _fftBuffer; audiofft::AudioFFT _fft; SplitComplex _preMultiplied; @@ -101,10 +98,10 @@ private: size_t _inputBufferFill; // Prevent uncontrolled usage - FFTConvolver(const FFTConvolver&); - FFTConvolver& operator=(const FFTConvolver&); + FFTConvolver(const FFTConvolver &); + FFTConvolver &operator=(const FFTConvolver &); }; - + } // End of namespace fftconvolver #endif // Header guard diff --git a/FFTConvolver/Utilities.cpp b/FFTConvolver/Utilities.cpp index 3d00e6f5..fde81851 100644 --- a/FFTConvolver/Utilities.cpp +++ b/FFTConvolver/Utilities.cpp @@ -21,12 +21,9 @@ #include "Utilities.h" +namespace fftconvolver { -namespace fftconvolver -{ - -bool SSEEnabled() -{ +bool SSEEnabled() { #if defined(FFTCONVOLVER_USE_SSE) return true; #else @@ -34,47 +31,35 @@ bool SSEEnabled() #endif } - -void Sum(Sample* FFTCONVOLVER_RESTRICT result, - const Sample* FFTCONVOLVER_RESTRICT a, - const Sample* FFTCONVOLVER_RESTRICT b, - size_t len) -{ +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 #include - -namespace fftconvolver -{ +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 +#if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE) +#define FFTCONVOLVER_USE_SSE +#endif #endif - -#if defined (FFTCONVOLVER_USE_SSE) - #include +#if defined(FFTCONVOLVER_USE_SSE) +#include #endif - #if defined(__GNUC__) - #define FFTCONVOLVER_RESTRICT __restrict__ +#define FFTCONVOLVER_RESTRICT __restrict__ #else - #define FFTCONVOLVER_RESTRICT +#define FFTCONVOLVER_RESTRICT #endif - /** -* @brief Returns whether SSE optimization for the convolver is enabled -* @return true: Enabled - false: Disabled -*/ + * @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); - } + * @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(); - } + virtual ~Buffer() { clear(); } - void clear() - { + void clear() { deallocate(_data); _data = 0; _size = 0; } - void resize(size_t size) - { - if (_size != size) - { + void resize(size_t size) { + if (_size != size) { clear(); - if (size > 0) - { + if (size > 0) { assert(!_data && _size == 0); _data = allocate(size); _size = size; @@ -101,172 +81,118 @@ public: setZero(); } - size_t size() const - { - return _size; - } + size_t size() const { return _size; } - void setZero() - { - ::memset(_data, 0, _size * sizeof(T)); - } + void setZero() { ::memset(_data, 0, _size * sizeof(T)); } - void copyFrom(const Buffer& other) - { + void copyFrom(const Buffer &other) { assert(_size == other._size); - if (this != &other) - { + if (this != &other) { ::memcpy(_data, other._data, _size * sizeof(T)); } } - T& operator[](size_t index) - { + T &operator[](size_t index) { assert(_data && index < _size); return _data[index]; } - const T& operator[](size_t index) const - { + const T &operator[](size_t index) const { assert(_data && index < _size); return _data[index]; } - operator bool() const - { - return (_data != 0 && _size > 0); - } + operator bool() const { return (_data != 0 && _size > 0); } - T* data() - { - return _data; - } + T *data() { return _data; } - const T* data() const - { - return _data; - } + const T *data() const { return _data; } - static void Swap(Buffer& a, Buffer& b) - { + 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) - { + T *allocate(size_t size) { #if defined(FFTCONVOLVER_USE_SSE) - return static_cast(_mm_malloc(size * sizeof(T), 16)); + return static_cast(_mm_malloc(size * sizeof(T), 16)); #else return new T[size]; #endif } - - void deallocate(T* ptr) - { + + void deallocate(T *ptr) { #if defined(FFTCONVOLVER_USE_SSE) _mm_free(ptr); #else - delete [] ptr; + delete[] ptr; #endif } - T* _data; + T *_data; size_t _size; // Prevent uncontrolled usage - Buffer(const Buffer&); - Buffer& operator=(const Buffer&); + Buffer(const Buffer &); + Buffer &operator=(const Buffer &); }; - /** -* @brief Type of one sample -*/ + * @brief Type of one sample + */ typedef float Sample; - /** -* @brief Buffer for samples -*/ + * @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 -{ + * @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); - } + explicit SplitComplex(size_t initialSize = 0) : _size(0), _re(), _im() { resize(initialSize); } - ~SplitComplex() - { - clear(); - } + ~SplitComplex() { clear(); } - void clear() - { + void clear() { _re.clear(); _im.clear(); _size = 0; } - void resize(size_t newSize) - { + void resize(size_t newSize) { _re.resize(newSize); _im.resize(newSize); _size = newSize; } - void setZero() - { + void setZero() { _re.setZero(); _im.setZero(); } - void copyFrom(const SplitComplex& other) - { + void copyFrom(const SplitComplex &other) { _re.copyFrom(other._re); _im.copyFrom(other._im); } - Sample* re() - { - return _re.data(); - } + Sample *re() { return _re.data(); } - const Sample* re() const - { - return _re.data(); - } + const Sample *re() const { return _re.data(); } - Sample* im() - { - return _im.data(); - } + Sample *im() { return _im.data(); } - const Sample* im() const - { - return _im.data(); - } + const Sample *im() const { return _im.data(); } - size_t size() const - { - return _size; - } + size_t size() const { return _size; } private: size_t _size; @@ -274,82 +200,68 @@ private: SampleBuffer _im; // Prevent uncontrolled usage - SplitComplex(const SplitComplex&); - SplitComplex& operator=(const SplitComplex&); + 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) -{ + * @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) - { + 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 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) -{ + * @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)); + ::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 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); - -/** -* @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 index c276d778..a7372e29 100644 --- a/FFTConvolver/convolver.cpp +++ b/FFTConvolver/convolver.cpp @@ -46,8 +46,8 @@ void convolver_pool_init(size_t numThreads, size_t numConvolvers) { if (!pool.init(numThreads, numConvolvers)) { debug(1, "failed to initialize thread pool!"); } else { - debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads, numThreads == 1 ? "" : "s", - numConvolvers, numConvolvers == 1 ? "" : "s"); + debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads, + numThreads == 1 ? "" : "s", numConvolvers, numConvolvers == 1 ? "" : "s"); } } @@ -56,8 +56,8 @@ void convolver_pool_closedown() { debug(3, "thread pool shut down"); } -int convolver_init(const char *filename, unsigned char channel_count, - double max_length_in_seconds, size_t block_size) { +int convolver_init(const char *filename, unsigned char channel_count, double max_length_in_seconds, + size_t block_size) { debug(3, "convolver_init"); int success = 0; SF_INFO info = {}; // Zero everything, including format @@ -67,10 +67,10 @@ int convolver_init(const char *filename, unsigned char channel_count, size_t max_length = (size_t)(max_length_in_seconds * info.samplerate); const size_t size = (unsigned int)info.frames > max_length ? max_length : (unsigned int)info.frames; - float *buffer = (float*)malloc(sizeof(float) * size * info.channels); + float *buffer = (float *)malloc(sizeof(float) * size * info.channels); if (buffer != NULL) { // float buffer[size * info.channels]; - float *abuffer = (float*)malloc(sizeof(float) * size); + float *abuffer = (float *)malloc(sizeof(float) * size); if (abuffer != NULL) { size_t l = sf_readf_float(file, buffer, size); if (l != 0) { @@ -91,7 +91,7 @@ int convolver_init(const char *filename, unsigned char channel_count, } if (!pool.initConvolver(cc, block_size, abuffer, size)) { debug(1, "new convolver failed to initialize convolver %u ", cc); - } + } } } success = 1; @@ -101,11 +101,11 @@ int convolver_init(const char *filename, unsigned char channel_count, "%d samples", filename, info.channels, info.channels == 1 ? "" : "s", size); sf_close(file); - free((void*)abuffer); + free((void *)abuffer); } else { debug(1, "failed to init convolvers because insufficient memory was available"); } - free((void*)buffer); + free((void *)buffer); } else { warn("failed to init convolvers because insufficient memory was available"); } @@ -124,9 +124,7 @@ void convolver_process(unsigned int channel, float *data, int length) { void convolver_wait_for_all() { pool.waitForAll(); } -void convolver_clear_state() { - pool.clearAllStates(); -} +void convolver_clear_state() { pool.clearAllStates(); } const unsigned int max_channels = 8; fftconvolver::FFTConvolver convolvers[max_channels]; diff --git a/FFTConvolver/convolver.h b/FFTConvolver/convolver.h index c49c70a2..5259fe67 100644 --- a/FFTConvolver/convolver.h +++ b/FFTConvolver/convolver.h @@ -5,22 +5,24 @@ extern "C" { #endif - #include - -// int convolver_init(const char* file, unsigned char channel_count, double max_length_in_seconds, size_t block_size); +#include + +// int convolver_init(const char* file, unsigned char channel_count, double max_length_in_seconds, +// size_t block_size); void convolver_reset(); -//void convolver_clear_state(); -// void convolver_process(unsigned int channel, float *data, int length); -// void convolver_process_l(float* data, int length); -// void convolver_process_r(float* data, int length); +// void convolver_clear_state(); +// void convolver_process(unsigned int channel, float *data, int length); +// void convolver_process_l(float* data, int length); +// void convolver_process_r(float* data, int length); void convolver_pool_init(size_t numThreads, size_t numConvolvers); void convolver_pool_closedown(); -int convolver_init(const char* file, unsigned char channel_count, double max_length_in_seconds, size_t block_size); +int convolver_init(const char *file, unsigned char channel_count, double max_length_in_seconds, + size_t block_size); void convolver_process(unsigned int channel, float *data, int length); void convolver_clear_state(); void convolver_wait_for_all(); - + #ifdef __cplusplus } #endif diff --git a/Makefile.am b/Makefile.am index b5c724d6..64763533 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,11 +5,11 @@ homedir = @HOME@ # Note that xmlmantohtml isn't producing correct HTML. # Uncomment the if/else and SUBDIR lines in this stanza to make xmltoman build shairport-sync.1 -# if USE_XMLTOMAN -# SUBDIRS = man -# else +if USE_XMLTOMAN + SUBDIRS = man +else man_MANS = $(top_srcdir)/man/shairport-sync.1 -# endif +endif lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread --include=utilities/debug.h lib_tinyhttp_a_CFLAGS = -pthread @@ -27,7 +27,11 @@ noinst_LIBRARIES = # See below for the flags for the test client program -shairport_sync_SOURCES = shairport.c bonjour_strings.c rtsp.c mdns.c common.c rtp.c player.c audio.c loudness.c activity_monitor.c utilities/debug.c utilities/network_utilities.c utilities/rtsp_message_utilities.c +shairport_sync_SOURCES = shairport.c bonjour_strings.c mdns.c common.c rtp.c \ + player.c audio.c loudness.c activity_monitor.c \ + rtsp.c \ + utilities/debug.c utilities/network_utilities.c utilities/rtsp_message_utilities.c \ + utilities/string_utilities.c if BUILD_FOR_DARWIN AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-deprecated-declarations -pthread -DSYSCONFDIR=\"$(sysconfdir)\" diff --git a/alac.c b/alac.c index 0b5587f3..daca81c9 100644 --- a/alac.c +++ b/alac.c @@ -44,13 +44,13 @@ static const int host_bigendian = 0; #define _Swap32(v) \ do { \ - v = (((v)&0x000000FF) << 0x18) | (((v)&0x0000FF00) << 0x08) | (((v)&0x00FF0000) >> 0x08) | \ - (((v)&0xFF000000) >> 0x18); \ + v = (((v) & 0x000000FF) << 0x18) | (((v) & 0x0000FF00) << 0x08) | \ + (((v) & 0x00FF0000) >> 0x08) | (((v) & 0xFF000000) >> 0x18); \ } while (0) #define _Swap16(v) \ do { \ - v = (((v)&0x00FF) << 0x08) | (((v)&0xFF00) >> 0x08); \ + v = (((v) & 0x00FF) << 0x08) | (((v) & 0xFF00) >> 0x08); \ } while (0) struct { @@ -603,11 +603,11 @@ static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b, int uncompresse right |= uncompressed_bytes_buffer_b[i] & mask; } - ((uint8_t *)buffer_out)[i * numchannels * 3] = (left)&0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3] = (left) & 0xFF; ((uint8_t *)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; ((uint8_t *)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; - ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right)&0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF; ((uint8_t *)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; ((uint8_t *)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; } @@ -631,11 +631,11 @@ static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b, int uncompresse right |= uncompressed_bytes_buffer_b[i] & mask; } - ((uint8_t *)buffer_out)[i * numchannels * 3] = (left)&0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3] = (left) & 0xFF; ((uint8_t *)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; ((uint8_t *)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; - ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right)&0xFF; + ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF; ((uint8_t *)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; ((uint8_t *)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; } @@ -798,7 +798,7 @@ void alac_decode_frame(alac_file *alac, unsigned char *inbuffer, void *outbuffer sample |= alac->uncompressed_bytes_buffer_a[i] & mask; } - ((uint8_t *)outbuffer)[i * alac->numchannels * 3] = (sample)&0xFF; + ((uint8_t *)outbuffer)[i * alac->numchannels * 3] = (sample) & 0xFF; ((uint8_t *)outbuffer)[i * alac->numchannels * 3 + 1] = (sample >> 8) & 0xFF; ((uint8_t *)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF; } diff --git a/ap2_buffered_audio_processor.c b/ap2_buffered_audio_processor.c index d8a6bada..66067dd4 100644 --- a/ap2_buffered_audio_processor.c +++ b/ap2_buffered_audio_processor.c @@ -30,6 +30,7 @@ #include "rtp.h" #include "utilities/buffered_read.h" #include "utilities/mod23.h" +#include "utilities/network_utilities.h" #include #include @@ -91,10 +92,9 @@ void addADTStoPacket(uint8_t *packet, int packetLen, int rate, int channel_confi void rtp_buffered_audio_cleanup_handler(__attribute__((unused)) void *arg) { debug(2, "Buffered Audio Receiver Cleanup Start."); rtsp_conn_info *conn = (rtsp_conn_info *)arg; - close(conn->buffered_audio_socket); + safe_socket_close(&conn->buffered_audio_socket); debug(3, "Connection %d: closing TCP Buffered Audio port: %u.", conn->connection_number, conn->local_buffered_audio_port); - conn->buffered_audio_socket = 0; debug(2, "Connection %d: rtp_buffered_audio_processor exit.", conn->connection_number); } @@ -198,7 +198,7 @@ void *rtp_buffered_audio_processor(void *arg) { int packets_played_in_this_sequence = 0; int play_enabled = 0; - + int very_early_packets_signalled = 0; // double requested_lead_time = 0.0; // normal lead time minimum -- maybe it should be about 0.1 @@ -276,20 +276,19 @@ void *rtp_buffered_audio_processor(void *arg) { if (payload_ssrc != SSRC_NONE) previous_ssrc = payload_ssrc; payload_ssrc = nctohl(&packet[8]); - - if ((payload_ssrc != previous_ssrc) && (payload_ssrc != SSRC_NONE)) { if (ssrc_is_recognised(payload_ssrc) == 0) { debug(2, "Unrecognised SSRC: %u.", payload_ssrc); } else { debug(2, "Connection %d: incoming audio encoding is%s \"%s\".", - conn->connection_number, previous_ssrc == SSRC_NONE ? "" : " switching to", get_ssrc_name(payload_ssrc)); + conn->connection_number, previous_ssrc == SSRC_NONE ? "" : " switching to", + get_ssrc_name(payload_ssrc)); } } if ((payload_ssrc != previous_ssrc) && (ssrc_is_recognised(payload_ssrc) == 0)) { - debug(2, "Unrecognised SSRC: %u.", payload_ssrc); + debug(2, "Unrecognised SSRC: %u.", payload_ssrc); } if (blocks_read_since_play_began == 1) { @@ -327,7 +326,8 @@ void *rtp_buffered_audio_processor(void *arg) { finished = 1; } else if (nread < 0) { char errorstring[1024]; - (void) !strerror_r(errno, (char *)errorstring, sizeof(errorstring)); // (void) ! to suppress unused response warning + (void)!strerror_r(errno, (char *)errorstring, + sizeof(errorstring)); // (void) ! to suppress unused response warning debug(1, "error in rtp_buffered_audio_processor %d: \"%s\". Could not recv a data_len .", errno, errorstring); finished = 1; @@ -343,34 +343,42 @@ void *rtp_buffered_audio_processor(void *arg) { debug(2, "immediate flush started at sequence number %u until sequence number of %u.", seq_no, conn->ap2_immediate_flush_until_sequence_number); } - if ((blocks_read != 0) && ((a_minus_b_mod23(seq_no, conn->ap2_immediate_flush_until_sequence_number) > 0))) { - debug(1, "immediate flush may have escaped its endpoint! Seq_no is %u, conn->ap2_immediate_flush_until_sequence_number is %u.", seq_no, conn->ap2_immediate_flush_until_sequence_number); + if ((blocks_read != 0) && + ((a_minus_b_mod23(seq_no, conn->ap2_immediate_flush_until_sequence_number) > 0))) { + debug(1, + "immediate flush may have escaped its endpoint! Seq_no is %u, " + "conn->ap2_immediate_flush_until_sequence_number is %u.", + seq_no, conn->ap2_immediate_flush_until_sequence_number); } - - if ((blocks_read != 0) && ((a_minus_b_mod23(seq_no, conn->ap2_immediate_flush_until_sequence_number) >= 0))) { - debug(2, "immediate flush completed at seq_no: %u, conn->ap2_immediate_flush_until_sequence_number: %u.", seq_no, conn->ap2_immediate_flush_until_sequence_number); + + if ((blocks_read != 0) && + ((a_minus_b_mod23(seq_no, conn->ap2_immediate_flush_until_sequence_number) >= 0))) { + debug(2, + "immediate flush completed at seq_no: %u, " + "conn->ap2_immediate_flush_until_sequence_number: %u.", + seq_no, conn->ap2_immediate_flush_until_sequence_number); conn->ap2_immediate_flush_requested = 0; ap2_immediate_flush_requested = 0; - // turn off all deferred requests. Not sure if this is right... unsigned int f = 0; for (f = 0; f < MAX_DEFERRED_FLUSH_REQUESTS; f++) { - if ((conn->ap2_deferred_flush_requests[f].inUse != 0) && (conn->ap2_deferred_flush_requests[f].active = 0)) { + if ((conn->ap2_deferred_flush_requests[f].inUse != 0) && + (conn->ap2_deferred_flush_requests[f].active = 0)) { debug(1, - "deferred flush cancelled by an immediate flush: flushFromTS: %12u, flushFromSeq: %12u, " - "flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.", - conn->ap2_deferred_flush_requests[f].flushFromTS, - conn->ap2_deferred_flush_requests[f].flushFromSeq, - conn->ap2_deferred_flush_requests[f].flushUntilTS, - conn->ap2_deferred_flush_requests[f].flushUntilSeq, timestamp); + "deferred flush cancelled by an immediate flush: flushFromTS: %12u, " + "flushFromSeq: %12u, " + "flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.", + conn->ap2_deferred_flush_requests[f].flushFromTS, + conn->ap2_deferred_flush_requests[f].flushFromSeq, + conn->ap2_deferred_flush_requests[f].flushUntilTS, + conn->ap2_deferred_flush_requests[f].flushUntilSeq, timestamp); } conn->ap2_deferred_flush_requests[f].inUse = 0; conn->ap2_deferred_flush_requests[f].active = 0; } - } else { debug(4, "immediate flush of block %u until block %u", seq_no, conn->ap2_immediate_flush_until_sequence_number); @@ -426,11 +434,10 @@ void *rtp_buffered_audio_processor(void *arg) { } else if (conn->ap2_deferred_flush_requests[f].active != 0) { new_audio_block_needed = 1; debug(4, - "deferred flush of block: %u, timestamp: %u, SSRC: \"%s\". flushFromTS: %12u, flushFromSeq: %12u, " + "deferred flush of block: %u, timestamp: %u, SSRC: \"%s\". flushFromTS: %12u, " + "flushFromSeq: %12u, " "flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.", - seq_no, - timestamp, - get_ssrc_name(payload_ssrc), + seq_no, timestamp, get_ssrc_name(payload_ssrc), conn->ap2_deferred_flush_requests[f].flushFromTS, conn->ap2_deferred_flush_requests[f].flushFromSeq, conn->ap2_deferred_flush_requests[f].flushUntilTS, @@ -453,22 +460,28 @@ void *rtp_buffered_audio_processor(void *arg) { // and send it to the player. Otherwise, keep the block and sleep for a while. // calculate if there is room in the decoded audio buffer... - - // debug(1, "frames buffered: %f seconds, desired length: %f seconds.", (1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate, config.audio_decoded_buffer_desired_length); - - // int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <= config.audio_decoded_buffer_desired_length; + + // debug(1, "frames buffered: %f seconds, desired length: %f seconds.", (1.0 * + // player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate, + // config.audio_decoded_buffer_desired_length); + + // int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * + // conn->frames_per_packet) / conn->input_rate) <= + // config.audio_decoded_buffer_desired_length; uint64_t buffer_should_be_time; - + int have_valid_time = (frame_to_local_time(timestamp, &buffer_should_be_time, conn) == 0); - + // calculate the lead time to make sure it's not too early... int64_t lead_time = buffer_should_be_time - get_absolute_time_in_ns(); - - - // debug(1,"play_enabled: %d, have_valid_time: %d, audio_decoded_buffer_below_desired_length: %d, lead_time * 1E-9: %f, (config.audio_decoded_buffer_desired_length + 0.1): %f, player_buffer_occupancy: %zu", - // play_enabled, have_valid_time, audio_decoded_buffer_below_desired_length, lead_time * 1E-9, (config.audio_decoded_buffer_desired_length + 0.1), player_buffer_occupancy + + // debug(1,"play_enabled: %d, have_valid_time: %d, + // audio_decoded_buffer_below_desired_length: %d, lead_time * 1E-9: %f, + // (config.audio_decoded_buffer_desired_length + 0.1): %f, player_buffer_occupancy: %zu", + // play_enabled, have_valid_time, audio_decoded_buffer_below_desired_length, lead_time * + // 1E-9, (config.audio_decoded_buffer_desired_length + 0.1), player_buffer_occupancy // ); - + // A slight problem here is that counting the number of buffers may not be sufficient, // because the actual device may be // taking data in large quantities at a single time. @@ -476,34 +489,35 @@ void *rtp_buffered_audio_processor(void *arg) { // So we just have to ensure that there // is enough of a lead time maintained for sufficient audio to be available to prevent // the device from under-running. - + // If means that the Shairport Sync player might riun out of audio occasionally, but // as long as the device has enough in its buffer, everything is fine. - + // But it also means that Shairport Sync's buffers must be sufficient to hold all the // entire lead-time's amount of audio in case the device has a zero-sized buffer. - - if ((play_enabled != 0) && (have_valid_time != 0) -// (audio_decoded_buffer_below_desired_length != 0) && + + if ((play_enabled != 0) && + (have_valid_time != 0) + // (audio_decoded_buffer_below_desired_length != 0) && && (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1)) -// && (audio_decoded_buffer_below_desired_length != 0) - ) { - - very_early_packets_signalled = 0; //reset very early packet warning signaller - + // && (audio_decoded_buffer_below_desired_length != 0) + ) { + + very_early_packets_signalled = 0; // reset very early packet warning signaller + // try to identify blocks that are timed to before the last buffer, and drop 'em int64_t time_from_last_buffer_time = buffer_should_be_time - previous_buffer_should_be_time; if ((packets_played_in_this_sequence == 0) || (time_from_last_buffer_time > 0)) { - + payload_length = 0; if (ssrc_is_recognised(payload_ssrc) != 0) { // prepare_decoding_chain(conn, payload_ssrc); unsigned long long new_payload_length = 0; payload_pointer = m + leading_free_space_length; if (lead_time >= 0) { // only decipher the packet if it's not too late - int response = -1; // guess that there is a problem + int response = -1; // guess that there is a problem if (conn->session_key != NULL) { unsigned char nonce[12]; memset(nonce, 0, sizeof(nonce)); @@ -661,11 +675,16 @@ void *rtp_buffered_audio_processor(void *arg) { } new_audio_block_needed = 1; // the block has been used up and is no longer current } else { - if ((have_valid_time != 0) && (very_early_packets_signalled == 0) && (lead_time * 1E-9 > (config.audio_decoded_buffer_desired_length + 0.2))) { - debug(1, "incoming frame suddenly (?) has a lead time of %f seconds, with a desired decoded buffer length of %f.", 1.0 * lead_time * 1E-9, config.audio_decoded_buffer_desired_length); + if ((have_valid_time != 0) && (very_early_packets_signalled == 0) && + (lead_time * 1E-9 > (config.audio_decoded_buffer_desired_length + 0.2))) { + debug(1, + "incoming frame suddenly (?) has a lead time of %f seconds, with a desired " + "decoded buffer length of %f.", + 1.0 * lead_time * 1E-9, config.audio_decoded_buffer_desired_length); very_early_packets_signalled = 1; } - usleep(((1000000 * conn->frames_per_packet) / conn->input_rate) * 2); // wait for approximately the length of two packets + usleep(((1000000 * conn->frames_per_packet) / conn->input_rate) * + 2); // wait for approximately the length of two packets } } } diff --git a/ap2_event_message_handler.c b/ap2_event_message_handler.c index 7b03f436..94a84d0d 100644 --- a/ap2_event_message_handler.c +++ b/ap2_event_message_handler.c @@ -27,11 +27,10 @@ #include "ap2_event_message_handler.h" #include "common.h" #include "rtsp.h" -#include "utilities/structured_buffer.h" #include "utilities/generate_random_uuid.h" +#include "utilities/structured_buffer.h" - -void decodeAndLogPlist(plist_t plist_to_log) { +void decodeAndLogPlist(plist_t plist_to_log) { if (plist_to_log != NULL) { char *plist_as_string = plist_as_xml_text(plist_to_log); if (plist_as_string != NULL) { @@ -80,13 +79,13 @@ ssize_t ap2_event_port_send_message(rtsp_conn_info *conn, char *data, size_t dat ssize_t ap2_event_port_post_command(rtsp_conn_info *conn, plist_t command) { ssize_t result = 0; - decodeAndLogPlist(command); + decodeAndLogPlist(command); structured_buffer *sbuf = sbuf_new(4096); if (sbuf != NULL) { pthread_cleanup_push(sbuf_cleanup, sbuf); char *plistString = NULL; uint32_t plistStringLength = 0; - + plist_to_bin(command, &plistString, &plistStringLength); if (plistString != NULL) { sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n", plistStringLength); @@ -104,7 +103,7 @@ ssize_t ap2_event_port_post_command(rtsp_conn_info *conn, plist_t command) { } pthread_cleanup_pop(1); // delete the structured buffer } - return result; + return result; } ssize_t ap2_event_send_update_info(rtsp_conn_info *conn) { @@ -121,14 +120,14 @@ ssize_t ap2_event_send_update_info(rtsp_conn_info *conn) { if (update_info_plist != NULL) { plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo")); plist_dict_set_item(update_info_plist, "value", value_plist); - + char *plist_as_string = plist_as_xml_text(update_info_plist); if (plist_as_string != NULL) { debug(3, "update_info_plist is:\n--\n\"%s\"\n--\n", plist_as_string); free(plist_as_string); } - - result = ap2_event_port_post_command(conn, update_info_plist); + + result = ap2_event_port_post_command(conn, update_info_plist); plist_free(update_info_plist); } else { debug(1, "Could not build an updateInfo plist"); diff --git a/ap2_event_message_handler.h b/ap2_event_message_handler.h index fef68626..a632c33a 100644 --- a/ap2_event_message_handler.h +++ b/ap2_event_message_handler.h @@ -1,12 +1,13 @@ #ifndef _AP2_EVENT_COMMAND_SENDER_H #define _AP2_EVENT_COMMAND_SENDER_H +#include "player.h" #include #include -#include "player.h" ssize_t ap2_event_send_unit_volume_notification(rtsp_conn_info *conn, double volume); -ssize_t ap2_event_send_modern_media_remote_command(rtsp_conn_info *conn, unsigned int command_number); +ssize_t ap2_event_send_modern_media_remote_command(rtsp_conn_info *conn, + unsigned int command_number); ssize_t ap2_event_send_update_info(rtsp_conn_info *conn); ssize_t ap2_event_send_dev_mule(); diff --git a/ap2_event_receiver.c b/ap2_event_receiver.c index abff7c23..98fa5191 100644 --- a/ap2_event_receiver.c +++ b/ap2_event_receiver.c @@ -43,8 +43,7 @@ void ap2_event_receiver_cleanup_handler(void *arg) { #endif debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4); pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex); - close(conn->event_channel_fd); - conn->event_channel_fd = 0; + safe_socket_close(&conn->event_channel_fd); pthread_cleanup_pop(1); // unlock the mutex if (conn->airplay_gid != NULL) { @@ -68,7 +67,8 @@ void *ap2_event_receiver(void *arg) { debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4); pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex); - conn->event_channel_fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size); + conn->event_channel_fd = + eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size); pthread_cleanup_pop(1); // unlock the mutex if (conn->event_channel_fd > 0) { debug(2, @@ -76,14 +76,13 @@ void *ap2_event_receiver(void *arg) { "socket %d.", conn->connection_number, conn->event_socket, conn->event_channel_fd); pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg); - + ap2_event_send_update_info(conn); - - + while (1) { usleep(1000000); }; - + debug(3, "Connection %d: AP2 Event Receiver RTP thread starting \"normal\" exit.", conn->connection_number); pthread_cleanup_pop(1); // do the cleanup diff --git a/audio_alsa.c b/audio_alsa.c index bf03dfc8..83ed20df 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -40,6 +40,7 @@ #include "activity_monitor.h" #include "audio.h" #include "common.h" +#include "utilities/string_utilities.h" enum alsa_backend_mode { abm_disconnected, @@ -951,8 +952,8 @@ static int actual_open_alsa_device() { if ((snd_pcm_hw_params_get_rate_numden(alsa_params, &uval, &uval2) == 0) && (uval2 != 0)) // watch for a divide by zero too! - debug(log_level, " precise (rational) rate = %.3f frames per second (i.e. %u/%u).", (1.0 * uval) / uval2, uval, - uval2); + debug(log_level, " precise (rational) rate = %.3f frames per second (i.e. %u/%u).", + (1.0 * uval) / uval2, uval, uval2); else debug(log_level, " precise (rational) rate information unavailable."); @@ -1260,7 +1261,8 @@ static int init(int argc, char **argv) { else { warn("Invalid disable_synchronization option choice \"%s\". It should " "be \"yes\" or " - "\"no\". It is set to \"no\".", str); + "\"no\". It is set to \"no\".", + str); config.no_sync = 0; } } @@ -1276,7 +1278,8 @@ static int init(int argc, char **argv) { else { warn("Invalid mute_using_playback_switch option choice \"%s\". It " "should be \"yes\" or " - "\"no\". It is set to \"no\".", str); + "\"no\". It is set to \"no\".", + str); config.alsa_use_hardware_mute = 0; } } @@ -1290,7 +1293,8 @@ static int init(int argc, char **argv) { else { warn("Invalid use_hardware_mute_if_available option choice \"%s\". It " "should be \"yes\" or " - "\"no\". It is set to \"no\".", str); + "\"no\". It is set to \"no\".", + str); config.alsa_use_hardware_mute = 0; } } @@ -1304,7 +1308,8 @@ static int init(int argc, char **argv) { else { warn("Invalid use_mmap_if_available option choice \"%s\". It should be " "\"yes\" or \"no\". " - "It remains set to \"yes\".", str); + "It remains set to \"yes\".", + str); config.no_mmap = 0; } } @@ -1424,7 +1429,8 @@ static int init(int argc, char **argv) { else { warn("Invalid use_precision_timing option choice \"%s\". It should be " "\"yes\", \"auto\" or \"no\". " - "It remains set to \"%s\".", str, + "It remains set to \"%s\".", + str, config.use_precision_timing == YNA_NO ? "no" : config.use_precision_timing == YNA_AUTO ? "auto" : "yes"); diff --git a/audio_pa.c b/audio_pa.c index a0c6afec..ce7eb328 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -506,7 +506,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * // get the default channel mapping setting basis -- "alsa" or "pulseaudio". - if (config_lookup_non_empty_string(config.cfg, "pulseaudio.default_channel_layouts", &default_channel_layouts)) { + if (config_lookup_non_empty_string(config.cfg, "pulseaudio.default_channel_layouts", + &default_channel_layouts)) { if ((strcasecmp(default_channel_layouts, "alsa") == 0) || (strcasecmp(default_channel_layouts, "pulseaudio") == 0)) { debug(1, "pulseaudio default_channel_layouts setting: \"%s\".", default_channel_layouts); diff --git a/audio_pipe.c b/audio_pipe.c index cc86b380..203bd2c5 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -39,6 +39,8 @@ #include #include +#include "utilities/network_utilities.h" + static int fd = -1; static int warned = 0; static unsigned int bytes_per_frame = 0; @@ -146,10 +148,7 @@ static int init(int argc, char **argv) { return 0; } -static void deinit(void) { - if (fd > 0) - close(fd); -} +static void deinit(void) { safe_socket_close(&fd); } static void help(void) { printf(" Provide the pipe's pathname. The default is \"%s\".\n", default_pipe_name); diff --git a/bonjour_strings.c b/bonjour_strings.c index ff9c37a7..5edf400c 100644 --- a/bonjour_strings.c +++ b/bonjour_strings.c @@ -76,72 +76,76 @@ void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) { // the secondary_txt_records are for the _airplay._tcp items. #ifdef CONFIG_AIRPLAY_2 - txt_records[entry_number++] = "cn=0,1"; - txt_records[entry_number++] = "da=true"; - txt_records[entry_number++] = "et=0,1"; - if (config.password != NULL) - txt_records[entry_number++] = "pw=true"; uint64_t features_hi = config.airplay_features; features_hi = (features_hi >> 32) & 0xffffffff; uint64_t features_lo = config.airplay_features; features_lo = features_lo & 0xffffffff; - txt_records[entry_number++] = - bnprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "", - features_lo, features_hi); + if (config.service_type == APST_airplay2) { + txt_records[entry_number++] = "cn=0,1"; + txt_records[entry_number++] = "da=true"; + txt_records[entry_number++] = "et=0,1"; + if (config.password != NULL) + txt_records[entry_number++] = "pw=true"; - txt_records[entry_number++] = - bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version); - txt_records[entry_number++] = bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString), - "sf=0x%" PRIX32, config.airplay_statusflags); -#ifdef CONFIG_METADATA - if (config.get_coverart == 0) - txt_records[entry_number++] = "md=0,2"; - else - txt_records[entry_number++] = "md=0,1,2"; -#endif - txt_records[entry_number++] = - bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model); - txt_records[entry_number++] = bnprintf(pkString, sizeof(pkString), "pk=%s", config.pk_string); - txt_records[entry_number++] = "tp=UDP"; - txt_records[entry_number++] = "vn=65537"; - txt_records[entry_number++] = - bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=%s", config.srcvers); - txt_records[entry_number++] = - bnprintf(ap1OsversString, sizeof(ap1OsversString), "ov=%s", config.osvers); - txt_records[entry_number++] = NULL; + txt_records[entry_number++] = + bnprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "", + features_lo, features_hi); -#else - // here, just replicate what happens in mdns.h when using those #defines - txt_records[entry_number++] = - bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString), "sf=0x4"); - txt_records[entry_number++] = - bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version); - txt_records[entry_number++] = - bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model); - txt_records[entry_number++] = bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=105.1"); - txt_records[entry_number++] = "tp=TCP,UDP"; - txt_records[entry_number++] = "vn=65537"; + txt_records[entry_number++] = + bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version); + txt_records[entry_number++] = bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString), + "sf=0x%" PRIX32, config.airplay_statusflags); #ifdef CONFIG_METADATA - if (config.get_coverart == 0) - txt_records[entry_number++] = "md=0,2"; - else - txt_records[entry_number++] = "md=0,1,2"; + if (config.get_coverart == 0) + txt_records[entry_number++] = "md=0,2"; + else + txt_records[entry_number++] = "md=0,1,2"; #endif - txt_records[entry_number++] = "ss=16"; - txt_records[entry_number++] = "sr=44100"; - txt_records[entry_number++] = "da=true"; - txt_records[entry_number++] = "sv=false"; - txt_records[entry_number++] = "et=0,1"; - txt_records[entry_number++] = "ek=1"; - txt_records[entry_number++] = "cn=0,1"; - txt_records[entry_number++] = "ch=2"; - txt_records[entry_number++] = "txtvers=1"; - if (config.password == NULL) - txt_records[entry_number++] = "pw=false"; - else - txt_records[entry_number++] = "pw=true"; - txt_records[entry_number++] = NULL; + txt_records[entry_number++] = + bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model); + txt_records[entry_number++] = bnprintf(pkString, sizeof(pkString), "pk=%s", config.pk_string); + txt_records[entry_number++] = "tp=UDP"; + txt_records[entry_number++] = "vn=65537"; + txt_records[entry_number++] = + bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=%s", config.srcvers); + txt_records[entry_number++] = + bnprintf(ap1OsversString, sizeof(ap1OsversString), "ov=%s", config.osvers); + txt_records[entry_number++] = NULL; + } else { +#endif + // here, just replicate what happens in mdns.h when using those #defines + txt_records[entry_number++] = + bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString), "sf=0x4"); + txt_records[entry_number++] = + bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version); + txt_records[entry_number++] = + bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model); + txt_records[entry_number++] = bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=105.1"); + txt_records[entry_number++] = "tp=TCP,UDP"; + txt_records[entry_number++] = "vn=65537"; +#ifdef CONFIG_METADATA + if (config.get_coverart == 0) + txt_records[entry_number++] = "md=0,2"; + else + txt_records[entry_number++] = "md=0,1,2"; +#endif + txt_records[entry_number++] = "ss=16"; + txt_records[entry_number++] = "sr=44100"; + txt_records[entry_number++] = "da=true"; + txt_records[entry_number++] = "sv=false"; + txt_records[entry_number++] = "et=0,1"; + txt_records[entry_number++] = "ek=1"; + txt_records[entry_number++] = "cn=0,1"; + txt_records[entry_number++] = "ch=2"; + txt_records[entry_number++] = "txtvers=1"; + if (config.password == NULL) + txt_records[entry_number++] = "pw=false"; + else + txt_records[entry_number++] = "pw=true"; + txt_records[entry_number++] = NULL; +#ifdef CONFIG_AIRPLAY_2 + } #endif #ifdef CONFIG_AIRPLAY_2 diff --git a/common.c b/common.c index e5961c98..c513077e 100644 --- a/common.c +++ b/common.c @@ -27,6 +27,7 @@ */ #include "common.h" +#include "utilities/network_utilities.h" #ifdef CONFIG_USE_GIT_VERSION_STRING #include "gitversion.h" @@ -51,8 +52,8 @@ #include #include -#include #include +#include #ifdef COMPILE_FOR_LINUX #include @@ -411,7 +412,7 @@ int bind_socket_and_port(int type, int ip_family, const char *self_ip_address, u #endif if (ret < 0) { ret = errno; - close(local_socket); + safe_socket_close(&local_socket); char errorstring[1024]; getErrorText((char *)errorstring, sizeof(errorstring)); warn("error %d: \"%s\". Could not bind a port!", errno, errorstring); @@ -422,7 +423,7 @@ int bind_socket_and_port(int type, int ip_family, const char *self_ip_address, u ret = getsockname(local_socket, (struct sockaddr *)&local, &local_len); if (ret < 0) { ret = errno; - close(local_socket); + safe_socket_close(&local_socket); char errorstring[1024]; getErrorText((char *)errorstring, sizeof(errorstring)); warn("error %d: \"%s\". Could not retrieve socket's port!", errno, errorstring); @@ -494,7 +495,7 @@ uint16_t bind_UDP_port(int ip_family, const char *self_ip_address, uint32_t scop // debug(1,"UDP port chosen: %d.",desired_port); if (ret < 0) { - close(local_socket); + safe_socket_close(&local_socket); char errorstring[1024]; getErrorText((char *)errorstring, sizeof(errorstring)); die("error %d: \"%s\". Could not bind a UDP port! Check the udp_port_range is large enough -- " @@ -950,7 +951,8 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE); outbuf = malloc(trsa->MBEDTLS_PRIVATE_V3_ONLY(len)); #if MBEDTLS_VERSION_MAJOR == 3 - rc = mbedtls_pk_sign(&pkctx, MBEDTLS_MD_NONE, input, inlen, outbuf, mbedtls_pk_get_len(&pkctx), &olen, mbedtls_ctr_drbg_random, &ctr_drbg); + rc = mbedtls_pk_sign(&pkctx, MBEDTLS_MD_NONE, input, inlen, outbuf, mbedtls_pk_get_len(&pkctx), + &olen, mbedtls_ctr_drbg_random, &ctr_drbg); *outlen = olen; #else rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, @@ -1066,7 +1068,7 @@ int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) { const char *str = NULL; int response = CONFIG_FALSE; config_setting_t *s = config_lookup(cfg, where); - if (s != NULL) { + if (s != NULL) { if (config_setting_type(s) == CONFIG_TYPE_STRING) { str = config_setting_get_string(s); if (strcasecmp(str, "no") == 0) { @@ -1076,11 +1078,14 @@ int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) { (*dst) = 1; response = CONFIG_TRUE; } else { - die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"", where, str); + die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"", + where, str); return 0; } } else { - warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, and has been ignored.", where); + warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, " + "and has been ignored.", + where); } } return response; @@ -1245,6 +1250,59 @@ int check_int_or_list_setting(config_setting_t *setting, const int item) { return result; } +void service_type_to_string(APST_t service_type, char *string_space) { + if (string_space != NULL) { + string_space[0] = '\0'; + switch (service_type) { + case APST_auto: + strcpy(string_space, "auto"); + break; + case APST_classic: + strcpy(string_space, "classic"); + break; + case APST_forced_classic: + strcpy(string_space, "forced_classic"); + break; + case APST_airplay2: + strcpy(string_space, "airplay2"); + break; + } + } +} + +APST_t string_to_service_type(const char *parameter, const char *setting_name) { + APST_t response = APST_auto; + if (parameter != NULL) { + if (strcasecmp(parameter, "auto") == 0) { + response = APST_auto; + } else if (strcasecmp(parameter, "classic") == 0) { + response = APST_classic; + } else if (strcasecmp(parameter, "airplay1") == 0) { + response = APST_classic; + } else if (strcasecmp(parameter, "airplay2") == 0) { + response = APST_airplay2; + } else { + warn("The %s \"%s\" was ignored. It must be \"auto\", \"classic\" or \"airplay2\". (You can " + "use \"airplay1\" instead of \"classic\".)", + setting_name, parameter); + } +#ifndef CONFIG_AIRPLAY_2 + if (response == APST_airplay2) { + warn("This version of Shairport Sync supports does not support AirPlay 2. The %s \"%s\" " + "setting has been ignored.", + setting_name, parameter); + response = APST_auto; // reset to default + } +#endif + } + /* + char service_type_string[32]; + service_type_to_string(response, service_type_string); + debug(1, "config.service_type read from %s is: \"%s\".", setting_name, service_type_string); + */ + return response; +} + void command_set_volume(double volume) { // this has a cancellation point if waiting is enabled if (config.cmd_set_volume) { @@ -1310,11 +1368,11 @@ void command_start(void) { char **argV; if (config.cmd_start_returns_output) { - close(pipes[0]); + safe_socket_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]); + safe_socket_close(&pipes[1]); return; } } @@ -1344,9 +1402,9 @@ void command_start(void) { if (config.cmd_start_returns_output) { static char buffer[256]; int len; - close(pipes[1]); + safe_socket_close(&pipes[1]); len = read(pipes[0], buffer, 255); - close(pipes[0]); + safe_socket_close(&pipes[0]); buffer[len] = '\0'; if (buffer[len - 1] == '\n') buffer[len - 1] = '\0'; // strip trailing newlines @@ -1705,45 +1763,6 @@ int try_to_open_pipe_for_writing(const char *pathname) { return fdis; } -/* from - * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 - */ - -char *str_replace(const char *string, const char *substr, const char *replacement) { - char *tok = NULL; - char *newstr = NULL; - char *oldstr = NULL; - char *head = NULL; - - /* if either substr or replacement is NULL, duplicate string a let caller handle it */ - if (substr == NULL || replacement == NULL) - return strdup(string); - newstr = strdup(string); - head = newstr; - if (head) { - while ((tok = strstr(head, substr))) { - oldstr = newstr; - newstr = malloc(strlen(oldstr) - strlen(substr) + strlen(replacement) + 1); - /*failed to alloc mem, free old string and return NULL */ - if (newstr == NULL) { - free(oldstr); - return NULL; - } - memcpy(newstr, oldstr, tok - oldstr); - memcpy(newstr + (tok - oldstr), replacement, strlen(replacement)); - memcpy(newstr + (tok - oldstr) + strlen(replacement), tok + strlen(substr), - strlen(oldstr) - strlen(substr) - (tok - oldstr)); - memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement), 0, 1); - /* move back head right after the last replacement */ - head = newstr + (tok - oldstr) + strlen(replacement); - free(oldstr); - } - } else { - die("failed to allocate memory in str_replace."); - } - return newstr; -} - /* from http://burtleburtle.net/bob/rand/smallprng.html */ // this is not thread-safe, so we need a mutex on it to use it properly. @@ -1826,7 +1845,8 @@ void sps_nanosleep(const time_t sec, const long nanosec) { rem = req; } while ((result == -1) && (errno == EINTR)); if (result == -1) - debug(1, "Error in sps_nanosleep of %" PRIdMAX " sec and %ld nanoseconds: %d.", (intmax_t)sec, nanosec, errno); + debug(1, "Error in sps_nanosleep of %" PRIdMAX " sec and %ld nanoseconds: %d.", (intmax_t)sec, + nanosec, errno); } // Mac OS X doesn't have pthread_mutex_timedlock @@ -1914,7 +1934,7 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha if (strerror_r(r, errstr, sizeof(errstr)) == 0) { debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, errstr, mutexname, dstring); } else { - debug(1, "error %d: unlocking mutex \"%s\" at \"%s\".", r, mutexname, dstring); + debug(1, "error %d: unlocking mutex \"%s\" at \"%s\".", r, mutexname, dstring); } } pthread_setcancelstate(oldState, NULL); @@ -1938,10 +1958,9 @@ void plist_cleanup(void *arg) { #endif void socket_cleanup(void *arg) { - intptr_t fdp = (intptr_t)arg; - int soc = fdp; - debug(3, "socket_cleanup called for socket: %d.", soc); - close(fdp); + int *p = (int *)arg; + debug(3, "socket_cleanup called for socket: %d.", *p); + safe_socket_close(p); } void cv_cleanup(void *arg) { @@ -2349,12 +2368,8 @@ int get_device_id(uint8_t *id, int int_length) { #ifdef AF_PACKET if ((ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET)) { struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr; - if ( - ((ifa->ifa_flags & IFF_UP) != 0) && - ((ifa->ifa_flags & IFF_RUNNING) != 0) && - ((ifa->ifa_flags & IFF_LOOPBACK) == 0) && - (ifa->ifa_addr != 0) - ) { + if (((ifa->ifa_flags & IFF_UP) != 0) && ((ifa->ifa_flags & IFF_RUNNING) != 0) && + ((ifa->ifa_flags & IFF_LOOPBACK) == 0) && (ifa->ifa_addr != 0)) { found = 1; response = 0; for (i = 0; ((i < s->sll_halen) && (i < int_length)); i++) { @@ -2430,7 +2445,7 @@ int named_pthread_create(pthread_t *thread, const pthread_attr_t *attr, debug(1, "error creating thread \"%s\"", actual_name); } #ifndef COMPILE_FOR_OSX - else { + if (response == 0) { pthread_setname_np(*thread, actual_name); } #endif @@ -2481,6 +2496,7 @@ int named_pthread_create_with_priority(pthread_t *thread, int priority, } // ret == 0 if creating and setting up the attributes was successful if (ret == 0) { + ret = pthread_create(thread, &attr, start_routine, arg); pthread_attr_destroy(&attr); } @@ -2489,7 +2505,7 @@ int named_pthread_create_with_priority(pthread_t *thread, int priority, if (ret != 0) { ret = pthread_create(thread, NULL, start_routine, arg); if (failed_to_set_rt == 0) { - inform("Can not set realtime properties of a thread."); + inform("Can not set realtime properties of thread \"%s\".", actual_name); failed_to_set_rt = 1; } } diff --git a/common.h b/common.h index e18af0f9..3c523833 100644 --- a/common.h +++ b/common.h @@ -14,6 +14,14 @@ extern "C" { #include "definitions.h" #include "mdns.h" +// service type to be offered +typedef enum { + APST_auto = 0, + APST_classic, // must be classic / airplay 1 + APST_forced_classic, // must be classic / airplay 1 because no nqptp + APST_airplay2, // must be airplay 2; error if no NQPTP +} APST_t; // AirPlay Service Type + // struct sockaddr_in6 is bigger than struct sockaddr. derp #ifdef AF_INET6 #define SOCKADDR struct sockaddr_storage @@ -24,8 +32,10 @@ extern "C" { #endif #if defined(CONFIG_CONVOLUTION) +// impulse response filter file status typedef enum { ev_unchecked, ev_okay, ev_invalid } ir_file_evaluation; +// impulse response filter file record typedef struct { unsigned int samplerate; // initialized to 0, will be filter frame rate unsigned int channels; @@ -147,12 +157,13 @@ typedef enum { #define SPS_FORMAT_SET (((1 << (SPS_FORMAT_HIGHEST_NATIVE + 1)) - 1) - (1 << SPS_FORMAT_UNKNOWN)) #define SPS_RATE_SET (((1 << (SPS_RATE_HIGHEST + 1)) - 1) - (1 << SPS_RATE_UNKNOWN)) -// in SPS_CHANNEL_SET, bit 0 set means a channel set of no channels, bit 1 set means a channel set of 1 channel -// and so on to bit 31 meaning a channel set of 31 channels. We want to consider all possible channel sets -// apart from channel set 0. +// in SPS_CHANNEL_SET, bit 0 set means a channel set of no channels, bit 1 set means a channel set +// of 1 channel and so on to bit 31 meaning a channel set of 31 channels. We want to consider all +// possible channel sets apart from channel set 0. #define SPS_GREATEST_CHANNEL_COUNT 31 // should be 32 to be fully in line with ALSA limits -#define SPS_CHANNEL_SET 0xFFFFFFFE // channel sets 31 to 1, but no channel set 0 -// #define SPS_CHANNEL_SET (((1 << (SPS_GREATEST_CHANNEL_COUNT + 1)) - 1) - (1 << 0)) // channels 1 to 31, not 0-based! +#define SPS_CHANNEL_SET 0xFFFFFFFE // channel sets 31 to 1, but no channel set 0 +// #define SPS_CHANNEL_SET (((1 << (SPS_GREATEST_CHANNEL_COUNT + 1)) - 1) - (1 << 0)) // channels 1 +// to 31, not 0-based! #ifndef CONFIG_AIRPLAY_2 #define SPS_FORMAT_NON_FFMPEG_SET SPS_FORMAT_SET @@ -408,8 +419,11 @@ typedef struct { uint8_t airplay_pk[32]; // public key char *pk_string; char *nqptp_shared_memory_interface_name; // client name for nqptp service - int enable_HK_Access_Control; // true if the device is part of an Apple Home + int enable_HK_Access_Control; // true if the device is part of an Apple Home #endif + + APST_t service_type; // APST_auto, APST_classic, APST_forced_classic, APST_airplay2 + int unfixable_error_reported; // only report once. uint64_t eight_channel_layout; // non-zero means enabled and is a channel layout @@ -460,11 +474,6 @@ void set_requested_connection_state_to_output(int v); int try_to_open_pipe_for_writing( const char *pathname); // open it without blocking if it's not hooked up -/* from - * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 - */ -char *str_replace(const char *string, const char *substr, const char *replacement); - // based on http://burtleburtle.net/bob/rand/smallprng.html void r64init(uint64_t seed); @@ -562,6 +571,9 @@ unsigned int config_get_string_settings_as_string_array(config_setting_t *settin const char ***result); unsigned int config_get_int_settings_as_int_array(config_setting_t *setting, int **result); +APST_t string_to_service_type(const char *parameter, const char *setting_name); +void service_type_to_string(APST_t service_type, char *string_space); + void command_start(void); void command_stop(void); void command_execute(const char *command, const char *extra_argument, const int block); diff --git a/configure.ac b/configure.ac index e066b94f..06ecdeb6 100644 --- a/configure.ac +++ b/configure.ac @@ -469,7 +469,7 @@ if test "x$with_airplay_2" = "xyes" ; then ]) AC_CHECK_LIB([uuid],[uuid_generate], [], [AC_MSG_ERROR([AirPlay 2 support requires the uuid library -- uuid-dev suggested])]) systemd_after_args="${systemd_after_args}${systemd_after_args:+ }nqptp.service" - systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }nqptp.service" + systemd_wants_args="${systemd_wants_args}${systemd_wants_args:+ }nqptp.service" fi AM_CONDITIONAL([USE_AIRPLAY_2], [test "x$with_airplay_2" = "xyes"]) @@ -586,8 +586,13 @@ if test "x${systemd_requires_args}" != x ; then systemd_requires_args="Requires=${systemd_requires_args}" fi +if test "x${systemd_wants_args}" != x ; then + systemd_wants_args="Wants=${systemd_wants_args}" +fi + AC_SUBST([SYSTEMD_AFTER_ARGS], [${systemd_after_args}]) AC_SUBST([SYSTEMD_REQUIRES_ARGS], [${systemd_requires_args}]) +AC_SUBST([SYSTEMD_WANTS_ARGS], [${systemd_wants_args}]) # The man directory will not be recursively made unless you set the SUBDIR in Makefile.am. # But leave this as is to enable a distclean to be done. diff --git a/dacp.c b/dacp.c index 2252482f..f8d90404 100644 --- a/dacp.c +++ b/dacp.c @@ -45,6 +45,7 @@ #include "metadata/hub.h" #include "tinyhttp/http.h" +#include "utilities/network_utilities.h" typedef struct { int players_connection_thread_index; // the connection thread index when a player thread is @@ -88,7 +89,7 @@ void *response_realloc(__attribute__((unused)) void *opaque, void *ptr, int size t = realloc(ptr, size); } if (t == NULL) - debug(1, "response_realloc of size %d to ptr %" PRIxPTR " failed!", size, (uintptr_t) ptr); + debug(1, "response_realloc of size %d to ptr %" PRIxPTR " failed!", size, (uintptr_t)ptr); } return t; } @@ -117,8 +118,7 @@ void response_body(void *opaque, const char *data, int size) { static void response_header(__attribute__((unused)) void *opaque, __attribute__((unused)) const char *ckey, __attribute__((unused)) int nkey, __attribute__((unused)) const char *cvalue, - __attribute__((unused)) int nvalue) { /* example doesn't care about headers */ -} + __attribute__((unused)) int nvalue) { /* example doesn't care about headers */ } static void response_code(void *opaque, int code) { struct HttpResponse *response = (struct HttpResponse *)opaque; @@ -150,12 +150,6 @@ void mutex_lock_cleanup(void *arg) { debug(1, "Error releasing mutex."); } -void connect_cleanup(void *arg) { - int *fd = (int *)arg; - // debug(2, "dacp_send_command: close socket %d.",*fd); - close(*fd); -} - void http_cleanup(void *arg) { // debug(1, "http cleanup called."); struct http_roundtripper *rt = (struct http_roundtripper *)arg; @@ -234,7 +228,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // \"%s\".",errno,strerror(errno)); response.code = 497; // Can't establish a socket to the DACP server } else { - pthread_cleanup_push(connect_cleanup, (void *)&sockfd); + pthread_cleanup_push(socket_cleanup, (void *)&sockfd); // debug(2, "dacp_send_command: open socket %d.",sockfd); // This is for limiting the time to be spent waiting for a response. @@ -350,9 +344,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { 0); // this should *not* free the malloced buffer -- just pop the malloc cleanup } } - pthread_cleanup_pop(1); // this should close the socket - // close(sockfd); - // debug(1,"DACP socket closed."); + pthread_cleanup_pop(1); // this will close the socket + // debug(1,"DACP socket closed."); } pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock); // pthread_mutex_unlock(&dacp_conversation_lock); @@ -1302,8 +1295,8 @@ int dacp_set_volume(int32_t vo) { int32_t active_speakers = 0; for (i = 0; i < speaker_count; i++) { if (speaker_info[i].speaker_number == machine_number) { - debug(2, "Our speaker number found: %" PRId64 " with relative volume %" PRId32 ".", machine_number, - speaker_info[i].volume); + debug(2, "Our speaker number found: %" PRId64 " with relative volume %" PRId32 ".", + machine_number, speaker_info[i].volume); } if (speaker_info[i].active == 1) { active_speakers++; diff --git a/dbus-service.c b/dbus-service.c index 781fed0e..4dbd1a6a 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -418,7 +418,7 @@ static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { -#ifdef CONFIG_DACP_CLIENT +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("play"); #endif shairport_sync_remote_control_complete_play(skeleton, invocation); @@ -596,9 +596,9 @@ gboolean notify_convolution_enabled_callback(__attribute__((unused)) ShairportSy #endif #ifdef CONFIG_CONVOLUTION -gboolean notify_convolution_maximum_length_in_seconds_callback(ShairportSync *skeleton, - __attribute__((unused)) - gpointer user_data) { +gboolean +notify_convolution_maximum_length_in_seconds_callback(ShairportSync *skeleton, + __attribute__((unused)) gpointer user_data) { gdouble th = shairport_sync_get_convolution_maximum_length_in_seconds(skeleton); if ((th >= 0.0) && (th <= 15.0)) { @@ -612,10 +612,8 @@ gboolean notify_convolution_maximum_length_in_seconds_callback(ShairportSync *sk return TRUE; } #else -gboolean notify_convolution_maximum_length_in_seconds_callback(__attribute__((unused)) - ShairportSync *skeleton, - __attribute__((unused)) - gpointer user_data) { +gboolean notify_convolution_maximum_length_in_seconds_callback( + __attribute__((unused)) ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { warn(">> Convolution support is not built in to this build of Shairport Sync."); return TRUE; } @@ -643,16 +641,16 @@ gboolean notify_convolution_gain_callback(__attribute__((unused)) ShairportSync } #endif #ifdef CONFIG_CONVOLUTION -gboolean notify_convolution_impulse_response_files_callback(ShairportSync *skeleton, - __attribute__((unused)) - gpointer user_data) { +gboolean +notify_convolution_impulse_response_files_callback(ShairportSync *skeleton, + __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_convolution_impulse_response_files(skeleton); if (th != NULL) { debug(1, ">> freeing current configuration impulse response filter files."); free_ir_filenames(config.convolution_ir_files, config.convolution_ir_file_count); config.convolution_ir_files = NULL; config.convolution_ir_file_count = 0; - + config.convolution_ir_files = parse_ir_filenames(th, &config.convolution_ir_file_count); sanity_check_ir_files(1, config.convolution_ir_files, config.convolution_ir_file_count); debug(1, ">> setting %d configuration impulse response filter%s", @@ -662,10 +660,9 @@ gboolean notify_convolution_impulse_response_files_callback(ShairportSync *skele return TRUE; } #else -gboolean notify_convolution_impulse_response_files_callback(__attribute__((unused)) - ShairportSync *skeleton, - __attribute__((unused)) - gpointer user_data) { +gboolean +notify_convolution_impulse_response_files_callback(__attribute__((unused)) ShairportSync *skeleton, + __attribute__((unused)) gpointer user_data) { __attribute__((unused)) char *th = (char *)shairport_sync_get_convolution_impulse_response_files(skeleton); return TRUE; @@ -716,7 +713,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton, gdouble iv = shairport_sync_get_volume(skeleton); if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) { debug(2, ">> set volume to %7.4f.", iv); - config.airplay_volume = iv; + config.airplay_volume = iv; } else { debug(1, ">> invalid volume: %f. Ignored.", iv); shairport_sync_set_volume(skeleton, config.airplay_volume); @@ -760,9 +757,10 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_alacdecoder(skeleton); -#ifdef CONFIG_AIRPLAY_2 +#ifdef CONFIG_AIRPLAY_2 if (strcasecmp(th, "ffmpeg") != 0) { - warn(" This request, to set the decoder to \"%s\", is ignored. For AirPlay 2, the FFmpeg decoder is always used.", + warn(" This request, to set the decoder to \"%s\", is ignored. For AirPlay 2, the FFmpeg " + "decoder is always used.", th); } #else @@ -879,7 +877,8 @@ gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton, return TRUE; } #else -gboolean notify_shuffle_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton, +gboolean notify_shuffle_callback(__attribute__((unused)) + ShairportSyncAdvancedRemoteControl *skeleton, __attribute__((unused)) gpointer user_data) { return TRUE; } @@ -924,7 +923,8 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto return TRUE; } #else -gboolean notify_loop_status_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton, +gboolean notify_loop_status_callback(__attribute__((unused)) + ShairportSyncAdvancedRemoteControl *skeleton, __attribute__((unused)) gpointer user_data) { return TRUE; } @@ -971,16 +971,14 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv static gboolean on_handle_drop_session(ShairportSync *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { - release_play_lock(NULL); // stop any current session and don't replace it + stop_play(); // stop any current session and don't replace it shairport_sync_complete_drop_session(skeleton, invocation); return TRUE; } -static gboolean on_handle_set_frame_position_update_interval(ShairportSync *skeleton, - GDBusMethodInvocation *invocation, - const gdouble seconds, - __attribute__((unused)) - gpointer user_data) { +static gboolean on_handle_set_frame_position_update_interval( + ShairportSync *skeleton, GDBusMethodInvocation *invocation, const gdouble seconds, + __attribute__((unused)) gpointer user_data) { debug(1, ">> set frame position update interval to %.6f.", seconds); config.metadata_progress_interval = seconds; shairport_sync_complete_set_frame_position_update_interval(skeleton, invocation); @@ -1062,10 +1060,10 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name g_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::file-and-line", G_CALLBACK(notify_file_and_line_callback), NULL); - + g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-fast-forward", G_CALLBACK(on_handle_fast_forward), NULL); - + g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-rewind", G_CALLBACK(on_handle_rewind), NULL); g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-toggle-mute", diff --git a/definitions.h b/definitions.h index 18a74d97..b27cba71 100644 --- a/definitions.h +++ b/definitions.h @@ -5,7 +5,6 @@ #include "config.h" - // for generating our own UUIDs #define SHAIRPORT_SYNC_DEVICE_NAMESPACE "01d7c137-6316-455d-a52f-dfb529f26adf" diff --git a/docker/Dockerfile b/docker/Dockerfile index dcae96ca..106e07f5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -106,6 +106,33 @@ RUN DESTDIR=install make install WORKDIR / ##### SPS END ##### +# Shairport Sync Development Environment +# This (large) image contains the custom-built FFmpeg library and NQPTP, Avahi, D-Bus, etc., Shairport Sync source and all tools. +# When started, D-Bus, Avahi and NQPTP are set to run in the background. +# To build it, here is a sample local build instruction: +# $ docker build --build-arg SHAIRPORT_SYNC_BRANCH=development --build-arg NQPTP_BRANCH=main --target dev -t shairport-sync-dev -f docker/Dockerfile . +# Sample local use (remember to save your changes): +# $ docker run -it \ +# --cap-add=SYS_NICE --net host --device /dev/snd \ +# -v /etc/shairport-sync.conf:/etc/shairport-sync.conf \ +# shairport-sync-dev + +##### DEV ##### +FROM shairport-sync AS dev +RUN apk add bash + +RUN addgroup shairport-sync && \ + adduser -D shairport-sync -G shairport-sync && \ + addgroup -g 29 docker_audio && \ + addgroup shairport-sync docker_audio && \ + addgroup shairport-sync audio && \ + mkdir -p /run/dbus + +COPY --from=nqptp /nqptp/nqptp /usr/local/bin/nqptp +COPY --chmod=755 ./docker/devrun.sh ./devrun.sh +ENTRYPOINT ["./devrun.sh"] +##### DEV END ##### + ##### STATIC FILES ##### FROM scratch AS files diff --git a/docker/devrun.sh b/docker/devrun.sh new file mode 100755 index 00000000..8d59a639 --- /dev/null +++ b/docker/devrun.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# exit if any command returns a non-zero result +set -e +echo "Shairport Sync Dev Startup ($(date))" +if [ -z ${ENABLE_AVAHI+x} ] || [ $ENABLE_AVAHI -eq 1 ]; then + rm -rf /run/dbus/dbus.pid + rm -rf /run/avahi-daemon/pid + dbus-uuidgen --ensure + dbus-daemon --system + avahi-daemon --daemonize --no-chroot +fi +# Don't launch NQPTP if classic only +SERVICE_TYPE="" +for arg in "$@"; do + case "$arg" in + --service-type=classic|--service-type=airplay1) + SERVICE_TYPE="${arg#--service-type=}" + ;; + esac +done +if [ -z "$SERVICE_TYPE" ]; then + # not looking for Classic aka AirPlay 1 so start NQPTP for AirPlay 2 + echo "Starting NQPTP ($(date))" + (/usr/local/bin/nqptp > /dev/null 2>&1) & +fi +while [ ! -f /var/run/avahi-daemon/pid ]; do + echo "Warning: avahi is not running, sleeping for 5 seconds before checking again" + sleep 5 +done +# for PipeWire +export XDG_RUNTIME_DIR=/tmp +# for PulseAudio +export PULSE_SERVER=unix:/tmp/pulseaudio.socket +export PULSE_COOKIE=/tmp/pulseaudio.cookie +echo "Finished startup tasks ($(date)), starting bash." +exec /bin/bash \ No newline at end of file diff --git a/docker/run.sh b/docker/run.sh index bb1b1da7..f02daa66 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -15,9 +15,23 @@ if [ -z ${ENABLE_AVAHI+x} ] || [ $ENABLE_AVAHI -eq 1 ]; then avahi-daemon --daemonize --no-chroot fi -echo "Starting NQPTP ($(date))" +# Don't launch NQPTP if it classic only -(/usr/local/bin/nqptp > /dev/null 2>&1) & +SERVICE_TYPE="" + +for arg in "$@"; do + case "$arg" in + --service-type=classic|--service-type=airplay1) + SERVICE_TYPE="${arg#--service-type=}" + ;; + esac +done + +if [ -z "$SERVICE_TYPE" ]; then + # not looking for Classic aka AirPlay 1 so start NQPTP for AirPlay 2 + echo "Starting NQPTP ($(date))" + (/usr/local/bin/nqptp > /dev/null 2>&1) & +fi while [ ! -f /var/run/avahi-daemon/pid ]; do echo "Warning: avahi is not running, sleeping for 5 seconds before trying to start shairport-sync" diff --git a/man/shairport-sync.1.xml b/man/shairport-sync.1.xml index 44a3ff36..28f0d11b 100644 --- a/man/shairport-sync.1.xml +++ b/man/shairport-sync.1.xml @@ -39,7 +39,9 @@ [-a service-name | --name=service-name] [-B command | --onstart=command] [-c configurationfile | --configfile=configurationfile] + [--classic-airplay-only] [-d | --daemon] + [--dbus-default-message-bus=bus] [-E command | --onstop=command] [-g | --get-cover-art] [-j | --justDaemoniseNoPIDFile] @@ -202,6 +204,15 @@ shairport-sync has been compiled with libdaemon support.

+ + + +