mirror of
https://github.com/badaix/snapcast.git
synced 2025-06-18 02:31:44 +02:00
Add documentation
This commit is contained in:
parent
f0985cbce4
commit
76e35e3e18
34 changed files with 199 additions and 78 deletions
|
@ -153,6 +153,7 @@ public:
|
||||||
virtual void getNextMessage(const MessageHandler<msg::BaseMessage>& handler) = 0;
|
virtual void getNextMessage(const MessageHandler<msg::BaseMessage>& handler) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/// Send @p buffer, return result in @p write_handler
|
||||||
virtual void write(boost::asio::streambuf& buffer, WriteHandler&& write_handler) = 0;
|
virtual void write(boost::asio::streambuf& buffer, WriteHandler&& write_handler) = 0;
|
||||||
|
|
||||||
/// Connect to @p endpoint
|
/// Connect to @p endpoint
|
||||||
|
|
|
@ -129,7 +129,7 @@ Controller::Controller(boost::asio::io_context& io_context, const ClientSettings
|
||||||
#endif // HAS_OPENSSL
|
#endif // HAS_OPENSSL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to create a player instance
|
||||||
template <typename PlayerType>
|
template <typename PlayerType>
|
||||||
std::unique_ptr<Player> Controller::createPlayer(ClientSettings::Player& settings, const std::string& player_name)
|
std::unique_ptr<Player> Controller::createPlayer(ClientSettings::Player& settings, const std::string& player_name)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,8 +46,8 @@ struct riff_wave_header
|
||||||
/// See https://en.wikipedia.org/wiki/WAV
|
/// See https://en.wikipedia.org/wiki/WAV
|
||||||
struct chunk_header
|
struct chunk_header
|
||||||
{
|
{
|
||||||
uint32_t id;
|
uint32_t id; ///< id
|
||||||
uint32_t sz;
|
uint32_t sz; ///< size
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,12 +55,12 @@ struct chunk_header
|
||||||
/// See https://en.wikipedia.org/wiki/WAV
|
/// See https://en.wikipedia.org/wiki/WAV
|
||||||
struct chunk_fmt
|
struct chunk_fmt
|
||||||
{
|
{
|
||||||
uint16_t audio_format; ///<
|
uint16_t audio_format; ///< format
|
||||||
uint16_t num_channels;
|
uint16_t num_channels; ///< channels
|
||||||
uint32_t sample_rate;
|
uint32_t sample_rate; ///< sample rate
|
||||||
uint32_t byte_rate;
|
uint32_t byte_rate; ///< byte rate
|
||||||
uint16_t block_align;
|
uint16_t block_align; ///< block align
|
||||||
uint16_t bits_per_sample;
|
uint16_t bits_per_sample; ///< bps
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2024 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -36,10 +36,12 @@ template <class T>
|
||||||
class DoubleBuffer
|
class DoubleBuffer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DoubleBuffer(size_t size = 10) : bufferSize(size)
|
/// c'tor
|
||||||
|
explicit DoubleBuffer(size_t size = 10) : bufferSize(size)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add @p element, pop last, if buffer is full
|
||||||
inline void add(const T& element)
|
inline void add(const T& element)
|
||||||
{
|
{
|
||||||
buffer.push_back(element);
|
buffer.push_back(element);
|
||||||
|
@ -47,6 +49,7 @@ public:
|
||||||
buffer.pop_front();
|
buffer.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add @p element, pop last, if buffer is full
|
||||||
inline void add(T&& element)
|
inline void add(T&& element)
|
||||||
{
|
{
|
||||||
buffer.push_back(std::move(element));
|
buffer.push_back(std::move(element));
|
||||||
|
@ -54,7 +57,7 @@ public:
|
||||||
buffer.pop_front();
|
buffer.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Median as mean over N values around the median
|
/// @return median as mean over N values around the median
|
||||||
T median(uint16_t mean = 1) const
|
T median(uint16_t mean = 1) const
|
||||||
{
|
{
|
||||||
if (buffer.empty())
|
if (buffer.empty())
|
||||||
|
@ -78,6 +81,7 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return mean value
|
||||||
double mean() const
|
double mean() const
|
||||||
{
|
{
|
||||||
if (buffer.empty())
|
if (buffer.empty())
|
||||||
|
@ -88,6 +92,7 @@ public:
|
||||||
return mean;
|
return mean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return @p percentile percentile
|
||||||
T percentile(unsigned int percentile) const
|
T percentile(unsigned int percentile) const
|
||||||
{
|
{
|
||||||
if (buffer.empty())
|
if (buffer.empty())
|
||||||
|
@ -97,6 +102,7 @@ public:
|
||||||
return tmpBuffer[(size_t)((tmpBuffer.size() - 1) * ((float)percentile / (float)100))];
|
return tmpBuffer[(size_t)((tmpBuffer.size() - 1) * ((float)percentile / (float)100))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return array of different percentiles
|
||||||
template <std::size_t Size>
|
template <std::size_t Size>
|
||||||
std::array<T, Size> percentiles(std::array<uint8_t, Size> percentiles) const
|
std::array<T, Size> percentiles(std::array<uint8_t, Size> percentiles) const
|
||||||
{
|
{
|
||||||
|
@ -112,31 +118,37 @@ public:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return if the buffer is full
|
||||||
inline bool full() const
|
inline bool full() const
|
||||||
{
|
{
|
||||||
return (buffer.size() == bufferSize);
|
return (buffer.size() == bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear the buffer
|
||||||
inline void clear()
|
inline void clear()
|
||||||
{
|
{
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return current size of the buffer
|
||||||
inline size_t size() const
|
inline size_t size() const
|
||||||
{
|
{
|
||||||
return buffer.size();
|
return buffer.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return if the buffer is empty
|
||||||
inline bool empty() const
|
inline bool empty() const
|
||||||
{
|
{
|
||||||
return buffer.empty();
|
return buffer.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set size of the buffer
|
||||||
void setSize(size_t size)
|
void setSize(size_t size)
|
||||||
{
|
{
|
||||||
bufferSize = size;
|
bufferSize = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return the raw buffer
|
||||||
const std::deque<T>& getBuffer() const
|
const std::deque<T>& getBuffer() const
|
||||||
{
|
{
|
||||||
return buffer;
|
return buffer;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2024 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -44,7 +44,9 @@ static constexpr auto ALSA = "alsa";
|
||||||
class AlsaPlayer : public Player
|
class AlsaPlayer : public Player
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
||||||
|
/// d'tor
|
||||||
~AlsaPlayer() override;
|
~AlsaPlayer() override;
|
||||||
|
|
||||||
void start() override;
|
void start() override;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2022 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,8 +16,8 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#ifndef CORE_AUDIO_PLAYER_HPP
|
|
||||||
#define CORE_AUDIO_PLAYER_HPP
|
#pragma once
|
||||||
|
|
||||||
// local headers
|
// local headers
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
|
@ -45,13 +45,17 @@ static constexpr auto COREAUDIO = "coreaudio";
|
||||||
class CoreAudioPlayer : public Player
|
class CoreAudioPlayer : public Player
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
CoreAudioPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
CoreAudioPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
||||||
|
/// d'tor
|
||||||
virtual ~CoreAudioPlayer();
|
virtual ~CoreAudioPlayer();
|
||||||
|
|
||||||
|
/// Callback funtion for audio data to be played
|
||||||
void playerCallback(AudioQueueRef queue, AudioQueueBufferRef bufferRef);
|
void playerCallback(AudioQueueRef queue, AudioQueueBufferRef bufferRef);
|
||||||
|
/// @return list of available pcm devices
|
||||||
static std::vector<PcmDevice> pcm_list();
|
static std::vector<PcmDevice> pcm_list();
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
void worker() override;
|
void worker() override;
|
||||||
bool needsThread() const override;
|
bool needsThread() const override;
|
||||||
|
|
||||||
|
@ -67,5 +71,3 @@ protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace player
|
} // namespace player
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2022 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,8 +16,9 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#ifndef FILE_PLAYER_HPP
|
|
||||||
#define FILE_PLAYER_HPP
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
// local headers
|
// local headers
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
|
@ -40,8 +41,10 @@ static constexpr auto FILE = "file";
|
||||||
class FilePlayer : public Player
|
class FilePlayer : public Player
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
FilePlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
FilePlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
||||||
virtual ~FilePlayer();
|
/// d'tor
|
||||||
|
~FilePlayer() override;
|
||||||
|
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
@ -49,7 +52,7 @@ public:
|
||||||
/// List the dummy file PCM device
|
/// List the dummy file PCM device
|
||||||
static std::vector<PcmDevice> pcm_list(const std::string& parameter);
|
static std::vector<PcmDevice> pcm_list(const std::string& parameter);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
void requestAudio();
|
void requestAudio();
|
||||||
void loop();
|
void loop();
|
||||||
bool needsThread() const override;
|
bool needsThread() const override;
|
||||||
|
@ -60,5 +63,3 @@ protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace player
|
} // namespace player
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2022 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,8 +16,8 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#ifndef OPEN_SL_PLAYER_HPP
|
#pragma once
|
||||||
#define OPEN_SL_PLAYER_HPP
|
|
||||||
|
|
||||||
// local headers
|
// local headers
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
|
@ -35,7 +35,9 @@ namespace player
|
||||||
|
|
||||||
static constexpr auto OPENSL = "opensl";
|
static constexpr auto OPENSL = "opensl";
|
||||||
|
|
||||||
typedef int (*AndroidAudioCallback)(short* buffer, int num_samples);
|
|
||||||
|
// typedef int (*AndroidAudioCallback)(short* buffer, int num_samples);
|
||||||
|
using AndroidAudioCallback = int (*)(short*, int);
|
||||||
|
|
||||||
|
|
||||||
/// OpenSL Audio Player
|
/// OpenSL Audio Player
|
||||||
|
@ -45,15 +47,18 @@ typedef int (*AndroidAudioCallback)(short* buffer, int num_samples);
|
||||||
class OpenslPlayer : public Player
|
class OpenslPlayer : public Player
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
OpenslPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
OpenslPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
||||||
virtual ~OpenslPlayer();
|
/// d'tor
|
||||||
|
~OpenslPlayer() override;
|
||||||
|
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
|
/// Callback to feed data to the player API
|
||||||
void playerCallback(SLAndroidSimpleBufferQueueItf bq);
|
void playerCallback(SLAndroidSimpleBufferQueueItf bq);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
void initOpensl();
|
void initOpensl();
|
||||||
void uninitOpensl();
|
void uninitOpensl();
|
||||||
|
|
||||||
|
@ -83,5 +88,3 @@ protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace player
|
} // namespace player
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ public:
|
||||||
bool mute{false}; ///< muted?
|
bool mute{false}; ///< muted?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Callback for volume changes on the local client, to notify the server
|
||||||
using volume_callback = std::function<void(const Volume& volume)>;
|
using volume_callback = std::function<void(const Volume& volume)>;
|
||||||
|
|
||||||
/// c'tor
|
/// c'tor
|
||||||
|
@ -89,7 +90,6 @@ protected:
|
||||||
|
|
||||||
/// set the hardware mixer volume
|
/// set the hardware mixer volume
|
||||||
/// @param volume the volume on range [0..1], muted or not
|
/// @param volume the volume on range [0..1], muted or not
|
||||||
/// @param muted muted or not
|
|
||||||
virtual void setHardwareVolume(const Volume& volume);
|
virtual void setHardwareVolume(const Volume& volume);
|
||||||
|
|
||||||
/// set volume polynomial: volume^exp
|
/// set volume polynomial: volume^exp
|
||||||
|
@ -102,7 +102,6 @@ protected:
|
||||||
|
|
||||||
/// Notify the server about hardware volume changes
|
/// Notify the server about hardware volume changes
|
||||||
/// @param volume the volume in range [0..1]
|
/// @param volume the volume in range [0..1]
|
||||||
/// @param muted if muted or not
|
|
||||||
void notifyVolumeChange(const Volume& volume) const
|
void notifyVolumeChange(const Volume& volume) const
|
||||||
{
|
{
|
||||||
if (onVolumeChanged_)
|
if (onVolumeChanged_)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2024 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -42,7 +42,9 @@ static constexpr auto PULSE = "pulse";
|
||||||
class PulsePlayer : public Player
|
class PulsePlayer : public Player
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
PulsePlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
PulsePlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
||||||
|
/// d'tor
|
||||||
virtual ~PulsePlayer();
|
virtual ~PulsePlayer();
|
||||||
|
|
||||||
void start() override;
|
void start() override;
|
||||||
|
@ -51,7 +53,7 @@ public:
|
||||||
/// List the system's audio output devices
|
/// List the system's audio output devices
|
||||||
static std::vector<PcmDevice> pcm_list(const std::string& parameter);
|
static std::vector<PcmDevice> pcm_list(const std::string& parameter);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
bool needsThread() const override;
|
bool needsThread() const override;
|
||||||
void worker() override;
|
void worker() override;
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ static constexpr auto LOG_TAG = "WASAPI";
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct COMMemDeleter
|
struct COMMemDeleter
|
||||||
{
|
{
|
||||||
|
/// Operator()
|
||||||
void operator()(T* obj)
|
void operator()(T* obj)
|
||||||
{
|
{
|
||||||
if (obj != NULL)
|
if (obj != NULL)
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#ifndef WASAPI_PLAYER_HPP
|
#pragma once
|
||||||
#define WASAPI_PLAYER_HPP
|
|
||||||
|
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable : 4100)
|
#pragma warning(disable : 4100)
|
||||||
|
@ -42,31 +41,37 @@ class AudioSessionEventListener : public IAudioSessionEvents
|
||||||
bool muted_ = false;
|
bool muted_ = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
AudioSessionEventListener() : _cRef(1)
|
AudioSessionEventListener() : _cRef(1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return volume
|
||||||
float getVolume()
|
float getVolume()
|
||||||
{
|
{
|
||||||
return volume_;
|
return volume_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return if muted
|
||||||
bool getMuted()
|
bool getMuted()
|
||||||
{
|
{
|
||||||
return muted_;
|
return muted_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// d'tor
|
||||||
~AudioSessionEventListener()
|
~AudioSessionEventListener()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// IUnknown methods -- AddRef, Release, and QueryInterface
|
// IUnknown methods -- AddRef, Release, and QueryInterface
|
||||||
|
|
||||||
|
/// documentation missing
|
||||||
ULONG STDMETHODCALLTYPE AddRef()
|
ULONG STDMETHODCALLTYPE AddRef()
|
||||||
{
|
{
|
||||||
return InterlockedIncrement(&_cRef);
|
return InterlockedIncrement(&_cRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// documentation missing
|
||||||
ULONG STDMETHODCALLTYPE Release()
|
ULONG STDMETHODCALLTYPE Release()
|
||||||
{
|
{
|
||||||
ULONG ulRef = InterlockedDecrement(&_cRef);
|
ULONG ulRef = InterlockedDecrement(&_cRef);
|
||||||
|
@ -77,34 +82,42 @@ public:
|
||||||
return ulRef;
|
return ulRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// documentation missing
|
||||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface);
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface);
|
||||||
|
|
||||||
// Notification methods for audio session events
|
// Notification methods for audio session events
|
||||||
|
|
||||||
|
/// OnDisplayNameChanged callback
|
||||||
HRESULT STDMETHODCALLTYPE OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext)
|
HRESULT STDMETHODCALLTYPE OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext)
|
||||||
{
|
{
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OnIconPathChanged callback
|
||||||
HRESULT STDMETHODCALLTYPE OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContext)
|
HRESULT STDMETHODCALLTYPE OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContext)
|
||||||
{
|
{
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OnSimpleVolumeChanged callback
|
||||||
HRESULT STDMETHODCALLTYPE OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext);
|
HRESULT STDMETHODCALLTYPE OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext);
|
||||||
|
|
||||||
|
/// OnChannelVolumeChanged callback
|
||||||
HRESULT STDMETHODCALLTYPE OnChannelVolumeChanged(DWORD ChannelCount, float NewChannelVolumeArray[], DWORD ChangedChannel, LPCGUID EventContext)
|
HRESULT STDMETHODCALLTYPE OnChannelVolumeChanged(DWORD ChannelCount, float NewChannelVolumeArray[], DWORD ChangedChannel, LPCGUID EventContext)
|
||||||
{
|
{
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OnGroupingParamChanged callback
|
||||||
HRESULT STDMETHODCALLTYPE OnGroupingParamChanged(LPCGUID NewGroupingParam, LPCGUID EventContext)
|
HRESULT STDMETHODCALLTYPE OnGroupingParamChanged(LPCGUID NewGroupingParam, LPCGUID EventContext)
|
||||||
{
|
{
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OnStateChanged callback
|
||||||
HRESULT STDMETHODCALLTYPE OnStateChanged(AudioSessionState NewState);
|
HRESULT STDMETHODCALLTYPE OnStateChanged(AudioSessionState NewState);
|
||||||
|
|
||||||
|
/// OnSessionDisconnected callback
|
||||||
HRESULT STDMETHODCALLTYPE OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason);
|
HRESULT STDMETHODCALLTYPE OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,19 +130,23 @@ class AudioEndpointVolumeCallback : public IAudioEndpointVolumeCallback
|
||||||
bool muted_ = false;
|
bool muted_ = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
AudioEndpointVolumeCallback() : _cRef(1)
|
AudioEndpointVolumeCallback() : _cRef(1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// d'tor
|
||||||
~AudioEndpointVolumeCallback()
|
~AudioEndpointVolumeCallback()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return volume
|
||||||
float getVolume()
|
float getVolume()
|
||||||
{
|
{
|
||||||
return volume_;
|
return volume_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return if muted
|
||||||
bool getMuted()
|
bool getMuted()
|
||||||
{
|
{
|
||||||
return muted_;
|
return muted_;
|
||||||
|
@ -137,11 +154,13 @@ public:
|
||||||
|
|
||||||
// IUnknown methods -- AddRef, Release, and QueryInterface
|
// IUnknown methods -- AddRef, Release, and QueryInterface
|
||||||
|
|
||||||
|
/// documentation missing
|
||||||
ULONG STDMETHODCALLTYPE AddRef()
|
ULONG STDMETHODCALLTYPE AddRef()
|
||||||
{
|
{
|
||||||
return InterlockedIncrement(&_cRef);
|
return InterlockedIncrement(&_cRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// documentation missing
|
||||||
ULONG STDMETHODCALLTYPE Release()
|
ULONG STDMETHODCALLTYPE Release()
|
||||||
{
|
{
|
||||||
ULONG ulRef = InterlockedDecrement(&_cRef);
|
ULONG ulRef = InterlockedDecrement(&_cRef);
|
||||||
|
@ -152,6 +171,7 @@ public:
|
||||||
return ulRef;
|
return ulRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// documentation missing
|
||||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface)
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface)
|
||||||
{
|
{
|
||||||
if (IID_IUnknown == riid)
|
if (IID_IUnknown == riid)
|
||||||
|
@ -175,6 +195,7 @@ public:
|
||||||
|
|
||||||
// Callback method for endpoint-volume-change notifications.
|
// Callback method for endpoint-volume-change notifications.
|
||||||
|
|
||||||
|
/// documentation missing
|
||||||
HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify);
|
HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -184,14 +205,17 @@ static constexpr auto WASAPI = "wasapi";
|
||||||
class WASAPIPlayer : public Player
|
class WASAPIPlayer : public Player
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
WASAPIPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
WASAPIPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
|
||||||
|
/// d'tor
|
||||||
virtual ~WASAPIPlayer();
|
virtual ~WASAPIPlayer();
|
||||||
|
|
||||||
|
/// @return list of available PCM devices
|
||||||
static std::vector<PcmDevice> pcm_list();
|
static std::vector<PcmDevice> pcm_list();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void worker();
|
void worker() override;
|
||||||
virtual bool needsThread() const override
|
bool needsThread() const override
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -206,5 +230,3 @@ private:
|
||||||
#pragma warning(pop)
|
#pragma warning(pop)
|
||||||
|
|
||||||
} // namespace player
|
} // namespace player
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -45,11 +45,14 @@
|
||||||
class Stream
|
class Stream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
Stream(const SampleFormat& in_format, const SampleFormat& out_format);
|
Stream(const SampleFormat& in_format, const SampleFormat& out_format);
|
||||||
|
/// d'tor
|
||||||
virtual ~Stream() = default;
|
virtual ~Stream() = default;
|
||||||
|
|
||||||
/// Adds PCM data to the queue
|
/// Adds PCM data to the queue
|
||||||
void addChunk(std::unique_ptr<msg::PcmChunk> chunk);
|
void addChunk(std::unique_ptr<msg::PcmChunk> chunk);
|
||||||
|
/// Remove all chunks from the queue
|
||||||
void clearChunks();
|
void clearChunks();
|
||||||
|
|
||||||
/// Get PCM data, which will be played out in "outputBufferDacTime" time
|
/// Get PCM data, which will be played out in "outputBufferDacTime" time
|
||||||
|
@ -68,11 +71,13 @@ public:
|
||||||
/// "Server buffer": playout latency, e.g. 1000ms
|
/// "Server buffer": playout latency, e.g. 1000ms
|
||||||
void setBufferLen(size_t bufferLenMs);
|
void setBufferLen(size_t bufferLenMs);
|
||||||
|
|
||||||
|
/// @return sampleformat
|
||||||
const SampleFormat& getFormat() const
|
const SampleFormat& getFormat() const
|
||||||
{
|
{
|
||||||
return format_;
|
return format_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return if chunk was avabilable within @p timeout
|
||||||
bool waitForChunk(const std::chrono::milliseconds& timeout) const;
|
bool waitForChunk(const std::chrono::milliseconds& timeout) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2023 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -40,15 +40,19 @@
|
||||||
class TimeProvider
|
class TimeProvider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// @return singleton
|
||||||
static TimeProvider& getInstance()
|
static TimeProvider& getInstance()
|
||||||
{
|
{
|
||||||
static TimeProvider instance;
|
static TimeProvider instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set diff to server
|
||||||
void setDiffToServer(double ms);
|
void setDiffToServer(double ms);
|
||||||
|
/// Set diff from round-trip-times client-to-server and server-to-client
|
||||||
void setDiff(const tv& c2s, const tv& s2c);
|
void setDiff(const tv& c2s, const tv& s2c);
|
||||||
|
|
||||||
|
/// @return time diff to server
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline T getDiffToServer() const
|
inline T getDiffToServer() const
|
||||||
{
|
{
|
||||||
|
@ -60,22 +64,26 @@ public:
|
||||||
long getDiffToServerMs();
|
long getDiffToServerMs();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/// @return time since epoch
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static T sinceEpoche(const chronos::time_point_clk& point)
|
static T sinceEpoche(const chronos::time_point_clk& point)
|
||||||
{
|
{
|
||||||
return std::chrono::duration_cast<T>(point.time_since_epoch());
|
return std::chrono::duration_cast<T>(point.time_since_epoch());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return time_point from @p timeval
|
||||||
static chronos::time_point_clk toTimePoint(const tv& timeval)
|
static chronos::time_point_clk toTimePoint(const tv& timeval)
|
||||||
{
|
{
|
||||||
return chronos::time_point_clk(chronos::usec(timeval.usec) + chronos::sec(timeval.sec));
|
return chronos::time_point_clk(chronos::usec(timeval.usec) + chronos::sec(timeval.sec));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return local time
|
||||||
inline static chronos::time_point_clk now()
|
inline static chronos::time_point_clk now()
|
||||||
{
|
{
|
||||||
return chronos::clk::now();
|
return chronos::clk::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return server time
|
||||||
inline static chronos::time_point_clk serverNow()
|
inline static chronos::time_point_clk serverNow()
|
||||||
{
|
{
|
||||||
return chronos::clk::now() + TimeProvider::getInstance().getDiffToServer<chronos::usec>();
|
return chronos::clk::now() + TimeProvider::getInstance().getDiffToServer<chronos::usec>();
|
||||||
|
|
|
@ -54,6 +54,7 @@ public:
|
||||||
return static_cast<uint32_t>(sizeof(uint32_t) + msg.dump().size());
|
return static_cast<uint32_t>(sizeof(uint32_t) + msg.dump().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the json message payload
|
||||||
json msg;
|
json msg;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2024 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -39,18 +39,21 @@ namespace msg
|
||||||
class WireChunk : public BaseMessage
|
class WireChunk : public BaseMessage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit WireChunk(uint32_t size = 0) : BaseMessage(message_type::kWireChunk), payloadSize(size), payload(nullptr)
|
/// c'tor
|
||||||
|
explicit WireChunk(uint32_t size = 0) : BaseMessage(message_type::kWireChunk), payloadSize(size)
|
||||||
{
|
{
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
payload = static_cast<char*>(malloc(size * sizeof(char)));
|
payload = static_cast<char*>(malloc(size * sizeof(char)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// c'tor
|
||||||
WireChunk(const WireChunk& wireChunk) : BaseMessage(message_type::kWireChunk), timestamp(wireChunk.timestamp), payloadSize(wireChunk.payloadSize)
|
WireChunk(const WireChunk& wireChunk) : BaseMessage(message_type::kWireChunk), timestamp(wireChunk.timestamp), payloadSize(wireChunk.payloadSize)
|
||||||
{
|
{
|
||||||
payload = static_cast<char*>(malloc(payloadSize));
|
payload = static_cast<char*>(malloc(payloadSize));
|
||||||
memcpy(payload, wireChunk.payload, payloadSize);
|
memcpy(payload, wireChunk.payload, payloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// d'tor
|
||||||
~WireChunk() override
|
~WireChunk() override
|
||||||
{
|
{
|
||||||
free(payload);
|
free(payload);
|
||||||
|
@ -68,15 +71,17 @@ public:
|
||||||
return sizeof(tv) + sizeof(int32_t) + payloadSize;
|
return sizeof(tv) + sizeof(int32_t) + payloadSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return playout time of the chunk (server time)
|
||||||
virtual chronos::time_point_clk start() const
|
virtual chronos::time_point_clk start() const
|
||||||
{
|
{
|
||||||
return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec));
|
return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec));
|
||||||
}
|
}
|
||||||
|
|
||||||
tv timestamp;
|
tv timestamp; ///< playout timestamp (server time)
|
||||||
uint32_t payloadSize;
|
uint32_t payloadSize; ///< size of the payload
|
||||||
char* payload;
|
char* payload{nullptr}; ///< raw chunk payload
|
||||||
|
|
||||||
|
/// @return tuple of payload and payload size
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::pair<T*, size_t> getPayload() const
|
std::pair<T*, size_t> getPayload() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,6 +30,12 @@ template <typename T>
|
||||||
class Queue
|
class Queue
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
|
Queue() = default;
|
||||||
|
Queue(const Queue&) = delete; ///< disable copying
|
||||||
|
Queue& operator=(const Queue&) = delete; ///< disable assignment
|
||||||
|
|
||||||
|
/// @return next element, delete from queue
|
||||||
T pop()
|
T pop()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> mlock(mutex_);
|
std::unique_lock<std::mutex> mlock(mutex_);
|
||||||
|
@ -41,6 +47,7 @@ public:
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// abort wait
|
||||||
void abort_wait()
|
void abort_wait()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
@ -50,6 +57,7 @@ public:
|
||||||
cond_.notify_one();
|
cond_.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// wait for @p timeout for new element, @return if an element has been added
|
||||||
bool wait_for(const std::chrono::microseconds& timeout) const
|
bool wait_for(const std::chrono::microseconds& timeout) const
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> mlock(mutex_);
|
std::unique_lock<std::mutex> mlock(mutex_);
|
||||||
|
@ -60,6 +68,7 @@ public:
|
||||||
return !queue_.empty() && !abort_;
|
return !queue_.empty() && !abort_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return if an element has been returned in @p item within @p timeout
|
||||||
bool try_pop(T& item, const std::chrono::microseconds& timeout = std::chrono::microseconds(0))
|
bool try_pop(T& item, const std::chrono::microseconds& timeout = std::chrono::microseconds(0))
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> mlock(mutex_);
|
std::unique_lock<std::mutex> mlock(mutex_);
|
||||||
|
@ -79,6 +88,7 @@ public:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// return next element in @p item, wait for an element if queue is empty
|
||||||
void pop(T& item)
|
void pop(T& item)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> mlock(mutex_);
|
std::unique_lock<std::mutex> mlock(mutex_);
|
||||||
|
@ -89,6 +99,7 @@ public:
|
||||||
queue_.pop_front();
|
queue_.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add @p item to the queue
|
||||||
void push_front(const T& item)
|
void push_front(const T& item)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
@ -98,6 +109,7 @@ public:
|
||||||
cond_.notify_one();
|
cond_.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// return a copy of the next element in @p copy, @return false if the queue is empty
|
||||||
bool back_copy(T& copy)
|
bool back_copy(T& copy)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> mlock(mutex_);
|
std::lock_guard<std::mutex> mlock(mutex_);
|
||||||
|
@ -107,6 +119,7 @@ public:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// return a copy of the last element in @p copy, @return false if the queue is empty
|
||||||
bool front_copy(T& copy)
|
bool front_copy(T& copy)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> mlock(mutex_);
|
std::lock_guard<std::mutex> mlock(mutex_);
|
||||||
|
@ -116,6 +129,7 @@ public:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add element @p item at the front of the queue
|
||||||
void push_front(T&& item)
|
void push_front(T&& item)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
@ -125,6 +139,7 @@ public:
|
||||||
cond_.notify_one();
|
cond_.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add element @p item at the end of the queue
|
||||||
void push(const T& item)
|
void push(const T& item)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
@ -134,6 +149,7 @@ public:
|
||||||
cond_.notify_one();
|
cond_.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add element @p item at the end of the queue
|
||||||
void push(T&& item)
|
void push(T&& item)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
@ -143,21 +159,19 @@ public:
|
||||||
cond_.notify_one();
|
cond_.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return number of elements in the queue
|
||||||
size_t size() const
|
size_t size() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> mlock(mutex_);
|
std::lock_guard<std::mutex> mlock(mutex_);
|
||||||
return queue_.size();
|
return queue_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return if the queue is empty
|
||||||
bool empty() const
|
bool empty() const
|
||||||
{
|
{
|
||||||
return (size() == 0);
|
return (size() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Queue() = default;
|
|
||||||
Queue(const Queue&) = delete; // disable copying
|
|
||||||
Queue& operator=(const Queue&) = delete; // disable assignment
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::deque<T> queue_;
|
std::deque<T> queue_;
|
||||||
mutable std::atomic<bool> abort_;
|
mutable std::atomic<bool> abort_;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2024 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -21,7 +21,9 @@
|
||||||
|
|
||||||
// local headers
|
// local headers
|
||||||
#include "common/aixlog.hpp"
|
#include "common/aixlog.hpp"
|
||||||
|
#ifndef HAS_SOXR
|
||||||
#include "common/snap_exception.hpp"
|
#include "common/snap_exception.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
// standard headers
|
// standard headers
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
@ -195,7 +197,7 @@ std::shared_ptr<msg::PcmChunk> Resampler::resample(const msg::PcmChunk& chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
shared_ptr<msg::PcmChunk> Resampler::resample(shared_ptr<msg::PcmChunk> chunk)
|
std::shared_ptr<msg::PcmChunk> Resampler::resample(std::shared_ptr<msg::PcmChunk> chunk)
|
||||||
{
|
{
|
||||||
#ifndef HAS_SOXR
|
#ifndef HAS_SOXR
|
||||||
return chunk;
|
return chunk;
|
||||||
|
|
|
@ -38,57 +38,70 @@
|
||||||
class SampleFormat
|
class SampleFormat
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
SampleFormat();
|
SampleFormat();
|
||||||
|
/// c'tor
|
||||||
SampleFormat(const std::string& format);
|
SampleFormat(const std::string& format);
|
||||||
|
/// c'tor
|
||||||
SampleFormat(uint32_t rate, uint16_t bits, uint16_t channels);
|
SampleFormat(uint32_t rate, uint16_t bits, uint16_t channels);
|
||||||
|
|
||||||
|
/// @return sampleformat as string rate:bits::channels
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
|
|
||||||
|
/// Set @p format (rate:bits::channels)
|
||||||
void setFormat(const std::string& format);
|
void setFormat(const std::string& format);
|
||||||
|
/// Set format
|
||||||
void setFormat(uint32_t rate, uint16_t bits, uint16_t channels);
|
void setFormat(uint32_t rate, uint16_t bits, uint16_t channels);
|
||||||
|
|
||||||
|
/// @return if has format
|
||||||
bool isInitialized() const
|
bool isInitialized() const
|
||||||
{
|
{
|
||||||
return ((rate_ != 0) || (bits_ != 0) || (channels_ != 0));
|
return ((rate_ != 0) || (bits_ != 0) || (channels_ != 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return rate
|
||||||
uint32_t rate() const
|
uint32_t rate() const
|
||||||
{
|
{
|
||||||
return rate_;
|
return rate_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return bits
|
||||||
uint16_t bits() const
|
uint16_t bits() const
|
||||||
{
|
{
|
||||||
return bits_;
|
return bits_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return channels
|
||||||
uint16_t channels() const
|
uint16_t channels() const
|
||||||
{
|
{
|
||||||
return channels_;
|
return channels_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// size in [bytes] of a single mono sample, e.g. 2 bytes (= 16 bits)
|
/// @return size in [bytes] of a single mono sample, e.g. 2 bytes (= 16 bits)
|
||||||
uint16_t sampleSize() const
|
uint16_t sampleSize() const
|
||||||
{
|
{
|
||||||
return sample_size_;
|
return sample_size_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// size in [bytes] of a frame (sum of sample sizes = #channel*sampleSize), e.g. 4 bytes (= 2 channel * 16 bit)
|
/// @return size in [bytes] of a frame (sum of sample sizes = num-channel*sampleSize), e.g. 4 bytes (= 2 channel * 16 bit)
|
||||||
uint16_t frameSize() const
|
uint16_t frameSize() const
|
||||||
{
|
{
|
||||||
return frame_size_;
|
return frame_size_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return rate per ms (= rate / 1000)
|
||||||
inline double msRate() const
|
inline double msRate() const
|
||||||
{
|
{
|
||||||
return static_cast<double>(rate_) / 1000.;
|
return static_cast<double>(rate_) / 1000.;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return rate per micro seconds (= rate / 1000000)
|
||||||
inline double usRate() const
|
inline double usRate() const
|
||||||
{
|
{
|
||||||
return static_cast<double>(rate_) / 1000000.;
|
return static_cast<double>(rate_) / 1000000.;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return rate per nano seconds (= rate / 1000000000)
|
||||||
inline double nsRate() const
|
inline double nsRate() const
|
||||||
{
|
{
|
||||||
return static_cast<double>(rate_) / 1000000000.;
|
return static_cast<double>(rate_) / 1000000000.;
|
||||||
|
|
|
@ -79,7 +79,7 @@ public:
|
||||||
|
|
||||||
/// Authenticate with basic scheme
|
/// Authenticate with basic scheme
|
||||||
ErrorCode authenticateBasic(const std::string& credentials);
|
ErrorCode authenticateBasic(const std::string& credentials);
|
||||||
/// Authenticate with <user>:<password>
|
/// Authenticate with user:password
|
||||||
ErrorCode authenticatePlain(const std::string& user_password);
|
ErrorCode authenticatePlain(const std::string& user_password);
|
||||||
/// Authenticate with bearer scheme
|
/// Authenticate with bearer scheme
|
||||||
// ErrorCode authenticateBearer(const std::string& token);
|
// ErrorCode authenticateBearer(const std::string& token);
|
||||||
|
@ -88,7 +88,7 @@ public:
|
||||||
/// Authenticate with scheme ("basic" or "bearer") and auth param
|
/// Authenticate with scheme ("basic" or "bearer") and auth param
|
||||||
ErrorCode authenticate(const std::string& scheme, const std::string& param);
|
ErrorCode authenticate(const std::string& scheme, const std::string& param);
|
||||||
|
|
||||||
/// @return JWS token for @p username and @p password
|
// @return JWS token for @p username and @p password
|
||||||
// ErrorOr<std::string> getToken(const std::string& username, const std::string& password) const;
|
// ErrorOr<std::string> getToken(const std::string& username, const std::string& password) const;
|
||||||
/// @return if the authenticated user has permission to access @p ressource
|
/// @return if the authenticated user has permission to access @p ressource
|
||||||
bool hasPermission(const std::string& resource) const;
|
bool hasPermission(const std::string& resource) const;
|
||||||
|
|
|
@ -146,7 +146,7 @@ void ControlServer::onMessageReceived(std::shared_ptr<ControlSession> session, c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ControlServer::onNewSession(shared_ptr<ControlSession> session)
|
void ControlServer::onNewSession(std::shared_ptr<ControlSession> session)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> mlock(session_mutex_);
|
std::lock_guard<std::recursive_mutex> mlock(session_mutex_);
|
||||||
session->start();
|
session->start();
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "FLAC/stream_encoder.h"
|
#include "FLAC/stream_encoder.h"
|
||||||
|
|
||||||
// standard headers
|
// standard headers
|
||||||
|
#include <array>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
|
@ -76,6 +76,7 @@ struct ServerSettings
|
||||||
/// c'tor
|
/// c'tor
|
||||||
Authorization() = default;
|
Authorization() = default;
|
||||||
|
|
||||||
|
/// Init with @p conf_roles and @p conf_users
|
||||||
void init(const std::vector<std::string>& conf_roles, const std::vector<std::string>& conf_users)
|
void init(const std::vector<std::string>& conf_roles, const std::vector<std::string>& conf_users)
|
||||||
{
|
{
|
||||||
roles.clear();
|
roles.clear();
|
||||||
|
|
|
@ -87,8 +87,9 @@ public:
|
||||||
buffer_ = boost::asio::buffer(message_->data);
|
buffer_ = boost::asio::buffer(message_->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the ConstBufferSequence requirements.
|
/// const buffer.
|
||||||
using value_type = boost::asio::const_buffer;
|
using value_type = boost::asio::const_buffer;
|
||||||
|
/// const buffer iterator
|
||||||
using const_iterator = const boost::asio::const_buffer*;
|
using const_iterator = const boost::asio::const_buffer*;
|
||||||
|
|
||||||
/// begin iterator
|
/// begin iterator
|
||||||
|
|
|
@ -35,11 +35,11 @@ namespace streamreader
|
||||||
/// Tage entry??
|
/// Tage entry??
|
||||||
struct TageEntry
|
struct TageEntry
|
||||||
{
|
{
|
||||||
std::string code;
|
std::string code; ///< code
|
||||||
std::string type;
|
std::string type; ///< type
|
||||||
std::string data;
|
std::string data; ///< data
|
||||||
bool isBase64{false};
|
bool isBase64{false}; ///< is base64?
|
||||||
int length{0};
|
int length{0}; ///< length
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Starts shairport-sync and reads PCM data from stdout
|
/// Starts shairport-sync and reads PCM data from stdout
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2024 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -47,7 +47,7 @@ public:
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
void do_read();
|
void do_read();
|
||||||
void initAlsa();
|
void initAlsa();
|
||||||
void uninitAlsa();
|
void uninitAlsa();
|
||||||
|
|
|
@ -45,7 +45,9 @@ public:
|
||||||
/// c'tor. Encoded PCM data is passed to the PipeListener
|
/// c'tor. Encoded PCM data is passed to the PipeListener
|
||||||
AsioStream(PcmStream::Listener* pcmListener, boost::asio::io_context& ioc, const ServerSettings& server_settings, const StreamUri& uri);
|
AsioStream(PcmStream::Listener* pcmListener, boost::asio::io_context& ioc, const ServerSettings& server_settings, const StreamUri& uri);
|
||||||
|
|
||||||
|
/// Start the stream reader, init the encoder and optionally the stream control
|
||||||
void start() override;
|
void start() override;
|
||||||
|
/// Stop the stream reader
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -30,7 +30,9 @@ namespace detail
|
||||||
struct category : public std::error_category
|
struct category : public std::error_category
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// @return category name
|
||||||
const char* name() const noexcept override;
|
const char* name() const noexcept override;
|
||||||
|
/// @return error message for @p value
|
||||||
std::string message(int value) const override;
|
std::string message(int value) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2024 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
Copyright (C) 2024 Marcus Weseloh <marcus@weseloh.cc>
|
Copyright (C) 2024 Marcus Weseloh <marcus@weseloh.cc>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -49,7 +49,7 @@ public:
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
bool openJackConnection();
|
bool openJackConnection();
|
||||||
void closeJackConnection();
|
void closeJackConnection();
|
||||||
bool createJackPorts();
|
bool createJackPorts();
|
||||||
|
|
|
@ -40,7 +40,7 @@ public:
|
||||||
/// ctor. Encoded PCM data is passed to the PipeListener
|
/// ctor. Encoded PCM data is passed to the PipeListener
|
||||||
LibrespotStream(PcmStream::Listener* pcmListener, boost::asio::io_context& ioc, const ServerSettings& server_settings, const StreamUri& uri);
|
LibrespotStream(PcmStream::Listener* pcmListener, boost::asio::io_context& ioc, const ServerSettings& server_settings, const StreamUri& uri);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
bool killall_;
|
bool killall_;
|
||||||
|
|
||||||
void onStderrMsg(const std::string& line) override;
|
void onStderrMsg(const std::string& line) override;
|
||||||
|
|
|
@ -114,13 +114,13 @@ public:
|
||||||
virtual void onStateChanged(const PcmStream* pcmStream, ReaderState state) = 0;
|
virtual void onStateChanged(const PcmStream* pcmStream, ReaderState state) = 0;
|
||||||
/// Chunk @p chunk of @p pcmStream has read
|
/// Chunk @p chunk of @p pcmStream has read
|
||||||
virtual void onChunkRead(const PcmStream* pcmStream, const msg::PcmChunk& chunk) = 0;
|
virtual void onChunkRead(const PcmStream* pcmStream, const msg::PcmChunk& chunk) = 0;
|
||||||
/// Chunk @p chunk with duration @p duration of stream @pcmStream has been encoded
|
/// Chunk @p chunk with duration @p duration of stream @p pcmStream has been encoded
|
||||||
virtual void onChunkEncoded(const PcmStream* pcmStream, std::shared_ptr<msg::PcmChunk> chunk, double duration) = 0;
|
virtual void onChunkEncoded(const PcmStream* pcmStream, std::shared_ptr<msg::PcmChunk> chunk, double duration) = 0;
|
||||||
/// Stream @p pcmStream muissed to read audio with duration @p ms
|
/// Stream @p pcmStream muissed to read audio with duration @p ms
|
||||||
virtual void onResync(const PcmStream* pcmStream, double ms) = 0;
|
virtual void onResync(const PcmStream* pcmStream, double ms) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Handler function for command results
|
||||||
using ResultHandler = std::function<void(const snapcast::ErrorCode& ec)>;
|
using ResultHandler = std::function<void(const snapcast::ErrorCode& ec)>;
|
||||||
|
|
||||||
/// c'tor. Encoded PCM data is passed to the PcmStream::Listener
|
/// c'tor. Encoded PCM data is passed to the PcmStream::Listener
|
||||||
|
|
|
@ -42,29 +42,45 @@ namespace streamreader
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// Stream control base class
|
||||||
|
/// Controls a stream via "command" (play, pause, next, ...)
|
||||||
|
/// Provides status information (playback status, position, metadata, ...)
|
||||||
class StreamControl
|
class StreamControl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// Request handler coming from the plugin
|
||||||
using OnRequest = std::function<void(const jsonrpcpp::Request& response)>;
|
using OnRequest = std::function<void(const jsonrpcpp::Request& response)>;
|
||||||
|
/// Notification handler coming from the plugin
|
||||||
using OnNotification = std::function<void(const jsonrpcpp::Notification& response)>;
|
using OnNotification = std::function<void(const jsonrpcpp::Notification& response)>;
|
||||||
|
/// Response handler coming from the plugin
|
||||||
using OnResponse = std::function<void(const jsonrpcpp::Response& response)>;
|
using OnResponse = std::function<void(const jsonrpcpp::Response& response)>;
|
||||||
|
/// Log handler coming from the plugin
|
||||||
using OnLog = std::function<void(std::string message)>;
|
using OnLog = std::function<void(std::string message)>;
|
||||||
|
|
||||||
|
/// c'tor
|
||||||
explicit StreamControl(const boost::asio::any_io_executor& executor);
|
explicit StreamControl(const boost::asio::any_io_executor& executor);
|
||||||
|
/// d'tor
|
||||||
virtual ~StreamControl() = default;
|
virtual ~StreamControl() = default;
|
||||||
|
|
||||||
|
/// Start the stream control, calls abstract "doStart"
|
||||||
void start(const std::string& stream_id, const ServerSettings& server_setttings, const OnNotification& notification_handler,
|
void start(const std::string& stream_id, const ServerSettings& server_setttings, const OnNotification& notification_handler,
|
||||||
const OnRequest& request_handler, const OnLog& log_handler);
|
const OnRequest& request_handler, const OnLog& log_handler);
|
||||||
|
|
||||||
|
/// Issue a command to the stream, calls abstract "doCommand"
|
||||||
void command(const jsonrpcpp::Request& request, const OnResponse& response_handler);
|
void command(const jsonrpcpp::Request& request, const OnResponse& response_handler);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/// abstract "command" interface: send a json request to the plugin
|
||||||
virtual void doCommand(const jsonrpcpp::Request& request) = 0;
|
virtual void doCommand(const jsonrpcpp::Request& request) = 0;
|
||||||
|
/// abstract "start" interface: starts and initializes the plugin
|
||||||
virtual void doStart(const std::string& stream_id, const ServerSettings& server_setttings) = 0;
|
virtual void doStart(const std::string& stream_id, const ServerSettings& server_setttings) = 0;
|
||||||
|
|
||||||
|
/// a @p json message has been received from the plugin
|
||||||
void onReceive(const std::string& json);
|
void onReceive(const std::string& json);
|
||||||
|
/// a @p message log request has been received from the plugin
|
||||||
void onLog(std::string message);
|
void onLog(std::string message);
|
||||||
|
|
||||||
|
/// asio executor
|
||||||
boost::asio::any_io_executor executor_;
|
boost::asio::any_io_executor executor_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -76,10 +92,14 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Script based stream control
|
||||||
|
/// Executes a script (e.g. Python) and communicates via stdout/stdin with the script
|
||||||
class ScriptStreamControl : public StreamControl
|
class ScriptStreamControl : public StreamControl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
ScriptStreamControl(const boost::asio::any_io_executor& executor, const std::filesystem::path& plugin_dir, std::string script, std::string params);
|
ScriptStreamControl(const boost::asio::any_io_executor& executor, const std::filesystem::path& plugin_dir, std::string script, std::string params);
|
||||||
|
/// d'tor
|
||||||
virtual ~ScriptStreamControl() = default;
|
virtual ~ScriptStreamControl() = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2024 Johannes Pohl
|
Copyright (C) 2014-2025 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -39,10 +39,10 @@ namespace streamreader
|
||||||
class TcpStream : public AsioStream<tcp::socket>
|
class TcpStream : public AsioStream<tcp::socket>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// ctor. Encoded PCM data is passed to the PipeListener
|
/// c'tor. Encoded PCM data is passed to the PipeListener
|
||||||
TcpStream(PcmStream::Listener* pcmListener, boost::asio::io_context& ioc, const ServerSettings& server_settings, const StreamUri& uri);
|
TcpStream(PcmStream::Listener* pcmListener, boost::asio::io_context& ioc, const ServerSettings& server_settings, const StreamUri& uri);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
void connect() override;
|
void connect() override;
|
||||||
void disconnect() override;
|
void disconnect() override;
|
||||||
std::unique_ptr<tcp::acceptor> acceptor_;
|
std::unique_ptr<tcp::acceptor> acceptor_;
|
||||||
|
|
|
@ -36,10 +36,11 @@ class Watchdog;
|
||||||
class Watchdog
|
class Watchdog
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// Watchdog timeout handler
|
||||||
using TimeoutHandler = std::function<void(std::chrono::milliseconds ms)>;
|
using TimeoutHandler = std::function<void(std::chrono::milliseconds ms)>;
|
||||||
|
|
||||||
/// c'tor
|
/// c'tor
|
||||||
explicit Watchdog(const boost::asio::any_io_executor& executor);//, WatchdogListener* listener = nullptr);
|
explicit Watchdog(const boost::asio::any_io_executor& executor);
|
||||||
/// d'tor
|
/// d'tor
|
||||||
virtual ~Watchdog();
|
virtual ~Watchdog();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue