Enhancements
Enable the AirPlay 2 build to operate an optional classic-AirPlay-only service or to gracefully degrade to classic AirPlay, as follows:
1. Add a new command-line option '-—service-type=<type>' and an equivalent configuration entry 'service_type = "<type>"' in the 'general' section of the configuration file,
where <type> can be "auto", "classic" or "airplay2":
1. "auto" (default) means that the service will be AirPlay 2 if NQPTP is running. If NQPTP is not running, classic AirPlay service will be provided instead, and in that case,
"(Classic)" will be appended to the default AirPlay service name visible to AirPlay clients like Apple Music, for example "RaspberryPi3B (Classic)".
2. "classic" means the service will be classic AirPlay (aka AirPlay 1).
3. "airplay2" means the service will be the modern AirPlay 2. In this case, as distinct from "auto", if NQPTP is not running, Shairport Sync will log an error and terminate.
2. In the systemd service file, NQPTP is now a "Want" rather than a "Require". If it's present, then it will be launched before Shairport Sync. If it's absent, Shairport Sync will launch anyway.
3. Improve the delivery of input format changes and emit 'sdsc' metadata when changes occur.
4. Emit new format information in the log if statistics is enabled.
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.
This commit is contained in:
@@ -23,7 +23,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
nqptp_branch: ${{ env.NQPTP_BRANCH }}
|
nqptp_branch: ${{ env.NQPTP_BRANCH }}
|
||||||
push_docker_image: ${{ env.PUSH_DOCKER_IMAGE }}
|
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:
|
steps:
|
||||||
- name: Push docker image if this is a tag.
|
- name: Push docker image if this is a tag.
|
||||||
if: github.ref_type == 'tag'
|
if: github.ref_type == 'tag'
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# Working with PulseAudio or PipeWire
|
# 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).
|
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.
|
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.
|
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.
|
4. Remember to specify which backend Shairport Sync should use in the configuration file or on the command line.
|
||||||
|
|
||||||
## Automatic Startup of Shairport Sync
|
## 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.
|
||||||
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.
|
|
||||||
|
|||||||
Executable → Regular
+734
-866
File diff suppressed because it is too large
Load Diff
Executable → Regular
+122
-126
@@ -22,147 +22,143 @@
|
|||||||
#ifndef _AUDIOFFT_H
|
#ifndef _AUDIOFFT_H
|
||||||
#define _AUDIOFFT_H
|
#define _AUDIOFFT_H
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AudioFFT provides real-to-complex/complex-to-real FFT routines.
|
* AudioFFT provides real-to-complex/complex-to-real FFT routines.
|
||||||
*
|
*
|
||||||
* Features:
|
* Features:
|
||||||
*
|
*
|
||||||
* - Real-complex FFT and complex-real inverse FFT for power-of-2-sized real data.
|
* - 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).
|
* - 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
|
* - Complex data is handled in "split-complex" format, i.e. there are separate
|
||||||
* (split-complex arrays have to be of length (size/2+1) representing bins from DC
|
* arrays for the real and imaginary parts which can be useful for SIMD optimizations
|
||||||
* to Nyquist frequency).
|
* (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).
|
*
|
||||||
*
|
* - 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).
|
* - 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:
|
*
|
||||||
*
|
* How to use it in your project:
|
||||||
* - Add the .h and .cpp file to your project - that's all.
|
*
|
||||||
*
|
* - 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
|
* - To get extra speed, you can link FFTW3 to your project and define
|
||||||
* according license).
|
* 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
|
* - To get the best speed on Apple platforms, you can link the Apple
|
||||||
* AUDIOFFT_APPLE_ACCELERATE (however, please check whether your
|
* Accelerate framework to your project and define
|
||||||
* project suits the according license).
|
* AUDIOFFT_APPLE_ACCELERATE (however, please check whether your
|
||||||
*
|
* project suits the according license).
|
||||||
*
|
*
|
||||||
* Remarks:
|
*
|
||||||
*
|
* Remarks:
|
||||||
* - AudioFFT is not intended to be the fastest FFT, but to be a fast-enough
|
*
|
||||||
* FFT suitable for most audio applications.
|
* - 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.
|
*
|
||||||
*
|
* - AudioFFT uses the quite liberal MIT license.
|
||||||
*
|
*
|
||||||
* Example usage:
|
*
|
||||||
* @code
|
* Example usage:
|
||||||
* #include "AudioFFT.h"
|
* @code
|
||||||
*
|
* #include "AudioFFT.h"
|
||||||
* void Example()
|
*
|
||||||
* {
|
* void Example()
|
||||||
* const size_t fftSize = 1024; // Needs to be power of 2!
|
* {
|
||||||
*
|
* const size_t fftSize = 1024; // Needs to be power of 2!
|
||||||
* std::vector<float> input(fftSize, 0.0f);
|
*
|
||||||
* std::vector<float> re(audiofft::AudioFFT::ComplexSize(fftSize));
|
* std::vector<float> input(fftSize, 0.0f);
|
||||||
* std::vector<float> im(audiofft::AudioFFT::ComplexSize(fftSize));
|
* std::vector<float> re(audiofft::AudioFFT::ComplexSize(fftSize));
|
||||||
* std::vector<float> output(fftSize);
|
* std::vector<float> im(audiofft::AudioFFT::ComplexSize(fftSize));
|
||||||
*
|
* std::vector<float> output(fftSize);
|
||||||
* audiofft::AudioFFT fft;
|
*
|
||||||
* fft.init(1024);
|
* audiofft::AudioFFT fft;
|
||||||
* fft.fft(input.data(), re.data(), im.data());
|
* fft.init(1024);
|
||||||
* fft.ifft(output.data(), re.data(), im.data());
|
* fft.fft(input.data(), re.data(), im.data());
|
||||||
* }
|
* fft.ifft(output.data(), re.data(), im.data());
|
||||||
* @endcode
|
* }
|
||||||
*/
|
* @endcode
|
||||||
|
*/
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
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 Destructor
|
||||||
* @brief Performs 1D FFTs
|
|
||||||
*/
|
*/
|
||||||
class AudioFFT
|
~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<detail::AudioFFTImpl> _impl;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @brief Initializes the FFT object
|
||||||
* @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.
|
* @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<detail::AudioFFTImpl> _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
|
#endif // Header guard
|
||||||
|
|||||||
@@ -140,8 +140,11 @@ void ConvolverThreadPool::clearState(size_t convolverId) {
|
|||||||
if (convolverId < _convolvers.size()) {
|
if (convolverId < _convolvers.size()) {
|
||||||
waitForAll();
|
waitForAll();
|
||||||
} else {
|
} 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
|
/* this is the old version
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public:
|
|||||||
|
|
||||||
// Get the number of threads
|
// Get the number of threads
|
||||||
size_t getNumThreads() const { return _threads.size(); }
|
size_t getNumThreads() const { return _threads.size(); }
|
||||||
|
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
Executable → Regular
+51
-87
@@ -20,47 +20,25 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#if defined (FFTCONVOLVER_USE_SSE)
|
#if defined(FFTCONVOLVER_USE_SSE)
|
||||||
#include <xmmintrin.h>
|
#include <xmmintrin.h>
|
||||||
#endif
|
#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() :
|
FFTConvolver::~FFTConvolver() { reset(); }
|
||||||
_blockSize(0),
|
|
||||||
_segSize(0),
|
|
||||||
_segCount(0),
|
|
||||||
_fftComplexSize(0),
|
|
||||||
_segments(),
|
|
||||||
_segmentsIR(),
|
|
||||||
_fftBuffer(),
|
|
||||||
_fft(),
|
|
||||||
_preMultiplied(),
|
|
||||||
_conv(),
|
|
||||||
_overlap(),
|
|
||||||
_current(0),
|
|
||||||
_inputBuffer(),
|
|
||||||
_inputBufferFill(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void FFTConvolver::reset() {
|
||||||
FFTConvolver::~FFTConvolver()
|
for (size_t i = 0; i < _segCount; ++i) {
|
||||||
{
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void FFTConvolver::reset()
|
|
||||||
{
|
|
||||||
for (size_t i=0; i<_segCount; ++i)
|
|
||||||
{
|
|
||||||
delete _segments[i];
|
delete _segments[i];
|
||||||
delete _segmentsIR[i];
|
delete _segmentsIR[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
_blockSize = 0;
|
_blockSize = 0;
|
||||||
_segSize = 0;
|
_segSize = 0;
|
||||||
_segCount = 0;
|
_segCount = 0;
|
||||||
@@ -77,115 +55,101 @@ void FFTConvolver::reset()
|
|||||||
_inputBufferFill = 0;
|
_inputBufferFill = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFTConvolver::clearState()
|
void FFTConvolver::clearState() {
|
||||||
{
|
if (_segCount == 0) {
|
||||||
if (_segCount == 0)
|
|
||||||
{
|
|
||||||
return; // Not initialized
|
return; // Not initialized
|
||||||
}
|
}
|
||||||
|
|
||||||
_inputBuffer.setZero();
|
_inputBuffer.setZero();
|
||||||
_inputBufferFill = 0;
|
_inputBufferFill = 0;
|
||||||
_overlap.setZero();
|
_overlap.setZero();
|
||||||
|
|
||||||
for (size_t i = 0; i < _segCount; ++i)
|
for (size_t i = 0; i < _segCount; ++i) {
|
||||||
{
|
|
||||||
_segments[i]->setZero();
|
_segments[i]->setZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
_preMultiplied.setZero();
|
_preMultiplied.setZero();
|
||||||
_conv.setZero();
|
_conv.setZero();
|
||||||
_current = 0;
|
_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();
|
reset();
|
||||||
|
|
||||||
if (blockSize == 0)
|
if (blockSize == 0) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore zeros at the end of the impulse response because they only waste computation time
|
// 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;
|
--irLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (irLen == 0)
|
if (irLen == 0) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_blockSize = NextPowerOf2(blockSize);
|
_blockSize = NextPowerOf2(blockSize);
|
||||||
_segSize = 2 * _blockSize;
|
_segSize = 2 * _blockSize;
|
||||||
_segCount = static_cast<size_t>(::ceil(static_cast<float>(irLen) / static_cast<float>(_blockSize)));
|
_segCount =
|
||||||
|
static_cast<size_t>(::ceil(static_cast<float>(irLen) / static_cast<float>(_blockSize)));
|
||||||
_fftComplexSize = audiofft::AudioFFT::ComplexSize(_segSize);
|
_fftComplexSize = audiofft::AudioFFT::ComplexSize(_segSize);
|
||||||
|
|
||||||
// FFT
|
// FFT
|
||||||
_fft.init(_segSize);
|
_fft.init(_segSize);
|
||||||
_fftBuffer.resize(_segSize);
|
_fftBuffer.resize(_segSize);
|
||||||
|
|
||||||
// Prepare segments
|
// Prepare segments
|
||||||
for (size_t i=0; i<_segCount; ++i)
|
for (size_t i = 0; i < _segCount; ++i) {
|
||||||
{
|
_segments.push_back(new SplitComplex(_fftComplexSize));
|
||||||
_segments.push_back(new SplitComplex(_fftComplexSize));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare IR
|
// Prepare IR
|
||||||
for (size_t i=0; i<_segCount; ++i)
|
for (size_t i = 0; i < _segCount; ++i) {
|
||||||
{
|
SplitComplex *segment = new SplitComplex(_fftComplexSize);
|
||||||
SplitComplex* segment = new SplitComplex(_fftComplexSize);
|
|
||||||
const size_t remaining = irLen - (i * _blockSize);
|
const size_t remaining = irLen - (i * _blockSize);
|
||||||
const size_t sizeCopy = (remaining >= _blockSize) ? _blockSize : remaining;
|
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());
|
_fft.fft(_fftBuffer.data(), segment->re(), segment->im());
|
||||||
_segmentsIR.push_back(segment);
|
_segmentsIR.push_back(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare convolution buffers
|
// Prepare convolution buffers
|
||||||
_preMultiplied.resize(_fftComplexSize);
|
_preMultiplied.resize(_fftComplexSize);
|
||||||
_conv.resize(_fftComplexSize);
|
_conv.resize(_fftComplexSize);
|
||||||
_overlap.resize(_blockSize);
|
_overlap.resize(_blockSize);
|
||||||
|
|
||||||
// Prepare input buffer
|
// Prepare input buffer
|
||||||
_inputBuffer.resize(_blockSize);
|
_inputBuffer.resize(_blockSize);
|
||||||
_inputBufferFill = 0;
|
_inputBufferFill = 0;
|
||||||
|
|
||||||
// Reset current position
|
// Reset current position
|
||||||
_current = 0;
|
_current = 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FFTConvolver::process(const Sample *input, Sample *output, size_t len) {
|
||||||
void FFTConvolver::process(const Sample* input, Sample* output, size_t len)
|
if (_segCount == 0) {
|
||||||
{
|
|
||||||
if (_segCount == 0)
|
|
||||||
{
|
|
||||||
::memset(output, 0, len * sizeof(Sample));
|
::memset(output, 0, len * sizeof(Sample));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t processed = 0;
|
size_t processed = 0;
|
||||||
while (processed < len)
|
while (processed < len) {
|
||||||
{
|
|
||||||
const bool inputBufferWasEmpty = (_inputBufferFill == 0);
|
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;
|
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
|
// Forward FFT
|
||||||
CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize);
|
CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize);
|
||||||
_fft.fft(_fftBuffer.data(), _segments[_current]->re(), _segments[_current]->im());
|
_fft.fft(_fftBuffer.data(), _segments[_current]->re(), _segments[_current]->im());
|
||||||
|
|
||||||
// Complex multiplication
|
// Complex multiplication
|
||||||
if (inputBufferWasEmpty)
|
if (inputBufferWasEmpty) {
|
||||||
{
|
|
||||||
_preMultiplied.setZero();
|
_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 indexIr = i;
|
||||||
const size_t indexAudio = (_current + i) % _segCount;
|
const size_t indexAudio = (_current + i) % _segCount;
|
||||||
ComplexMultiplyAccumulate(_preMultiplied, *_segmentsIR[indexIr], *_segments[indexAudio]);
|
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());
|
_fft.ifft(_fftBuffer.data(), _conv.re(), _conv.im());
|
||||||
|
|
||||||
// Add overlap
|
// 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
|
// Input buffer full => Next block
|
||||||
_inputBufferFill += processing;
|
_inputBufferFill += processing;
|
||||||
if (_inputBufferFill == _blockSize)
|
if (_inputBufferFill == _blockSize) {
|
||||||
{
|
|
||||||
// Input buffer is empty again now
|
// Input buffer is empty again now
|
||||||
_inputBuffer.setZero();
|
_inputBuffer.setZero();
|
||||||
_inputBufferFill = 0;
|
_inputBufferFill = 0;
|
||||||
|
|
||||||
// Save the overlap
|
// Save the overlap
|
||||||
::memcpy(_overlap.data(), _fftBuffer.data()+_blockSize, _blockSize * sizeof(Sample));
|
::memcpy(_overlap.data(), _fftBuffer.data() + _blockSize, _blockSize * sizeof(Sample));
|
||||||
|
|
||||||
// Update current segment
|
// Update current segment
|
||||||
_current = (_current > 0) ? (_current - 1) : (_segCount - 1);
|
_current = (_current > 0) ? (_current - 1) : (_segCount - 1);
|
||||||
@@ -218,5 +182,5 @@ void FFTConvolver::process(const Sample* input, Sample* output, size_t len)
|
|||||||
processed += processing;
|
processed += processing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // End of namespace fftconvolver
|
} // End of namespace fftconvolver
|
||||||
|
|||||||
Executable → Regular
+47
-50
@@ -27,61 +27,58 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace fftconvolver {
|
||||||
namespace fftconvolver
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class FFTConvolver
|
* @class FFTConvolver
|
||||||
* @brief Implementation of a partitioned FFT convolution algorithm with uniform block size
|
* @brief Implementation of a partitioned FFT convolution algorithm with uniform block size
|
||||||
*
|
*
|
||||||
* Some notes on how to use it:
|
* Some notes on how to use it:
|
||||||
*
|
*
|
||||||
* - After initialization with an impulse response, subsequent data portions of
|
* - After initialization with an impulse response, subsequent data portions of
|
||||||
* arbitrary length can be convolved. The convolver internally can handle
|
* arbitrary length can be convolved. The convolver internally can handle
|
||||||
* this by using appropriate buffering.
|
* this by using appropriate buffering.
|
||||||
*
|
*
|
||||||
* - The convolver works without "latency" (except for the required
|
* - The convolver works without "latency" (except for the required
|
||||||
* processing time, of course), i.e. the output always is the convolved
|
* processing time, of course), i.e. the output always is the convolved
|
||||||
* input for each processing call.
|
* input for each processing call.
|
||||||
*
|
*
|
||||||
* - The convolver is suitable for real-time processing which means that no
|
* - The convolver is suitable for real-time processing which means that no
|
||||||
* "unpredictable" operations like allocations, locking, API calls, etc. are
|
* "unpredictable" operations like allocations, locking, API calls, etc. are
|
||||||
* performed during processing (all necessary allocations and preparations take
|
* performed during processing (all necessary allocations and preparations take
|
||||||
* place during initialization).
|
* place during initialization).
|
||||||
*/
|
*/
|
||||||
class FFTConvolver
|
class FFTConvolver {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
FFTConvolver();
|
FFTConvolver();
|
||||||
virtual ~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
|
* @brief Initializes the convolver
|
||||||
* @param input The input samples
|
* @param blockSize Block size internally used by the convolver (partition size)
|
||||||
* @param output The convolution result
|
* @param ir The impulse response
|
||||||
* @param len Number of input/output samples
|
* @param irLen Length of the impulse response
|
||||||
*/
|
* @return true: Success - false: Failed
|
||||||
void process(const Sample* input, Sample* output, size_t len);
|
*/
|
||||||
|
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();
|
void reset();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Clears audio history
|
* @brief Clears audio history
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void clearState();
|
void clearState();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -89,8 +86,8 @@ private:
|
|||||||
size_t _segSize;
|
size_t _segSize;
|
||||||
size_t _segCount;
|
size_t _segCount;
|
||||||
size_t _fftComplexSize;
|
size_t _fftComplexSize;
|
||||||
std::vector<SplitComplex*> _segments;
|
std::vector<SplitComplex *> _segments;
|
||||||
std::vector<SplitComplex*> _segmentsIR;
|
std::vector<SplitComplex *> _segmentsIR;
|
||||||
SampleBuffer _fftBuffer;
|
SampleBuffer _fftBuffer;
|
||||||
audiofft::AudioFFT _fft;
|
audiofft::AudioFFT _fft;
|
||||||
SplitComplex _preMultiplied;
|
SplitComplex _preMultiplied;
|
||||||
@@ -101,10 +98,10 @@ private:
|
|||||||
size_t _inputBufferFill;
|
size_t _inputBufferFill;
|
||||||
|
|
||||||
// Prevent uncontrolled usage
|
// Prevent uncontrolled usage
|
||||||
FFTConvolver(const FFTConvolver&);
|
FFTConvolver(const FFTConvolver &);
|
||||||
FFTConvolver& operator=(const FFTConvolver&);
|
FFTConvolver &operator=(const FFTConvolver &);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // End of namespace fftconvolver
|
} // End of namespace fftconvolver
|
||||||
|
|
||||||
#endif // Header guard
|
#endif // Header guard
|
||||||
|
|||||||
+30
-48
@@ -21,12 +21,9 @@
|
|||||||
|
|
||||||
#include "Utilities.h"
|
#include "Utilities.h"
|
||||||
|
|
||||||
|
namespace fftconvolver {
|
||||||
|
|
||||||
namespace fftconvolver
|
bool SSEEnabled() {
|
||||||
{
|
|
||||||
|
|
||||||
bool SSEEnabled()
|
|
||||||
{
|
|
||||||
#if defined(FFTCONVOLVER_USE_SSE)
|
#if defined(FFTCONVOLVER_USE_SSE)
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
@@ -34,47 +31,35 @@ bool SSEEnabled()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Sum(Sample *FFTCONVOLVER_RESTRICT result, const Sample *FFTCONVOLVER_RESTRICT a,
|
||||||
void Sum(Sample* FFTCONVOLVER_RESTRICT result,
|
const Sample *FFTCONVOLVER_RESTRICT b, size_t len) {
|
||||||
const Sample* FFTCONVOLVER_RESTRICT a,
|
|
||||||
const Sample* FFTCONVOLVER_RESTRICT b,
|
|
||||||
size_t len)
|
|
||||||
{
|
|
||||||
const size_t end4 = 4 * (len / 4);
|
const size_t end4 = 4 * (len / 4);
|
||||||
for (size_t i=0; i<end4; i+=4)
|
for (size_t i = 0; i < end4; i += 4) {
|
||||||
{
|
result[i + 0] = a[i + 0] + b[i + 0];
|
||||||
result[i+0] = a[i+0] + b[i+0];
|
result[i + 1] = a[i + 1] + b[i + 1];
|
||||||
result[i+1] = a[i+1] + b[i+1];
|
result[i + 2] = a[i + 2] + b[i + 2];
|
||||||
result[i+2] = a[i+2] + b[i+2];
|
result[i + 3] = a[i + 3] + b[i + 3];
|
||||||
result[i+3] = a[i+3] + b[i+3];
|
|
||||||
}
|
}
|
||||||
for (size_t i=end4; i<len; ++i)
|
for (size_t i = end4; i < len; ++i) {
|
||||||
{
|
|
||||||
result[i] = a[i] + b[i];
|
result[i] = a[i] + b[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComplexMultiplyAccumulate(SplitComplex &result, const SplitComplex &a, const SplitComplex &b) {
|
||||||
void ComplexMultiplyAccumulate(SplitComplex& result, const SplitComplex& a, const SplitComplex& b)
|
|
||||||
{
|
|
||||||
assert(result.size() == a.size());
|
assert(result.size() == a.size());
|
||||||
assert(result.size() == b.size());
|
assert(result.size() == b.size());
|
||||||
ComplexMultiplyAccumulate(result.re(), result.im(), a.re(), a.im(), b.re(), b.im(), result.size());
|
ComplexMultiplyAccumulate(result.re(), result.im(), a.re(), a.im(), b.re(), b.im(),
|
||||||
|
result.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComplexMultiplyAccumulate(Sample *FFTCONVOLVER_RESTRICT re, Sample *FFTCONVOLVER_RESTRICT im,
|
||||||
void ComplexMultiplyAccumulate(Sample* FFTCONVOLVER_RESTRICT re,
|
const Sample *FFTCONVOLVER_RESTRICT reA,
|
||||||
Sample* FFTCONVOLVER_RESTRICT im,
|
const Sample *FFTCONVOLVER_RESTRICT imA,
|
||||||
const Sample* FFTCONVOLVER_RESTRICT reA,
|
const Sample *FFTCONVOLVER_RESTRICT reB,
|
||||||
const Sample* FFTCONVOLVER_RESTRICT imA,
|
const Sample *FFTCONVOLVER_RESTRICT imB, const size_t len) {
|
||||||
const Sample* FFTCONVOLVER_RESTRICT reB,
|
|
||||||
const Sample* FFTCONVOLVER_RESTRICT imB,
|
|
||||||
const size_t len)
|
|
||||||
{
|
|
||||||
#if defined(FFTCONVOLVER_USE_SSE)
|
#if defined(FFTCONVOLVER_USE_SSE)
|
||||||
const size_t end4 = 4 * (len / 4);
|
const size_t end4 = 4 * (len / 4);
|
||||||
for (size_t i=0; i<end4; i+=4)
|
for (size_t i = 0; i < end4; i += 4) {
|
||||||
{
|
|
||||||
const __m128 ra = _mm_load_ps(&reA[i]);
|
const __m128 ra = _mm_load_ps(&reA[i]);
|
||||||
const __m128 rb = _mm_load_ps(&reB[i]);
|
const __m128 rb = _mm_load_ps(&reB[i]);
|
||||||
const __m128 ia = _mm_load_ps(&imA[i]);
|
const __m128 ia = _mm_load_ps(&imA[i]);
|
||||||
@@ -88,26 +73,23 @@ void ComplexMultiplyAccumulate(Sample* FFTCONVOLVER_RESTRICT re,
|
|||||||
imag = _mm_add_ps(imag, _mm_mul_ps(ia, rb));
|
imag = _mm_add_ps(imag, _mm_mul_ps(ia, rb));
|
||||||
_mm_store_ps(&im[i], imag);
|
_mm_store_ps(&im[i], imag);
|
||||||
}
|
}
|
||||||
for (size_t i=end4; i<len; ++i)
|
for (size_t i = end4; i < len; ++i) {
|
||||||
{
|
|
||||||
re[i] += reA[i] * reB[i] - imA[i] * imB[i];
|
re[i] += reA[i] * reB[i] - imA[i] * imB[i];
|
||||||
im[i] += reA[i] * imB[i] + imA[i] * reB[i];
|
im[i] += reA[i] * imB[i] + imA[i] * reB[i];
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
const size_t end4 = 4 * (len / 4);
|
const size_t end4 = 4 * (len / 4);
|
||||||
for (size_t i=0; i<end4; i+=4)
|
for (size_t i = 0; i < end4; i += 4) {
|
||||||
{
|
re[i + 0] += reA[i + 0] * reB[i + 0] - imA[i + 0] * imB[i + 0];
|
||||||
re[i+0] += reA[i+0] * reB[i+0] - imA[i+0] * imB[i+0];
|
re[i + 1] += reA[i + 1] * reB[i + 1] - imA[i + 1] * imB[i + 1];
|
||||||
re[i+1] += reA[i+1] * reB[i+1] - imA[i+1] * imB[i+1];
|
re[i + 2] += reA[i + 2] * reB[i + 2] - imA[i + 2] * imB[i + 2];
|
||||||
re[i+2] += reA[i+2] * reB[i+2] - imA[i+2] * imB[i+2];
|
re[i + 3] += reA[i + 3] * reB[i + 3] - imA[i + 3] * imB[i + 3];
|
||||||
re[i+3] += reA[i+3] * reB[i+3] - imA[i+3] * imB[i+3];
|
im[i + 0] += reA[i + 0] * imB[i + 0] + imA[i + 0] * reB[i + 0];
|
||||||
im[i+0] += reA[i+0] * imB[i+0] + imA[i+0] * reB[i+0];
|
im[i + 1] += reA[i + 1] * imB[i + 1] + imA[i + 1] * reB[i + 1];
|
||||||
im[i+1] += reA[i+1] * imB[i+1] + imA[i+1] * reB[i+1];
|
im[i + 2] += reA[i + 2] * imB[i + 2] + imA[i + 2] * reB[i + 2];
|
||||||
im[i+2] += reA[i+2] * imB[i+2] + imA[i+2] * reB[i+2];
|
im[i + 3] += reA[i + 3] * imB[i + 3] + imA[i + 3] * reB[i + 3];
|
||||||
im[i+3] += reA[i+3] * imB[i+3] + imA[i+3] * reB[i+3];
|
|
||||||
}
|
}
|
||||||
for (size_t i=end4; i<len; ++i)
|
for (size_t i = end4; i < len; ++i) {
|
||||||
{
|
|
||||||
re[i] += reA[i] * reB[i] - imA[i] * imB[i];
|
re[i] += reA[i] * reB[i] - imA[i] * imB[i];
|
||||||
im[i] += reA[i] * imB[i] + imA[i] * reB[i];
|
im[i] += reA[i] * imB[i] + imA[i] * reB[i];
|
||||||
}
|
}
|
||||||
|
|||||||
+106
-194
@@ -28,71 +28,51 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <new>
|
#include <new>
|
||||||
|
|
||||||
|
namespace fftconvolver {
|
||||||
namespace fftconvolver
|
|
||||||
{
|
|
||||||
|
|
||||||
#if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
|
#if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
|
||||||
#if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE)
|
#if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE)
|
||||||
#define FFTCONVOLVER_USE_SSE
|
#define FFTCONVOLVER_USE_SSE
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(FFTCONVOLVER_USE_SSE)
|
||||||
#if defined (FFTCONVOLVER_USE_SSE)
|
#include <xmmintrin.h>
|
||||||
#include <xmmintrin.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
#define FFTCONVOLVER_RESTRICT __restrict__
|
#define FFTCONVOLVER_RESTRICT __restrict__
|
||||||
#else
|
#else
|
||||||
#define FFTCONVOLVER_RESTRICT
|
#define FFTCONVOLVER_RESTRICT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns whether SSE optimization for the convolver is enabled
|
* @brief Returns whether SSE optimization for the convolver is enabled
|
||||||
* @return true: Enabled - false: Disabled
|
* @return true: Enabled - false: Disabled
|
||||||
*/
|
*/
|
||||||
bool SSEEnabled();
|
bool SSEEnabled();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Buffer
|
* @class Buffer
|
||||||
* @brief Simple buffer implementation (uses 16-byte alignment if SSE optimization is enabled)
|
* @brief Simple buffer implementation (uses 16-byte alignment if SSE optimization is enabled)
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> class Buffer {
|
||||||
class Buffer
|
public:
|
||||||
{
|
explicit Buffer(size_t initialSize = 0) : _data(0), _size(0) { resize(initialSize); }
|
||||||
public:
|
|
||||||
explicit Buffer(size_t initialSize = 0) :
|
|
||||||
_data(0),
|
|
||||||
_size(0)
|
|
||||||
{
|
|
||||||
resize(initialSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~Buffer()
|
virtual ~Buffer() { clear(); }
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear()
|
void clear() {
|
||||||
{
|
|
||||||
deallocate(_data);
|
deallocate(_data);
|
||||||
_data = 0;
|
_data = 0;
|
||||||
_size = 0;
|
_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resize(size_t size)
|
void resize(size_t size) {
|
||||||
{
|
if (_size != size) {
|
||||||
if (_size != size)
|
|
||||||
{
|
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
if (size > 0)
|
if (size > 0) {
|
||||||
{
|
|
||||||
assert(!_data && _size == 0);
|
assert(!_data && _size == 0);
|
||||||
_data = allocate(size);
|
_data = allocate(size);
|
||||||
_size = size;
|
_size = size;
|
||||||
@@ -101,172 +81,118 @@ public:
|
|||||||
setZero();
|
setZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size() const
|
size_t size() const { return _size; }
|
||||||
{
|
|
||||||
return _size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setZero()
|
void setZero() { ::memset(_data, 0, _size * sizeof(T)); }
|
||||||
{
|
|
||||||
::memset(_data, 0, _size * sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
void copyFrom(const Buffer<T>& other)
|
void copyFrom(const Buffer<T> &other) {
|
||||||
{
|
|
||||||
assert(_size == other._size);
|
assert(_size == other._size);
|
||||||
if (this != &other)
|
if (this != &other) {
|
||||||
{
|
|
||||||
::memcpy(_data, other._data, _size * sizeof(T));
|
::memcpy(_data, other._data, _size * sizeof(T));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
T& operator[](size_t index)
|
T &operator[](size_t index) {
|
||||||
{
|
|
||||||
assert(_data && index < _size);
|
assert(_data && index < _size);
|
||||||
return _data[index];
|
return _data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
const T& operator[](size_t index) const
|
const T &operator[](size_t index) const {
|
||||||
{
|
|
||||||
assert(_data && index < _size);
|
assert(_data && index < _size);
|
||||||
return _data[index];
|
return _data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
operator bool() const
|
operator bool() const { return (_data != 0 && _size > 0); }
|
||||||
{
|
|
||||||
return (_data != 0 && _size > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
T* data()
|
T *data() { return _data; }
|
||||||
{
|
|
||||||
return _data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const T* data() const
|
const T *data() const { return _data; }
|
||||||
{
|
|
||||||
return _data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Swap(Buffer<T>& a, Buffer<T>& b)
|
static void Swap(Buffer<T> &a, Buffer<T> &b) {
|
||||||
{
|
|
||||||
std::swap(a._data, b._data);
|
std::swap(a._data, b._data);
|
||||||
std::swap(a._size, b._size);
|
std::swap(a._size, b._size);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T* allocate(size_t size)
|
T *allocate(size_t size) {
|
||||||
{
|
|
||||||
#if defined(FFTCONVOLVER_USE_SSE)
|
#if defined(FFTCONVOLVER_USE_SSE)
|
||||||
return static_cast<T*>(_mm_malloc(size * sizeof(T), 16));
|
return static_cast<T *>(_mm_malloc(size * sizeof(T), 16));
|
||||||
#else
|
#else
|
||||||
return new T[size];
|
return new T[size];
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void deallocate(T* ptr)
|
void deallocate(T *ptr) {
|
||||||
{
|
|
||||||
#if defined(FFTCONVOLVER_USE_SSE)
|
#if defined(FFTCONVOLVER_USE_SSE)
|
||||||
_mm_free(ptr);
|
_mm_free(ptr);
|
||||||
#else
|
#else
|
||||||
delete [] ptr;
|
delete[] ptr;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
T* _data;
|
T *_data;
|
||||||
size_t _size;
|
size_t _size;
|
||||||
|
|
||||||
// Prevent uncontrolled usage
|
// Prevent uncontrolled usage
|
||||||
Buffer(const Buffer&);
|
Buffer(const Buffer &);
|
||||||
Buffer& operator=(const Buffer&);
|
Buffer &operator=(const Buffer &);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Type of one sample
|
* @brief Type of one sample
|
||||||
*/
|
*/
|
||||||
typedef float Sample;
|
typedef float Sample;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Buffer for samples
|
* @brief Buffer for samples
|
||||||
*/
|
*/
|
||||||
typedef Buffer<Sample> SampleBuffer;
|
typedef Buffer<Sample> SampleBuffer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class SplitComplex
|
* @class SplitComplex
|
||||||
* @brief Buffer for split-complex representation of FFT results
|
* @brief Buffer for split-complex representation of FFT results
|
||||||
*
|
*
|
||||||
* The split-complex representation stores the real and imaginary parts
|
* The split-complex representation stores the real and imaginary parts
|
||||||
* of FFT results in two different memory buffers which is useful e.g. for
|
* of FFT results in two different memory buffers which is useful e.g. for
|
||||||
* SIMD optimizations.
|
* SIMD optimizations.
|
||||||
*/
|
*/
|
||||||
class SplitComplex
|
class SplitComplex {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
explicit SplitComplex(size_t initialSize = 0) :
|
explicit SplitComplex(size_t initialSize = 0) : _size(0), _re(), _im() { resize(initialSize); }
|
||||||
_size(0),
|
|
||||||
_re(),
|
|
||||||
_im()
|
|
||||||
{
|
|
||||||
resize(initialSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
~SplitComplex()
|
~SplitComplex() { clear(); }
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear()
|
void clear() {
|
||||||
{
|
|
||||||
_re.clear();
|
_re.clear();
|
||||||
_im.clear();
|
_im.clear();
|
||||||
_size = 0;
|
_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resize(size_t newSize)
|
void resize(size_t newSize) {
|
||||||
{
|
|
||||||
_re.resize(newSize);
|
_re.resize(newSize);
|
||||||
_im.resize(newSize);
|
_im.resize(newSize);
|
||||||
_size = newSize;
|
_size = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setZero()
|
void setZero() {
|
||||||
{
|
|
||||||
_re.setZero();
|
_re.setZero();
|
||||||
_im.setZero();
|
_im.setZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
void copyFrom(const SplitComplex& other)
|
void copyFrom(const SplitComplex &other) {
|
||||||
{
|
|
||||||
_re.copyFrom(other._re);
|
_re.copyFrom(other._re);
|
||||||
_im.copyFrom(other._im);
|
_im.copyFrom(other._im);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sample* re()
|
Sample *re() { return _re.data(); }
|
||||||
{
|
|
||||||
return _re.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Sample* re() const
|
const Sample *re() const { return _re.data(); }
|
||||||
{
|
|
||||||
return _re.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sample* im()
|
Sample *im() { return _im.data(); }
|
||||||
{
|
|
||||||
return _im.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Sample* im() const
|
const Sample *im() const { return _im.data(); }
|
||||||
{
|
|
||||||
return _im.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t size() const
|
size_t size() const { return _size; }
|
||||||
{
|
|
||||||
return _size;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t _size;
|
size_t _size;
|
||||||
@@ -274,82 +200,68 @@ private:
|
|||||||
SampleBuffer _im;
|
SampleBuffer _im;
|
||||||
|
|
||||||
// Prevent uncontrolled usage
|
// Prevent uncontrolled usage
|
||||||
SplitComplex(const SplitComplex&);
|
SplitComplex(const SplitComplex &);
|
||||||
SplitComplex& operator=(const SplitComplex&);
|
SplitComplex &operator=(const SplitComplex &);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the next power of 2 of a given number
|
* @brief Returns the next power of 2 of a given number
|
||||||
* @param val The number
|
* @param val The number
|
||||||
* @return The next power of 2
|
* @return The next power of 2
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> T NextPowerOf2(const T &val) {
|
||||||
T NextPowerOf2(const T& val)
|
|
||||||
{
|
|
||||||
T nextPowerOf2 = 1;
|
T nextPowerOf2 = 1;
|
||||||
while (nextPowerOf2 < val)
|
while (nextPowerOf2 < val) {
|
||||||
{
|
|
||||||
nextPowerOf2 *= 2;
|
nextPowerOf2 *= 2;
|
||||||
}
|
}
|
||||||
return nextPowerOf2;
|
return nextPowerOf2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sums two given sample arrays
|
* @brief Sums two given sample arrays
|
||||||
* @param result The result array
|
* @param result The result array
|
||||||
* @param a The 1st array
|
* @param a The 1st array
|
||||||
* @param b The 2nd array
|
* @param b The 2nd array
|
||||||
* @param len The length of the arrays
|
* @param len The length of the arrays
|
||||||
*/
|
*/
|
||||||
void Sum(Sample* FFTCONVOLVER_RESTRICT result,
|
void Sum(Sample *FFTCONVOLVER_RESTRICT result, const Sample *FFTCONVOLVER_RESTRICT a,
|
||||||
const Sample* FFTCONVOLVER_RESTRICT a,
|
const Sample *FFTCONVOLVER_RESTRICT b, size_t len);
|
||||||
const Sample* FFTCONVOLVER_RESTRICT b,
|
|
||||||
size_t len);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Copies a source array into a destination buffer and pads the destination buffer with zeros
|
* @brief Copies a source array into a destination buffer and pads the destination buffer with zeros
|
||||||
* @param dest The destination buffer
|
* @param dest The destination buffer
|
||||||
* @param src The source array
|
* @param src The source array
|
||||||
* @param srcSize The size of the source array
|
* @param srcSize The size of the source array
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> void CopyAndPad(Buffer<T> &dest, const T *src, size_t srcSize) {
|
||||||
void CopyAndPad(Buffer<T>& dest, const T* src, size_t srcSize)
|
|
||||||
{
|
|
||||||
assert(dest.size() >= srcSize);
|
assert(dest.size() >= srcSize);
|
||||||
::memcpy(dest.data(), src, srcSize * sizeof(T));
|
::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
|
* @brief Adds the complex product of two split-complex arrays to a result array
|
||||||
* @param result The result buffer
|
* @param re The real part of the result buffer
|
||||||
* @param a The 1st factor of the complex product
|
* @param im The imaginary part of the result buffer
|
||||||
* @param b The 2nd factor of the complex product
|
* @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
|
||||||
void ComplexMultiplyAccumulate(SplitComplex& result, const SplitComplex& a, const SplitComplex& b);
|
* @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
|
} // End of namespace fftconvolver
|
||||||
|
|
||||||
#endif // Header guard
|
#endif // Header guard
|
||||||
|
|||||||
+10
-12
@@ -46,8 +46,8 @@ void convolver_pool_init(size_t numThreads, size_t numConvolvers) {
|
|||||||
if (!pool.init(numThreads, numConvolvers)) {
|
if (!pool.init(numThreads, numConvolvers)) {
|
||||||
debug(1, "failed to initialize thread pool!");
|
debug(1, "failed to initialize thread pool!");
|
||||||
} else {
|
} else {
|
||||||
debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads, numThreads == 1 ? "" : "s",
|
debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads,
|
||||||
numConvolvers, numConvolvers == 1 ? "" : "s");
|
numThreads == 1 ? "" : "s", numConvolvers, numConvolvers == 1 ? "" : "s");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ void convolver_pool_closedown() {
|
|||||||
debug(3, "thread pool shut down");
|
debug(3, "thread pool shut down");
|
||||||
}
|
}
|
||||||
|
|
||||||
int convolver_init(const char *filename, unsigned char channel_count,
|
int convolver_init(const char *filename, unsigned char channel_count, double max_length_in_seconds,
|
||||||
double max_length_in_seconds, size_t block_size) {
|
size_t block_size) {
|
||||||
debug(3, "convolver_init");
|
debug(3, "convolver_init");
|
||||||
int success = 0;
|
int success = 0;
|
||||||
SF_INFO info = {}; // Zero everything, including format
|
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);
|
size_t max_length = (size_t)(max_length_in_seconds * info.samplerate);
|
||||||
const size_t size =
|
const size_t size =
|
||||||
(unsigned int)info.frames > max_length ? max_length : (unsigned int)info.frames;
|
(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) {
|
if (buffer != NULL) {
|
||||||
// float buffer[size * info.channels];
|
// float buffer[size * info.channels];
|
||||||
float *abuffer = (float*)malloc(sizeof(float) * size);
|
float *abuffer = (float *)malloc(sizeof(float) * size);
|
||||||
if (abuffer != NULL) {
|
if (abuffer != NULL) {
|
||||||
size_t l = sf_readf_float(file, buffer, size);
|
size_t l = sf_readf_float(file, buffer, size);
|
||||||
if (l != 0) {
|
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)) {
|
if (!pool.initConvolver(cc, block_size, abuffer, size)) {
|
||||||
debug(1, "new convolver failed to initialize convolver %u ", cc);
|
debug(1, "new convolver failed to initialize convolver %u ", cc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
success = 1;
|
success = 1;
|
||||||
@@ -101,11 +101,11 @@ int convolver_init(const char *filename, unsigned char channel_count,
|
|||||||
"%d samples",
|
"%d samples",
|
||||||
filename, info.channels, info.channels == 1 ? "" : "s", size);
|
filename, info.channels, info.channels == 1 ? "" : "s", size);
|
||||||
sf_close(file);
|
sf_close(file);
|
||||||
free((void*)abuffer);
|
free((void *)abuffer);
|
||||||
} else {
|
} else {
|
||||||
debug(1, "failed to init convolvers because insufficient memory was available");
|
debug(1, "failed to init convolvers because insufficient memory was available");
|
||||||
}
|
}
|
||||||
free((void*)buffer);
|
free((void *)buffer);
|
||||||
} else {
|
} else {
|
||||||
warn("failed to init convolvers because insufficient memory was available");
|
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_wait_for_all() { pool.waitForAll(); }
|
||||||
|
|
||||||
void convolver_clear_state() {
|
void convolver_clear_state() { pool.clearAllStates(); }
|
||||||
pool.clearAllStates();
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsigned int max_channels = 8;
|
const unsigned int max_channels = 8;
|
||||||
fftconvolver::FFTConvolver convolvers[max_channels];
|
fftconvolver::FFTConvolver convolvers[max_channels];
|
||||||
|
|||||||
@@ -5,22 +5,24 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
// 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_reset();
|
void convolver_reset();
|
||||||
//void convolver_clear_state();
|
// void convolver_clear_state();
|
||||||
// void convolver_process(unsigned int channel, float *data, int length);
|
// void convolver_process(unsigned int channel, float *data, int length);
|
||||||
// void convolver_process_l(float* data, int length);
|
// void convolver_process_l(float* data, int length);
|
||||||
// void convolver_process_r(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_init(size_t numThreads, size_t numConvolvers);
|
||||||
void convolver_pool_closedown();
|
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_process(unsigned int channel, float *data, int length);
|
||||||
void convolver_clear_state();
|
void convolver_clear_state();
|
||||||
void convolver_wait_for_all();
|
void convolver_wait_for_all();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+9
-5
@@ -5,11 +5,11 @@ homedir = @HOME@
|
|||||||
|
|
||||||
# Note that xmlmantohtml isn't producing correct HTML.
|
# 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
|
# Uncomment the if/else and SUBDIR lines in this stanza to make xmltoman build shairport-sync.1
|
||||||
# if USE_XMLTOMAN
|
if USE_XMLTOMAN
|
||||||
# SUBDIRS = man
|
SUBDIRS = man
|
||||||
# else
|
else
|
||||||
man_MANS = $(top_srcdir)/man/shairport-sync.1
|
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_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread --include=utilities/debug.h
|
||||||
lib_tinyhttp_a_CFLAGS = -pthread
|
lib_tinyhttp_a_CFLAGS = -pthread
|
||||||
@@ -27,7 +27,11 @@ noinst_LIBRARIES =
|
|||||||
|
|
||||||
# See below for the flags for the test client program
|
# 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
|
if BUILD_FOR_DARWIN
|
||||||
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-deprecated-declarations -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-deprecated-declarations -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ static const int host_bigendian = 0;
|
|||||||
|
|
||||||
#define _Swap32(v) \
|
#define _Swap32(v) \
|
||||||
do { \
|
do { \
|
||||||
v = (((v)&0x000000FF) << 0x18) | (((v)&0x0000FF00) << 0x08) | (((v)&0x00FF0000) >> 0x08) | \
|
v = (((v) & 0x000000FF) << 0x18) | (((v) & 0x0000FF00) << 0x08) | \
|
||||||
(((v)&0xFF000000) >> 0x18); \
|
(((v) & 0x00FF0000) >> 0x08) | (((v) & 0xFF000000) >> 0x18); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define _Swap16(v) \
|
#define _Swap16(v) \
|
||||||
do { \
|
do { \
|
||||||
v = (((v)&0x00FF) << 0x08) | (((v)&0xFF00) >> 0x08); \
|
v = (((v) & 0x00FF) << 0x08) | (((v) & 0xFF00) >> 0x08); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
struct {
|
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;
|
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 + 1] = (left >> 8) & 0xFF;
|
||||||
((uint8_t *)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 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 + 4] = (right >> 8) & 0xFF;
|
||||||
((uint8_t *)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 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;
|
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 + 1] = (left >> 8) & 0xFF;
|
||||||
((uint8_t *)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 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 + 4] = (right >> 8) & 0xFF;
|
||||||
((uint8_t *)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 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;
|
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 + 1] = (sample >> 8) & 0xFF;
|
||||||
((uint8_t *)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF;
|
((uint8_t *)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
#include "utilities/buffered_read.h"
|
#include "utilities/buffered_read.h"
|
||||||
#include "utilities/mod23.h"
|
#include "utilities/mod23.h"
|
||||||
|
#include "utilities/network_utilities.h"
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@@ -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) {
|
void rtp_buffered_audio_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||||
debug(2, "Buffered Audio Receiver Cleanup Start.");
|
debug(2, "Buffered Audio Receiver Cleanup Start.");
|
||||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
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,
|
debug(3, "Connection %d: closing TCP Buffered Audio port: %u.", conn->connection_number,
|
||||||
conn->local_buffered_audio_port);
|
conn->local_buffered_audio_port);
|
||||||
conn->buffered_audio_socket = 0;
|
|
||||||
debug(2, "Connection %d: rtp_buffered_audio_processor exit.", conn->connection_number);
|
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 packets_played_in_this_sequence = 0;
|
||||||
|
|
||||||
int play_enabled = 0;
|
int play_enabled = 0;
|
||||||
|
|
||||||
int very_early_packets_signalled = 0;
|
int very_early_packets_signalled = 0;
|
||||||
// double requested_lead_time = 0.0; // normal lead time minimum -- maybe it should be about 0.1
|
// 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)
|
if (payload_ssrc != SSRC_NONE)
|
||||||
previous_ssrc = payload_ssrc;
|
previous_ssrc = payload_ssrc;
|
||||||
payload_ssrc = nctohl(&packet[8]);
|
payload_ssrc = nctohl(&packet[8]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ((payload_ssrc != previous_ssrc) && (payload_ssrc != SSRC_NONE)) {
|
if ((payload_ssrc != previous_ssrc) && (payload_ssrc != SSRC_NONE)) {
|
||||||
if (ssrc_is_recognised(payload_ssrc) == 0) {
|
if (ssrc_is_recognised(payload_ssrc) == 0) {
|
||||||
debug(2, "Unrecognised SSRC: %u.", payload_ssrc);
|
debug(2, "Unrecognised SSRC: %u.", payload_ssrc);
|
||||||
} else {
|
} else {
|
||||||
debug(2, "Connection %d: incoming audio encoding is%s \"%s\".",
|
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)) {
|
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) {
|
if (blocks_read_since_play_began == 1) {
|
||||||
@@ -327,7 +326,8 @@ void *rtp_buffered_audio_processor(void *arg) {
|
|||||||
finished = 1;
|
finished = 1;
|
||||||
} else if (nread < 0) {
|
} else if (nread < 0) {
|
||||||
char errorstring[1024];
|
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 .",
|
debug(1, "error in rtp_buffered_audio_processor %d: \"%s\". Could not recv a data_len .",
|
||||||
errno, errorstring);
|
errno, errorstring);
|
||||||
finished = 1;
|
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.",
|
debug(2, "immediate flush started at sequence number %u until sequence number of %u.",
|
||||||
seq_no, conn->ap2_immediate_flush_until_sequence_number);
|
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))) {
|
if ((blocks_read != 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);
|
((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))) {
|
if ((blocks_read != 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);
|
((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;
|
conn->ap2_immediate_flush_requested = 0;
|
||||||
ap2_immediate_flush_requested = 0;
|
ap2_immediate_flush_requested = 0;
|
||||||
|
|
||||||
|
|
||||||
// turn off all deferred requests. Not sure if this is right...
|
// turn off all deferred requests. Not sure if this is right...
|
||||||
unsigned int f = 0;
|
unsigned int f = 0;
|
||||||
for (f = 0; f < MAX_DEFERRED_FLUSH_REQUESTS; f++) {
|
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,
|
debug(1,
|
||||||
"deferred flush cancelled by an immediate flush: flushFromTS: %12u, flushFromSeq: %12u, "
|
"deferred flush cancelled by an immediate flush: flushFromTS: %12u, "
|
||||||
"flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.",
|
"flushFromSeq: %12u, "
|
||||||
conn->ap2_deferred_flush_requests[f].flushFromTS,
|
"flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.",
|
||||||
conn->ap2_deferred_flush_requests[f].flushFromSeq,
|
conn->ap2_deferred_flush_requests[f].flushFromTS,
|
||||||
conn->ap2_deferred_flush_requests[f].flushUntilTS,
|
conn->ap2_deferred_flush_requests[f].flushFromSeq,
|
||||||
conn->ap2_deferred_flush_requests[f].flushUntilSeq, timestamp);
|
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].inUse = 0;
|
||||||
conn->ap2_deferred_flush_requests[f].active = 0;
|
conn->ap2_deferred_flush_requests[f].active = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
debug(4, "immediate flush of block %u until block %u", seq_no,
|
debug(4, "immediate flush of block %u until block %u", seq_no,
|
||||||
conn->ap2_immediate_flush_until_sequence_number);
|
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) {
|
} else if (conn->ap2_deferred_flush_requests[f].active != 0) {
|
||||||
new_audio_block_needed = 1;
|
new_audio_block_needed = 1;
|
||||||
debug(4,
|
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.",
|
"flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.",
|
||||||
seq_no,
|
seq_no, timestamp, get_ssrc_name(payload_ssrc),
|
||||||
timestamp,
|
|
||||||
get_ssrc_name(payload_ssrc),
|
|
||||||
conn->ap2_deferred_flush_requests[f].flushFromTS,
|
conn->ap2_deferred_flush_requests[f].flushFromTS,
|
||||||
conn->ap2_deferred_flush_requests[f].flushFromSeq,
|
conn->ap2_deferred_flush_requests[f].flushFromSeq,
|
||||||
conn->ap2_deferred_flush_requests[f].flushUntilTS,
|
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.
|
// 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...
|
// 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);
|
// debug(1, "frames buffered: %f seconds, desired length: %f seconds.", (1.0 *
|
||||||
|
// player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate,
|
||||||
// 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;
|
// 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;
|
uint64_t buffer_should_be_time;
|
||||||
|
|
||||||
int have_valid_time = (frame_to_local_time(timestamp, &buffer_should_be_time, conn) == 0);
|
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...
|
// 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();
|
int64_t lead_time = buffer_should_be_time - get_absolute_time_in_ns();
|
||||||
|
|
||||||
|
// debug(1,"play_enabled: %d, have_valid_time: %d,
|
||||||
// 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",
|
// audio_decoded_buffer_below_desired_length: %d, lead_time * 1E-9: %f,
|
||||||
// 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
|
// (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,
|
// A slight problem here is that counting the number of buffers may not be sufficient,
|
||||||
// because the actual device may be
|
// because the actual device may be
|
||||||
// taking data in large quantities at a single time.
|
// 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
|
// So we just have to ensure that there
|
||||||
// is enough of a lead time maintained for sufficient audio to be available to prevent
|
// is enough of a lead time maintained for sufficient audio to be available to prevent
|
||||||
// the device from under-running.
|
// the device from under-running.
|
||||||
|
|
||||||
// If means that the Shairport Sync player might riun out of audio occasionally, but
|
// 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.
|
// 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
|
// 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.
|
// entire lead-time's amount of audio in case the device has a zero-sized buffer.
|
||||||
|
|
||||||
if ((play_enabled != 0) && (have_valid_time != 0)
|
if ((play_enabled != 0) &&
|
||||||
// (audio_decoded_buffer_below_desired_length != 0) &&
|
(have_valid_time != 0)
|
||||||
|
// (audio_decoded_buffer_below_desired_length != 0) &&
|
||||||
&& (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))
|
&& (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))
|
||||||
// && (audio_decoded_buffer_below_desired_length != 0)
|
// && (audio_decoded_buffer_below_desired_length != 0)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
very_early_packets_signalled = 0; //reset very early packet warning signaller
|
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
|
// try to identify blocks that are timed to before the last buffer, and drop 'em
|
||||||
int64_t time_from_last_buffer_time =
|
int64_t time_from_last_buffer_time =
|
||||||
buffer_should_be_time - previous_buffer_should_be_time;
|
buffer_should_be_time - previous_buffer_should_be_time;
|
||||||
|
|
||||||
if ((packets_played_in_this_sequence == 0) || (time_from_last_buffer_time > 0)) {
|
if ((packets_played_in_this_sequence == 0) || (time_from_last_buffer_time > 0)) {
|
||||||
|
|
||||||
payload_length = 0;
|
payload_length = 0;
|
||||||
if (ssrc_is_recognised(payload_ssrc) != 0) {
|
if (ssrc_is_recognised(payload_ssrc) != 0) {
|
||||||
// prepare_decoding_chain(conn, payload_ssrc);
|
// prepare_decoding_chain(conn, payload_ssrc);
|
||||||
unsigned long long new_payload_length = 0;
|
unsigned long long new_payload_length = 0;
|
||||||
payload_pointer = m + leading_free_space_length;
|
payload_pointer = m + leading_free_space_length;
|
||||||
if (lead_time >= 0) { // only decipher the packet if it's not too late
|
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) {
|
if (conn->session_key != NULL) {
|
||||||
unsigned char nonce[12];
|
unsigned char nonce[12];
|
||||||
memset(nonce, 0, sizeof(nonce));
|
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
|
new_audio_block_needed = 1; // the block has been used up and is no longer current
|
||||||
} else {
|
} else {
|
||||||
if ((have_valid_time != 0) && (very_early_packets_signalled == 0) && (lead_time * 1E-9 > (config.audio_decoded_buffer_desired_length + 0.2))) {
|
if ((have_valid_time != 0) && (very_early_packets_signalled == 0) &&
|
||||||
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);
|
(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;
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,10 @@
|
|||||||
#include "ap2_event_message_handler.h"
|
#include "ap2_event_message_handler.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
#include "utilities/structured_buffer.h"
|
|
||||||
#include "utilities/generate_random_uuid.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) {
|
if (plist_to_log != NULL) {
|
||||||
char *plist_as_string = plist_as_xml_text(plist_to_log);
|
char *plist_as_string = plist_as_xml_text(plist_to_log);
|
||||||
if (plist_as_string != NULL) {
|
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 ap2_event_port_post_command(rtsp_conn_info *conn, plist_t command) {
|
||||||
ssize_t result = 0;
|
ssize_t result = 0;
|
||||||
decodeAndLogPlist(command);
|
decodeAndLogPlist(command);
|
||||||
structured_buffer *sbuf = sbuf_new(4096);
|
structured_buffer *sbuf = sbuf_new(4096);
|
||||||
if (sbuf != NULL) {
|
if (sbuf != NULL) {
|
||||||
pthread_cleanup_push(sbuf_cleanup, sbuf);
|
pthread_cleanup_push(sbuf_cleanup, sbuf);
|
||||||
char *plistString = NULL;
|
char *plistString = NULL;
|
||||||
uint32_t plistStringLength = 0;
|
uint32_t plistStringLength = 0;
|
||||||
|
|
||||||
plist_to_bin(command, &plistString, &plistStringLength);
|
plist_to_bin(command, &plistString, &plistStringLength);
|
||||||
if (plistString != NULL) {
|
if (plistString != NULL) {
|
||||||
sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n", plistStringLength);
|
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
|
pthread_cleanup_pop(1); // delete the structured buffer
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t ap2_event_send_update_info(rtsp_conn_info *conn) {
|
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) {
|
if (update_info_plist != NULL) {
|
||||||
plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo"));
|
plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo"));
|
||||||
plist_dict_set_item(update_info_plist, "value", value_plist);
|
plist_dict_set_item(update_info_plist, "value", value_plist);
|
||||||
|
|
||||||
char *plist_as_string = plist_as_xml_text(update_info_plist);
|
char *plist_as_string = plist_as_xml_text(update_info_plist);
|
||||||
if (plist_as_string != NULL) {
|
if (plist_as_string != NULL) {
|
||||||
debug(3, "update_info_plist is:\n--\n\"%s\"\n--\n", plist_as_string);
|
debug(3, "update_info_plist is:\n--\n\"%s\"\n--\n", plist_as_string);
|
||||||
free(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);
|
plist_free(update_info_plist);
|
||||||
} else {
|
} else {
|
||||||
debug(1, "Could not build an updateInfo plist");
|
debug(1, "Could not build an updateInfo plist");
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#ifndef _AP2_EVENT_COMMAND_SENDER_H
|
#ifndef _AP2_EVENT_COMMAND_SENDER_H
|
||||||
#define _AP2_EVENT_COMMAND_SENDER_H
|
#define _AP2_EVENT_COMMAND_SENDER_H
|
||||||
|
|
||||||
|
#include "player.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include "player.h"
|
|
||||||
|
|
||||||
ssize_t ap2_event_send_unit_volume_notification(rtsp_conn_info *conn, double volume);
|
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_update_info(rtsp_conn_info *conn);
|
||||||
|
|
||||||
ssize_t ap2_event_send_dev_mule();
|
ssize_t ap2_event_send_dev_mule();
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ void ap2_event_receiver_cleanup_handler(void *arg) {
|
|||||||
#endif
|
#endif
|
||||||
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
|
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
|
||||||
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
|
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
|
||||||
close(conn->event_channel_fd);
|
safe_socket_close(&conn->event_channel_fd);
|
||||||
conn->event_channel_fd = 0;
|
|
||||||
pthread_cleanup_pop(1); // unlock the mutex
|
pthread_cleanup_pop(1); // unlock the mutex
|
||||||
|
|
||||||
if (conn->airplay_gid != NULL) {
|
if (conn->airplay_gid != NULL) {
|
||||||
@@ -68,7 +67,8 @@ void *ap2_event_receiver(void *arg) {
|
|||||||
|
|
||||||
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
|
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
|
||||||
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
|
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
|
pthread_cleanup_pop(1); // unlock the mutex
|
||||||
if (conn->event_channel_fd > 0) {
|
if (conn->event_channel_fd > 0) {
|
||||||
debug(2,
|
debug(2,
|
||||||
@@ -76,14 +76,13 @@ void *ap2_event_receiver(void *arg) {
|
|||||||
"socket %d.",
|
"socket %d.",
|
||||||
conn->connection_number, conn->event_socket, conn->event_channel_fd);
|
conn->connection_number, conn->event_socket, conn->event_channel_fd);
|
||||||
pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg);
|
pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg);
|
||||||
|
|
||||||
ap2_event_send_update_info(conn);
|
ap2_event_send_update_info(conn);
|
||||||
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
usleep(1000000);
|
usleep(1000000);
|
||||||
};
|
};
|
||||||
|
|
||||||
debug(3, "Connection %d: AP2 Event Receiver RTP thread starting \"normal\" exit.",
|
debug(3, "Connection %d: AP2 Event Receiver RTP thread starting \"normal\" exit.",
|
||||||
conn->connection_number);
|
conn->connection_number);
|
||||||
pthread_cleanup_pop(1); // do the cleanup
|
pthread_cleanup_pop(1); // do the cleanup
|
||||||
|
|||||||
+13
-7
@@ -40,6 +40,7 @@
|
|||||||
#include "activity_monitor.h"
|
#include "activity_monitor.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "utilities/string_utilities.h"
|
||||||
|
|
||||||
enum alsa_backend_mode {
|
enum alsa_backend_mode {
|
||||||
abm_disconnected,
|
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))
|
if ((snd_pcm_hw_params_get_rate_numden(alsa_params, &uval, &uval2) == 0) && (uval2 != 0))
|
||||||
// watch for a divide by zero too!
|
// 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,
|
debug(log_level, " precise (rational) rate = %.3f frames per second (i.e. %u/%u).",
|
||||||
uval2);
|
(1.0 * uval) / uval2, uval, uval2);
|
||||||
else
|
else
|
||||||
debug(log_level, " precise (rational) rate information unavailable.");
|
debug(log_level, " precise (rational) rate information unavailable.");
|
||||||
|
|
||||||
@@ -1260,7 +1261,8 @@ static int init(int argc, char **argv) {
|
|||||||
else {
|
else {
|
||||||
warn("Invalid disable_synchronization option choice \"%s\". It should "
|
warn("Invalid disable_synchronization option choice \"%s\". It should "
|
||||||
"be \"yes\" or "
|
"be \"yes\" or "
|
||||||
"\"no\". It is set to \"no\".", str);
|
"\"no\". It is set to \"no\".",
|
||||||
|
str);
|
||||||
config.no_sync = 0;
|
config.no_sync = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1276,7 +1278,8 @@ static int init(int argc, char **argv) {
|
|||||||
else {
|
else {
|
||||||
warn("Invalid mute_using_playback_switch option choice \"%s\". It "
|
warn("Invalid mute_using_playback_switch option choice \"%s\". It "
|
||||||
"should be \"yes\" or "
|
"should be \"yes\" or "
|
||||||
"\"no\". It is set to \"no\".", str);
|
"\"no\". It is set to \"no\".",
|
||||||
|
str);
|
||||||
config.alsa_use_hardware_mute = 0;
|
config.alsa_use_hardware_mute = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1290,7 +1293,8 @@ static int init(int argc, char **argv) {
|
|||||||
else {
|
else {
|
||||||
warn("Invalid use_hardware_mute_if_available option choice \"%s\". It "
|
warn("Invalid use_hardware_mute_if_available option choice \"%s\". It "
|
||||||
"should be \"yes\" or "
|
"should be \"yes\" or "
|
||||||
"\"no\". It is set to \"no\".", str);
|
"\"no\". It is set to \"no\".",
|
||||||
|
str);
|
||||||
config.alsa_use_hardware_mute = 0;
|
config.alsa_use_hardware_mute = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1304,7 +1308,8 @@ static int init(int argc, char **argv) {
|
|||||||
else {
|
else {
|
||||||
warn("Invalid use_mmap_if_available option choice \"%s\". It should be "
|
warn("Invalid use_mmap_if_available option choice \"%s\". It should be "
|
||||||
"\"yes\" or \"no\". "
|
"\"yes\" or \"no\". "
|
||||||
"It remains set to \"yes\".", str);
|
"It remains set to \"yes\".",
|
||||||
|
str);
|
||||||
config.no_mmap = 0;
|
config.no_mmap = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1424,7 +1429,8 @@ static int init(int argc, char **argv) {
|
|||||||
else {
|
else {
|
||||||
warn("Invalid use_precision_timing option choice \"%s\". It should be "
|
warn("Invalid use_precision_timing option choice \"%s\". It should be "
|
||||||
"\"yes\", \"auto\" or \"no\". "
|
"\"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_NO ? "no"
|
||||||
: config.use_precision_timing == YNA_AUTO ? "auto"
|
: config.use_precision_timing == YNA_AUTO ? "auto"
|
||||||
: "yes");
|
: "yes");
|
||||||
|
|||||||
+2
-1
@@ -506,7 +506,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
|
|||||||
|
|
||||||
// get the default channel mapping setting basis -- "alsa" or "pulseaudio".
|
// 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) ||
|
if ((strcasecmp(default_channel_layouts, "alsa") == 0) ||
|
||||||
(strcasecmp(default_channel_layouts, "pulseaudio") == 0)) {
|
(strcasecmp(default_channel_layouts, "pulseaudio") == 0)) {
|
||||||
debug(1, "pulseaudio default_channel_layouts setting: \"%s\".", default_channel_layouts);
|
debug(1, "pulseaudio default_channel_layouts setting: \"%s\".", default_channel_layouts);
|
||||||
|
|||||||
+3
-4
@@ -39,6 +39,8 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "utilities/network_utilities.h"
|
||||||
|
|
||||||
static int fd = -1;
|
static int fd = -1;
|
||||||
static int warned = 0;
|
static int warned = 0;
|
||||||
static unsigned int bytes_per_frame = 0;
|
static unsigned int bytes_per_frame = 0;
|
||||||
@@ -146,10 +148,7 @@ static int init(int argc, char **argv) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void deinit(void) {
|
static void deinit(void) { safe_socket_close(&fd); }
|
||||||
if (fd > 0)
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void help(void) {
|
static void help(void) {
|
||||||
printf(" Provide the pipe's pathname. The default is \"%s\".\n", default_pipe_name);
|
printf(" Provide the pipe's pathname. The default is \"%s\".\n", default_pipe_name);
|
||||||
|
|||||||
+61
-57
@@ -76,72 +76,76 @@ void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) {
|
|||||||
// the secondary_txt_records are for the _airplay._tcp items.
|
// the secondary_txt_records are for the _airplay._tcp items.
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#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;
|
uint64_t features_hi = config.airplay_features;
|
||||||
features_hi = (features_hi >> 32) & 0xffffffff;
|
features_hi = (features_hi >> 32) & 0xffffffff;
|
||||||
uint64_t features_lo = config.airplay_features;
|
uint64_t features_lo = config.airplay_features;
|
||||||
features_lo = features_lo & 0xffffffff;
|
features_lo = features_lo & 0xffffffff;
|
||||||
|
|
||||||
txt_records[entry_number++] =
|
if (config.service_type == APST_airplay2) {
|
||||||
bnprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
|
txt_records[entry_number++] = "cn=0,1";
|
||||||
features_lo, features_hi);
|
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++] =
|
txt_records[entry_number++] =
|
||||||
bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version);
|
bnprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
|
||||||
txt_records[entry_number++] = bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString),
|
features_lo, features_hi);
|
||||||
"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;
|
|
||||||
|
|
||||||
#else
|
txt_records[entry_number++] =
|
||||||
// here, just replicate what happens in mdns.h when using those #defines
|
bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version);
|
||||||
txt_records[entry_number++] =
|
txt_records[entry_number++] = bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString),
|
||||||
bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString), "sf=0x4");
|
"sf=0x%" PRIX32, config.airplay_statusflags);
|
||||||
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
|
#ifdef CONFIG_METADATA
|
||||||
if (config.get_coverart == 0)
|
if (config.get_coverart == 0)
|
||||||
txt_records[entry_number++] = "md=0,2";
|
txt_records[entry_number++] = "md=0,2";
|
||||||
else
|
else
|
||||||
txt_records[entry_number++] = "md=0,1,2";
|
txt_records[entry_number++] = "md=0,1,2";
|
||||||
#endif
|
#endif
|
||||||
txt_records[entry_number++] = "ss=16";
|
txt_records[entry_number++] =
|
||||||
txt_records[entry_number++] = "sr=44100";
|
bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model);
|
||||||
txt_records[entry_number++] = "da=true";
|
txt_records[entry_number++] = bnprintf(pkString, sizeof(pkString), "pk=%s", config.pk_string);
|
||||||
txt_records[entry_number++] = "sv=false";
|
txt_records[entry_number++] = "tp=UDP";
|
||||||
txt_records[entry_number++] = "et=0,1";
|
txt_records[entry_number++] = "vn=65537";
|
||||||
txt_records[entry_number++] = "ek=1";
|
txt_records[entry_number++] =
|
||||||
txt_records[entry_number++] = "cn=0,1";
|
bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=%s", config.srcvers);
|
||||||
txt_records[entry_number++] = "ch=2";
|
txt_records[entry_number++] =
|
||||||
txt_records[entry_number++] = "txtvers=1";
|
bnprintf(ap1OsversString, sizeof(ap1OsversString), "ov=%s", config.osvers);
|
||||||
if (config.password == NULL)
|
txt_records[entry_number++] = NULL;
|
||||||
txt_records[entry_number++] = "pw=false";
|
} else {
|
||||||
else
|
#endif
|
||||||
txt_records[entry_number++] = "pw=true";
|
// here, just replicate what happens in mdns.h when using those #defines
|
||||||
txt_records[entry_number++] = NULL;
|
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
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "utilities/network_utilities.h"
|
||||||
|
|
||||||
#ifdef CONFIG_USE_GIT_VERSION_STRING
|
#ifdef CONFIG_USE_GIT_VERSION_STRING
|
||||||
#include "gitversion.h"
|
#include "gitversion.h"
|
||||||
@@ -51,8 +52,8 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <net/if.h>
|
|
||||||
#include <ifaddrs.h>
|
#include <ifaddrs.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
|
||||||
#ifdef COMPILE_FOR_LINUX
|
#ifdef COMPILE_FOR_LINUX
|
||||||
#include <netpacket/packet.h>
|
#include <netpacket/packet.h>
|
||||||
@@ -411,7 +412,7 @@ int bind_socket_and_port(int type, int ip_family, const char *self_ip_address, u
|
|||||||
#endif
|
#endif
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ret = errno;
|
ret = errno;
|
||||||
close(local_socket);
|
safe_socket_close(&local_socket);
|
||||||
char errorstring[1024];
|
char errorstring[1024];
|
||||||
getErrorText((char *)errorstring, sizeof(errorstring));
|
getErrorText((char *)errorstring, sizeof(errorstring));
|
||||||
warn("error %d: \"%s\". Could not bind a port!", errno, 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);
|
ret = getsockname(local_socket, (struct sockaddr *)&local, &local_len);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ret = errno;
|
ret = errno;
|
||||||
close(local_socket);
|
safe_socket_close(&local_socket);
|
||||||
char errorstring[1024];
|
char errorstring[1024];
|
||||||
getErrorText((char *)errorstring, sizeof(errorstring));
|
getErrorText((char *)errorstring, sizeof(errorstring));
|
||||||
warn("error %d: \"%s\". Could not retrieve socket's port!", errno, 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);
|
// debug(1,"UDP port chosen: %d.",desired_port);
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
close(local_socket);
|
safe_socket_close(&local_socket);
|
||||||
char errorstring[1024];
|
char errorstring[1024];
|
||||||
getErrorText((char *)errorstring, sizeof(errorstring));
|
getErrorText((char *)errorstring, sizeof(errorstring));
|
||||||
die("error %d: \"%s\". Could not bind a UDP port! Check the udp_port_range is large enough -- "
|
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);
|
mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
|
||||||
outbuf = malloc(trsa->MBEDTLS_PRIVATE_V3_ONLY(len));
|
outbuf = malloc(trsa->MBEDTLS_PRIVATE_V3_ONLY(len));
|
||||||
#if MBEDTLS_VERSION_MAJOR == 3
|
#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;
|
*outlen = olen;
|
||||||
#else
|
#else
|
||||||
rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE,
|
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;
|
const char *str = NULL;
|
||||||
int response = CONFIG_FALSE;
|
int response = CONFIG_FALSE;
|
||||||
config_setting_t *s = config_lookup(cfg, where);
|
config_setting_t *s = config_lookup(cfg, where);
|
||||||
if (s != NULL) {
|
if (s != NULL) {
|
||||||
if (config_setting_type(s) == CONFIG_TYPE_STRING) {
|
if (config_setting_type(s) == CONFIG_TYPE_STRING) {
|
||||||
str = config_setting_get_string(s);
|
str = config_setting_get_string(s);
|
||||||
if (strcasecmp(str, "no") == 0) {
|
if (strcasecmp(str, "no") == 0) {
|
||||||
@@ -1076,11 +1078,14 @@ int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) {
|
|||||||
(*dst) = 1;
|
(*dst) = 1;
|
||||||
response = CONFIG_TRUE;
|
response = CONFIG_TRUE;
|
||||||
} else {
|
} 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;
|
return 0;
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
return response;
|
||||||
@@ -1245,6 +1250,59 @@ int check_int_or_list_setting(config_setting_t *setting, const int item) {
|
|||||||
return result;
|
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) {
|
void command_set_volume(double volume) {
|
||||||
// this has a cancellation point if waiting is enabled
|
// this has a cancellation point if waiting is enabled
|
||||||
if (config.cmd_set_volume) {
|
if (config.cmd_set_volume) {
|
||||||
@@ -1310,11 +1368,11 @@ void command_start(void) {
|
|||||||
char **argV;
|
char **argV;
|
||||||
|
|
||||||
if (config.cmd_start_returns_output) {
|
if (config.cmd_start_returns_output) {
|
||||||
close(pipes[0]);
|
safe_socket_close(&pipes[0]);
|
||||||
if (dup2(pipes[1], 1) < 0) {
|
if (dup2(pipes[1], 1) < 0) {
|
||||||
warn("Unable to reopen pipe as stdout for popen of start command");
|
warn("Unable to reopen pipe as stdout for popen of start command");
|
||||||
debug(1, "dup2 finished with error %d", errno);
|
debug(1, "dup2 finished with error %d", errno);
|
||||||
close(pipes[1]);
|
safe_socket_close(&pipes[1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1344,9 +1402,9 @@ void command_start(void) {
|
|||||||
if (config.cmd_start_returns_output) {
|
if (config.cmd_start_returns_output) {
|
||||||
static char buffer[256];
|
static char buffer[256];
|
||||||
int len;
|
int len;
|
||||||
close(pipes[1]);
|
safe_socket_close(&pipes[1]);
|
||||||
len = read(pipes[0], buffer, 255);
|
len = read(pipes[0], buffer, 255);
|
||||||
close(pipes[0]);
|
safe_socket_close(&pipes[0]);
|
||||||
buffer[len] = '\0';
|
buffer[len] = '\0';
|
||||||
if (buffer[len - 1] == '\n')
|
if (buffer[len - 1] == '\n')
|
||||||
buffer[len - 1] = '\0'; // strip trailing newlines
|
buffer[len - 1] = '\0'; // strip trailing newlines
|
||||||
@@ -1705,45 +1763,6 @@ int try_to_open_pipe_for_writing(const char *pathname) {
|
|||||||
return fdis;
|
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 */
|
/* from http://burtleburtle.net/bob/rand/smallprng.html */
|
||||||
|
|
||||||
// this is not thread-safe, so we need a mutex on it to use it properly.
|
// 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;
|
rem = req;
|
||||||
} while ((result == -1) && (errno == EINTR));
|
} while ((result == -1) && (errno == EINTR));
|
||||||
if (result == -1)
|
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
|
// 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) {
|
if (strerror_r(r, errstr, sizeof(errstr)) == 0) {
|
||||||
debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, errstr, mutexname, dstring);
|
debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, errstr, mutexname, dstring);
|
||||||
} else {
|
} 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);
|
pthread_setcancelstate(oldState, NULL);
|
||||||
@@ -1938,10 +1958,9 @@ void plist_cleanup(void *arg) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void socket_cleanup(void *arg) {
|
void socket_cleanup(void *arg) {
|
||||||
intptr_t fdp = (intptr_t)arg;
|
int *p = (int *)arg;
|
||||||
int soc = fdp;
|
debug(3, "socket_cleanup called for socket: %d.", *p);
|
||||||
debug(3, "socket_cleanup called for socket: %d.", soc);
|
safe_socket_close(p);
|
||||||
close(fdp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cv_cleanup(void *arg) {
|
void cv_cleanup(void *arg) {
|
||||||
@@ -2349,12 +2368,8 @@ int get_device_id(uint8_t *id, int int_length) {
|
|||||||
#ifdef AF_PACKET
|
#ifdef AF_PACKET
|
||||||
if ((ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET)) {
|
if ((ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET)) {
|
||||||
struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr;
|
struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr;
|
||||||
if (
|
if (((ifa->ifa_flags & IFF_UP) != 0) && ((ifa->ifa_flags & IFF_RUNNING) != 0) &&
|
||||||
((ifa->ifa_flags & IFF_UP) != 0) &&
|
((ifa->ifa_flags & IFF_LOOPBACK) == 0) && (ifa->ifa_addr != 0)) {
|
||||||
((ifa->ifa_flags & IFF_RUNNING) != 0) &&
|
|
||||||
((ifa->ifa_flags & IFF_LOOPBACK) == 0) &&
|
|
||||||
(ifa->ifa_addr != 0)
|
|
||||||
) {
|
|
||||||
found = 1;
|
found = 1;
|
||||||
response = 0;
|
response = 0;
|
||||||
for (i = 0; ((i < s->sll_halen) && (i < int_length)); i++) {
|
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);
|
debug(1, "error creating thread \"%s\"", actual_name);
|
||||||
}
|
}
|
||||||
#ifndef COMPILE_FOR_OSX
|
#ifndef COMPILE_FOR_OSX
|
||||||
else {
|
if (response == 0) {
|
||||||
pthread_setname_np(*thread, actual_name);
|
pthread_setname_np(*thread, actual_name);
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
// ret == 0 if creating and setting up the attributes was successful
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
|
|
||||||
ret = pthread_create(thread, &attr, start_routine, arg);
|
ret = pthread_create(thread, &attr, start_routine, arg);
|
||||||
pthread_attr_destroy(&attr);
|
pthread_attr_destroy(&attr);
|
||||||
}
|
}
|
||||||
@@ -2489,7 +2505,7 @@ int named_pthread_create_with_priority(pthread_t *thread, int priority,
|
|||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
ret = pthread_create(thread, NULL, start_routine, arg);
|
ret = pthread_create(thread, NULL, start_routine, arg);
|
||||||
if (failed_to_set_rt == 0) {
|
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;
|
failed_to_set_rt = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ extern "C" {
|
|||||||
#include "definitions.h"
|
#include "definitions.h"
|
||||||
#include "mdns.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
|
// struct sockaddr_in6 is bigger than struct sockaddr. derp
|
||||||
#ifdef AF_INET6
|
#ifdef AF_INET6
|
||||||
#define SOCKADDR struct sockaddr_storage
|
#define SOCKADDR struct sockaddr_storage
|
||||||
@@ -24,8 +32,10 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(CONFIG_CONVOLUTION)
|
#if defined(CONFIG_CONVOLUTION)
|
||||||
|
// impulse response filter file status
|
||||||
typedef enum { ev_unchecked, ev_okay, ev_invalid } ir_file_evaluation;
|
typedef enum { ev_unchecked, ev_okay, ev_invalid } ir_file_evaluation;
|
||||||
|
|
||||||
|
// impulse response filter file record
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned int samplerate; // initialized to 0, will be filter frame rate
|
unsigned int samplerate; // initialized to 0, will be filter frame rate
|
||||||
unsigned int channels;
|
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_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))
|
#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
|
// in SPS_CHANNEL_SET, bit 0 set means a channel set of no channels, bit 1 set means a channel set
|
||||||
// and so on to bit 31 meaning a channel set of 31 channels. We want to consider all possible channel sets
|
// of 1 channel and so on to bit 31 meaning a channel set of 31 channels. We want to consider all
|
||||||
// apart from channel set 0.
|
// 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_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 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 (((1 << (SPS_GREATEST_CHANNEL_COUNT + 1)) - 1) - (1 << 0)) // channels 1
|
||||||
|
// to 31, not 0-based!
|
||||||
|
|
||||||
#ifndef CONFIG_AIRPLAY_2
|
#ifndef CONFIG_AIRPLAY_2
|
||||||
#define SPS_FORMAT_NON_FFMPEG_SET SPS_FORMAT_SET
|
#define SPS_FORMAT_NON_FFMPEG_SET SPS_FORMAT_SET
|
||||||
@@ -408,8 +419,11 @@ typedef struct {
|
|||||||
uint8_t airplay_pk[32]; // public key
|
uint8_t airplay_pk[32]; // public key
|
||||||
char *pk_string;
|
char *pk_string;
|
||||||
char *nqptp_shared_memory_interface_name; // client name for nqptp service
|
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
|
#endif
|
||||||
|
|
||||||
|
APST_t service_type; // APST_auto, APST_classic, APST_forced_classic, APST_airplay2
|
||||||
|
|
||||||
int unfixable_error_reported; // only report once.
|
int unfixable_error_reported; // only report once.
|
||||||
|
|
||||||
uint64_t eight_channel_layout; // non-zero means enabled and is a channel layout
|
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(
|
int try_to_open_pipe_for_writing(
|
||||||
const char *pathname); // open it without blocking if it's not hooked up
|
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
|
// based on http://burtleburtle.net/bob/rand/smallprng.html
|
||||||
|
|
||||||
void r64init(uint64_t seed);
|
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);
|
const char ***result);
|
||||||
unsigned int config_get_int_settings_as_int_array(config_setting_t *setting, int **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_start(void);
|
||||||
void command_stop(void);
|
void command_stop(void);
|
||||||
void command_execute(const char *command, const char *extra_argument, const int block);
|
void command_execute(const char *command, const char *extra_argument, const int block);
|
||||||
|
|||||||
+6
-1
@@ -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])])
|
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_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
|
fi
|
||||||
AM_CONDITIONAL([USE_AIRPLAY_2], [test "x$with_airplay_2" = "xyes"])
|
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}"
|
systemd_requires_args="Requires=${systemd_requires_args}"
|
||||||
fi
|
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_AFTER_ARGS], [${systemd_after_args}])
|
||||||
AC_SUBST([SYSTEMD_REQUIRES_ARGS], [${systemd_requires_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.
|
# 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.
|
# But leave this as is to enable a distclean to be done.
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
#include "metadata/hub.h"
|
#include "metadata/hub.h"
|
||||||
#include "tinyhttp/http.h"
|
#include "tinyhttp/http.h"
|
||||||
|
#include "utilities/network_utilities.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int players_connection_thread_index; // the connection thread index when a player thread is
|
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);
|
t = realloc(ptr, size);
|
||||||
}
|
}
|
||||||
if (t == NULL)
|
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;
|
return t;
|
||||||
}
|
}
|
||||||
@@ -117,8 +118,7 @@ void response_body(void *opaque, const char *data, int size) {
|
|||||||
static void
|
static void
|
||||||
response_header(__attribute__((unused)) void *opaque, __attribute__((unused)) const char *ckey,
|
response_header(__attribute__((unused)) void *opaque, __attribute__((unused)) const char *ckey,
|
||||||
__attribute__((unused)) int nkey, __attribute__((unused)) const char *cvalue,
|
__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) {
|
static void response_code(void *opaque, int code) {
|
||||||
struct HttpResponse *response = (struct HttpResponse *)opaque;
|
struct HttpResponse *response = (struct HttpResponse *)opaque;
|
||||||
@@ -150,12 +150,6 @@ void mutex_lock_cleanup(void *arg) {
|
|||||||
debug(1, "Error releasing mutex.");
|
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) {
|
void http_cleanup(void *arg) {
|
||||||
// debug(1, "http cleanup called.");
|
// debug(1, "http cleanup called.");
|
||||||
struct http_roundtripper *rt = (struct http_roundtripper *)arg;
|
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));
|
// \"%s\".",errno,strerror(errno));
|
||||||
response.code = 497; // Can't establish a socket to the DACP server
|
response.code = 497; // Can't establish a socket to the DACP server
|
||||||
} else {
|
} else {
|
||||||
pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
|
pthread_cleanup_push(socket_cleanup, (void *)&sockfd);
|
||||||
// debug(2, "dacp_send_command: open socket %d.",sockfd);
|
// debug(2, "dacp_send_command: open socket %d.",sockfd);
|
||||||
|
|
||||||
// This is for limiting the time to be spent waiting for a response.
|
// 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
|
0); // this should *not* free the malloced buffer -- just pop the malloc cleanup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pthread_cleanup_pop(1); // this should close the socket
|
pthread_cleanup_pop(1); // this will close the socket
|
||||||
// close(sockfd);
|
// debug(1,"DACP socket closed.");
|
||||||
// debug(1,"DACP socket closed.");
|
|
||||||
}
|
}
|
||||||
pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
|
pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
|
||||||
// pthread_mutex_unlock(&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;
|
int32_t active_speakers = 0;
|
||||||
for (i = 0; i < speaker_count; i++) {
|
for (i = 0; i < speaker_count; i++) {
|
||||||
if (speaker_info[i].speaker_number == machine_number) {
|
if (speaker_info[i].speaker_number == machine_number) {
|
||||||
debug(2, "Our speaker number found: %" PRId64 " with relative volume %" PRId32 ".", machine_number,
|
debug(2, "Our speaker number found: %" PRId64 " with relative volume %" PRId32 ".",
|
||||||
speaker_info[i].volume);
|
machine_number, speaker_info[i].volume);
|
||||||
}
|
}
|
||||||
if (speaker_info[i].active == 1) {
|
if (speaker_info[i].active == 1) {
|
||||||
active_speakers++;
|
active_speakers++;
|
||||||
|
|||||||
+27
-29
@@ -418,7 +418,7 @@ static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
#ifdef CONFIG_DACP_CLIENT
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("play");
|
send_simple_dacp_command("play");
|
||||||
#endif
|
#endif
|
||||||
shairport_sync_remote_control_complete_play(skeleton, invocation);
|
shairport_sync_remote_control_complete_play(skeleton, invocation);
|
||||||
@@ -596,9 +596,9 @@ gboolean notify_convolution_enabled_callback(__attribute__((unused)) ShairportSy
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_CONVOLUTION
|
#ifdef CONFIG_CONVOLUTION
|
||||||
gboolean notify_convolution_maximum_length_in_seconds_callback(ShairportSync *skeleton,
|
gboolean
|
||||||
__attribute__((unused))
|
notify_convolution_maximum_length_in_seconds_callback(ShairportSync *skeleton,
|
||||||
gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
|
||||||
gdouble th = shairport_sync_get_convolution_maximum_length_in_seconds(skeleton);
|
gdouble th = shairport_sync_get_convolution_maximum_length_in_seconds(skeleton);
|
||||||
if ((th >= 0.0) && (th <= 15.0)) {
|
if ((th >= 0.0) && (th <= 15.0)) {
|
||||||
@@ -612,10 +612,8 @@ gboolean notify_convolution_maximum_length_in_seconds_callback(ShairportSync *sk
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
gboolean notify_convolution_maximum_length_in_seconds_callback(__attribute__((unused))
|
gboolean notify_convolution_maximum_length_in_seconds_callback(
|
||||||
ShairportSync *skeleton,
|
__attribute__((unused)) ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) {
|
||||||
__attribute__((unused))
|
|
||||||
gpointer user_data) {
|
|
||||||
warn(">> Convolution support is not built in to this build of Shairport Sync.");
|
warn(">> Convolution support is not built in to this build of Shairport Sync.");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -643,16 +641,16 @@ gboolean notify_convolution_gain_callback(__attribute__((unused)) ShairportSync
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_CONVOLUTION
|
#ifdef CONFIG_CONVOLUTION
|
||||||
gboolean notify_convolution_impulse_response_files_callback(ShairportSync *skeleton,
|
gboolean
|
||||||
__attribute__((unused))
|
notify_convolution_impulse_response_files_callback(ShairportSync *skeleton,
|
||||||
gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
char *th = (char *)shairport_sync_get_convolution_impulse_response_files(skeleton);
|
char *th = (char *)shairport_sync_get_convolution_impulse_response_files(skeleton);
|
||||||
if (th != NULL) {
|
if (th != NULL) {
|
||||||
debug(1, ">> freeing current configuration impulse response filter files.");
|
debug(1, ">> freeing current configuration impulse response filter files.");
|
||||||
free_ir_filenames(config.convolution_ir_files, config.convolution_ir_file_count);
|
free_ir_filenames(config.convolution_ir_files, config.convolution_ir_file_count);
|
||||||
config.convolution_ir_files = NULL;
|
config.convolution_ir_files = NULL;
|
||||||
config.convolution_ir_file_count = 0;
|
config.convolution_ir_file_count = 0;
|
||||||
|
|
||||||
config.convolution_ir_files = parse_ir_filenames(th, &config.convolution_ir_file_count);
|
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);
|
sanity_check_ir_files(1, config.convolution_ir_files, config.convolution_ir_file_count);
|
||||||
debug(1, ">> setting %d configuration impulse response filter%s",
|
debug(1, ">> setting %d configuration impulse response filter%s",
|
||||||
@@ -662,10 +660,9 @@ gboolean notify_convolution_impulse_response_files_callback(ShairportSync *skele
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
gboolean notify_convolution_impulse_response_files_callback(__attribute__((unused))
|
gboolean
|
||||||
ShairportSync *skeleton,
|
notify_convolution_impulse_response_files_callback(__attribute__((unused)) ShairportSync *skeleton,
|
||||||
__attribute__((unused))
|
__attribute__((unused)) gpointer user_data) {
|
||||||
gpointer user_data) {
|
|
||||||
__attribute__((unused)) char *th =
|
__attribute__((unused)) char *th =
|
||||||
(char *)shairport_sync_get_convolution_impulse_response_files(skeleton);
|
(char *)shairport_sync_get_convolution_impulse_response_files(skeleton);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -716,7 +713,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton,
|
|||||||
gdouble iv = shairport_sync_get_volume(skeleton);
|
gdouble iv = shairport_sync_get_volume(skeleton);
|
||||||
if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) {
|
if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) {
|
||||||
debug(2, ">> set volume to %7.4f.", iv);
|
debug(2, ">> set volume to %7.4f.", iv);
|
||||||
config.airplay_volume = iv;
|
config.airplay_volume = iv;
|
||||||
} else {
|
} else {
|
||||||
debug(1, ">> invalid volume: %f. Ignored.", iv);
|
debug(1, ">> invalid volume: %f. Ignored.", iv);
|
||||||
shairport_sync_set_volume(skeleton, config.airplay_volume);
|
shairport_sync_set_volume(skeleton, config.airplay_volume);
|
||||||
@@ -760,9 +757,10 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
|
|||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
|
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
if (strcasecmp(th, "ffmpeg") != 0) {
|
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);
|
th);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@@ -879,7 +877,8 @@ gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton,
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
gboolean notify_shuffle_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton,
|
gboolean notify_shuffle_callback(__attribute__((unused))
|
||||||
|
ShairportSyncAdvancedRemoteControl *skeleton,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -924,7 +923,8 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
gboolean notify_loop_status_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton,
|
gboolean notify_loop_status_callback(__attribute__((unused))
|
||||||
|
ShairportSyncAdvancedRemoteControl *skeleton,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
return TRUE;
|
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,
|
static gboolean on_handle_drop_session(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__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);
|
shairport_sync_complete_drop_session(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean on_handle_set_frame_position_update_interval(ShairportSync *skeleton,
|
static gboolean on_handle_set_frame_position_update_interval(
|
||||||
GDBusMethodInvocation *invocation,
|
ShairportSync *skeleton, GDBusMethodInvocation *invocation, const gdouble seconds,
|
||||||
const gdouble seconds,
|
__attribute__((unused)) gpointer user_data) {
|
||||||
__attribute__((unused))
|
|
||||||
gpointer user_data) {
|
|
||||||
debug(1, ">> set frame position update interval to %.6f.", seconds);
|
debug(1, ">> set frame position update interval to %.6f.", seconds);
|
||||||
config.metadata_progress_interval = seconds;
|
config.metadata_progress_interval = seconds;
|
||||||
shairport_sync_complete_set_frame_position_update_interval(skeleton, invocation);
|
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_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::file-and-line",
|
||||||
G_CALLBACK(notify_file_and_line_callback), NULL);
|
G_CALLBACK(notify_file_and_line_callback), NULL);
|
||||||
|
|
||||||
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-fast-forward",
|
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-fast-forward",
|
||||||
G_CALLBACK(on_handle_fast_forward), NULL);
|
G_CALLBACK(on_handle_fast_forward), NULL);
|
||||||
|
|
||||||
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-rewind",
|
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-rewind",
|
||||||
G_CALLBACK(on_handle_rewind), NULL);
|
G_CALLBACK(on_handle_rewind), NULL);
|
||||||
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-toggle-mute",
|
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-toggle-mute",
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
// for generating our own UUIDs
|
// for generating our own UUIDs
|
||||||
#define SHAIRPORT_SYNC_DEVICE_NAMESPACE "01d7c137-6316-455d-a52f-dfb529f26adf"
|
#define SHAIRPORT_SYNC_DEVICE_NAMESPACE "01d7c137-6316-455d-a52f-dfb529f26adf"
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,33 @@ RUN DESTDIR=install make install
|
|||||||
WORKDIR /
|
WORKDIR /
|
||||||
##### SPS END #####
|
##### 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 #####
|
##### STATIC FILES #####
|
||||||
FROM scratch AS files
|
FROM scratch AS files
|
||||||
|
|
||||||
|
|||||||
Executable
+36
@@ -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
|
||||||
+16
-2
@@ -15,9 +15,23 @@ if [ -z ${ENABLE_AVAHI+x} ] || [ $ENABLE_AVAHI -eq 1 ]; then
|
|||||||
avahi-daemon --daemonize --no-chroot
|
avahi-daemon --daemonize --no-chroot
|
||||||
fi
|
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
|
while [ ! -f /var/run/avahi-daemon/pid ]; do
|
||||||
echo "Warning: avahi is not running, sleeping for 5 seconds before trying to start shairport-sync"
|
echo "Warning: avahi is not running, sleeping for 5 seconds before trying to start shairport-sync"
|
||||||
|
|||||||
@@ -39,7 +39,9 @@
|
|||||||
<opt>[-a </opt><arg>service-name</arg><opt> | --name=</opt><arg>service-name</arg><opt>]</opt>
|
<opt>[-a </opt><arg>service-name</arg><opt> | --name=</opt><arg>service-name</arg><opt>]</opt>
|
||||||
<opt>[-B </opt><arg>command</arg><opt> | --onstart=</opt><arg>command</arg><opt>]</opt>
|
<opt>[-B </opt><arg>command</arg><opt> | --onstart=</opt><arg>command</arg><opt>]</opt>
|
||||||
<opt>[-c </opt><arg>configurationfile</arg><opt> | --configfile=</opt><arg>configurationfile</arg><opt>]</opt>
|
<opt>[-c </opt><arg>configurationfile</arg><opt> | --configfile=</opt><arg>configurationfile</arg><opt>]</opt>
|
||||||
|
<opt>[--classic-airplay-only]</opt>
|
||||||
<opt>[-d | --daemon]</opt>
|
<opt>[-d | --daemon]</opt>
|
||||||
|
<opt>[--dbus-default-message-bus=</opt><arg>bus</arg><opt>]</opt>
|
||||||
<opt>[-E </opt><arg>command</arg><opt> | --onstop=</opt><arg>command</arg><opt>]</opt>
|
<opt>[-E </opt><arg>command</arg><opt> | --onstop=</opt><arg>command</arg><opt>]</opt>
|
||||||
<opt>[-g | --get-cover-art]</opt>
|
<opt>[-g | --get-cover-art]</opt>
|
||||||
<opt>[-j | --justDaemoniseNoPIDFile]</opt>
|
<opt>[-j | --justDaemoniseNoPIDFile]</opt>
|
||||||
@@ -202,6 +204,15 @@
|
|||||||
shairport-sync has been compiled with libdaemon support.
|
shairport-sync has been compiled with libdaemon support.
|
||||||
</p></optdesc>
|
</p></optdesc>
|
||||||
</option>
|
</option>
|
||||||
|
|
||||||
|
<option>
|
||||||
|
<p><opt>--dbus-default-message-bus=</opt><arg>bus</arg></p>
|
||||||
|
<optdesc><p>
|
||||||
|
Use the <arg>bus</arg> specified for the native D-Bus and MPRIS interfaces.
|
||||||
|
The <arg>bus</arg> must be <opt>system</opt> or <opt>session</opt>. This option is
|
||||||
|
only available if Shairport Sync is built with the D-Bus or MPRIS interface.
|
||||||
|
</p></optdesc>
|
||||||
|
</option>
|
||||||
|
|
||||||
<option>
|
<option>
|
||||||
<p><opt>-E </opt><arg>program</arg><opt> | --on-stop=</opt><arg>program</arg></p>
|
<p><opt>-E </opt><arg>program</arg><opt> | --on-stop=</opt><arg>program</arg></p>
|
||||||
@@ -350,6 +361,23 @@
|
|||||||
</p></optdesc>
|
</p></optdesc>
|
||||||
</option>
|
</option>
|
||||||
|
|
||||||
|
<option>
|
||||||
|
<p><opt>--service-type</opt></p>
|
||||||
|
<optdesc><p>
|
||||||
|
On an AirPlay-2-capable Shairport Sync, if you need offer only the older
|
||||||
|
"classic" AirPlay (sometimes called AirPlay 1) service, set
|
||||||
|
<opt>--service-type=classic</opt>.
|
||||||
|
A potential use of this would be for Apple Music on Windows, which is not compatible
|
||||||
|
with the AirPlay 2 service offered by Shairport Sync,
|
||||||
|
but which is compatible with the classic-only service.
|
||||||
|
To offer AirPlay 2 service only, set <opt>--service-type=airplay2</opt> (note that this
|
||||||
|
requires NQPTP).
|
||||||
|
Set <opt>--service-type=auto</opt> to allow Shairport Sync to choose AirPlay 2
|
||||||
|
service if NQPTP is present or classic AirPlay service if not, with
|
||||||
|
"(Classic)" appended to the default service name.
|
||||||
|
</p></optdesc>
|
||||||
|
</option>
|
||||||
|
|
||||||
<option>
|
<option>
|
||||||
<p><opt>--statistics</opt></p>
|
<p><opt>--statistics</opt></p>
|
||||||
<optdesc><p>
|
<optdesc><p>
|
||||||
@@ -365,11 +393,13 @@
|
|||||||
process of adding or removing frames of audio to or from the
|
process of adding or removing frames of audio to or from the
|
||||||
stream sent to the output device in order to keep it synchronised
|
stream sent to the output device in order to keep it synchronised
|
||||||
with the player.
|
with the player.
|
||||||
The <opt>basic</opt> mode is normally almost completely inaudible.
|
The <opt>vernier</opt> mode is normally almost completely inaudible and suitable for low-powered devices.
|
||||||
The alternative mode, <opt>soxr</opt>, is even less obtrusive but
|
An alternative mode, <opt>soxr</opt>, is less obtrusive but
|
||||||
requires much more processing power. For this mode, support for
|
requires much more processing power. For this mode, support for
|
||||||
<opt>libsoxr</opt>, the SoX Resampler Library, must be selected when
|
<opt>libsoxr</opt>, the SoX Resampler Library, must be selected when
|
||||||
<opt>shairport-sync</opt> is built.
|
<opt>shairport-sync</opt> is built.
|
||||||
|
The <opt>basic</opt> mode is also suitable for low-powered device,
|
||||||
|
but <opt>vernier</opt> is better.
|
||||||
|
|
||||||
The default setting, <opt>auto</opt>, allows Shairport Sync to choose
|
The default setting, <opt>auto</opt>, allows Shairport Sync to choose
|
||||||
<opt>soxr</opt> mode if the system is powerful enough.
|
<opt>soxr</opt> mode if the system is powerful enough.
|
||||||
@@ -485,6 +515,10 @@
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section name="Note">
|
||||||
|
<p>The <opt>dacquery</opt> utility (<url href="https://github.com/mikebrady/dacquery"/>) might be useful for discovering the properties of your system's DACs.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section name="Credits">
|
<section name="Credits">
|
||||||
<p>Mike Brady (<url href="https://github.com/mikebrady"/>) developed Shairport Sync from Shairport by James Wah (<url href="https://github.com/abrasive"/>).</p>
|
<p>Mike Brady (<url href="https://github.com/mikebrady"/>) developed Shairport Sync from Shairport by James Wah (<url href="https://github.com/abrasive"/>).</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+3
-4
@@ -91,8 +91,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
|
|||||||
const char *name, const char *type, const char *domain,
|
const char *name, const char *type, const char *domain,
|
||||||
__attribute__((unused)) const char *host_name,
|
__attribute__((unused)) const char *host_name,
|
||||||
#if defined(CONFIG_METADATA) || defined(CONFIG_DACP_CLIENT)
|
#if defined(CONFIG_METADATA) || defined(CONFIG_DACP_CLIENT)
|
||||||
__attribute__((unused)) const AvahiAddress *address,
|
__attribute__((unused)) const AvahiAddress *address, uint16_t lport,
|
||||||
uint16_t lport,
|
|
||||||
#else
|
#else
|
||||||
__attribute__((unused)) const AvahiAddress *address,
|
__attribute__((unused)) const AvahiAddress *address,
|
||||||
__attribute__((unused)) uint16_t lport,
|
__attribute__((unused)) uint16_t lport,
|
||||||
@@ -120,7 +119,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
|
|||||||
while (*dacpid == '0')
|
while (*dacpid == '0')
|
||||||
dacpid++; // skip any leading zeroes
|
dacpid++; // skip any leading zeroes
|
||||||
if (strcmp(dacpid, dbs->dacp_id) == 0) {
|
if (strcmp(dacpid, dbs->dacp_id) == 0) {
|
||||||
debug(1, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, lport);
|
debug(4, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, lport);
|
||||||
#ifdef CONFIG_DACP_CLIENT
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
dacp_monitor_port_update_callback(dacpid, lport);
|
dacp_monitor_port_update_callback(dacpid, lport);
|
||||||
#endif
|
#endif
|
||||||
@@ -178,7 +177,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
|
|||||||
dacp_monitor_port_update_callback(dbs->dacp_id, 0); // say the port is withdrawn
|
dacp_monitor_port_update_callback(dbs->dacp_id, 0); // say the port is withdrawn
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_METADATA
|
#ifdef CONFIG_METADATA
|
||||||
send_ssnc_metadata('dapo', "0", strlen("0"), 0);
|
send_ssnc_metadata('dapo', "0", strlen("0"), 0);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+16
-21
@@ -25,15 +25,14 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "pc_queue.h"
|
#include "pc_queue.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA_PIPE
|
#ifdef CONFIG_METADATA_PIPE
|
||||||
#include "pipe.h"
|
#include "pipe.h"
|
||||||
@@ -51,7 +50,6 @@
|
|||||||
#include "mqtt.h"
|
#include "mqtt.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// Metadata is not used by shairport-sync.
|
// Metadata is not used by shairport-sync.
|
||||||
// Instead we send all metadata to a fifo pipe, so that other apps can listen to
|
// Instead we send all metadata to a fifo pipe, so that other apps can listen to
|
||||||
// the pipe and use the metadata.
|
// the pipe and use the metadata.
|
||||||
@@ -167,14 +165,14 @@ void metadata_init(void) {
|
|||||||
|
|
||||||
#ifdef CONFIG_METADATA_PIPE
|
#ifdef CONFIG_METADATA_PIPE
|
||||||
metadata_pipe_queue_init();
|
metadata_pipe_queue_init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA_MULTICAST
|
#ifdef CONFIG_METADATA_MULTICAST
|
||||||
metadata_multicast_queue_init();
|
metadata_multicast_queue_init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA_HUB
|
#ifdef CONFIG_METADATA_HUB
|
||||||
metadata_hub_queue_init();
|
metadata_hub_queue_init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_MQTT
|
#ifdef CONFIG_MQTT
|
||||||
@@ -195,14 +193,13 @@ void metadata_stop(void) {
|
|||||||
metadata_hub_queue_stop();
|
metadata_hub_queue_stop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA_MULTICAST
|
#ifdef CONFIG_METADATA_MULTICAST
|
||||||
metadata_multicast_queue_stop();
|
metadata_multicast_queue_stop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA_PIPE
|
#ifdef CONFIG_METADATA_PIPE
|
||||||
metadata_pipe_queue_stop();
|
metadata_pipe_queue_stop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,17 +256,18 @@ int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t
|
|||||||
if (pack.carrier) {
|
if (pack.carrier) {
|
||||||
if (rc == EWOULDBLOCK)
|
if (rc == EWOULDBLOCK)
|
||||||
debug(2,
|
debug(2,
|
||||||
"metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR ", "
|
"metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR
|
||||||
|
", "
|
||||||
"length %u, message %d.",
|
"length %u, message %d.",
|
||||||
queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length,
|
queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length,
|
||||||
pack.carrier->index_number);
|
pack.carrier->index_number);
|
||||||
msg_free(&pack.carrier);
|
msg_free(&pack.carrier);
|
||||||
} else {
|
} else {
|
||||||
if (rc == EWOULDBLOCK)
|
if (rc == EWOULDBLOCK)
|
||||||
debug(
|
debug(2,
|
||||||
2,
|
"metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR
|
||||||
"metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR ", length %u.",
|
", length %u.",
|
||||||
queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length);
|
queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length);
|
||||||
if (pack.data)
|
if (pack.data)
|
||||||
free(pack.data);
|
free(pack.data);
|
||||||
}
|
}
|
||||||
@@ -287,8 +285,7 @@ int send_metadata(const uint32_t type, const uint32_t code, const char *data, co
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA_MULTICAST
|
#ifdef CONFIG_METADATA_MULTICAST
|
||||||
rc =
|
rc = send_metadata_to_multicast_queue(type, code, data, length, carrier, block);
|
||||||
send_metadata_to_multicast_queue(type, code, data, length, carrier, block);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA_HUB
|
#ifdef CONFIG_METADATA_HUB
|
||||||
@@ -298,7 +295,6 @@ int send_metadata(const uint32_t type, const uint32_t code, const char *data, co
|
|||||||
#ifdef CONFIG_MQTT
|
#ifdef CONFIG_MQTT
|
||||||
rc = send_metadata_to_mqtt_queue(type, code, data, length, carrier, block);
|
rc = send_metadata_to_mqtt_queue(type, code, data, length, carrier, block);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@@ -307,4 +303,3 @@ int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t len
|
|||||||
const int block) {
|
const int block) {
|
||||||
return send_metadata('ssnc', code, data, length, NULL, block);
|
return send_metadata('ssnc', code, data, length, NULL, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -1,8 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "../rtsp.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "../rtsp.h"
|
|
||||||
#include "pc_queue.h"
|
#include "pc_queue.h"
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
@@ -21,7 +21,6 @@ void metadata_pack_cleanup_function(void *arg);
|
|||||||
void metadata_init(void);
|
void metadata_init(void);
|
||||||
void metadata_stop(void);
|
void metadata_stop(void);
|
||||||
|
|
||||||
|
|
||||||
int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code,
|
int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
int block);
|
int block);
|
||||||
|
|||||||
+9
-10
@@ -43,10 +43,10 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "core.h"
|
||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
#include "hub.h"
|
#include "hub.h"
|
||||||
#include "pc_queue.h"
|
#include "pc_queue.h"
|
||||||
#include "core.h"
|
|
||||||
|
|
||||||
#ifdef CONFIG_MBEDTLS
|
#ifdef CONFIG_MBEDTLS
|
||||||
#include <mbedtls/md5.h>
|
#include <mbedtls/md5.h>
|
||||||
@@ -759,15 +759,14 @@ void metadata_hub_queue_init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void metadata_hub_queue_stop() {
|
void metadata_hub_queue_stop() {
|
||||||
// debug(2, "metadata stop hub thread.");
|
// debug(2, "metadata stop hub thread.");
|
||||||
pthread_cancel(metadata_hub_thread);
|
pthread_cancel(metadata_hub_thread);
|
||||||
pthread_join(metadata_hub_thread, NULL);
|
pthread_join(metadata_hub_thread, NULL);
|
||||||
pc_queue_delete(&metadata_hub_queue);
|
pc_queue_delete(&metadata_hub_queue);
|
||||||
// debug(2, "metadata stop hub done.");
|
// debug(2, "metadata stop hub done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code,
|
int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code, const char *data,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const uint32_t length, rtsp_message *carrier, int block) {
|
||||||
int block) {
|
return send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
|
||||||
return send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -194,7 +194,5 @@ void _metadata_hub_read_epilog(const char *filename, const int linenumber);
|
|||||||
// metadata queue stuff
|
// metadata queue stuff
|
||||||
void metadata_hub_queue_init();
|
void metadata_hub_queue_init();
|
||||||
void metadata_hub_queue_stop();
|
void metadata_hub_queue_stop();
|
||||||
int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code,
|
int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code, const char *data,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const uint32_t length, rtsp_message *carrier, int block);
|
||||||
int block);
|
|
||||||
|
|
||||||
|
|||||||
+18
-19
@@ -1,16 +1,15 @@
|
|||||||
#include "multicast.h"
|
#include "multicast.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "pc_queue.h"
|
#include "pc_queue.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
// A special sub-protocol is used for sending large data items over UDP
|
// A special sub-protocol is used for sending large data items over UDP
|
||||||
// If the payload exceeded 4 MB, it is chunked using the following format:
|
// If the payload exceeded 4 MB, it is chunked using the following format:
|
||||||
// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
|
// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
|
||||||
// Notice that the number of items is different to the standard
|
// Notice that the number of items is different to the standard
|
||||||
|
|
||||||
#define METADATA_SNDBUF (4 * 1024 * 1024)
|
#define METADATA_SNDBUF (4 * 1024 * 1024)
|
||||||
static int metadata_sock = -1;
|
static int metadata_sock = -1;
|
||||||
static struct sockaddr_in metadata_sockaddr;
|
static struct sockaddr_in metadata_sockaddr;
|
||||||
@@ -20,10 +19,10 @@ pc_queue metadata_multicast_queue;
|
|||||||
metadata_package metadata_multicast_queue_items[metadata_multicast_queue_size];
|
metadata_package metadata_multicast_queue_items[metadata_multicast_queue_size];
|
||||||
pthread_t metadata_multicast_thread;
|
pthread_t metadata_multicast_thread;
|
||||||
|
|
||||||
int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code,
|
int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code, const char *data,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const uint32_t length, rtsp_message *carrier, int block) {
|
||||||
int block) {
|
return send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier,
|
||||||
return send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
|
block);
|
||||||
}
|
}
|
||||||
|
|
||||||
void metadata_create_multicast_socket(void) {
|
void metadata_create_multicast_socket(void) {
|
||||||
@@ -162,12 +161,12 @@ void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void metadata_multicast_queue_init() {
|
void metadata_multicast_queue_init() {
|
||||||
// create a pc_queue for the metadata_multicast_queue
|
// create a pc_queue for the metadata_multicast_queue
|
||||||
pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
|
pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
|
||||||
sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
|
sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
|
||||||
if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function,
|
if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function,
|
||||||
NULL, "metadata mcst") != 0)
|
NULL, "metadata mcst") != 0)
|
||||||
debug(1, "Failed to create metadata multicast thread!");
|
debug(1, "Failed to create metadata multicast thread!");
|
||||||
}
|
}
|
||||||
|
|
||||||
void metadata_multicast_queue_stop() {
|
void metadata_multicast_queue_stop() {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
void metadata_multicast_queue_init();
|
void metadata_multicast_queue_init();
|
||||||
void metadata_multicast_queue_stop();
|
void metadata_multicast_queue_stop();
|
||||||
int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code,
|
int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code, const char *data,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const uint32_t length, rtsp_message *carrier, int block);
|
||||||
int block);
|
|
||||||
+1
-2
@@ -27,9 +27,9 @@
|
|||||||
|
|
||||||
#include "pc_queue.h"
|
#include "pc_queue.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
|
void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
|
||||||
const char *name) {
|
const char *name) {
|
||||||
@@ -169,4 +169,3 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-15
@@ -1,14 +1,14 @@
|
|||||||
#include "pipe.h"
|
#include "pipe.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "pc_queue.h"
|
#include "pc_queue.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <errno.h>
|
||||||
#include <sys/stat.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
static int fd = -1; // the pipe file descriptor
|
static int fd = -1; // the pipe file descriptor
|
||||||
|
|
||||||
pc_queue metadata_queue;
|
pc_queue metadata_queue;
|
||||||
#define metadata_queue_size 500
|
#define metadata_queue_size 500
|
||||||
metadata_package metadata_queue_items[metadata_queue_size];
|
metadata_package metadata_queue_items[metadata_queue_size];
|
||||||
@@ -152,8 +152,8 @@ void metadata_pipe_queue_init() {
|
|||||||
if ((fd == -1) && (errno != ENXIO)) {
|
if ((fd == -1) && (errno != ENXIO)) {
|
||||||
char errorstring[1024];
|
char errorstring[1024];
|
||||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||||
debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno,
|
debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno, (char *)errorstring,
|
||||||
(char *)errorstring, path);
|
path);
|
||||||
warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
|
warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
|
||||||
(char *)errorstring, path);
|
(char *)errorstring, path);
|
||||||
}
|
}
|
||||||
@@ -179,8 +179,7 @@ void metadata_pipe_queue_stop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code,
|
int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code, const char *data,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const uint32_t length, rtsp_message *carrier, int block) {
|
||||||
int block) {
|
return send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
|
||||||
return send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -1,10 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
void metadata_pipe_queue_init();
|
void metadata_pipe_queue_init();
|
||||||
void metadata_pipe_queue_stop();
|
void metadata_pipe_queue_stop();
|
||||||
int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code,
|
int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code, const char *data,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const uint32_t length, rtsp_message *carrier, int block);
|
||||||
int block);
|
|
||||||
@@ -8,14 +8,16 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
|
#include "utilities/string_utilities.h"
|
||||||
|
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
|
|
||||||
#ifdef CONFIG_DACP_CLIENT
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
#endif
|
#endif
|
||||||
#include "metadata/hub.h"
|
|
||||||
#include "metadata/core.h"
|
#include "metadata/core.h"
|
||||||
|
#include "metadata/hub.h"
|
||||||
#include "metadata/pc_queue.h"
|
#include "metadata/pc_queue.h"
|
||||||
|
|
||||||
#include "mqtt.h"
|
#include "mqtt.h"
|
||||||
@@ -79,7 +81,7 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
|
|||||||
debug(2, "[MQTT]: Received Recognized Command: %s\n", commands[it]);
|
debug(2, "[MQTT]: Received Recognized Command: %s\n", commands[it]);
|
||||||
if (strcmp(commands[it], "disconnect") == 0) {
|
if (strcmp(commands[it], "disconnect") == 0) {
|
||||||
debug(2, "[MQTT]: Disconnect Command: %s\n", commands[it]);
|
debug(2, "[MQTT]: Disconnect Command: %s\n", commands[it]);
|
||||||
release_play_lock(NULL); // stop any current session and don't replace it
|
stop_play(); // stop any current session and don't replace it
|
||||||
} else {
|
} else {
|
||||||
debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
|
debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
|
||||||
#ifdef CONFIG_DACP_CLIENT
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
@@ -211,10 +213,10 @@ void send_autodiscovery_messages(struct mosquitto *mosq) {
|
|||||||
(strcmp(sensors[i], "active") == 0 || strcmp(sensors[i], "playing") == 0);
|
(strcmp(sensors[i], "active") == 0 || strcmp(sensors[i], "playing") == 0);
|
||||||
bool is_volume_sensor = strcmp(sensors[i], "volume") == 0;
|
bool is_volume_sensor = strcmp(sensors[i], "volume") == 0;
|
||||||
|
|
||||||
const char* entity_type = is_binary_sensor ? "binary_sensor" : "sensor";
|
const char *entity_type = is_binary_sensor ? "binary_sensor" : "sensor";
|
||||||
|
|
||||||
snprintf(topic, sizeof(topic), "%s/%s/%s_%s/%s/config", autodiscovery_prefix,
|
snprintf(topic, sizeof(topic), "%s/%s/%s_%s/%s/config", autodiscovery_prefix, entity_type,
|
||||||
entity_type, model, device_id_no_colons, sensors[i]);
|
model, device_id_no_colons, sensors[i]);
|
||||||
|
|
||||||
snprintf(id_string, sizeof(id_string), "%s_%s_%s", model, device_name, sensors[i]);
|
snprintf(id_string, sizeof(id_string), "%s_%s_%s", model, device_name, sensors[i]);
|
||||||
|
|
||||||
@@ -232,12 +234,12 @@ void send_autodiscovery_messages(struct mosquitto *mosq) {
|
|||||||
"\"default_entity_id\": \"%s.%s\","
|
"\"default_entity_id\": \"%s.%s\","
|
||||||
"%s%s%s"
|
"%s%s%s"
|
||||||
"}",
|
"}",
|
||||||
sensor_names[i], // name
|
sensor_names[i], // name
|
||||||
config.mqtt_topic, sensors[i], //state_topic
|
config.mqtt_topic, sensors[i], // state_topic
|
||||||
icons[i], // icon
|
icons[i], // icon
|
||||||
id_string, // unique_id
|
id_string, // unique_id
|
||||||
id_string, // object_id
|
id_string, // object_id
|
||||||
entity_type, id_string, // default_entity_id
|
entity_type, id_string, // default_entity_id
|
||||||
is_binary_sensor ? "\"payload_on\": \"1\",\"payload_off\": \"0\"," : "",
|
is_binary_sensor ? "\"payload_on\": \"1\",\"payload_off\": \"0\"," : "",
|
||||||
is_volume_sensor
|
is_volume_sensor
|
||||||
? "\"value_template\": \"{{ ((value | regex_findall_index("
|
? "\"value_template\": \"{{ ((value | regex_findall_index("
|
||||||
@@ -268,8 +270,7 @@ void mqtt_publish(char *topic, char *data_in, uint32_t length_in) {
|
|||||||
|
|
||||||
int rc;
|
int rc;
|
||||||
if ((rc = mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0,
|
if ((rc = mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0,
|
||||||
config.mqtt_publish_retain)) !=
|
config.mqtt_publish_retain)) != MOSQ_ERR_SUCCESS) {
|
||||||
MOSQ_ERR_SUCCESS) {
|
|
||||||
switch (rc) {
|
switch (rc) {
|
||||||
case MOSQ_ERR_NO_CONN:
|
case MOSQ_ERR_NO_CONN:
|
||||||
debug(1, "[MQTT]: Publish failed: not connected to broker");
|
debug(1, "[MQTT]: Publish failed: not connected to broker");
|
||||||
@@ -503,15 +504,13 @@ void metadata_mqtt_queue_init() {
|
|||||||
debug(1, "Failed to create metadata mqtt thread!");
|
debug(1, "Failed to create metadata mqtt thread!");
|
||||||
}
|
}
|
||||||
void metadata_mqtt_queue_stop() {
|
void metadata_mqtt_queue_stop() {
|
||||||
// debug(2, "metadata stop mqtt thread.");
|
// debug(2, "metadata stop mqtt thread.");
|
||||||
pthread_cancel(metadata_mqtt_thread);
|
pthread_cancel(metadata_mqtt_thread);
|
||||||
pthread_join(metadata_mqtt_thread, NULL);
|
pthread_join(metadata_mqtt_thread, NULL);
|
||||||
pc_queue_delete(&metadata_mqtt_queue);
|
pc_queue_delete(&metadata_mqtt_queue);
|
||||||
// debug(2, "metadata stop mqtt done.");
|
// debug(2, "metadata stop mqtt done.");
|
||||||
}
|
}
|
||||||
int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code,
|
int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code, const char *data,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const uint32_t length, rtsp_message *carrier, int block) {
|
||||||
int block) {
|
return send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
|
||||||
return send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ void _cb_log(struct mosquitto *mosq, void *userdata, int level, const char *str)
|
|||||||
|
|
||||||
void metadata_mqtt_queue_init();
|
void metadata_mqtt_queue_init();
|
||||||
void metadata_mqtt_queue_stop();
|
void metadata_mqtt_queue_stop();
|
||||||
int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code,
|
int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code, const char *data,
|
||||||
const char *data, const uint32_t length, rtsp_message *carrier,
|
const uint32_t length, rtsp_message *carrier, int block);
|
||||||
int block);
|
|
||||||
#endif /* #ifndef MQTT_H */
|
#endif /* #ifndef MQTT_H */
|
||||||
|
|||||||
@@ -657,17 +657,17 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
// or by using the setting that has been given.
|
// or by using the setting that has been given.
|
||||||
|
|
||||||
// Upmixing will not be done if the setting is "auto".
|
// Upmixing will not be done if the setting is "auto".
|
||||||
|
|
||||||
// Similarly, on the "auto" setting, downmixing will be done
|
// Similarly, on the "auto" setting, downmixing will be done
|
||||||
// only if the number of output channels available
|
// only if the number of output channels available
|
||||||
// is strictly less than the number of input channels.
|
// is strictly less than the number of input channels.
|
||||||
|
|
||||||
// If the number of output channels equals the number of input channels,
|
// If the number of output channels equals the number of input channels,
|
||||||
// no mixing is done on the "auto" setting.
|
// no mixing is done on the "auto" setting.
|
||||||
|
|
||||||
// To do an upmix or a custom downmix, specify
|
// To do an upmix or a custom downmix, specify
|
||||||
// the target format, e.g. "7.1" in the mixdown setting.
|
// the target format, e.g. "7.1" in the mixdown setting.
|
||||||
|
|
||||||
// NOTE: upmixing, by default, simply copies the input channels to their
|
// NOTE: upmixing, by default, simply copies the input channels to their
|
||||||
// equivalents in the output channels. All other channels are
|
// equivalents in the output channels. All other channels are
|
||||||
// left silent.
|
// left silent.
|
||||||
@@ -683,9 +683,9 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
AVChannelLayout output_channel_layout;
|
AVChannelLayout output_channel_layout;
|
||||||
if (config.mixdown_enable != 0) {
|
if (config.mixdown_enable != 0) {
|
||||||
if (config.mixdown_channel_layout == 0) {
|
if (config.mixdown_channel_layout == 0) {
|
||||||
if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) < (unsigned) input_channel_count) {
|
if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) < (unsigned)input_channel_count) {
|
||||||
av_channel_layout_default(&output_channel_layout,
|
av_channel_layout_default(&output_channel_layout,
|
||||||
CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
|
CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
|
||||||
} else {
|
} else {
|
||||||
av_channel_layout_from_mask(&output_channel_layout, input_layout);
|
av_channel_layout_from_mask(&output_channel_layout, input_layout);
|
||||||
}
|
}
|
||||||
@@ -705,7 +705,8 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
av_opt_set_int(swr, "in_channel_layout", input_layout, 0);
|
av_opt_set_int(swr, "in_channel_layout", input_layout, 0);
|
||||||
if (config.mixdown_enable != 0) {
|
if (config.mixdown_enable != 0) {
|
||||||
if (config.mixdown_channel_layout == 0) {
|
if (config.mixdown_channel_layout == 0) {
|
||||||
if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) < av_get_channel_layout_nb_channels(input_layout)) {
|
if ((signed)CHANNELS_FROM_ENCODED_FORMAT(output_configuration) <
|
||||||
|
(av_get_channel_layout_nb_channels(input_layout)) {
|
||||||
output_layout =
|
output_layout =
|
||||||
av_get_default_channel_layout(CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
|
av_get_default_channel_layout(CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
|
||||||
} // else leave output_layout as it was: the sames as the input_layout.
|
} // else leave output_layout as it was: the sames as the input_layout.
|
||||||
@@ -778,7 +779,7 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
// get information about the output from the resampler
|
// get information about the output from the resampler
|
||||||
int64_t resampler_output_format = 0;
|
int64_t resampler_output_format = 0;
|
||||||
int resampler_channels_found = 0;
|
int resampler_channels_found = 0;
|
||||||
|
|
||||||
int oldState;
|
int oldState;
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||||
|
|
||||||
@@ -891,7 +892,7 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
free(device_channels);
|
free(device_channels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_setcancelstate(oldState, NULL);
|
pthread_setcancelstate(oldState, NULL);
|
||||||
|
|
||||||
if (output_channel_map_faulty != 0) {
|
if (output_channel_map_faulty != 0) {
|
||||||
@@ -1035,17 +1036,26 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
return response; // 0 if everything is okay
|
return response; // 0 if everything is okay
|
||||||
}
|
}
|
||||||
void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
|
void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
|
||||||
|
if ((ssrc != SSRC_NONE) && (ssrc != conn->incoming_ssrc) && (ssrc_is_recognised(ssrc) != 0)) {
|
||||||
|
// conn->incoming_ssrc will be SSRC_NONE only before the first valid encoding is found
|
||||||
|
if ((config.statistics_requested) && (conn->incoming_ssrc != SSRC_NONE))
|
||||||
|
inform("Connection %d: Incoming Audio Encoding is switching to: \"%s\".",
|
||||||
|
conn->connection_number, get_ssrc_name(ssrc));
|
||||||
|
// conn->incoming_ssrc = payload_ssrc;
|
||||||
|
#ifdef CONFIG_METADATA
|
||||||
|
send_ssnc_metadata('sdsc', get_ssrc_name(ssrc), strlen(get_ssrc_name(ssrc)), 1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
if ((ssrc_is_recognised(ssrc)) && (ssrc != conn->incoming_ssrc)) {
|
if ((ssrc_is_recognised(ssrc)) && (ssrc != conn->incoming_ssrc)) {
|
||||||
|
|
||||||
if ((config.statistics_requested != 0) && (ssrc != SSRC_NONE) &&
|
/*
|
||||||
(conn->incoming_ssrc != SSRC_NONE)) {
|
if ((config.statistics_requested != 0) && (ssrc != SSRC_NONE) &&
|
||||||
debug(2, "Connection %d: incoming audio switching to \"%s\".", conn->connection_number,
|
(conn->incoming_ssrc != SSRC_NONE)) {
|
||||||
get_ssrc_name(ssrc));
|
debug(2, "Connection %d: incoming audio switching to \"%s\".", conn->connection_number,
|
||||||
#ifdef CONFIG_METADATA
|
get_ssrc_name(ssrc));
|
||||||
send_ssnc_metadata('sdsc', get_ssrc_name(ssrc), strlen(get_ssrc_name(ssrc)), 1);
|
}
|
||||||
#endif
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
// the ssrc of the incoming packet is different to the ssrc of the decoding chain
|
// the ssrc of the incoming packet is different to the ssrc of the decoding chain
|
||||||
// so the decoding chain must be rebuilt
|
// so the decoding chain must be rebuilt
|
||||||
|
|
||||||
@@ -1071,7 +1081,8 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
conn->codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
|
conn->codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
die("Connection %d: can't find a suitable codec for SSRC: %s", conn->connection_number, get_ssrc_name(ssrc));
|
die("Connection %d: can't find a suitable codec for SSRC: %s", conn->connection_number,
|
||||||
|
get_ssrc_name(ssrc));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1080,17 +1091,18 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
|
|
||||||
// Get a decoder-dependent codec context
|
// Get a decoder-dependent codec context
|
||||||
conn->codec_context = avcodec_alloc_context3(conn->codec);
|
conn->codec_context = avcodec_alloc_context3(conn->codec);
|
||||||
if (conn->codec_context != NULL) {
|
if (conn->codec_context != NULL) {
|
||||||
// push a deallocator -- av_free(codec_context)
|
// push a deallocator -- av_free(codec_context)
|
||||||
// pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, &conn->codec_context);
|
// pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, &conn->codec_context);
|
||||||
|
|
||||||
// prepare to open the codec context with that codec
|
// prepare to open the codec context with that codec
|
||||||
// but first, if it's the ALAC decoder, prepare a magic cookie
|
// but first, if it's the ALAC decoder, prepare a magic cookie
|
||||||
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
|
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
|
||||||
alac_ffmpeg_magic_cookie *extradata =
|
alac_ffmpeg_magic_cookie *extradata =
|
||||||
malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it
|
malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it
|
||||||
if (extradata == NULL)
|
if (extradata == NULL)
|
||||||
die("connection %d: could not allocate memory for a magic cookie.", conn->connection_number);
|
die("connection %d: could not allocate memory for a magic cookie.",
|
||||||
|
conn->connection_number);
|
||||||
// creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?)
|
// creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?)
|
||||||
memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie));
|
memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie));
|
||||||
extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie));
|
extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie));
|
||||||
@@ -1115,18 +1127,19 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
}
|
}
|
||||||
// pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata);
|
// pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata);
|
||||||
// avcodec_free_context() will free extradata
|
// avcodec_free_context() will free extradata
|
||||||
|
|
||||||
if (avcodec_open2(conn->codec_context, conn->codec, NULL) < 0) {
|
if (avcodec_open2(conn->codec_context, conn->codec, NULL) < 0) {
|
||||||
pthread_setcancelstate(oldState, NULL);
|
pthread_setcancelstate(oldState, NULL);
|
||||||
die("connection %d: could not initialise the codec context", conn->connection_number);
|
die("connection %d: could not initialise the codec context", conn->connection_number);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
die("connection %d: could not allocate a codec context!", conn->connection_number);
|
die("connection %d: could not allocate a codec context!", conn->connection_number);
|
||||||
}
|
}
|
||||||
pthread_setcancelstate(oldState, NULL);
|
pthread_setcancelstate(oldState, NULL);
|
||||||
|
|
||||||
conn->input_rate = get_ssrc_rate(ssrc);
|
conn->input_rate = get_ssrc_rate(ssrc);
|
||||||
debug(2, "Connection %d: set conn->input_rate: %u.", conn->connection_number, conn->input_rate);
|
debug(2, "Connection %d: set conn->input_rate: %u.", conn->connection_number,
|
||||||
|
conn->input_rate);
|
||||||
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
|
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
|
||||||
conn->frames_per_packet = 352;
|
conn->frames_per_packet = 352;
|
||||||
} else {
|
} else {
|
||||||
@@ -1583,28 +1596,26 @@ uint32_t player_put_packet(uint32_t ssrc, seq_t seqno, uint32_t actual_timestamp
|
|||||||
} else if (config.decoder_in_use == 1 << decoder_ffmpeg_alac) {
|
} else if (config.decoder_in_use == 1 << decoder_ffmpeg_alac) {
|
||||||
#ifdef CONFIG_FFMPEG
|
#ifdef CONFIG_FFMPEG
|
||||||
prepare_decoding_chain(conn, ALAC_44100_S16_2);
|
prepare_decoding_chain(conn, ALAC_44100_S16_2);
|
||||||
// if (len > 8) {
|
abuf->avframe = block_to_avframe(conn, data_to_use, len);
|
||||||
abuf->avframe = block_to_avframe(conn, data_to_use, len);
|
abuf->ssrc = ALAC_44100_S16_2;
|
||||||
abuf->ssrc = ALAC_44100_S16_2;
|
if (abuf->avframe) {
|
||||||
if (abuf->avframe) {
|
input_packets_used = abuf->avframe->nb_samples;
|
||||||
input_packets_used = abuf->avframe->nb_samples;
|
}
|
||||||
}
|
if (mute) {
|
||||||
if (mute) {
|
// it's important to have already run it through the decoder before dropping it
|
||||||
// it's important to have already run it through the decoder before dropping it
|
// especially if it an AAC decoder
|
||||||
// especially if it an AAC decoder
|
debug(2, "ap1 muting frame %u.", actual_timestamp);
|
||||||
debug(2, "ap1 muting frame %u.", actual_timestamp);
|
abuf->length = abuf->avframe->nb_samples;
|
||||||
abuf->length = abuf->avframe->nb_samples;
|
av_frame_free(&abuf->avframe);
|
||||||
av_frame_free(&abuf->avframe);
|
abuf->avframe = NULL;
|
||||||
abuf->avframe = NULL;
|
}
|
||||||
}
|
if (len <= 8) {
|
||||||
// } else {
|
debug(2,
|
||||||
if (len <= 8) {
|
"Connection %d, using FFMPEG on an ALAC_44100_S16_2 stream, a short audio packet "
|
||||||
debug(1,
|
"%u, rtptime %u, of length %zu has been decoded but not discarded. Contents "
|
||||||
"Using the FFMPEG ALAC_44100_S16_2 decoder, a short audio packet %u, rtptime %u, of length %zu has been decoded but not discarded. Contents follow:", seqno,
|
"follow:",
|
||||||
actual_timestamp, len);
|
conn->connection_number, seqno, actual_timestamp, len);
|
||||||
debug_print_buffer(1, data, len);
|
debug_print_buffer(2, data, len);
|
||||||
// abuf->length = conn->frames_per_packet;
|
|
||||||
// abuf->avframe = NULL;
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
debug(1, "FFMPEG support has not been built into this version Shairport Sync!");
|
debug(1, "FFMPEG support has not been built into this version Shairport Sync!");
|
||||||
@@ -1636,27 +1647,25 @@ uint32_t player_put_packet(uint32_t ssrc, seq_t seqno, uint32_t actual_timestamp
|
|||||||
|
|
||||||
prepare_decoding_chain(conn, ssrc); // dynamically set the decoding environment
|
prepare_decoding_chain(conn, ssrc); // dynamically set the decoding environment
|
||||||
|
|
||||||
// if (len > 8) {
|
abuf->avframe = block_to_avframe(conn, data, len);
|
||||||
abuf->avframe = block_to_avframe(conn, data, len);
|
abuf->ssrc = ssrc; // tag the avframe with its specific SSRC
|
||||||
abuf->ssrc = ssrc; // tag the avframe with its specific SSRC
|
if (abuf->avframe) {
|
||||||
if (abuf->avframe) {
|
input_packets_used = abuf->avframe->nb_samples;
|
||||||
input_packets_used = abuf->avframe->nb_samples;
|
}
|
||||||
}
|
if (mute) {
|
||||||
if (mute) {
|
// it's important to have already run it through the decoder before dropping it
|
||||||
// it's important to have already run it through the decoder before dropping it
|
debug(2, "ap2 muting frame %u.", actual_timestamp);
|
||||||
debug(2, "ap2 muting frame %u.", actual_timestamp);
|
abuf->length = abuf->avframe->nb_samples;
|
||||||
abuf->length = abuf->avframe->nb_samples;
|
av_frame_free(&abuf->avframe);
|
||||||
av_frame_free(&abuf->avframe);
|
abuf->avframe = NULL;
|
||||||
abuf->avframe = NULL;
|
}
|
||||||
}
|
|
||||||
//} else {
|
|
||||||
if (len <= 8) {
|
if (len <= 8) {
|
||||||
debug(1,
|
debug(2,
|
||||||
"Using an FFMPEG decoder, a short audio packet %u, rtptime %u, of length %zu has been decoded but not discarded. Contents follow:", seqno,
|
"Connection %d: using FFMPEG on a %s stream, a short audio packet %u, rtptime %u, "
|
||||||
actual_timestamp, len);
|
"of length %zu has been decoded but not discarded. Contents follow:",
|
||||||
debug_print_buffer(1, data, len);
|
conn->connection_number, get_ssrc_name(ssrc), seqno, actual_timestamp, len);
|
||||||
// abuf->length = 0;
|
debug_print_buffer(2, data, len);
|
||||||
// abuf->avframe = NULL;
|
|
||||||
}
|
}
|
||||||
abuf->ready = 1;
|
abuf->ready = 1;
|
||||||
abuf->status = 0; // signifying that it was received
|
abuf->status = 0; // signifying that it was received
|
||||||
@@ -2497,12 +2506,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
|
|||||||
RATE_FROM_ENCODED_FORMAT(config.current_output_configuration)) /
|
RATE_FROM_ENCODED_FORMAT(config.current_output_configuration)) /
|
||||||
1000000000;
|
1000000000;
|
||||||
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
|
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
|
||||||
|
|
||||||
|
|
||||||
debug(4,
|
debug(4,
|
||||||
"Exact frame gap: %" PRId64
|
"Exact frame gap: %" PRId64 ". DAC delay: %ld. Total: %" PRId64
|
||||||
". DAC delay: %ld. Total: %" PRId64 ". First packet timestamp: %u. ",
|
". First packet timestamp: %u. ",
|
||||||
exact_frame_gap, dac_delay, gross_frame_gap, conn->first_packet_timestamp);
|
exact_frame_gap, dac_delay, gross_frame_gap,
|
||||||
|
conn->first_packet_timestamp);
|
||||||
// int64_t frames_needed_to_maintain_desired_buffer =
|
// int64_t frames_needed_to_maintain_desired_buffer =
|
||||||
// (int64_t)(config.audio_backend_buffer_desired_length *
|
// (int64_t)(config.audio_backend_buffer_desired_length *
|
||||||
// config.current_output_configuration->rate) -
|
// config.current_output_configuration->rate) -
|
||||||
@@ -2720,9 +2729,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
|
|||||||
}
|
}
|
||||||
if (wait) {
|
if (wait) {
|
||||||
uint64_t time_to_wait_for_wakeup_ns = 10000000; // default
|
uint64_t time_to_wait_for_wakeup_ns = 10000000; // default
|
||||||
if (conn->input_format_is_valid != 0) {
|
if (conn->input_format_is_valid != 0) {
|
||||||
time_to_wait_for_wakeup_ns = 1000000000 / conn->input_rate; // this is time period of one frame
|
time_to_wait_for_wakeup_ns =
|
||||||
time_to_wait_for_wakeup_ns *= 4 * conn->frames_per_packet; // about 4 * 7 mS for 352 frames per second
|
1000000000 / conn->input_rate; // this is time period of one frame
|
||||||
|
time_to_wait_for_wakeup_ns *=
|
||||||
|
4 * conn->frames_per_packet; // about 4 * 7 mS for 352 frames per second
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
||||||
@@ -2733,13 +2744,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
|
|||||||
struct timespec time_of_wakeup;
|
struct timespec time_of_wakeup;
|
||||||
time_of_wakeup.tv_sec = sec;
|
time_of_wakeup.tv_sec = sec;
|
||||||
time_of_wakeup.tv_nsec = nsec;
|
time_of_wakeup.tv_nsec = nsec;
|
||||||
// debug(1, "wait for up to %f mS or for the next packet...", time_to_wait_for_wakeup_ns * 1E-6);
|
// debug(1, "wait for up to %f mS or for the next packet...", time_to_wait_for_wakeup_ns *
|
||||||
|
// 1E-6);
|
||||||
int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex,
|
int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex,
|
||||||
&time_of_wakeup); // this is a pthread cancellation point
|
&time_of_wakeup); // this is a pthread cancellation point
|
||||||
if ((rc != 0) && (rc != ETIMEDOUT))
|
if ((rc != 0) && (rc != ETIMEDOUT))
|
||||||
// if (rc)
|
// if (rc)
|
||||||
debug(3, "pthread_cond_timedwait returned error code %d.", rc);
|
debug(3, "pthread_cond_timedwait returned error code %d.", rc);
|
||||||
// debug(1, "waited");
|
// debug(1, "waited");
|
||||||
#endif
|
#endif
|
||||||
#ifdef COMPILE_FOR_OSX
|
#ifdef COMPILE_FOR_OSX
|
||||||
uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
|
uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
|
||||||
@@ -2789,8 +2801,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
|
|||||||
debug(2, "setting up software resampler for %s for the first time.",
|
debug(2, "setting up software resampler for %s for the first time.",
|
||||||
get_ssrc_name(curframe->ssrc));
|
get_ssrc_name(curframe->ssrc));
|
||||||
} else {
|
} else {
|
||||||
debug(1, "Connection %d: queued audio buffers switching to \"%s\".", conn->connection_number,
|
debug(2, "Connection %d: queued audio buffers switching to \"%s\".",
|
||||||
get_ssrc_name(curframe->ssrc));
|
conn->connection_number, get_ssrc_name(curframe->ssrc));
|
||||||
clear_software_resampler(conn);
|
clear_software_resampler(conn);
|
||||||
// ask the backend if it can give us its best choice for an ffmpeg configuration:
|
// ask the backend if it can give us its best choice for an ffmpeg configuration:
|
||||||
}
|
}
|
||||||
@@ -3029,8 +3041,8 @@ static int stuff_buffer_vernier(int32_t *inptr, int length, sps_format_t l_outpu
|
|||||||
debug(1,
|
debug(1,
|
||||||
"Can't see how this could ever happen, but "
|
"Can't see how this could ever happen, but "
|
||||||
"current_input_sample_floor_index %" PRId64
|
"current_input_sample_floor_index %" PRId64
|
||||||
" has just stepped outside the frame of %d samples, with stuff %d and current_input_sample_index_fp at %" PRId64 ".%05" PRId64
|
" has just stepped outside the frame of %d samples, with stuff %d and "
|
||||||
".",
|
"current_input_sample_index_fp at %" PRId64 ".%05" PRId64 ".",
|
||||||
current_input_sample_floor_index, length, stuff, current_input_sample_index_int,
|
current_input_sample_floor_index, length, stuff, current_input_sample_index_int,
|
||||||
current_input_sample_index_low);
|
current_input_sample_index_low);
|
||||||
current_input_sample_floor_index = length - 1; // hack
|
current_input_sample_floor_index = length - 1; // hack
|
||||||
@@ -3052,8 +3064,8 @@ static int stuff_buffer_vernier(int32_t *inptr, int length, sps_format_t l_outpu
|
|||||||
debug(1,
|
debug(1,
|
||||||
"Can't see how this could ever happen, but "
|
"Can't see how this could ever happen, but "
|
||||||
"current_input_sample_ceil_index %" PRId64
|
"current_input_sample_ceil_index %" PRId64
|
||||||
" has just stepped outside the frame of %d samples, with stuff %d and current_input_sample_index_fp at %" PRId64
|
" has just stepped outside the frame of %d samples, with stuff %d and "
|
||||||
".%05" PRId64 ".",
|
"current_input_sample_index_fp at %" PRId64 ".%05" PRId64 ".",
|
||||||
current_input_sample_floor_index, length, stuff, current_input_sample_index_int,
|
current_input_sample_floor_index, length, stuff, current_input_sample_index_int,
|
||||||
current_input_sample_index_low);
|
current_input_sample_index_low);
|
||||||
}
|
}
|
||||||
@@ -3314,17 +3326,10 @@ void player_thread_cleanup_handler(void *arg) {
|
|||||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||||
// debug(1, "Connection %d: player_thread_cleanup_handler start.", conn->connection_number);
|
// debug(1, "Connection %d: player_thread_cleanup_handler start.", conn->connection_number);
|
||||||
|
|
||||||
#ifdef CONFIG_FFMPEG
|
|
||||||
// debug(1, "FFmpeg clearup");
|
|
||||||
clear_software_resampler(conn);
|
|
||||||
clear_decoding_chain(conn);
|
|
||||||
// debug(1, "FFmpeg clearup done");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (config.output->stop) {
|
if (config.output->stop) {
|
||||||
#ifdef CONFIG_FFMPEG
|
#ifdef CONFIG_FFMPEG
|
||||||
if (avflush(conn) > 1)
|
if ((config.decoder_in_use == 1 << decoder_ffmpeg_alac) && (avflush(conn) > 1))
|
||||||
debug(3, "ffmpeg flush at stop!");
|
debug(1, "ffmpeg flush at stop!");
|
||||||
#endif
|
#endif
|
||||||
debug(2, "Connection %d: player: stop the output backend.", conn->connection_number);
|
debug(2, "Connection %d: player: stop the output backend.", conn->connection_number);
|
||||||
config.output->stop();
|
config.output->stop();
|
||||||
@@ -3418,6 +3423,15 @@ void player_thread_cleanup_handler(void *arg) {
|
|||||||
ptp_send_control_message_string("E");
|
ptp_send_control_message_string("E");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_FFMPEG
|
||||||
|
if (config.decoder_in_use == 1 << decoder_ffmpeg_alac) {
|
||||||
|
// debug(1, "FFmpeg clearup");
|
||||||
|
clear_software_resampler(conn);
|
||||||
|
clear_decoding_chain(conn);
|
||||||
|
// debug(1, "FFmpeg clearup done");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (conn->outbuf) {
|
if (conn->outbuf) {
|
||||||
free(conn->outbuf);
|
free(conn->outbuf);
|
||||||
conn->outbuf = NULL;
|
conn->outbuf = NULL;
|
||||||
@@ -3512,11 +3526,17 @@ void *player_thread_func(void *arg) {
|
|||||||
init_alac_decoder((int32_t *)&conn->stream.fmtp,
|
init_alac_decoder((int32_t *)&conn->stream.fmtp,
|
||||||
conn); // this sets up incoming rate, bit depth, channels.
|
conn); // this sets up incoming rate, bit depth, channels.
|
||||||
// No pthread cancellation point in here
|
// No pthread cancellation point in here
|
||||||
|
#ifdef CONFIG_METADATA
|
||||||
|
send_ssnc_metadata('sdsc', "ALAC/44100/S16_LE/2", strlen("ALAC/44100/S16_LE/2"), 1);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_APPLE_ALAC
|
#ifdef CONFIG_APPLE_ALAC
|
||||||
if (config.decoder_in_use == 1 << decoder_apple_alac) {
|
if (config.decoder_in_use == 1 << decoder_apple_alac) {
|
||||||
apple_alac_init(conn->stream.fmtp); // no pthread cancellation point in here
|
apple_alac_init(conn->stream.fmtp); // no pthread cancellation point in here
|
||||||
|
#ifdef CONFIG_METADATA
|
||||||
|
send_ssnc_metadata('sdsc', "ALAC/44100/S16_LE/2", strlen("ALAC/44100/S16_LE/2"), 1);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -4145,7 +4165,7 @@ void *player_thread_func(void *arg) {
|
|||||||
send_ssnc_stream_description("Classic", get_ssrc_name(conn->incoming_ssrc));
|
send_ssnc_stream_description("Classic", get_ssrc_name(conn->incoming_ssrc));
|
||||||
#endif
|
#endif
|
||||||
if (config.statistics_requested)
|
if (config.statistics_requested)
|
||||||
inform("Connection %d: Classic AirPlay (\"AirPlay 1\") Compatible playback. "
|
inform("Connection %d: Classic AirPlay (\"AirPlay 1\") playback. "
|
||||||
"Input format: %s. Output format: %s.",
|
"Input format: %s. Output format: %s.",
|
||||||
conn->connection_number, get_ssrc_name(conn->incoming_ssrc),
|
conn->connection_number, get_ssrc_name(conn->incoming_ssrc),
|
||||||
short_description);
|
short_description);
|
||||||
@@ -4369,10 +4389,14 @@ void *player_thread_func(void *arg) {
|
|||||||
if (inframe->timestamp_gap < 0) {
|
if (inframe->timestamp_gap < 0) {
|
||||||
gap_to_fix = -inframe->timestamp_gap; // this is frames at the input rate
|
gap_to_fix = -inframe->timestamp_gap; // this is frames at the input rate
|
||||||
int64_t gap_to_fix_ns = (gap_to_fix * 1000000000) / conn->input_rate;
|
int64_t gap_to_fix_ns = (gap_to_fix * 1000000000) / conn->input_rate;
|
||||||
gap_to_fix = (gap_to_fix_ns *
|
gap_to_fix = (gap_to_fix_ns * RATE_FROM_ENCODED_FORMAT(
|
||||||
RATE_FROM_ENCODED_FORMAT(config.current_output_configuration) + 1000000000/2) /
|
config.current_output_configuration) +
|
||||||
|
1000000000 / 2) /
|
||||||
1000000000; // this is frames at the output rate
|
1000000000; // this is frames at the output rate
|
||||||
debug(4, "gap_to_fix: %u frames at input rate, %" PRId64 " frames at output rate.", -inframe->timestamp_gap, gap_to_fix);
|
debug(4,
|
||||||
|
"gap_to_fix: %u frames at input rate, %" PRId64
|
||||||
|
" frames at output rate.",
|
||||||
|
-inframe->timestamp_gap, gap_to_fix);
|
||||||
// debug(3, "due to timstamp gap of %d frames, skip %" PRId64 " output
|
// debug(3, "due to timstamp gap of %d frames, skip %" PRId64 " output
|
||||||
// frames.", inframe->timestamp_gap, gap_to_fix);
|
// frames.", inframe->timestamp_gap, gap_to_fix);
|
||||||
}
|
}
|
||||||
@@ -4580,7 +4604,7 @@ void *player_thread_func(void *arg) {
|
|||||||
#endif
|
#endif
|
||||||
) {
|
) {
|
||||||
|
|
||||||
float(*fbufs)[1024] = malloc(conn->input_num_channels * sizeof(*fbufs));
|
float (*fbufs)[1024] = malloc(conn->input_num_channels * sizeof(*fbufs));
|
||||||
// debug(1, "size of array allocated is %d bytes.", conn->input_num_channels *
|
// debug(1, "size of array allocated is %d bytes.", conn->input_num_channels *
|
||||||
// sizeof(*fbufs));
|
// sizeof(*fbufs));
|
||||||
int32_t *tbuf32 = conn->tbuf;
|
int32_t *tbuf32 = conn->tbuf;
|
||||||
@@ -4794,15 +4818,14 @@ void *player_thread_func(void *arg) {
|
|||||||
frames_to_skip -= play_samples;
|
frames_to_skip -= play_samples;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
size_t bytes_to_skip =
|
||||||
|
frames_to_skip *
|
||||||
size_t bytes_to_skip = frames_to_skip *
|
|
||||||
CHANNELS_FROM_ENCODED_FORMAT(config.current_output_configuration) *
|
CHANNELS_FROM_ENCODED_FORMAT(config.current_output_configuration) *
|
||||||
sps_format_sample_size(
|
sps_format_sample_size(
|
||||||
FORMAT_FROM_ENCODED_FORMAT(config.current_output_configuration));
|
FORMAT_FROM_ENCODED_FORMAT(config.current_output_configuration));
|
||||||
|
|
||||||
char *play_starting_point = conn->outbuf + bytes_to_skip;
|
char *play_starting_point = conn->outbuf + bytes_to_skip;
|
||||||
|
|
||||||
config.output->play(play_starting_point, play_samples - frames_to_skip,
|
config.output->play(play_starting_point, play_samples - frames_to_skip,
|
||||||
play_samples_are_timed, inframe->timestamp,
|
play_samples_are_timed, inframe->timestamp,
|
||||||
should_be_time);
|
should_be_time);
|
||||||
|
|||||||
@@ -273,7 +273,8 @@ typedef struct {
|
|||||||
// otherwise
|
// otherwise
|
||||||
int software_mute_enabled; // if we don't have a real mute that we can use
|
int software_mute_enabled; // if we don't have a real mute that we can use
|
||||||
int fd;
|
int fd;
|
||||||
int authorized; // set if a password is required and has been supplied or not required. Also always set in AirPlay 2 mode.
|
int authorized; // set if a password is required and has been supplied or not required. Also
|
||||||
|
// always set in AirPlay 2 mode.
|
||||||
char *auth_nonce; // the session nonce, if needed
|
char *auth_nonce; // the session nonce, if needed
|
||||||
stream_cfg stream;
|
stream_cfg stream;
|
||||||
SOCKADDR remote, local;
|
SOCKADDR remote, local;
|
||||||
|
|||||||
+2
-1
@@ -42,6 +42,7 @@
|
|||||||
#define __STDC_FORMAT_MACROS
|
#define __STDC_FORMAT_MACROS
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "ptp-utilities.h"
|
#include "ptp-utilities.h"
|
||||||
|
#include "utilities/network_utilities.h"
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@@ -253,7 +254,7 @@ void ptp_send_control_message_string(const char *msg) {
|
|||||||
die("error sending timing_peer_list to NQPTP");
|
die("error sending timing_peer_list to NQPTP");
|
||||||
}
|
}
|
||||||
/* Deallocate the socket */
|
/* Deallocate the socket */
|
||||||
close(s);
|
safe_socket_close(&s);
|
||||||
freeaddrinfo(info);
|
freeaddrinfo(info);
|
||||||
|
|
||||||
/* deallocate the message string */
|
/* deallocate the message string */
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
|
#include "utilities/network_utilities.h"
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -311,11 +312,6 @@ void *rtp_audio_receiver(void *arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
debug(3, "Audio receiver -- Server RTP thread interrupted. terminating.");
|
|
||||||
close(conn->audio_socket);
|
|
||||||
*/
|
|
||||||
|
|
||||||
debug(1, "Audio receiver thread \"normal\" exit -- this can't happen. Hah!");
|
debug(1, "Audio receiver thread \"normal\" exit -- this can't happen. Hah!");
|
||||||
pthread_cleanup_pop(0); // don't execute anything here.
|
pthread_cleanup_pop(0); // don't execute anything here.
|
||||||
debug(2, "Audio receiver thread exit.");
|
debug(2, "Audio receiver thread exit.");
|
||||||
@@ -981,7 +977,8 @@ void *rtp_timing_receiver(void *arg) {
|
|||||||
return_time);
|
return_time);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %zd.", packet[1], nread);
|
debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %zd.", packet[1],
|
||||||
|
nread);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug(3, "Timing Receiver Thread -- dropping incoming packet to simulate a bad network.");
|
debug(3, "Timing Receiver Thread -- dropping incoming packet to simulate a bad network.");
|
||||||
@@ -1387,9 +1384,11 @@ int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP,
|
|||||||
if ((previous_offset != 0) && (previous_clock_id == actual_clock_id) &&
|
if ((previous_offset != 0) && (previous_clock_id == actual_clock_id) &&
|
||||||
((jitter > 3000000) || (jitter < -3000000)))
|
((jitter > 3000000) || (jitter < -3000000)))
|
||||||
debug(1,
|
debug(1,
|
||||||
"Clock jitter: %.3f mS. Time since sample: %.3f mS. Time since start of mastership: %.3f "
|
"Clock jitter: %.3f mS. Time since sample: %.3f mS. Time since start of "
|
||||||
|
"mastership: %.3f "
|
||||||
"seconds.",
|
"seconds.",
|
||||||
jitter * 0.000001, time_since_sample * 0.000001, time_since_start_of_mastership * 0.000000001);
|
jitter * 0.000001, time_since_sample * 0.000001,
|
||||||
|
time_since_start_of_mastership * 0.000000001);
|
||||||
|
|
||||||
previous_offset = actual_offset;
|
previous_offset = actual_offset;
|
||||||
previous_clock_id = actual_clock_id;
|
previous_clock_id = actual_clock_id;
|
||||||
@@ -1514,7 +1513,8 @@ int frame_to_ptp_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *
|
|||||||
int64_t time_difference = frame_difference;
|
int64_t time_difference = frame_difference;
|
||||||
time_difference = time_difference * 1000000000;
|
time_difference = time_difference * 1000000000;
|
||||||
if (conn->input_rate == 0) {
|
if (conn->input_rate == 0) {
|
||||||
debug(1,"Connection %d: in a call to frame_to_ptp_local_time, conn->input_rate is zero!", conn->connection_number);
|
debug(1, "Connection %d: in a call to frame_to_ptp_local_time, conn->input_rate is zero!",
|
||||||
|
conn->connection_number);
|
||||||
time_difference = 0;
|
time_difference = 0;
|
||||||
} else {
|
} else {
|
||||||
time_difference = time_difference / conn->input_rate;
|
time_difference = time_difference / conn->input_rate;
|
||||||
@@ -1550,10 +1550,9 @@ int local_ptp_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn
|
|||||||
void rtp_ap2_control_handler_cleanup_handler(void *arg) {
|
void rtp_ap2_control_handler_cleanup_handler(void *arg) {
|
||||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||||
debug(2, "Connection %d: AP2 Control Receiver Cleanup.", conn->connection_number);
|
debug(2, "Connection %d: AP2 Control Receiver Cleanup.", conn->connection_number);
|
||||||
close(conn->ap2_control_socket);
|
safe_socket_close(&conn->ap2_control_socket);
|
||||||
debug(2, "Connection %d: UDP control port %u closed.", conn->connection_number,
|
debug(2, "Connection %d: UDP control port %u closed.", conn->connection_number,
|
||||||
conn->local_ap2_control_port);
|
conn->local_ap2_control_port);
|
||||||
conn->ap2_control_socket = 0;
|
|
||||||
conn->ap2_remote_control_socket_addr_length =
|
conn->ap2_remote_control_socket_addr_length =
|
||||||
0; // indicates to the control receiver thread that the socket address need to be
|
0; // indicates to the control receiver thread that the socket address need to be
|
||||||
// recreated (needed for resend requests in the realtime mode)
|
// recreated (needed for resend requests in the realtime mode)
|
||||||
@@ -1806,9 +1805,9 @@ void *rtp_ap2_control_receiver(void *arg) {
|
|||||||
void rtp_realtime_audio_cleanup_handler(__attribute__((unused)) void *arg) {
|
void rtp_realtime_audio_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||||
debug(2, "Realtime Audio Receiver Cleanup Start.");
|
debug(2, "Realtime Audio Receiver Cleanup Start.");
|
||||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||||
close(conn->realtime_audio_socket);
|
debug(2, "Connection %d: closing realtime audio port %u", conn->connection_number,
|
||||||
debug(2, "Connection %d: closing realtime audio port %u", conn->connection_number, conn->local_realtime_audio_port);
|
conn->local_realtime_audio_port);
|
||||||
conn->realtime_audio_socket = 0;
|
safe_socket_close(&conn->realtime_audio_socket);
|
||||||
debug(2, "Realtime Audio Receiver Cleanup Done.");
|
debug(2, "Realtime Audio Receiver Cleanup Done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,30 +24,13 @@ typedef struct {
|
|||||||
void msg_retain(rtsp_message *msg);
|
void msg_retain(rtsp_message *msg);
|
||||||
void msg_free(rtsp_message **msgh);
|
void msg_free(rtsp_message **msgh);
|
||||||
|
|
||||||
extern pthread_rwlock_t principal_conn_lock;
|
|
||||||
extern rtsp_conn_info *principal_conn;
|
|
||||||
extern rtsp_conn_info **conns;
|
|
||||||
|
|
||||||
void *rtsp_listen_loop(__attribute((unused)) void *arg);
|
void *rtsp_listen_loop(__attribute((unused)) void *arg);
|
||||||
|
|
||||||
void lock_player();
|
|
||||||
void unlock_player();
|
|
||||||
|
|
||||||
// result of trying to acquire or release the play lock
|
|
||||||
typedef enum {
|
|
||||||
play_lock_released,
|
|
||||||
play_lock_already_released,
|
|
||||||
play_lock_already_acquired,
|
|
||||||
play_lock_acquired_without_breaking_in,
|
|
||||||
play_lock_acquired_by_breaking_in,
|
|
||||||
play_lock_aquisition_failed
|
|
||||||
} play_lock_r;
|
|
||||||
|
|
||||||
// this can be used to [try to] forcibly stop a play session
|
// this can be used to [try to] forcibly stop a play session
|
||||||
play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption);
|
// play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption);
|
||||||
// this will release the play lock only if the conn has it or if the conn is NULL
|
// this will release the play lock only if the conn has it or if the conn is NULL
|
||||||
void release_play_lock(rtsp_conn_info *conn);
|
|
||||||
|
|
||||||
|
void stop_play(); // stop and drop a playing connection
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count);
|
ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count);
|
||||||
|
|||||||
@@ -14,11 +14,16 @@ general =
|
|||||||
// %v for the version number, e.g. 3.0 and
|
// %v for the version number, e.g. 3.0 and
|
||||||
// %V for the full version string, e.g. 3.3-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
|
// %V for the full version string, e.g. 3.3-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
|
||||||
// Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H".
|
// Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H".
|
||||||
|
//
|
||||||
// password = "secret"; // leave this commented out if you don't want to require a password
|
// password = "secret"; // leave this commented out if you don't want to require a password
|
||||||
|
// About the "service_type" setting below: On an AirPlay-2-capable Shairport Sync, if you wish to offer only the older "classic" AirPlay (sometimes called AirPlay 1), set service_type to "classic".
|
||||||
|
// A potential use of this would be for Apple Music on Windows, which is not compatible with the AirPlay 2 service offered by Shairport Sync, but which is compatible with the classic service.
|
||||||
|
// The default is "auto", which means that the AirPlay 2 service will be provided if NQPTP is present and "classic" AirPlay will be provided otherwise, with "(Classic)" appended to the service name (see above).
|
||||||
|
// service_type = "auto"; // This can be "auto", "airplay2" or "classic".
|
||||||
// The interpolation setting below controls how Shairport Sync adds or removes frames of audio to keep in sync.
|
// The interpolation setting below controls how Shairport Sync adds or removes frames of audio to keep in sync.
|
||||||
// "auto" (default) measures the processor's floating point speed and chooses "soxr" if available and it is fast enough. Otherwise, "vernier" is selected.
|
// "auto" (default) measures the processor's floating point speed and chooses "soxr" if available and it is fast enough. Otherwise, "vernier" is selected.
|
||||||
// "soxr" uses the SoX library to recode a packet of frames to a new packet containing more or fewer frames. This needs a processor with fast floating point capability.
|
|
||||||
// "vernier" recodes a packet of frames to a new packet containing more or fewer frames. This is recommended for low powered devices.
|
// "vernier" recodes a packet of frames to a new packet containing more or fewer frames. This is recommended for low powered devices.
|
||||||
|
// "soxr" uses the SoX library to recode a packet of frames to a new packet containing more or fewer frames. This needs a processor with fast floating point capability.
|
||||||
// "basic" causes the simple removal or insertion of frames in a packet of frames. Not recommended.
|
// "basic" causes the simple removal or insertion of frames in a packet of frames. Not recommended.
|
||||||
// interpolation = "auto"; // aka "stuffing". Default is "auto". Alternatives are "vernier", "basic" or "soxr". Choose "soxr" only if you have a reasonably fast processor and Shairport Sync has been built with "soxr" support.
|
// interpolation = "auto"; // aka "stuffing". Default is "auto". Alternatives are "vernier", "basic" or "soxr". Choose "soxr" only if you have a reasonably fast processor and Shairport Sync has been built with "soxr" support.
|
||||||
// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one.
|
// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ After=sound.target
|
|||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network.target network-online.target
|
After=network.target network-online.target
|
||||||
@SYSTEMD_AFTER_ARGS@
|
@SYSTEMD_AFTER_ARGS@
|
||||||
|
@SYSTEMD_WANTS_ARGS@
|
||||||
@SYSTEMD_REQUIRES_ARGS@
|
@SYSTEMD_REQUIRES_ARGS@
|
||||||
StartLimitIntervalSec=300
|
StartLimitIntervalSec=300
|
||||||
StartLimitBurst=5
|
StartLimitBurst=5
|
||||||
|
|||||||
+348
-328
@@ -2,7 +2,7 @@
|
|||||||
* Shairport, an Apple Airplay receiver
|
* Shairport, an Apple Airplay receiver
|
||||||
* Copyright (c) James Laird 2013
|
* Copyright (c) James Laird 2013
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
* Modifications and additions (c) Mike Brady 2014--2025
|
* Modifications and additions (c) Mike Brady 2014--2026
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation
|
* obtaining a copy of this software and associated documentation
|
||||||
@@ -50,12 +50,12 @@
|
|||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
#include "ptp-utilities.h"
|
#include "ptp-utilities.h"
|
||||||
|
#include "utilities/generate_device_uuid.h"
|
||||||
|
#include "utilities/generate_random_uuid.h"
|
||||||
#include <gcrypt.h>
|
#include <gcrypt.h>
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
#include <uuid/uuid.h>
|
#include <uuid/uuid.h>
|
||||||
#include "utilities/generate_random_uuid.h"
|
|
||||||
#include "utilities/generate_device_uuid.h"
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -82,6 +82,7 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
|
#include "utilities/string_utilities.h"
|
||||||
|
|
||||||
#if defined(CONFIG_DACP_CLIENT)
|
#if defined(CONFIG_DACP_CLIENT)
|
||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
@@ -265,7 +266,7 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
|
|||||||
outbuffer, buffer_length - 1, &odone, // Output.
|
outbuffer, buffer_length - 1, &odone, // Output.
|
||||||
&io_spec, // Input, output and transfer spec.
|
&io_spec, // Input, output and transfer spec.
|
||||||
NULL, NULL); // Default configuration.
|
NULL, NULL); // Default configuration.
|
||||||
|
|
||||||
pthread_setcancelstate(oldState, NULL);
|
pthread_setcancelstate(oldState, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +322,7 @@ void usage(char *progname) {
|
|||||||
printf("Options:\n");
|
printf("Options:\n");
|
||||||
printf(" -h, --help Show this help.\n");
|
printf(" -h, --help Show this help.\n");
|
||||||
printf(" -V, --version Show version information -- the version string.\n");
|
printf(" -V, --version Show version information -- the version string.\n");
|
||||||
|
printf(" --service-type Select the type of AirPlay service: \"auto\", \"airplay2\" or \"classic\". (You can use \"airplay1\" in place of \"classic\".)\n");
|
||||||
printf(" -X, --displayConfig Output OS information, version string, command line, configuration file and active settings to the log.\n");
|
printf(" -X, --displayConfig Output OS information, version string, command line, configuration file and active settings to the log.\n");
|
||||||
printf(" --statistics Print some interesting statistics. More will be printed if -v / -vv / -vvv are also chosen.\n");
|
printf(" --statistics Print some interesting statistics. More will be printed if -v / -vv / -vvv are also chosen.\n");
|
||||||
printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n");
|
printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n");
|
||||||
@@ -382,6 +384,7 @@ void usage(char *progname) {
|
|||||||
int parse_options(int argc, char **argv) {
|
int parse_options(int argc, char **argv) {
|
||||||
// there are potential memory leaks here -- it's called a second time, previously allocated
|
// there are potential memory leaks here -- it's called a second time, previously allocated
|
||||||
// strings will dangle.
|
// strings will dangle.
|
||||||
|
char *cli_service_type_string = NULL;
|
||||||
char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */
|
char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */
|
||||||
char *stuffing = NULL; /* used for picking up the stuffing option */
|
char *stuffing = NULL; /* used for picking up the stuffing option */
|
||||||
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
|
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
|
||||||
@@ -394,40 +397,40 @@ int parse_options(int argc, char **argv) {
|
|||||||
int tolerance_in_frames = 0;
|
int tolerance_in_frames = 0;
|
||||||
poptContext optCon; /* context for parsing command-line options */
|
poptContext optCon; /* context for parsing command-line options */
|
||||||
struct poptOption optionsTable[] = {
|
struct poptOption optionsTable[] = {
|
||||||
{"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL},
|
{"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL},
|
||||||
{"kill", 'k', POPT_ARG_NONE, &killOption, 0, NULL, NULL},
|
{"kill", 'k', POPT_ARG_NONE, &killOption, 0, NULL, NULL},
|
||||||
{"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL, NULL},
|
{"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL, NULL},
|
||||||
{"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL, NULL},
|
{"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL, NULL},
|
||||||
{"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL, NULL},
|
{"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL, NULL},
|
||||||
{"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL},
|
{"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL},
|
||||||
{"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL},
|
{"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL},
|
||||||
{"version", 'V', POPT_ARG_NONE, NULL, 0, NULL, NULL},
|
{"version", 'V', POPT_ARG_NONE, NULL, 0, NULL, NULL},
|
||||||
{"displayConfig", 'X', POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL},
|
{"displayConfig", 'X', POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL},
|
||||||
{"port", 'p', POPT_ARG_INT, &config.port, 0, NULL, NULL},
|
{"port", 'p', POPT_ARG_INT, &config.port, 0, NULL, NULL},
|
||||||
{"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL, NULL},
|
{"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL, NULL},
|
||||||
{"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL, NULL},
|
{"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL, NULL},
|
||||||
{"on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL, NULL},
|
{"on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL, NULL},
|
||||||
{"on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL, NULL},
|
{"on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL, NULL},
|
||||||
{"wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL, NULL},
|
{"wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL, NULL},
|
||||||
{"mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL, NULL},
|
{"mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL, NULL},
|
||||||
{"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL, NULL},
|
{"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL, NULL},
|
||||||
{"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL, NULL},
|
{"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL, NULL},
|
||||||
{"resync", 'r', POPT_ARG_INT, &resync_threshold_in_frames, 'r', NULL, NULL},
|
{"resync", 'r', POPT_ARG_INT, &resync_threshold_in_frames, 'r', NULL, NULL},
|
||||||
{"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL, NULL},
|
{"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL, NULL},
|
||||||
{"password", 0, POPT_ARG_STRING, &config.password, 0, NULL, NULL},
|
{"password", 0, POPT_ARG_STRING, &config.password, 0, NULL, NULL},
|
||||||
|
{"service-type", 0, POPT_ARG_STRING, &cli_service_type_string, 0, NULL, NULL},
|
||||||
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
|
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
|
||||||
{"dbus-default-message-bus", 0, POPT_ARG_STRING, &dbus_default_message_bus, 0, NULL, NULL},
|
{"dbus-default-message-bus", 0, POPT_ARG_STRING, &dbus_default_message_bus, 0, NULL, NULL},
|
||||||
#endif
|
#endif
|
||||||
{"tolerance", 'z', POPT_ARG_INT, &tolerance_in_frames, 'z', NULL, NULL},
|
{"tolerance", 'z', POPT_ARG_INT, &tolerance_in_frames, 'z', NULL, NULL},
|
||||||
{"use-stderr", 'u', POPT_ARG_NONE, NULL, 'u', NULL, NULL},
|
{"use-stderr", 'u', POPT_ARG_NONE, NULL, 'u', NULL, NULL},
|
||||||
{"log-to-syslog", 0, POPT_ARG_NONE, &log_to_syslog_selected, 0, NULL, NULL},
|
{"log-to-syslog", 0, POPT_ARG_NONE, &log_to_syslog_selected, 0, NULL, NULL},
|
||||||
#ifdef CONFIG_METADATA
|
#ifdef CONFIG_METADATA
|
||||||
{"metadata-enable", 'M', POPT_ARG_NONE, &config.metadata_enabled, 'M', NULL, NULL},
|
{"metadata-enable", 'M', POPT_ARG_NONE, &config.metadata_enabled, 'M', NULL, NULL},
|
||||||
{"metadata-pipename", 0, POPT_ARG_STRING, &config.metadata_pipename, 0, NULL, NULL},
|
{"metadata-pipename", 0, POPT_ARG_STRING, &config.metadata_pipename, 0, NULL, NULL},
|
||||||
{"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL, NULL},
|
{"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL, NULL},
|
||||||
#endif
|
#endif
|
||||||
POPT_AUTOHELP{NULL, 0, 0, NULL, 0, NULL, NULL}
|
POPT_AUTOHELP{NULL, 0, 0, NULL, 0, NULL, NULL}};
|
||||||
};
|
|
||||||
|
|
||||||
// we have to parse the command line arguments to look for a config file
|
// we have to parse the command line arguments to look for a config file
|
||||||
int optind;
|
int optind;
|
||||||
@@ -552,8 +555,8 @@ int parse_options(int argc, char **argv) {
|
|||||||
// for unexpected circumstances
|
// for unexpected circumstances
|
||||||
|
|
||||||
config.model = strdup("ShairportSync");
|
config.model = strdup("ShairportSync");
|
||||||
//config.model = strdup("AirPort10,115");
|
// config.model = strdup("AirPort10,115");
|
||||||
// config.model = strdup("AudioAccessory5,1");
|
// config.model = strdup("AudioAccessory5,1");
|
||||||
|
|
||||||
// config.srcvers = strdup(PACKAGE_VERSION);
|
// config.srcvers = strdup(PACKAGE_VERSION);
|
||||||
// config.srcvers = strdup("760.13.1");
|
// config.srcvers = strdup("760.13.1");
|
||||||
@@ -612,8 +615,8 @@ int parse_options(int argc, char **argv) {
|
|||||||
// use the MAC address placed in config.hw_addr to generate the default airplay_device_id
|
// use the MAC address placed in config.hw_addr to generate the default airplay_device_id
|
||||||
uint64_t temporary_airplay_id = nctoh64(config.hw_addr);
|
uint64_t temporary_airplay_id = nctoh64(config.hw_addr);
|
||||||
temporary_airplay_id =
|
temporary_airplay_id =
|
||||||
temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8.
|
temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8.
|
||||||
|
|
||||||
config_init(&config_file_stuff);
|
config_init(&config_file_stuff);
|
||||||
|
|
||||||
config_file_real_path = realpath(config.configfile, NULL);
|
config_file_real_path = realpath(config.configfile, NULL);
|
||||||
@@ -628,6 +631,10 @@ int parse_options(int argc, char **argv) {
|
|||||||
// make config.cfg point to it
|
// make config.cfg point to it
|
||||||
config.cfg = &config_file_stuff;
|
config.cfg = &config_file_stuff;
|
||||||
|
|
||||||
|
/* See if a specific service type has been requested */
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "general.service_type", &str)) {
|
||||||
|
config.service_type = string_to_service_type(str, "general service_type");
|
||||||
|
}
|
||||||
/* Get the Service Name. */
|
/* Get the Service Name. */
|
||||||
if (config_lookup_non_empty_string(config.cfg, "general.name", &str)) {
|
if (config_lookup_non_empty_string(config.cfg, "general.name", &str)) {
|
||||||
raw_service_name = (char *)str;
|
raw_service_name = (char *)str;
|
||||||
@@ -680,8 +687,7 @@ int parse_options(int argc, char **argv) {
|
|||||||
* starting at the port base. Only three ports are needed. */
|
* starting at the port base. Only three ports are needed. */
|
||||||
if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) {
|
if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) {
|
||||||
if ((value < 3) || (value > 65535))
|
if ((value < 3) || (value > 65535))
|
||||||
die("Invalid port range \"%d\". It should be between 3 and 65535, default is 10",
|
die("Invalid port range \"%d\". It should be between 3 and 65535, default is 10", value);
|
||||||
value);
|
|
||||||
else
|
else
|
||||||
config.udp_port_range = value;
|
config.udp_port_range = value;
|
||||||
}
|
}
|
||||||
@@ -1297,6 +1303,132 @@ int parse_options(int argc, char **argv) {
|
|||||||
"controlled by a hardware mixer. "
|
"controlled by a hardware mixer. "
|
||||||
"You must not use a hardware mixer when using the loudness filter.");
|
"You must not use a hardware mixer when using the loudness filter.");
|
||||||
|
|
||||||
|
#if defined(CONFIG_DBUS_INTERFACE)
|
||||||
|
/* Get the dbus service sbus setting. */
|
||||||
|
if (config_lookup_string(config.cfg, "general.dbus_service_bus", &str)) {
|
||||||
|
if (strcasecmp(str, "system") == 0)
|
||||||
|
config.dbus_service_bus_type = DBT_system;
|
||||||
|
else if (strcasecmp(str, "session") == 0)
|
||||||
|
config.dbus_service_bus_type = DBT_session;
|
||||||
|
else
|
||||||
|
die("Invalid dbus_service_bus option choice \"%s\". It should be \"system\" (default) or "
|
||||||
|
"\"session\"",
|
||||||
|
str);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_MPRIS_INTERFACE)
|
||||||
|
/* Get the mpris service sbus setting. */
|
||||||
|
if (config_lookup_string(config.cfg, "general.mpris_service_bus", &str)) {
|
||||||
|
if (strcasecmp(str, "system") == 0)
|
||||||
|
config.mpris_service_bus_type = DBT_system;
|
||||||
|
else if (strcasecmp(str, "session") == 0)
|
||||||
|
config.mpris_service_bus_type = DBT_session;
|
||||||
|
else
|
||||||
|
die("Invalid mpris_service_bus option choice \"%s\". It should be \"system\" (default) "
|
||||||
|
"or "
|
||||||
|
"\"session\"",
|
||||||
|
str);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_MQTT
|
||||||
|
config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled);
|
||||||
|
if (config.mqtt_enabled && !config.metadata_enabled) {
|
||||||
|
die("You need to have metadata enabled in order to use mqtt");
|
||||||
|
}
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.hostname", &str)) {
|
||||||
|
config.mqtt_hostname = (char *)str;
|
||||||
|
// TODO: Document that, if this is false, whole mqtt func is disabled
|
||||||
|
}
|
||||||
|
config.mqtt_port = 1883;
|
||||||
|
if (config_lookup_int(config.cfg, "mqtt.port", &value)) {
|
||||||
|
if ((value < 0) || (value > 65535))
|
||||||
|
die("Invalid mqtt port number \"%d\". It should be between 0 and 65535, default is 1883",
|
||||||
|
value);
|
||||||
|
else
|
||||||
|
config.mqtt_port = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.username", &str)) {
|
||||||
|
config.mqtt_username = (char *)str;
|
||||||
|
}
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.password", &str)) {
|
||||||
|
config.mqtt_password = (char *)str;
|
||||||
|
}
|
||||||
|
int capath = 0;
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.capath", &str)) {
|
||||||
|
config.mqtt_capath = (char *)str;
|
||||||
|
capath = 1;
|
||||||
|
}
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.cafile", &str)) {
|
||||||
|
if (capath)
|
||||||
|
die("Supply either mqtt cafile or mqtt capath -- you have supplied both!");
|
||||||
|
config.mqtt_cafile = (char *)str;
|
||||||
|
}
|
||||||
|
int certkeynum = 0;
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.certfile", &str)) {
|
||||||
|
config.mqtt_certfile = (char *)str;
|
||||||
|
certkeynum++;
|
||||||
|
}
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.keyfile", &str)) {
|
||||||
|
config.mqtt_keyfile = (char *)str;
|
||||||
|
certkeynum++;
|
||||||
|
}
|
||||||
|
if (certkeynum != 0 && certkeynum != 2) {
|
||||||
|
die("If you want to use TLS Client Authentication, you have to specify "
|
||||||
|
"mqtt.certfile AND mqtt.keyfile.\nYou have supplied only one of them.\n"
|
||||||
|
"If you do not want to use TLS Client Authentication, leave both empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.topic", &str)) {
|
||||||
|
config.mqtt_topic = (char *)str;
|
||||||
|
}
|
||||||
|
config_set_lookup_bool(config.cfg, "mqtt.publish_raw", &config.mqtt_publish_raw);
|
||||||
|
config_set_lookup_bool(config.cfg, "mqtt.publish_parsed", &config.mqtt_publish_parsed);
|
||||||
|
config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover);
|
||||||
|
config_set_lookup_bool(config.cfg, "mqtt.publish_retain", &config.mqtt_publish_retain);
|
||||||
|
if (config.mqtt_publish_cover && !config.get_coverart) {
|
||||||
|
die("You need to have metadata.include_cover_art enabled in order to use "
|
||||||
|
"mqtt.publish_cover");
|
||||||
|
}
|
||||||
|
config_set_lookup_bool(config.cfg, "mqtt.enable_autodiscovery",
|
||||||
|
&config.mqtt_enable_autodiscovery);
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.autodiscovery_prefix", &str)) {
|
||||||
|
config.mqtt_autodiscovery_prefix = (char *)str;
|
||||||
|
}
|
||||||
|
config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote);
|
||||||
|
if (config_lookup_non_empty_string(config.cfg, "mqtt.empty_payload_substitute", &str)) {
|
||||||
|
if (strlen(str) == 0)
|
||||||
|
config.mqtt_empty_payload_substitute = NULL;
|
||||||
|
else
|
||||||
|
config.mqtt_empty_payload_substitute = strdup(str);
|
||||||
|
} else {
|
||||||
|
config.mqtt_empty_payload_substitute = strdup("--");
|
||||||
|
}
|
||||||
|
#ifndef CONFIG_AVAHI
|
||||||
|
if (config.mqtt_enable_remote) {
|
||||||
|
die("You have enabled MQTT remote control which requires shairport-sync to be built with "
|
||||||
|
"Avahi, but your installation is not using avahi. Please reinstall/recompile with "
|
||||||
|
"avahi enabled, or disable remote control.");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
long long aid;
|
||||||
|
|
||||||
|
// replace the airplay_device_id with this, if provided
|
||||||
|
if (config_lookup_int64(config.cfg, "general.airplay_device_id", &aid)) {
|
||||||
|
temporary_airplay_id = aid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the airplay_device_id_offset if provided
|
||||||
|
if (config_lookup_int64(config.cfg, "general.airplay_device_id_offset", &aid)) {
|
||||||
|
temporary_airplay_id += aid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO)
|
if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO)
|
||||||
die("Error reading configuration file \"%s\": \"%s\".", config_file_real_path,
|
die("Error reading configuration file \"%s\": \"%s\".", config_file_real_path,
|
||||||
@@ -1306,131 +1438,6 @@ int parse_options(int argc, char **argv) {
|
|||||||
config_error_file(&config_file_stuff), config_error_text(&config_file_stuff));
|
config_error_file(&config_file_stuff), config_error_text(&config_file_stuff));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if defined(CONFIG_DBUS_INTERFACE)
|
|
||||||
/* Get the dbus service sbus setting. */
|
|
||||||
if (config_lookup_string(config.cfg, "general.dbus_service_bus", &str)) {
|
|
||||||
if (strcasecmp(str, "system") == 0)
|
|
||||||
config.dbus_service_bus_type = DBT_system;
|
|
||||||
else if (strcasecmp(str, "session") == 0)
|
|
||||||
config.dbus_service_bus_type = DBT_session;
|
|
||||||
else
|
|
||||||
die("Invalid dbus_service_bus option choice \"%s\". It should be \"system\" (default) or "
|
|
||||||
"\"session\"",
|
|
||||||
str);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(CONFIG_MPRIS_INTERFACE)
|
|
||||||
/* Get the mpris service sbus setting. */
|
|
||||||
if (config_lookup_string(config.cfg, "general.mpris_service_bus", &str)) {
|
|
||||||
if (strcasecmp(str, "system") == 0)
|
|
||||||
config.mpris_service_bus_type = DBT_system;
|
|
||||||
else if (strcasecmp(str, "session") == 0)
|
|
||||||
config.mpris_service_bus_type = DBT_session;
|
|
||||||
else
|
|
||||||
die("Invalid mpris_service_bus option choice \"%s\". It should be \"system\" (default) or "
|
|
||||||
"\"session\"",
|
|
||||||
str);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_MQTT
|
|
||||||
config_set_lookup_bool(config.cfg, "mqtt.enabled", &config.mqtt_enabled);
|
|
||||||
if (config.mqtt_enabled && !config.metadata_enabled) {
|
|
||||||
die("You need to have metadata enabled in order to use mqtt");
|
|
||||||
}
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.hostname", &str)) {
|
|
||||||
config.mqtt_hostname = (char *)str;
|
|
||||||
// TODO: Document that, if this is false, whole mqtt func is disabled
|
|
||||||
}
|
|
||||||
config.mqtt_port = 1883;
|
|
||||||
if (config_lookup_int(config.cfg, "mqtt.port", &value)) {
|
|
||||||
if ((value < 0) || (value > 65535))
|
|
||||||
die("Invalid mqtt port number \"%d\". It should be between 0 and 65535, default is 1883",
|
|
||||||
value);
|
|
||||||
else
|
|
||||||
config.mqtt_port = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.username", &str)) {
|
|
||||||
config.mqtt_username = (char *)str;
|
|
||||||
}
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.password", &str)) {
|
|
||||||
config.mqtt_password = (char *)str;
|
|
||||||
}
|
|
||||||
int capath = 0;
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.capath", &str)) {
|
|
||||||
config.mqtt_capath = (char *)str;
|
|
||||||
capath = 1;
|
|
||||||
}
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.cafile", &str)) {
|
|
||||||
if (capath)
|
|
||||||
die("Supply either mqtt cafile or mqtt capath -- you have supplied both!");
|
|
||||||
config.mqtt_cafile = (char *)str;
|
|
||||||
}
|
|
||||||
int certkeynum = 0;
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.certfile", &str)) {
|
|
||||||
config.mqtt_certfile = (char *)str;
|
|
||||||
certkeynum++;
|
|
||||||
}
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.keyfile", &str)) {
|
|
||||||
config.mqtt_keyfile = (char *)str;
|
|
||||||
certkeynum++;
|
|
||||||
}
|
|
||||||
if (certkeynum != 0 && certkeynum != 2) {
|
|
||||||
die("If you want to use TLS Client Authentication, you have to specify "
|
|
||||||
"mqtt.certfile AND mqtt.keyfile.\nYou have supplied only one of them.\n"
|
|
||||||
"If you do not want to use TLS Client Authentication, leave both empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.topic", &str)) {
|
|
||||||
config.mqtt_topic = (char *)str;
|
|
||||||
}
|
|
||||||
config_set_lookup_bool(config.cfg, "mqtt.publish_raw", &config.mqtt_publish_raw);
|
|
||||||
config_set_lookup_bool(config.cfg, "mqtt.publish_parsed", &config.mqtt_publish_parsed);
|
|
||||||
config_set_lookup_bool(config.cfg, "mqtt.publish_cover", &config.mqtt_publish_cover);
|
|
||||||
config_set_lookup_bool(config.cfg, "mqtt.publish_retain", &config.mqtt_publish_retain);
|
|
||||||
if (config.mqtt_publish_cover && !config.get_coverart) {
|
|
||||||
die("You need to have metadata.include_cover_art enabled in order to use mqtt.publish_cover");
|
|
||||||
}
|
|
||||||
config_set_lookup_bool(config.cfg, "mqtt.enable_autodiscovery",
|
|
||||||
&config.mqtt_enable_autodiscovery);
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.autodiscovery_prefix", &str)) {
|
|
||||||
config.mqtt_autodiscovery_prefix = (char *)str;
|
|
||||||
}
|
|
||||||
config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote);
|
|
||||||
if (config_lookup_non_empty_string(config.cfg, "mqtt.empty_payload_substitute", &str)) {
|
|
||||||
if (strlen(str) == 0)
|
|
||||||
config.mqtt_empty_payload_substitute = NULL;
|
|
||||||
else
|
|
||||||
config.mqtt_empty_payload_substitute = strdup(str);
|
|
||||||
} else {
|
|
||||||
config.mqtt_empty_payload_substitute = strdup("--");
|
|
||||||
}
|
|
||||||
#ifndef CONFIG_AVAHI
|
|
||||||
if (config.mqtt_enable_remote) {
|
|
||||||
die("You have enabled MQTT remote control which requires shairport-sync to be built with "
|
|
||||||
"Avahi, but your installation is not using avahi. Please reinstall/recompile with "
|
|
||||||
"avahi enabled, or disable remote control.");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
|
||||||
long long aid;
|
|
||||||
|
|
||||||
// replace the airplay_device_id with this, if provided
|
|
||||||
if (config_lookup_int64(config.cfg, "general.airplay_device_id", &aid)) {
|
|
||||||
temporary_airplay_id = aid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the airplay_device_id_offset if provided
|
|
||||||
if (config_lookup_int64(config.cfg, "general.airplay_device_id_offset", &aid)) {
|
|
||||||
temporary_airplay_id += aid;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now, do the command line options again, but this time do them fully -- it's a unix convention
|
// now, do the command line options again, but this time do them fully -- it's a unix convention
|
||||||
@@ -1497,6 +1504,10 @@ int parse_options(int argc, char **argv) {
|
|||||||
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
|
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cli_service_type_string != NULL)
|
||||||
|
config.service_type = string_to_service_type(cli_service_type_string,
|
||||||
|
"command line option \"--service-type\" argument");
|
||||||
|
|
||||||
poptFreeContext(optCon);
|
poptFreeContext(optCon);
|
||||||
|
|
||||||
#if defined(CONFIG_DBUS_INTERFACE) || (CONFIG_MPRIS_INTERFACE)
|
#if defined(CONFIG_DBUS_INTERFACE) || (CONFIG_MPRIS_INTERFACE)
|
||||||
@@ -1527,6 +1538,7 @@ int parse_options(int argc, char **argv) {
|
|||||||
|
|
||||||
config.nqptp_shared_memory_interface_name = strdup(NQPTP_INTERFACE_NAME);
|
config.nqptp_shared_memory_interface_name = strdup(NQPTP_INTERFACE_NAME);
|
||||||
|
|
||||||
|
// create the config.ap1_prefix[i]
|
||||||
char apids[6 * 2 + 5 + 1]; // six pairs of digits, 5 colons and a NUL
|
char apids[6 * 2 + 5 + 1]; // six pairs of digits, 5 colons and a NUL
|
||||||
apids[6 * 2 + 5] = 0; // NUL termination
|
apids[6 * 2 + 5] = 0; // NUL termination
|
||||||
int i;
|
int i;
|
||||||
@@ -1555,15 +1567,13 @@ int parse_options(int argc, char **argv) {
|
|||||||
else
|
else
|
||||||
memcpy(result, config.ap1_prefix, sizeof(result));
|
memcpy(result, config.ap1_prefix, sizeof(result));
|
||||||
|
|
||||||
// OpenSSL is mandatory for AirPlay 2 anyway
|
// OpenSSL is mandatory for AirPlay 2
|
||||||
#ifdef CONFIG_OPENSSL
|
|
||||||
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
|
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
|
||||||
EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
|
EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
|
||||||
EVP_DigestUpdate(mdctx, config.ap1_prefix, sizeof(config.ap1_prefix));
|
EVP_DigestUpdate(mdctx, config.ap1_prefix, sizeof(config.ap1_prefix));
|
||||||
unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
|
unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
|
||||||
EVP_DigestFinal_ex(mdctx, result, &md5_digest_len);
|
EVP_DigestFinal_ex(mdctx, result, &md5_digest_len);
|
||||||
EVP_MD_CTX_free(mdctx);
|
EVP_MD_CTX_free(mdctx);
|
||||||
#endif
|
|
||||||
|
|
||||||
// now, convert it into a type 4 UUID
|
// now, convert it into a type 4 UUID
|
||||||
// see https://stackoverflow.com/questions/10867405/generating-v5-uuid-what-is-name-and-namespace
|
// see https://stackoverflow.com/questions/10867405/generating-v5-uuid-what-is-name-and-namespace
|
||||||
@@ -1580,7 +1590,7 @@ int parse_options(int argc, char **argv) {
|
|||||||
// Produces a UUID string at uuid consisting of lower-case letters
|
// Produces a UUID string at uuid consisting of lower-case letters
|
||||||
uuid_unparse_lower(result, psi_uuid);
|
uuid_unparse_lower(result, psi_uuid);
|
||||||
config.airplay_psi = psi_uuid;
|
config.airplay_psi = psi_uuid;
|
||||||
|
|
||||||
debug(3, "size of pk is %zu.", sizeof(config.airplay_pk));
|
debug(3, "size of pk is %zu.", sizeof(config.airplay_pk));
|
||||||
pair_public_key_get(PAIR_SERVER_HOMEKIT, config.airplay_pk, config.airplay_device_id);
|
pair_public_key_get(PAIR_SERVER_HOMEKIT, config.airplay_pk, config.airplay_device_id);
|
||||||
char buf[128];
|
char buf[128];
|
||||||
@@ -1590,24 +1600,24 @@ int parse_options(int argc, char **argv) {
|
|||||||
ptr += sprintf(ptr, "%02x", config.airplay_pk[pk_index]);
|
ptr += sprintf(ptr, "%02x", config.airplay_pk[pk_index]);
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
config.pk_string = strdup(buf);
|
config.pk_string = strdup(buf);
|
||||||
|
|
||||||
// the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32
|
// the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32
|
||||||
// bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as
|
// bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as
|
||||||
// features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
|
// features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
|
||||||
// 496155702020608 this setting here is the source of both the plist features response and the
|
// 496155702020608 this setting here is the source of both the plist features response and the
|
||||||
// mDNS string.
|
// mDNS string.
|
||||||
|
|
||||||
config.airplay_features =
|
config.airplay_features = 0x00018340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1
|
||||||
0x00018340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no AP1 artwork (b15)
|
// progress (b16), no AP1 artwork (b15)
|
||||||
// 0x0001C340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no AP1 artwork (b15)
|
// 0x0001C340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no
|
||||||
// 0x0001C340445D0A00;
|
// AP1 artwork (b15) 0x0001C340445D0A00;
|
||||||
// config.airplay_features |= (1 << 26); // 0x0x4000000
|
// config.airplay_features |= (1 << 26); // 0x0x4000000
|
||||||
|
|
||||||
// features=0x0001C340445D0A00 -- AirPort Express
|
// features=0x0001C340445D0A00 -- AirPort Express
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA
|
#ifdef CONFIG_METADATA
|
||||||
// If we are asking for metadata, turn on the relevant bits
|
// If we are asking for metadata, turn on the relevant bits
|
||||||
|
|
||||||
// If bit 50 is set, metadata is sent via plists in POST /command payloads.
|
// If bit 50 is set, metadata is sent via plists in POST /command payloads.
|
||||||
// The data consists of textual information about what is playing and cover art
|
// The data consists of textual information about what is playing and cover art
|
||||||
// It does not seem possible to turn off the cover art.
|
// It does not seem possible to turn off the cover art.
|
||||||
@@ -1615,13 +1625,15 @@ int parse_options(int argc, char **argv) {
|
|||||||
// While bit 50 is set, no data comes through the "classic" way. That is
|
// While bit 50 is set, no data comes through the "classic" way. That is
|
||||||
// no progress, text or picture data comes through in the way that
|
// no progress, text or picture data comes through in the way that
|
||||||
// it comes through in Classic AirPlay (aka AirPlay 1).
|
// it comes through in Classic AirPlay (aka AirPlay 1).
|
||||||
|
|
||||||
// Although it is less flexible about what metadata is sent, bit 50 being set
|
// Although it is less flexible about what metadata is sent, bit 50 being set
|
||||||
// provides much more information, so should be the default for AirPlay 2
|
// provides much more information, so should be the default for AirPlay 2
|
||||||
|
|
||||||
if (config.metadata_enabled != 0) {
|
if (config.metadata_enabled != 0) {
|
||||||
config.airplay_features |= (uint64_t)1 << 50; // metadata in a binary plist, including more state information
|
config.airplay_features |=
|
||||||
// config.airplay_features |= ((uint64_t)1 << 15) | ((uint64_t)1 << 16) | ((uint64_t)1 << 17); // older metadata flags artwork, progress and text respectively
|
(uint64_t)1 << 50; // metadata in a binary plist, including more state information
|
||||||
|
// config.airplay_features |= ((uint64_t)1 << 15) | ((uint64_t)1 << 16) | ((uint64_t)1 << 17);
|
||||||
|
// // older metadata flags artwork, progress and text respectively
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1642,11 +1654,11 @@ int parse_options(int argc, char **argv) {
|
|||||||
if (padding)
|
if (padding)
|
||||||
*padding = 0;
|
*padding = 0;
|
||||||
debug(2, "airplay_fex is \"%s\"", config.airplay_fex);
|
debug(2, "airplay_fex is \"%s\"", config.airplay_fex);
|
||||||
|
|
||||||
// now the status flags
|
// now the status flags
|
||||||
// Advertised with mDNS and returned with GET /info, see
|
// Advertised with mDNS and returned with GET /info, see
|
||||||
// https://openairplay.github.io/airplay-spec/status_flags.html
|
// https://openairplay.github.io/airplay-spec/status_flags.html
|
||||||
|
|
||||||
config.airplay_statusflags = 0;
|
config.airplay_statusflags = 0;
|
||||||
config.airplay_statusflags |= 1 << 2; // Audio cable is attached
|
config.airplay_statusflags |= 1 << 2; // Audio cable is attached
|
||||||
if (config.password != NULL) {
|
if (config.password != NULL) {
|
||||||
@@ -1708,34 +1720,88 @@ int parse_options(int argc, char **argv) {
|
|||||||
config.airplay_volume =
|
config.airplay_volume =
|
||||||
config.default_airplay_volume; // if no volume is ever set or requested, default to initial
|
config.default_airplay_volume; // if no volume is ever set or requested, default to initial
|
||||||
// default value if nothing else comes in first.
|
// default value if nothing else comes in first.
|
||||||
// now, do the substitutions in the service name
|
|
||||||
char hostname[100];
|
|
||||||
gethostname(hostname, 100);
|
|
||||||
|
|
||||||
// strip off a terminating .<anything>, e.g. .local from the hostname
|
// let's see if we have AirPlay 2 and NQPTP is installed or not and set service_type accordingly
|
||||||
char *last_dot = strrchr(hostname, '.');
|
// or exit. the outcome will be APST_airplay2, APST_classic or APST_forced_classic, and APST_auto
|
||||||
if (last_dot != NULL)
|
// will be gone.
|
||||||
*last_dot = '\0';
|
|
||||||
|
|
||||||
char *i0;
|
char service_type_string[32];
|
||||||
if (raw_service_name == NULL)
|
service_type_to_string(config.service_type, service_type_string);
|
||||||
i0 = strdup("%H"); // this is the default it the Service Name wasn't specified
|
debug(1, "config.service_type is: \"%s\".", service_type_string);
|
||||||
else
|
|
||||||
i0 = strdup(raw_service_name);
|
|
||||||
|
|
||||||
// here, do the substitutions for %h, %H, %v and %V
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
char *i1 = str_replace(i0, "%h", hostname);
|
// don't bother checking for NQPTP if we are providing a classic service
|
||||||
if ((hostname[0] >= 'a') && (hostname[0] <= 'z'))
|
if (config.service_type != APST_classic) {
|
||||||
hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter
|
ptp_send_control_message_string(
|
||||||
char *i2 = str_replace(i1, "%H", hostname);
|
"T"); // send this message to get nqptp to create the named shm interface
|
||||||
char *i3 = str_replace(i2, "%v", PACKAGE_VERSION);
|
int response = 0;
|
||||||
char *vs = get_version_string();
|
/*
|
||||||
config.service_name = str_replace(i3, "%V", vs); // service name complete
|
uint64_t nqptp_start_waiting_time = get_absolute_time_in_ns();
|
||||||
free(i0);
|
int continue_waiting = 0;
|
||||||
free(i1);
|
int64_t time_spent_waiting = 0;
|
||||||
free(i2);
|
do {
|
||||||
free(i3);
|
continue_waiting = 0;
|
||||||
free(vs);
|
response = ptp_shm_interface_open();
|
||||||
|
if ((response == -1) && (errno == ENOENT)) {
|
||||||
|
time_spent_waiting = get_absolute_time_in_ns() - nqptp_start_waiting_time;
|
||||||
|
if (time_spent_waiting < 10000000000L) {
|
||||||
|
continue_waiting = 1;
|
||||||
|
usleep(50000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (continue_waiting != 0);
|
||||||
|
*/
|
||||||
|
|
||||||
|
response = ptp_shm_interface_open(); // look for NQPTP service
|
||||||
|
|
||||||
|
if ((response == -1) && (errno == ENOENT)) {
|
||||||
|
debug(1, "NQPTP service not found.");
|
||||||
|
// change auto to forced classic
|
||||||
|
if (config.service_type == APST_auto) {
|
||||||
|
config.service_type = APST_forced_classic;
|
||||||
|
} else if (config.service_type == APST_airplay2) {
|
||||||
|
die("The NQPTP service can not be found. NQPTP must be installed and running to provide "
|
||||||
|
"AirPlay 2 service.");
|
||||||
|
}
|
||||||
|
} else if ((response == -1) && (errno == EACCES)) {
|
||||||
|
die("Shairport Sync must have read access to the NQPTP shared memory file in /dev/shm/.");
|
||||||
|
} else if (response != 0) {
|
||||||
|
die("an error occurred accessing the NQPTP service.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == 0) {
|
||||||
|
// change "auto" to "airplay2"
|
||||||
|
if (config.service_type == APST_auto) {
|
||||||
|
config.service_type = APST_airplay2;
|
||||||
|
}
|
||||||
|
// check that the version of Shairport Sync and NQPTP match...
|
||||||
|
debug(1, "NQPTP service found.");
|
||||||
|
if (config.service_type == APST_airplay2) {
|
||||||
|
// now that we are using AirPlay 2, check NQPTP and SPS match...
|
||||||
|
int ptp_clock_version = ptp_get_clock_version();
|
||||||
|
if (ptp_clock_version == 0) {
|
||||||
|
die("The NQPTP service on this system, which is required for Shairport Sync to operate, "
|
||||||
|
"does "
|
||||||
|
"not seem to be initialised.");
|
||||||
|
} else if (ptp_clock_version < NQPTP_SHM_STRUCTURES_VERSION) {
|
||||||
|
die("The NQPTP service (SMI Version %d) on this system is too old for this version of "
|
||||||
|
"Shairport Sync, which requires SMI Version %d. Please update.",
|
||||||
|
ptp_clock_version, NQPTP_SHM_STRUCTURES_VERSION);
|
||||||
|
} else if (ptp_clock_version > NQPTP_SHM_STRUCTURES_VERSION) {
|
||||||
|
die("This version of Shairport Sync (SMI Version %d) is too old for the version of NQPTP "
|
||||||
|
"(SMI "
|
||||||
|
"Version %d) on this system. Please update.",
|
||||||
|
NQPTP_SHM_STRUCTURES_VERSION, ptp_clock_version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// if we don't have AirPLay 2, the service type must be classic
|
||||||
|
config.service_type = APST_classic;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
config.service_name = service_name(raw_service_name);
|
||||||
|
|
||||||
#ifdef CONFIG_MQTT
|
#ifdef CONFIG_MQTT
|
||||||
// mqtt topic was not set. As we have the service name just now, set it
|
// mqtt topic was not set. As we have the service name just now, set it
|
||||||
@@ -1905,9 +1971,6 @@ void exit_function() {
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (conns)
|
|
||||||
free(conns); // make sure the connections have been deleted first
|
|
||||||
|
|
||||||
if (config.service_name)
|
if (config.service_name)
|
||||||
free(config.service_name);
|
free(config.service_name);
|
||||||
|
|
||||||
@@ -1971,7 +2034,7 @@ void exit_function() {
|
|||||||
if (config.appName)
|
if (config.appName)
|
||||||
free(config.appName);
|
free(config.appName);
|
||||||
|
|
||||||
// probably should be freeing malloc'ed memory here, including strdup-created strings...
|
// probably should be freeing malloc'ed memory here, including strdup-created strings...
|
||||||
|
|
||||||
#ifdef CONFIG_LIBDAEMON
|
#ifdef CONFIG_LIBDAEMON
|
||||||
if (this_is_the_daemon_process) { // this is the daemon that is exiting
|
if (this_is_the_daemon_process) { // this is the daemon that is exiting
|
||||||
@@ -2016,12 +2079,12 @@ void termHandler(__attribute__((unused)) int k) {
|
|||||||
|
|
||||||
void _display_config(const char *filename, const int linenumber, __attribute__((unused)) int argc,
|
void _display_config(const char *filename, const int linenumber, __attribute__((unused)) int argc,
|
||||||
__attribute__((unused)) char **argv) {
|
__attribute__((unused)) char **argv) {
|
||||||
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wformat-zero-length"
|
#pragma GCC diagnostic ignored "-Wformat-zero-length"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_inform(filename, linenumber, ">> Display Config Start.");
|
_inform(filename, linenumber, ">> Display Config Start.");
|
||||||
|
|
||||||
// see the man entry on popen
|
// see the man entry on popen
|
||||||
@@ -2200,11 +2263,11 @@ void _display_config(const char *filename, const int linenumber, __attribute__((
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_inform(filename, linenumber, "");
|
_inform(filename, linenumber, "");
|
||||||
_inform(filename, linenumber, ">> Display Config End.");
|
_inform(filename, linenumber, ">> Display Config End.");
|
||||||
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#define display_config(argc, argv) _display_config(__FILE__, __LINE__, argc, argv)
|
#define display_config(argc, argv) _display_config(__FILE__, __LINE__, argc, argv)
|
||||||
@@ -2328,7 +2391,6 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
pid = getpid();
|
pid = getpid();
|
||||||
config.log_fd = -1;
|
config.log_fd = -1;
|
||||||
conns = NULL; // no connections active
|
|
||||||
|
|
||||||
#ifdef CONFIG_LIBDAEMON
|
#ifdef CONFIG_LIBDAEMON
|
||||||
daemon_set_verbosity(LOG_DEBUG);
|
daemon_set_verbosity(LOG_DEBUG);
|
||||||
@@ -2340,6 +2402,8 @@ int main(int argc, char **argv) {
|
|||||||
debug(1, "adding the exit function");
|
debug(1, "adding the exit function");
|
||||||
atexit(exit_function);
|
atexit(exit_function);
|
||||||
|
|
||||||
|
config.service_type = APST_auto; // this may be changed by the settings...
|
||||||
|
|
||||||
// get a device id -- the first non-local MAC address
|
// get a device id -- the first non-local MAC address
|
||||||
get_device_id((uint8_t *)&config.hw_addr, 6);
|
get_device_id((uint8_t *)&config.hw_addr, 6);
|
||||||
|
|
||||||
@@ -2389,12 +2453,6 @@ int main(int argc, char **argv) {
|
|||||||
config.resync_threshold = 0.050; // default
|
config.resync_threshold = 0.050; // default
|
||||||
config.tolerance = 0.002;
|
config.tolerance = 0.002;
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
|
||||||
config.port = 7000;
|
|
||||||
#else
|
|
||||||
config.port = 5000;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_SOXR
|
#ifdef CONFIG_SOXR
|
||||||
config.packet_stuffing = ST_auto; // use soxr interpolation by default if support has been
|
config.packet_stuffing = ST_auto; // use soxr interpolation by default if support has been
|
||||||
// included and if the CPU is fast enough
|
// included and if the CPU is fast enough
|
||||||
@@ -2402,10 +2460,6 @@ int main(int argc, char **argv) {
|
|||||||
config.packet_stuffing = ST_vernier; // you need to explicitly ask for "basic" (ST_basic)
|
config.packet_stuffing = ST_vernier; // you need to explicitly ask for "basic" (ST_basic)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// char hostname[100];
|
|
||||||
// gethostname(hostname, 100);
|
|
||||||
// config.service_name = malloc(20 + 100);
|
|
||||||
// snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname);
|
|
||||||
set_requested_connection_state_to_output(
|
set_requested_connection_state_to_output(
|
||||||
1); // we expect to be able to connect to the output device
|
1); // we expect to be able to connect to the output device
|
||||||
config.audio_backend_buffer_desired_length = 0.15; // seconds
|
config.audio_backend_buffer_desired_length = 0.15; // seconds
|
||||||
@@ -2465,13 +2519,6 @@ int main(int argc, char **argv) {
|
|||||||
// parse arguments into config -- needed to locate pid_dir
|
// parse arguments into config -- needed to locate pid_dir
|
||||||
int audio_arg = parse_options(argc, argv);
|
int audio_arg = parse_options(argc, argv);
|
||||||
|
|
||||||
// mDNS supports maximum of 63-character names (we append 13).
|
|
||||||
if (strlen(config.service_name) > 50) {
|
|
||||||
warn("The service name \"%s\" is too long (max 50 characters) and has been truncated.",
|
|
||||||
config.service_name);
|
|
||||||
config.service_name[50] = '\0'; // truncate it and carry on...
|
|
||||||
}
|
|
||||||
|
|
||||||
if (display_config_selected != 0) {
|
if (display_config_selected != 0) {
|
||||||
display_config(argc, argv);
|
display_config(argc, argv);
|
||||||
if (argc == 2) {
|
if (argc == 2) {
|
||||||
@@ -2601,21 +2648,36 @@ int main(int argc, char **argv) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
if (config.service_type == APST_airplay2) {
|
||||||
if (has_fltp_capable_aac_decoder() == 0) {
|
config.port = 7000;
|
||||||
die("Shairport Sync can not run on this system. Run \"shairport-sync -h\" for more "
|
} else {
|
||||||
"information.");
|
config.port = 5000;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
config.port = 5000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
if (config.service_type == APST_airplay2) {
|
||||||
|
if (has_fltp_capable_aac_decoder() == 0) {
|
||||||
|
die("Shairport Sync can not run on this system. Run \"shairport-sync -h\" for more "
|
||||||
|
"information.");
|
||||||
|
}
|
||||||
|
}
|
||||||
uint64_t apf = config.airplay_features;
|
uint64_t apf = config.airplay_features;
|
||||||
uint64_t apfh = config.airplay_features;
|
uint64_t apfh = config.airplay_features;
|
||||||
apfh = apfh >> 32;
|
apfh = apfh >> 32;
|
||||||
uint32_t apf32 = apf;
|
uint32_t apf32 = apf;
|
||||||
uint32_t apfh32 = apfh;
|
uint32_t apfh32 = apfh;
|
||||||
debug(1, "Startup in AirPlay 2 mode, with features 0x%" PRIx32 ",0x%" PRIx32 " on device \"%s\".",
|
if (config.service_type == APST_airplay2) {
|
||||||
apf32, apfh32, config.airplay_device_id);
|
debug(1,
|
||||||
|
"Startup in AirPlay 2 mode, with features 0x%" PRIx32 ",0x%" PRIx32 " on device \"%s\".",
|
||||||
|
apf32, apfh32, config.airplay_device_id);
|
||||||
|
} else {
|
||||||
|
debug(1, "Startup in Classic AirPlay (aka \"AirPlay 1\") mode. (AirPlay2 build.)");
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
debug(1, "Startup in classic Airplay (aka \"AirPlay 1\") mode.");
|
debug(1, "Startup in Classic AirPlay (aka \"AirPlay 1\") mode.");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// control-c (SIGINT) cleanly
|
// control-c (SIGINT) cleanly
|
||||||
@@ -2949,9 +3011,9 @@ int main(int argc, char **argv) {
|
|||||||
config.output_channel_map[i] = strdup(channel_id);
|
config.output_channel_map[i] = strdup(channel_id);
|
||||||
debug(2, "output channel %d is \"%s\".", i, config.output_channel_map[i]);
|
debug(2, "output channel %d is \"%s\".", i, config.output_channel_map[i]);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
warn("during channel mapping, \"%s\" was not recognised as a channel name -- as a "
|
||||||
warn("during channel mapping, \"%s\" was not recognised as a channel name -- as a result, output channel %d will be silent.",
|
"result, output channel %d will be silent.",
|
||||||
channel_id, i);
|
channel_id, i);
|
||||||
config.output_channel_map[i] = strdup("--");
|
config.output_channel_map[i] = strdup("--");
|
||||||
}
|
}
|
||||||
@@ -3155,12 +3217,11 @@ int main(int argc, char **argv) {
|
|||||||
if (soxr_time_check_thread != NULL) {
|
if (soxr_time_check_thread != NULL) {
|
||||||
named_pthread_create(soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker");
|
named_pthread_create(soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker");
|
||||||
} else {
|
} else {
|
||||||
debug(1,"couldn't get memory to start the soxr_checker");
|
debug(1, "couldn't get memory to start the soxr_checker");
|
||||||
}
|
}
|
||||||
pthread_setcancelstate(oldState, NULL); // make this un-cancellable
|
pthread_setcancelstate(oldState, NULL); // make this un-cancellable
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_FFMPEG
|
#ifdef CONFIG_FFMPEG
|
||||||
@@ -3208,47 +3269,53 @@ int main(int argc, char **argv) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// In AirPlay 2 mode, the AP1 prefix is the same as the device ID less the colons
|
// In AirPlay 2 mode, the AP1 prefix is the same as the device ID less the colons
|
||||||
|
// and has already been calculated.
|
||||||
|
|
||||||
// In AirPlay 1 mode, the AP1 prefix is calculated by hashing the service name.
|
// In AirPlay 1 mode, the AP1 prefix is calculated by hashing the service name.
|
||||||
#ifndef CONFIG_AIRPLAY_2
|
|
||||||
|
|
||||||
uint8_t ap_md5[16];
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
if (config.service_type != APST_airplay2) {
|
||||||
// debug(1, "size of hw_addr is %u.", sizeof(config.hw_addr));
|
#endif
|
||||||
|
uint8_t ap_md5[16];
|
||||||
|
// debug(1, "size of hw_addr is %u.", sizeof(config.hw_addr));
|
||||||
#ifdef CONFIG_OPENSSL
|
#ifdef CONFIG_OPENSSL
|
||||||
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
|
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
|
||||||
EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
|
EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
|
||||||
EVP_DigestUpdate(mdctx, config.service_name, strlen(config.service_name));
|
EVP_DigestUpdate(mdctx, config.service_name, strlen(config.service_name));
|
||||||
EVP_DigestUpdate(mdctx, config.hw_addr, sizeof(config.hw_addr));
|
EVP_DigestUpdate(mdctx, config.hw_addr, sizeof(config.hw_addr));
|
||||||
unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
|
unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
|
||||||
EVP_DigestFinal_ex(mdctx, ap_md5, &md5_digest_len);
|
EVP_DigestFinal_ex(mdctx, ap_md5, &md5_digest_len);
|
||||||
EVP_MD_CTX_free(mdctx);
|
EVP_MD_CTX_free(mdctx);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_MBEDTLS
|
#ifdef CONFIG_MBEDTLS
|
||||||
#if MBEDTLS_VERSION_MINOR >= 7
|
#if MBEDTLS_VERSION_MINOR >= 7
|
||||||
mbedtls_md5_context tctx;
|
mbedtls_md5_context tctx;
|
||||||
mbedtls_md5_starts_ret(&tctx);
|
mbedtls_md5_starts_ret(&tctx);
|
||||||
mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name,
|
||||||
mbedtls_md5_update_ret(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
|
strlen(config.service_name));
|
||||||
mbedtls_md5_finish_ret(&tctx, ap_md5);
|
mbedtls_md5_update_ret(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
|
||||||
|
mbedtls_md5_finish_ret(&tctx, ap_md5);
|
||||||
#else
|
#else
|
||||||
mbedtls_md5_context tctx;
|
mbedtls_md5_context tctx;
|
||||||
mbedtls_md5_starts(&tctx);
|
mbedtls_md5_starts(&tctx);
|
||||||
mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
||||||
mbedtls_md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
|
mbedtls_md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
|
||||||
mbedtls_md5_finish(&tctx, ap_md5);
|
mbedtls_md5_finish(&tctx, ap_md5);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_POLARSSL
|
#ifdef CONFIG_POLARSSL
|
||||||
md5_context tctx;
|
md5_context tctx;
|
||||||
md5_starts(&tctx);
|
md5_starts(&tctx);
|
||||||
md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
||||||
md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
|
md5_update(&tctx, (unsigned char *)config.hw_addr, sizeof(config.hw_addr));
|
||||||
md5_finish(&tctx, ap_md5);
|
md5_finish(&tctx, ap_md5);
|
||||||
#endif
|
#endif
|
||||||
|
memcpy(config.ap1_prefix, ap_md5, sizeof(config.ap1_prefix));
|
||||||
|
|
||||||
memcpy(config.ap1_prefix, ap_md5, sizeof(config.ap1_prefix));
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA
|
#ifdef CONFIG_METADATA
|
||||||
@@ -3275,55 +3342,6 @@ int main(int argc, char **argv) {
|
|||||||
glib_worker_thread = g_thread_new("glib worker", glib_worker_thread_function, NULL);
|
glib_worker_thread = g_thread_new("glib worker", glib_worker_thread_function, NULL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
|
||||||
|
|
||||||
ptp_send_control_message_string(
|
|
||||||
"T"); // send this message to get nqptp to create the named shm interface
|
|
||||||
uint64_t nqptp_start_waiting_time = get_absolute_time_in_ns();
|
|
||||||
int continue_waiting = 0;
|
|
||||||
int response = 0;
|
|
||||||
int64_t time_spent_waiting = 0;
|
|
||||||
do {
|
|
||||||
continue_waiting = 0;
|
|
||||||
response = ptp_shm_interface_open();
|
|
||||||
if ((response == -1) && (errno == ENOENT)) {
|
|
||||||
time_spent_waiting = get_absolute_time_in_ns() - nqptp_start_waiting_time;
|
|
||||||
if (time_spent_waiting < 10000000000L) {
|
|
||||||
continue_waiting = 1;
|
|
||||||
usleep(50000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (continue_waiting != 0);
|
|
||||||
|
|
||||||
if ((response == -1) && (errno == ENOENT)) {
|
|
||||||
die("Shairport Sync can not find the nqptp service on this system. Is nqptp installed and "
|
|
||||||
"running?");
|
|
||||||
} else if ((response == -1) && (errno == EACCES)) {
|
|
||||||
die("Shairport Sync must have read access to the nqptp shared memory file in /dev/shm/.");
|
|
||||||
} else if (response != 0) {
|
|
||||||
die("an error occurred accessing the nqptp service.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int ptp_clock_version = ptp_get_clock_version();
|
|
||||||
if (ptp_clock_version == 0) {
|
|
||||||
die("The nqptp service on this system, which is required for Shairport Sync to operate, does "
|
|
||||||
"not seem to be initialised.");
|
|
||||||
} else if (ptp_clock_version < NQPTP_SHM_STRUCTURES_VERSION) {
|
|
||||||
die("The nqptp service (SMI Version %d) on this system is too old for this version of "
|
|
||||||
"Shairport Sync, which requires SMI Version %d. Please update.",
|
|
||||||
ptp_clock_version, NQPTP_SHM_STRUCTURES_VERSION);
|
|
||||||
} else if (ptp_clock_version > NQPTP_SHM_STRUCTURES_VERSION) {
|
|
||||||
die("This version of Shairport Sync (SMI Version %d) is too old for the version of nqptp (SMI "
|
|
||||||
"Version %d) on this system. Please update.",
|
|
||||||
NQPTP_SHM_STRUCTURES_VERSION, ptp_clock_version);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (time_spent_waiting == 0)
|
|
||||||
debug(1, "NQPTP is online.");
|
|
||||||
else
|
|
||||||
debug(1, "NQPTP came online after %.3f milliseconds.", 0.000001 * time_spent_waiting);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA
|
#ifdef CONFIG_METADATA
|
||||||
send_ssnc_metadata('svna', config.service_name, strlen(config.service_name), 1);
|
send_ssnc_metadata('svna', config.service_name, strlen(config.service_name), 1);
|
||||||
#endif
|
#endif
|
||||||
@@ -3332,8 +3350,10 @@ int main(int argc, char **argv) {
|
|||||||
convolver_pool_init(config.convolution_threads, 8); // 8 channels
|
convolver_pool_init(config.convolution_threads, 8); // 8 channels
|
||||||
#endif
|
#endif
|
||||||
activity_monitor_start();
|
activity_monitor_start();
|
||||||
debug(3, "create an RTSP listener");
|
debug(4, "create an RTSP listener");
|
||||||
named_pthread_create(&rtsp_listener_thread, NULL, &rtsp_listen_loop, NULL, "bonjour");
|
// note: the Avahi Threaded Poll thread will be named after whatever name you use here too, so
|
||||||
|
// you'll see two threads named "listener" or whatever...
|
||||||
|
named_pthread_create(&rtsp_listener_thread, NULL, &rtsp_listen_loop, NULL, "listener");
|
||||||
atexit(exit_rtsp_listener);
|
atexit(exit_rtsp_listener);
|
||||||
pthread_join(rtsp_listener_thread, NULL);
|
pthread_join(rtsp_listener_thread, NULL);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+7
-6
@@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
#include "tinysvcmdns.h"
|
#include "tinysvcmdns.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "utilities/network_utilities.h"
|
||||||
|
|
||||||
#define DEBUG_PRINTF(...) debug(3, __VA_ARGS__)
|
#define DEBUG_PRINTF(...) debug(3, __VA_ARGS__)
|
||||||
#define log_message(level, ...) \
|
#define log_message(level, ...) \
|
||||||
@@ -1457,11 +1458,11 @@ int write_pipe(int s, char *buf, int len) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int close_pipe(int s) {
|
int close_pipe(int *s) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return closesocket(s);
|
return closesocket(*s);
|
||||||
#else
|
#else
|
||||||
return close(s);
|
return safe_socket_close(s);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1566,7 +1567,7 @@ void *main_loop(struct mdnsd *svr) {
|
|||||||
|
|
||||||
free(pkt_buffer);
|
free(pkt_buffer);
|
||||||
|
|
||||||
close_pipe(svr->sockfd);
|
close_pipe(&svr->sockfd);
|
||||||
|
|
||||||
svr->stop_flag = 2;
|
svr->stop_flag = 2;
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1749,8 +1750,8 @@ void mdnsd_stop(struct mdnsd *s) {
|
|||||||
while (s->stop_flag != 2)
|
while (s->stop_flag != 2)
|
||||||
select(0, NULL, NULL, NULL, &tv);
|
select(0, NULL, NULL, NULL, &tv);
|
||||||
|
|
||||||
close_pipe(s->notify_pipe[0]);
|
close_pipe(&s->notify_pipe[0]);
|
||||||
close_pipe(s->notify_pipe[1]);
|
close_pipe(&s->notify_pipe[1]);
|
||||||
|
|
||||||
pthread_mutex_destroy(&s->data_lock);
|
pthread_mutex_destroy(&s->data_lock);
|
||||||
rr_group_destroy(s->group);
|
rr_group_destroy(s->group);
|
||||||
|
|||||||
@@ -26,10 +26,11 @@
|
|||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include "buffered_read.h"
|
||||||
#include "buffered_read.h"
|
#include "common.h"
|
||||||
#include "common.h"
|
#include <pthread.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
ssize_t buffered_read(buffered_tcp_desc *descriptor, void *buf, size_t count,
|
ssize_t buffered_read(buffered_tcp_desc *descriptor, void *buf, size_t count,
|
||||||
size_t *bytes_remaining) {
|
size_t *bytes_remaining) {
|
||||||
@@ -110,8 +111,8 @@ void *buffered_tcp_reader(void *arg) {
|
|||||||
int finished = 0;
|
int finished = 0;
|
||||||
int fd = accept(descriptor->sock_fd, (struct sockaddr *)&remote_addr, &addr_size);
|
int fd = accept(descriptor->sock_fd, (struct sockaddr *)&remote_addr, &addr_size);
|
||||||
// debug(1, "buffered_tcp_reader: the client has opened a buffered audio link.");
|
// debug(1, "buffered_tcp_reader: the client has opened a buffered audio link.");
|
||||||
intptr_t pfd = fd;
|
// intptr_t pfd = fd;
|
||||||
pthread_cleanup_push(socket_cleanup, (void *)pfd);
|
pthread_cleanup_push(socket_cleanup, (void *)&fd);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
int have_time_to_sleep = 0;
|
int have_time_to_sleep = 0;
|
||||||
@@ -188,7 +189,7 @@ void *buffered_tcp_reader(void *arg) {
|
|||||||
// this will read a block of the size specified to the buffer
|
// this will read a block of the size specified to the buffer
|
||||||
// and will return either with the block or on error
|
// and will return either with the block or on error
|
||||||
ssize_t read_sized_block(buffered_tcp_desc *descriptor, void *buf, size_t count,
|
ssize_t read_sized_block(buffered_tcp_desc *descriptor, void *buf, size_t count,
|
||||||
size_t *bytes_remaining) {
|
size_t *bytes_remaining) {
|
||||||
ssize_t response, nread;
|
ssize_t response, nread;
|
||||||
size_t inbuf = 0; // bytes already in the buffer
|
size_t inbuf = 0; // bytes already in the buffer
|
||||||
int keep_trying = 1;
|
int keep_trying = 1;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#ifndef _BUFFERED_READ_H
|
#pragma once
|
||||||
#define _BUFFERED_READ_H
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int closed;
|
int closed;
|
||||||
@@ -19,6 +21,4 @@ void *buffered_tcp_reader(void *arg);
|
|||||||
|
|
||||||
// read the number of bytes specified by "count".
|
// read the number of bytes specified by "count".
|
||||||
ssize_t read_sized_block(buffered_tcp_desc *descriptor, void *buf, size_t count,
|
ssize_t read_sized_block(buffered_tcp_desc *descriptor, void *buf, size_t count,
|
||||||
size_t *bytes_remaining);
|
size_t *bytes_remaining);
|
||||||
|
|
||||||
#endif // _BUFFERED_READ_H
|
|
||||||
|
|||||||
+54
-56
@@ -228,7 +228,6 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
|
|||||||
pthread_setcancelstate(oldState, NULL);
|
pthread_setcancelstate(oldState, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *ibuf,
|
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *ibuf,
|
||||||
size_t buf_len) {
|
size_t buf_len) {
|
||||||
|
|
||||||
@@ -238,78 +237,77 @@ void _debug_print_buffer(const char *thefilename, const int linenumber, int leve
|
|||||||
char *vbuf = (char *)ibuf; // this to stop the compiler complaining about indexing through a void
|
char *vbuf = (char *)ibuf; // this to stop the compiler complaining about indexing through a void
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
unsigned int hexdump_cols = 16;
|
unsigned int hexdump_cols = 16;
|
||||||
|
|
||||||
// 0x123456: <hexdump_cols * 3> <hexdump_cols>\n
|
// 0x123456: <hexdump_cols * 3> <hexdump_cols>\n
|
||||||
const size_t buffer_size = 2 + 6 + 2 + hexdump_cols * 3 + hexdump_cols + 1;
|
const size_t buffer_size = 2 + 6 + 2 + hexdump_cols * 3 + hexdump_cols + 1;
|
||||||
char *buf = malloc(buffer_size);
|
char *buf = malloc(buffer_size);
|
||||||
if (buf) {
|
if (buf) {
|
||||||
//char *bufp = buf;
|
// char *bufp = buf;
|
||||||
// *buf = '\0';
|
// *buf = '\0';
|
||||||
|
|
||||||
// if (msg)
|
// if (msg)
|
||||||
// printf("%s", msg);
|
// printf("%s", msg);
|
||||||
|
|
||||||
#define ADDR_PREFIX_LEN 10 // "0x000000: "
|
|
||||||
|
|
||||||
// each complete row
|
#define ADDR_PREFIX_LEN 10 // "0x000000: "
|
||||||
for (i = 0; i < (buf_len / hexdump_cols); i++) {
|
|
||||||
char *bufp = buf;
|
// each complete row
|
||||||
*buf = '\0';
|
for (i = 0; i < (buf_len / hexdump_cols); i++) {
|
||||||
snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06x ", (i * hexdump_cols) & 0xFFFFFF);
|
char *bufp = buf;
|
||||||
bufp += ADDR_PREFIX_LEN;
|
*buf = '\0';
|
||||||
|
snprintf(bufp, ADDR_PREFIX_LEN + 1, "0x%06x ", (i * hexdump_cols) & 0xFFFFFF);
|
||||||
unsigned int j;
|
bufp += ADDR_PREFIX_LEN;
|
||||||
for (j = 0; j < hexdump_cols; j++) {
|
|
||||||
snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[i * hexdump_cols + j]);
|
unsigned int j;
|
||||||
bufp += strlen("12 ");
|
for (j = 0; j < hexdump_cols; j++) {
|
||||||
}
|
snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[i * hexdump_cols + j]);
|
||||||
|
bufp += strlen("12 ");
|
||||||
for (j = 0; j < hexdump_cols; j++) {
|
}
|
||||||
if (isprint(vbuf[i * hexdump_cols + j])) {
|
|
||||||
|
for (j = 0; j < hexdump_cols; j++) {
|
||||||
|
if (isprint(vbuf[i * hexdump_cols + j])) {
|
||||||
*bufp = 0xFF & vbuf[i * hexdump_cols + j];
|
*bufp = 0xFF & vbuf[i * hexdump_cols + j];
|
||||||
} else {
|
} else {
|
||||||
*bufp = '.';
|
*bufp = '.';
|
||||||
|
}
|
||||||
|
bufp++;
|
||||||
}
|
}
|
||||||
bufp++;
|
*bufp = '\0';
|
||||||
}
|
|
||||||
*bufp = '\0';
|
|
||||||
_debug(thefilename, linenumber, level, "%s", buf);
|
_debug(thefilename, linenumber, level, "%s", buf);
|
||||||
}
|
|
||||||
|
|
||||||
size_t remaining = buf_len % hexdump_cols;
|
|
||||||
if (remaining != 0) {
|
|
||||||
size_t starting_offset = buf_len - remaining;
|
|
||||||
char *bufp = buf;
|
|
||||||
*buf = '\0';
|
|
||||||
snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06zx ", starting_offset & 0xFFFFFF);
|
|
||||||
bufp += ADDR_PREFIX_LEN;
|
|
||||||
|
|
||||||
unsigned int j;
|
|
||||||
for (j = 0; j < hexdump_cols; j++) {
|
|
||||||
if (j < remaining) {
|
|
||||||
snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[starting_offset + j]);
|
|
||||||
} else {
|
|
||||||
snprintf(bufp, strlen(" ") + 1, " "); // fille with blanks
|
|
||||||
}
|
|
||||||
bufp += strlen("12 ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j = 0; j < remaining; j++) {
|
size_t remaining = buf_len % hexdump_cols;
|
||||||
if (isprint(vbuf[i * hexdump_cols + j])) {
|
if (remaining != 0) {
|
||||||
|
size_t starting_offset = buf_len - remaining;
|
||||||
|
char *bufp = buf;
|
||||||
|
*buf = '\0';
|
||||||
|
snprintf(bufp, ADDR_PREFIX_LEN + 1, "0x%06zx ", starting_offset & 0xFFFFFF);
|
||||||
|
bufp += ADDR_PREFIX_LEN;
|
||||||
|
|
||||||
|
unsigned int j;
|
||||||
|
for (j = 0; j < hexdump_cols; j++) {
|
||||||
|
if (j < remaining) {
|
||||||
|
snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[starting_offset + j]);
|
||||||
|
} else {
|
||||||
|
snprintf(bufp, strlen(" ") + 1, " "); // fille with blanks
|
||||||
|
}
|
||||||
|
bufp += strlen("12 ");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < remaining; j++) {
|
||||||
|
if (isprint(vbuf[i * hexdump_cols + j])) {
|
||||||
*bufp = 0xFF & vbuf[starting_offset + j];
|
*bufp = 0xFF & vbuf[starting_offset + j];
|
||||||
} else {
|
} else {
|
||||||
*bufp = '.';
|
*bufp = '.';
|
||||||
|
}
|
||||||
|
bufp++;
|
||||||
}
|
}
|
||||||
bufp++;
|
*bufp = '\0';
|
||||||
}
|
|
||||||
*bufp = '\0';
|
|
||||||
_debug(thefilename, linenumber, level, "%s", buf);
|
_debug(thefilename, linenumber, level, "%s", buf);
|
||||||
}
|
}
|
||||||
free(buf);
|
free(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf,
|
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf,
|
||||||
size_t buf_len) {
|
size_t buf_len) {
|
||||||
|
|||||||
+11
-6
@@ -58,12 +58,17 @@ EXTERNC void set_show_file_and_line(int setting);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Function declarations with printf-style format checking
|
// Function declarations with printf-style format checking
|
||||||
EXTERNC void _die(const char *filename, const int linenumber, const char *format, ...) PRINTF_LIKE(3,4);
|
EXTERNC void _die(const char *filename, const int linenumber, const char *format, ...)
|
||||||
EXTERNC void _warn(const char *filename, const int linenumber, const char *format, ...) PRINTF_LIKE(3,4);
|
PRINTF_LIKE(3, 4);
|
||||||
EXTERNC void _inform(const char *filename, const int linenumber, const char *format, ...) PRINTF_LIKE(3,4);
|
EXTERNC void _warn(const char *filename, const int linenumber, const char *format, ...)
|
||||||
EXTERNC void _debug(const char *filename, const int linenumber, int level, const char *format, ...) PRINTF_LIKE(4,5);
|
PRINTF_LIKE(3, 4);
|
||||||
EXTERNC void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *buf,
|
EXTERNC void _inform(const char *filename, const int linenumber, const char *format, ...)
|
||||||
size_t buf_len); // not printf-style, no change needed
|
PRINTF_LIKE(3, 4);
|
||||||
|
EXTERNC void _debug(const char *filename, const int linenumber, int level, const char *format, ...)
|
||||||
|
PRINTF_LIKE(4, 5);
|
||||||
|
EXTERNC void _debug_print_buffer(const char *thefilename, const int linenumber, int level,
|
||||||
|
void *buf,
|
||||||
|
size_t buf_len); // not printf-style, no change needed
|
||||||
#define die(...) _die(__FILE__, __LINE__, __VA_ARGS__)
|
#define die(...) _die(__FILE__, __LINE__, __VA_ARGS__)
|
||||||
#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__)
|
#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__)
|
||||||
#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__)
|
#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generate a device-related UUID. This file is part of Shairport Sync
|
* Generate a device-related UUID. This file is part of Shairport Sync
|
||||||
* Copyright (c) Mike Brady 2026
|
* Copyright (c) Mike Brady 2026
|
||||||
|
|
||||||
* Modifications, including those associated with audio synchronization, multithreading and
|
|
||||||
* metadata handling copyright (c) Mike Brady 2014--2025
|
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person
|
* Permission is hereby granted, free of charge, to any person
|
||||||
@@ -26,23 +23,23 @@
|
|||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <uuid/uuid.h>
|
#include <uuid/uuid.h>
|
||||||
|
|
||||||
#include "generate_device_uuid.h"
|
|
||||||
#include "definitions.h"
|
#include "definitions.h"
|
||||||
|
#include "generate_device_uuid.h"
|
||||||
|
|
||||||
// user is responsible for deallocating returned string
|
// user is responsible for deallocating returned string
|
||||||
char *generate_device_uuid(const char *device_id) {
|
char *generate_device_uuid(const char *device_id) {
|
||||||
uuid_t namespace_uuid;
|
uuid_t namespace_uuid;
|
||||||
uuid_t derived_uuid;
|
uuid_t derived_uuid;
|
||||||
|
|
||||||
uuid_parse(SHAIRPORT_SYNC_DEVICE_NAMESPACE, namespace_uuid);
|
uuid_parse(SHAIRPORT_SYNC_DEVICE_NAMESPACE, namespace_uuid);
|
||||||
uuid_generate_sha1(derived_uuid, namespace_uuid, device_id, strlen(device_id));
|
uuid_generate_sha1(derived_uuid, namespace_uuid, device_id, strlen(device_id));
|
||||||
|
|
||||||
char *uuid = malloc(UUID_STR_LEN + 1);
|
char *uuid = malloc(UUID_STR_LEN + 1);
|
||||||
uuid_unparse_lower(derived_uuid, uuid);
|
uuid_unparse_lower(derived_uuid, uuid);
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Generate a random UUID. This file is part of Shairport Sync
|
* Generate a random UUID. This file is part of Shairport Sync
|
||||||
* Copyright (c) Mike Brady 2026
|
* Copyright (c) Mike Brady 2026
|
||||||
|
|
||||||
* Modifications, including those associated with audio synchronization, multithreading and
|
|
||||||
* metadata handling copyright (c) Mike Brady 2014--2025
|
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person
|
* Permission is hereby granted, free of charge, to any person
|
||||||
@@ -26,10 +23,10 @@
|
|||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <uuid/uuid.h>
|
#include <uuid/uuid.h>
|
||||||
|
|
||||||
#include "generate_random_uuid.h"
|
#include "generate_random_uuid.h"
|
||||||
|
|
||||||
// user is responsible for deallocating returned string
|
// user is responsible for deallocating returned string
|
||||||
|
|||||||
+3
-6
@@ -1,9 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* 23-bit modular arithmetic. This file is part of Shairport Sync
|
* 23-bit modular arithmetic. This file is part of Shairport Sync
|
||||||
* Copyright (c) Mike Brady 2025
|
* Copyright (c) Mike Brady 2025
|
||||||
|
|
||||||
* Modifications, including those associated with audio synchronization, multithreading and
|
|
||||||
* metadata handling copyright (c) Mike Brady 2014--2025
|
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person
|
* Permission is hereby granted, free of charge, to any person
|
||||||
@@ -26,9 +23,9 @@
|
|||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdint.h>
|
#include "mod23.h"
|
||||||
#include "mod23.h"
|
#include <stdint.h>
|
||||||
|
|
||||||
#define MOD_23BIT 0x7FFFFF // 2^23 - 1
|
#define MOD_23BIT 0x7FFFFF // 2^23 - 1
|
||||||
|
|
||||||
|
|||||||
+3
-4
@@ -1,6 +1,5 @@
|
|||||||
#ifndef _MOD23_H
|
#pragma once
|
||||||
#define _MOD23_H
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
int32_t a_minus_b_mod23(uint32_t a, uint32_t b);
|
int32_t a_minus_b_mod23(uint32_t a, uint32_t b);
|
||||||
|
|
||||||
#endif // _MOD23_H
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Network Utilities. This file is part of Shairport Sync.
|
* Network Utilities. This file is part of Shairport Sync.
|
||||||
* Copyright (c) Mike Brady 2014--2025
|
* Copyright (c) Mike Brady 2026
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person
|
* Permission is hereby granted, free of charge, to any person
|
||||||
@@ -24,24 +24,47 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "network_utilities.h"
|
#include "network_utilities.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
int eintr_checked_accept(int sockfd, struct sockaddr *addr,
|
int eintr_checked_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
|
||||||
socklen_t *addrlen) {
|
|
||||||
int response;
|
int response;
|
||||||
do {
|
do {
|
||||||
response = accept(sockfd, addr, addrlen);
|
response = accept(sockfd, addr, addrlen);
|
||||||
|
|
||||||
if (response == -1) {
|
if (response == -1) {
|
||||||
char errorstring[1024];
|
char errorstring[1024];
|
||||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||||
debug(1,
|
debug(1, "error %d accept()ing a socket %d: \"%s\". (Note: error %d will be ignored.)", errno,
|
||||||
"error %d accept()ing a socket %d: \"%s\". (Note: error %d will be ignored.)",
|
sockfd, errorstring, EINTR);
|
||||||
errno, sockfd, errorstring, EINTR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} while((response == -1) && (errno == EINTR));
|
} while ((response == -1) && (errno == EINTR));
|
||||||
return response;
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_t safe_socket_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
int _safe_socket_close(const char *filename, const int linenumber, int *sockfd) {
|
||||||
|
int result = 0;
|
||||||
|
int oldstate;
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
|
||||||
|
debug_mutex_lock(&safe_socket_lock, 1000000, 1);
|
||||||
|
if (*sockfd == 0) {
|
||||||
|
_debug(filename, linenumber, 1, "_safe_socket_close: socket is zero!");
|
||||||
|
}
|
||||||
|
if ((*sockfd != -1) && (*sockfd != 0)) {
|
||||||
|
_debug(filename, linenumber, 4, "_safe_socket_close: closing socket %d.", *sockfd);
|
||||||
|
result = close(*sockfd);
|
||||||
|
if (result == 0)
|
||||||
|
*sockfd = -1;
|
||||||
|
} else {
|
||||||
|
_debug(filename, linenumber, 1, "_safe_socket_close: socket already closed!");
|
||||||
|
}
|
||||||
|
debug_mutex_unlock(&safe_socket_lock, 4);
|
||||||
|
pthread_setcancelstate(oldstate, NULL);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#ifndef _NETWORK_UTILITIES_H
|
#pragma once
|
||||||
#define _NETWORK_UTILITIES_H
|
|
||||||
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
|
||||||
#define restrict
|
#define restrict
|
||||||
|
int eintr_checked_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
||||||
|
|
||||||
int eintr_checked_accept(int sockfd, struct sockaddr *addr,
|
int _safe_socket_close(const char *filename, const int linenumber, int *sockfd);
|
||||||
socklen_t *addrlen);
|
|
||||||
#endif // _NETWORK_UTILITIES_H
|
#define safe_socket_close(sockfd) _safe_socket_close(__FILE__, __LINE__, sockfd)
|
||||||
|
|||||||
@@ -1,9 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* RTSP Message Utilities. This file is part of Shairport Sync.
|
||||||
|
* Copyright (c) Mike Brady 2026
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rtsp_message_utilities.h"
|
||||||
|
#include "../common.h"
|
||||||
|
#include "../rtsp.h"
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "rtsp_message_utilities.h"
|
|
||||||
#include "../rtsp.h"
|
|
||||||
#include "../common.h"
|
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
#include <plist/plist.h>
|
#include <plist/plist.h>
|
||||||
@@ -80,15 +106,16 @@ char *msg_get_header(rtsp_message *msg, char *name) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
|
void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber,
|
||||||
rtsp_message *msg) {
|
int level, rtsp_message *msg) {
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
if (conn != NULL) {
|
if (conn != NULL) {
|
||||||
if (msg->respcode != 0)
|
if (msg->respcode != 0)
|
||||||
_debug(filename, linenumber, level, "Connection: %d, Response Code: %d.", conn->connection_number, msg->respcode);
|
_debug(filename, linenumber, level, "Connection: %d, Response Code: %d.",
|
||||||
|
conn->connection_number, msg->respcode);
|
||||||
for (i = 0; i < msg->nheaders; i++) {
|
for (i = 0; i < msg->nheaders; i++) {
|
||||||
_debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"", conn->connection_number, msg->name[i],
|
_debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"",
|
||||||
msg->value[i]);
|
conn->connection_number, msg->name[i], msg->value[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (msg->respcode != 0)
|
if (msg->respcode != 0)
|
||||||
@@ -243,8 +270,8 @@ char *rtsp_plist_content(rtsp_message *message) {
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
|
void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber,
|
||||||
rtsp_message *message) {
|
int level, char *prompt, rtsp_message *message) {
|
||||||
if (level > debug_level())
|
if (level > debug_level())
|
||||||
return;
|
return;
|
||||||
if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list...
|
if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list...
|
||||||
@@ -253,13 +280,13 @@ void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const i
|
|||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
char *plist_content = rtsp_plist_content(message);
|
char *plist_content = rtsp_plist_content(message);
|
||||||
if (plist_content) {
|
if (plist_content) {
|
||||||
_debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--", message->contentlength, plist_content);
|
_debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--",
|
||||||
|
message->contentlength, plist_content);
|
||||||
free(plist_content);
|
free(plist_content);
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
_debug(filename, linenumber, level, " Content length: %u.",
|
_debug(filename, linenumber, level, " Content length: %u.", message->contentlength);
|
||||||
message->contentlength);
|
|
||||||
if (message->contentlength > 0) {
|
if (message->contentlength > 0) {
|
||||||
_debug_print_buffer(filename, linenumber, level, message->content, message->contentlength);
|
_debug_print_buffer(filename, linenumber, level, message->content, message->contentlength);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ int msg_handle_line(rtsp_message **pmsg, char *line);
|
|||||||
int msg_add_header(rtsp_message *msg, char *name, char *value);
|
int msg_add_header(rtsp_message *msg, char *name, char *value);
|
||||||
char *msg_get_header(rtsp_message *msg, char *name);
|
char *msg_get_header(rtsp_message *msg, char *name);
|
||||||
|
|
||||||
void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
|
void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber,
|
||||||
rtsp_message *message);
|
int level, char *prompt, rtsp_message *message);
|
||||||
|
|
||||||
#define debug_log_rtsp_message_conn(conn, level, prompt, message) \
|
#define debug_log_rtsp_message_conn(conn, level, prompt, message) \
|
||||||
_debug_log_rtsp_message(conn, __FILE__, __LINE__, level, prompt, message)
|
_debug_log_rtsp_message(conn, __FILE__, __LINE__, level, prompt, message)
|
||||||
@@ -17,16 +17,14 @@ void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const i
|
|||||||
#define debug_log_rtsp_message(level, prompt, message) \
|
#define debug_log_rtsp_message(level, prompt, message) \
|
||||||
_debug_log_rtsp_message(NULL, __FILE__, __LINE__, level, prompt, message)
|
_debug_log_rtsp_message(NULL, __FILE__, __LINE__, level, prompt, message)
|
||||||
|
|
||||||
|
void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber,
|
||||||
void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
|
int level, rtsp_message *msg);
|
||||||
rtsp_message *msg);
|
|
||||||
#define debug_print_msg_headers(level, message) \
|
#define debug_print_msg_headers(level, message) \
|
||||||
_debug_print_msg_headers(NULL, __FILE__, __LINE__, level, message)
|
_debug_print_msg_headers(NULL, __FILE__, __LINE__, level, message)
|
||||||
|
|
||||||
#define debug_print_msg_headers_conn(level, message) \
|
#define debug_print_msg_headers_conn(level, message) \
|
||||||
_debug_print_msg_headers(conn, __FILE__, __LINE__, level, message)
|
_debug_print_msg_headers(conn, __FILE__, __LINE__, level, message)
|
||||||
|
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
int rtsp_message_contains_plist(rtsp_message *message);
|
int rtsp_message_contains_plist(rtsp_message *message);
|
||||||
plist_t plist_from_rtsp_content(rtsp_message *message);
|
plist_t plist_from_rtsp_content(rtsp_message *message);
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* Some character string utilities. This file is part of Shairport Sync
|
||||||
|
* Copyright (c) Mike Brady 2026
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "string_utilities.h"
|
||||||
|
|
||||||
|
// mDNS supports maximum of 63-character names (we prepend 13, so only 50 left).
|
||||||
|
#define MAX_AIRPLAY_SERVICE_NAME_LENGTH 50
|
||||||
|
|
||||||
|
/* from
|
||||||
|
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
|
||||||
|
* with thanks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* append_truncated - Append `suffix` to `base`, returning a newly malloc'd
|
||||||
|
* string. If the combined result would exceed `limit` bytes (excluding the
|
||||||
|
* NUL terminator), the base is truncated so that the output is:
|
||||||
|
*
|
||||||
|
* <truncated base>...<suffix>
|
||||||
|
*
|
||||||
|
* The returned string is always NUL-terminated and never exceeds `limit`
|
||||||
|
* bytes of content. The caller is responsible for free()'ing it.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* base - the primary string
|
||||||
|
* suffix - the string to append
|
||||||
|
* limit - maximum byte count of the resulting string (excl. NUL)
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* A newly malloc'd string on success, or NULL on failure (limit too small
|
||||||
|
* to fit "..." + suffix, or malloc failure).
|
||||||
|
*/
|
||||||
|
char *append_truncated(const char *base, const char *suffix, size_t limit) {
|
||||||
|
|
||||||
|
const char *ellipsis = "...";
|
||||||
|
size_t ellipsis_len = strlen(ellipsis);
|
||||||
|
size_t suffix_len = strlen(suffix);
|
||||||
|
size_t base_len = strlen(base);
|
||||||
|
size_t combined_len = base_len + suffix_len;
|
||||||
|
size_t result_len;
|
||||||
|
size_t head_len;
|
||||||
|
char *out;
|
||||||
|
|
||||||
|
if (combined_len <= limit) {
|
||||||
|
/* Fits without truncation. */
|
||||||
|
result_len = combined_len;
|
||||||
|
out = malloc(result_len + 1);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
memcpy(out, base, base_len);
|
||||||
|
memcpy(out + base_len, suffix, suffix_len + 1); /* +1 for NUL */
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Not enough room to fit even "..." + suffix. */
|
||||||
|
if (limit < ellipsis_len + suffix_len)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
head_len = limit - ellipsis_len - suffix_len;
|
||||||
|
|
||||||
|
/* Respect UTF-8: don't cut in the middle of a multibyte sequence.
|
||||||
|
* Walk back from head_len until we're at a character boundary
|
||||||
|
* (i.e. a byte that is not a UTF-8 continuation byte 0x80–0xBF). */
|
||||||
|
while (head_len > 0 && (base[head_len] & 0xC0) == 0x80)
|
||||||
|
head_len--;
|
||||||
|
|
||||||
|
result_len = head_len + ellipsis_len + suffix_len;
|
||||||
|
out = malloc(result_len + 1);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char *p = out;
|
||||||
|
memcpy(p, base, head_len);
|
||||||
|
p += head_len;
|
||||||
|
memcpy(p, ellipsis, ellipsis_len);
|
||||||
|
p += ellipsis_len;
|
||||||
|
memcpy(p, suffix, suffix_len);
|
||||||
|
p += suffix_len;
|
||||||
|
*p = '\0';
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *service_name(const char *raw_service_name) {
|
||||||
|
char *response = NULL;
|
||||||
|
// now, do the substitutions in the service name
|
||||||
|
char hostname[256];
|
||||||
|
gethostname(hostname, sizeof(hostname));
|
||||||
|
// strip off a terminating .<anything>, e.g. .local from the hostname
|
||||||
|
char *last_dot = strrchr(hostname, '.');
|
||||||
|
if (last_dot != NULL)
|
||||||
|
*last_dot = '\0';
|
||||||
|
|
||||||
|
char *i0;
|
||||||
|
if (raw_service_name == NULL)
|
||||||
|
i0 = strdup("%H"); // default
|
||||||
|
else
|
||||||
|
i0 = strdup(raw_service_name); // this is the string provided in the configuration or on the
|
||||||
|
// command line.
|
||||||
|
// here, do the substitutions for %h, %H, %v and %V
|
||||||
|
char *i1 = str_replace(i0, "%h", hostname);
|
||||||
|
if ((hostname[0] >= 'a') && (hostname[0] <= 'z'))
|
||||||
|
hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter
|
||||||
|
char *i2 = str_replace(i1, "%H", hostname);
|
||||||
|
char *i3 = str_replace(i2, "%v", PACKAGE_VERSION);
|
||||||
|
char *vs = get_version_string();
|
||||||
|
char *i4 = str_replace(i3, "%V", vs); // service name complete
|
||||||
|
// now, we may need to add "(Classic)" and/or truncate it to MAX_AIRPLAY_SERVICE_NAME_LENGTH
|
||||||
|
// characters.
|
||||||
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
if ((raw_service_name == NULL) && (config.service_type == APST_forced_classic)) {
|
||||||
|
response = append_truncated(
|
||||||
|
i4, " (Classic)", MAX_AIRPLAY_SERVICE_NAME_LENGTH); // append "(Classic)" to default service
|
||||||
|
// name if forced to Classic
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
|
response = append_truncated(
|
||||||
|
i4, "",
|
||||||
|
MAX_AIRPLAY_SERVICE_NAME_LENGTH); // make sure it doesn't exceed the max length: 63 - 13
|
||||||
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
free(i0);
|
||||||
|
free(i1);
|
||||||
|
free(i2);
|
||||||
|
free(i3);
|
||||||
|
free(i4);
|
||||||
|
free(vs);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* 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 *service_name(const char *raw_service_name);
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Structured Buffer. This file is part of Shairport Sync
|
* Structured Buffer. This file is part of Shairport Sync
|
||||||
* Copyright (c) Mike Brady 2025
|
* Copyright (c) Mike Brady 2025
|
||||||
|
|
||||||
* Modifications, including those associated with audio synchronization, multithreading and
|
|
||||||
* metadata handling copyright (c) Mike Brady 2014--2025
|
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person
|
* Permission is hereby granted, free of charge, to any person
|
||||||
@@ -27,12 +24,11 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *buf;
|
char *buf;
|
||||||
|
|||||||
Reference in New Issue
Block a user