mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-05 13:16:36 +02:00
Add sharing mode for Oboe, log device settings
This commit is contained in:
parent
ad2351a42b
commit
424487a48e
14 changed files with 101 additions and 49 deletions
|
@ -30,6 +30,7 @@ struct ClientSettings
|
||||||
{
|
{
|
||||||
enum class SharingMode
|
enum class SharingMode
|
||||||
{
|
{
|
||||||
|
unspecified,
|
||||||
exclusive,
|
exclusive,
|
||||||
shared
|
shared
|
||||||
};
|
};
|
||||||
|
@ -60,7 +61,7 @@ struct ClientSettings
|
||||||
int latency{0};
|
int latency{0};
|
||||||
PcmDevice pcm_device;
|
PcmDevice pcm_device;
|
||||||
SampleFormat sample_format;
|
SampleFormat sample_format;
|
||||||
SharingMode sharing_mode{SharingMode::shared};
|
SharingMode sharing_mode{SharingMode::unspecified};
|
||||||
Mixer mixer;
|
Mixer mixer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,18 @@ Controller::Controller(boost::asio::io_context& io_context, const ClientSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename PlayerType>
|
||||||
|
std::unique_ptr<Player> Controller::createPlayer(ClientSettings::Player& settings, const std::string& player_name)
|
||||||
|
{
|
||||||
|
if (settings.player_name.empty() || settings.player_name == player_name)
|
||||||
|
{
|
||||||
|
settings.player_name = player_name;
|
||||||
|
return make_unique<AlsaPlayer>(io_context_, settings, stream_);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Controller::getNextMessage()
|
void Controller::getNextMessage()
|
||||||
{
|
{
|
||||||
clientConnection_->getNextMessage([this](const boost::system::error_code& ec, std::unique_ptr<msg::BaseMessage> response) {
|
clientConnection_->getNextMessage([this](const boost::system::error_code& ec, std::unique_ptr<msg::BaseMessage> response) {
|
||||||
|
@ -68,7 +80,7 @@ void Controller::getNextMessage()
|
||||||
// boost::asio::post(io_context_, [this, response = std::move(response)]() mutable {
|
// boost::asio::post(io_context_, [this, response = std::move(response)]() mutable {
|
||||||
auto pcmChunk = msg::message_cast<msg::PcmChunk>(std::move(response));
|
auto pcmChunk = msg::message_cast<msg::PcmChunk>(std::move(response));
|
||||||
pcmChunk->format = sampleFormat_;
|
pcmChunk->format = sampleFormat_;
|
||||||
// LOG(TRACE, LOG_TAG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.getFormat() << "\n";
|
// LOG(TRACE, LOG_TAG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.toString() << "\n";
|
||||||
if (decoder_->decode(pcmChunk.get()))
|
if (decoder_->decode(pcmChunk.get()))
|
||||||
{
|
{
|
||||||
// LOG(TRACE, LOG_TAG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->durationMs() << ", sec: " <<
|
// LOG(TRACE, LOG_TAG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->durationMs() << ", sec: " <<
|
||||||
|
@ -99,7 +111,6 @@ void Controller::getNextMessage()
|
||||||
else if (response->type == message_type::kCodecHeader)
|
else if (response->type == message_type::kCodecHeader)
|
||||||
{
|
{
|
||||||
headerChunk_ = msg::message_cast<msg::CodecHeader>(std::move(response));
|
headerChunk_ = msg::message_cast<msg::CodecHeader>(std::move(response));
|
||||||
LOG(INFO, LOG_TAG) << "Codec: " << headerChunk_->codec << "\n";
|
|
||||||
decoder_.reset(nullptr);
|
decoder_.reset(nullptr);
|
||||||
stream_ = nullptr;
|
stream_ = nullptr;
|
||||||
player_.reset(nullptr);
|
player_.reset(nullptr);
|
||||||
|
@ -122,33 +133,32 @@ void Controller::getNextMessage()
|
||||||
throw SnapException("codec not supported: \"" + headerChunk_->codec + "\"");
|
throw SnapException("codec not supported: \"" + headerChunk_->codec + "\"");
|
||||||
|
|
||||||
sampleFormat_ = decoder_->setHeader(headerChunk_.get());
|
sampleFormat_ = decoder_->setHeader(headerChunk_.get());
|
||||||
LOG(NOTICE, LOG_TAG) << TAG("state") << "sampleformat: " << sampleFormat_.getFormat() << "\n";
|
LOG(INFO, LOG_TAG) << "Codec: " << headerChunk_->codec << ", sampleformat: " << sampleFormat_.toString() << "\n";
|
||||||
|
LOG(NOTICE, LOG_TAG) << TAG("state") << "sampleformat: " << sampleFormat_.toString() << "\n";
|
||||||
|
|
||||||
stream_ = make_shared<Stream>(sampleFormat_, settings_.player.sample_format);
|
stream_ = make_shared<Stream>(sampleFormat_, settings_.player.sample_format);
|
||||||
stream_->setBufferLen(std::max(0, serverSettings_->getBufferMs() - serverSettings_->getLatency() - settings_.player.latency));
|
stream_->setBufferLen(std::max(0, serverSettings_->getBufferMs() - serverSettings_->getLatency() - settings_.player.latency));
|
||||||
|
|
||||||
const auto& player_settings = settings_.player;
|
auto& player_settings = settings_.player;
|
||||||
const auto& player_name = settings_.player.player_name;
|
|
||||||
player_ = nullptr;
|
|
||||||
#ifdef HAS_ALSA
|
#ifdef HAS_ALSA
|
||||||
if (!player_ && (player_name.empty() || (player_name == "alsa")))
|
if (!player_)
|
||||||
player_ = make_unique<AlsaPlayer>(io_context_, player_settings, stream_);
|
player_ = createPlayer<AlsaPlayer>(settings_.player, "alsa");
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAS_OBOE
|
#ifdef HAS_OBOE
|
||||||
if (!player_ && (player_name.empty() || (player_name == "oboe")))
|
if (!player_)
|
||||||
player_ = make_unique<OboePlayer>(io_context_, player_settings, stream_);
|
player_ = createPlayer<OboePlayer>(settings_.player, "oboe");
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAS_OPENSL
|
#ifdef HAS_OPENSL
|
||||||
if (!player_ && (player_name.empty() || (player_name == "opensl")))
|
if (!player_)
|
||||||
player_ = make_unique<OpenslPlayer>(io_context_, player_settings, stream_);
|
player_ = createPlayer<OpenslPlayer>(settings_.player, "opensl");
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAS_COREAUDIO
|
#ifdef HAS_COREAUDIO
|
||||||
if (!player_ && (player_name.empty() || (player_name == "coreaudio")))
|
if (!player_)
|
||||||
player_ = make_unique<CoreAudioPlayer>(io_context_, player_settings, stream_);
|
player_ = createPlayer<CoreAudioPlayer>(settings_.player, "coreaudio");
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAS_WASAPI
|
#ifdef HAS_WASAPI
|
||||||
if (!player_ && (player_name.empty() || (player_name == "wasapi")))
|
if (!player_)
|
||||||
player_ = make_unique<WASAPIPlayer>(io_context_, player_settings, stream_);
|
player_ = createPlayer<WASAPIPlayer>(settings_.player, "wasapi");
|
||||||
#endif
|
#endif
|
||||||
if (!player_)
|
if (!player_)
|
||||||
throw SnapException("No audio player support");
|
throw SnapException("No audio player support");
|
||||||
|
|
|
@ -67,6 +67,9 @@ private:
|
||||||
void reconnect();
|
void reconnect();
|
||||||
void browseMdns(const MdnsHandler& handler);
|
void browseMdns(const MdnsHandler& handler);
|
||||||
|
|
||||||
|
template <typename PlayerType>
|
||||||
|
std::unique_ptr<Player> createPlayer(ClientSettings::Player& settings, const std::string& player_name);
|
||||||
|
|
||||||
void getNextMessage();
|
void getNextMessage();
|
||||||
void sendTimeSyncMessage(int quick_syncs);
|
void sendTimeSyncMessage(int quick_syncs);
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ SampleFormat OpusDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
memcpy(&channels, chunk->payload + 10, sizeof(channels));
|
memcpy(&channels, chunk->payload + 10, sizeof(channels));
|
||||||
|
|
||||||
sample_format_.setFormat(SWAP_32(rate), SWAP_16(bits), SWAP_16(channels));
|
sample_format_.setFormat(SWAP_32(rate), SWAP_16(bits), SWAP_16(channels));
|
||||||
LOG(DEBUG, LOG_TAG) << "Opus sampleformat: " << sample_format_.getFormat() << "\n";
|
LOG(DEBUG, LOG_TAG) << "Opus sampleformat: " << sample_format_.toString() << "\n";
|
||||||
|
|
||||||
// create the decoder
|
// create the decoder
|
||||||
int error;
|
int error;
|
||||||
|
|
|
@ -332,7 +332,7 @@ void AlsaPlayer::initAlsa()
|
||||||
|
|
||||||
/* Allocate buffer to hold single period */
|
/* Allocate buffer to hold single period */
|
||||||
snd_pcm_hw_params_get_period_size(params, &frames_, nullptr);
|
snd_pcm_hw_params_get_period_size(params, &frames_, nullptr);
|
||||||
LOG(INFO, LOG_TAG) << "frames: " << frames_ << "\n";
|
LOG(DEBUG, LOG_TAG) << "frames: " << frames_ << "\n";
|
||||||
|
|
||||||
snd_pcm_hw_params_get_period_time(params, &tmp, nullptr);
|
snd_pcm_hw_params_get_period_time(params, &tmp, nullptr);
|
||||||
LOG(DEBUG, LOG_TAG) << "period time: " << tmp << "\n";
|
LOG(DEBUG, LOG_TAG) << "period time: " << tmp << "\n";
|
||||||
|
|
|
@ -33,7 +33,6 @@ public:
|
||||||
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);
|
||||||
~AlsaPlayer() override;
|
~AlsaPlayer() override;
|
||||||
|
|
||||||
/// Set audio volume in range [0..1]
|
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,13 @@ OboePlayer::OboePlayer(boost::asio::io_context& io_context, const ClientSettings
|
||||||
LOG(INFO, LOG_TAG) << "DefaultStreamValues::SampleRate: " << oboe::DefaultStreamValues::SampleRate
|
LOG(INFO, LOG_TAG) << "DefaultStreamValues::SampleRate: " << oboe::DefaultStreamValues::SampleRate
|
||||||
<< ", DefaultStreamValues::FramesPerBurst: " << oboe::DefaultStreamValues::FramesPerBurst << "\n";
|
<< ", DefaultStreamValues::FramesPerBurst: " << oboe::DefaultStreamValues::FramesPerBurst << "\n";
|
||||||
|
|
||||||
|
oboe::SharingMode sharing_mode = oboe::SharingMode::Shared;
|
||||||
|
if (settings.sharing_mode == ClientSettings::SharingMode::exclusive)
|
||||||
|
sharing_mode = oboe::SharingMode::Exclusive;
|
||||||
|
|
||||||
// The builder set methods can be chained for convenience.
|
// The builder set methods can be chained for convenience.
|
||||||
oboe::AudioStreamBuilder builder;
|
oboe::AudioStreamBuilder builder;
|
||||||
auto result = builder.setSharingMode(oboe::SharingMode::Exclusive)
|
auto result = builder.setSharingMode(sharing_mode)
|
||||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||||
->setChannelCount(stream->getFormat().channels())
|
->setChannelCount(stream->getFormat().channels())
|
||||||
->setSampleRate(stream->getFormat().rate())
|
->setSampleRate(stream->getFormat().rate())
|
||||||
|
|
|
@ -39,6 +39,49 @@ static constexpr auto LOG_TAG = "Player";
|
||||||
Player::Player(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream)
|
Player::Player(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream)
|
||||||
: io_context_(io_context), active_(false), stream_(stream), settings_(settings), volume_(1.0), muted_(false), volCorrection_(1.0)
|
: io_context_(io_context), active_(false), stream_(stream), settings_(settings), volume_(1.0), muted_(false), volCorrection_(1.0)
|
||||||
{
|
{
|
||||||
|
string sharing_mode;
|
||||||
|
switch (settings_.sharing_mode)
|
||||||
|
{
|
||||||
|
case ClientSettings::SharingMode::unspecified:
|
||||||
|
sharing_mode = "unspecified";
|
||||||
|
break;
|
||||||
|
case ClientSettings::SharingMode::exclusive:
|
||||||
|
sharing_mode = "exclusive";
|
||||||
|
break;
|
||||||
|
case ClientSettings::SharingMode::shared:
|
||||||
|
sharing_mode = "shared";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto coalesce = [](const std::string& value, const std::string& fallback = "<none>") {
|
||||||
|
if (!value.empty())
|
||||||
|
return value;
|
||||||
|
else
|
||||||
|
return fallback;
|
||||||
|
};
|
||||||
|
LOG(INFO, LOG_TAG) << "Player name: " << coalesce(settings_.player_name) << ", device: " << coalesce(settings_.pcm_device.name)
|
||||||
|
<< ", description: " << coalesce(settings_.pcm_device.description) << ", idx: " << settings_.pcm_device.idx
|
||||||
|
<< ", sharing mode: " << sharing_mode << "\n";
|
||||||
|
|
||||||
|
string mixer;
|
||||||
|
switch (settings_.mixer.mode)
|
||||||
|
{
|
||||||
|
case ClientSettings::Mixer::Mode::hardware:
|
||||||
|
mixer = "hardware";
|
||||||
|
break;
|
||||||
|
case ClientSettings::Mixer::Mode::software:
|
||||||
|
mixer = "software";
|
||||||
|
break;
|
||||||
|
case ClientSettings::Mixer::Mode::script:
|
||||||
|
mixer = "script";
|
||||||
|
break;
|
||||||
|
case ClientSettings::Mixer::Mode::none:
|
||||||
|
mixer = "none";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LOG(INFO, LOG_TAG) << "Mixer mode: " << mixer << ", parameters: " << coalesce(settings_.mixer.parameter) << "\n";
|
||||||
|
LOG(INFO, LOG_TAG) << "Sampleformat: " << (settings_.sample_format.isInitialized() ? settings_.sample_format.toString() : stream->getFormat().toString())
|
||||||
|
<< ", stream: " << stream->getFormat().toString() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -141,7 +141,7 @@ int main(int argc, char** argv)
|
||||||
#ifdef HAS_SOXR
|
#ifdef HAS_SOXR
|
||||||
auto sample_format = op.add<Value<string>>("", "sampleformat", "resample audio stream to <rate>:<bits>:<channels>", "");
|
auto sample_format = op.add<Value<string>>("", "sampleformat", "resample audio stream to <rate>:<bits>:<channels>", "");
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAS_WASAPI
|
#if defined(HAS_OBOE) || defined(HAS_WASAPI)
|
||||||
auto sharing_mode = op.add<Value<string>>("", "sharingmode", "audio mode to use [shared|exclusive]", "shared");
|
auto sharing_mode = op.add<Value<string>>("", "sharingmode", "audio mode to use [shared|exclusive]", "shared");
|
||||||
#endif
|
#endif
|
||||||
bool hw_mixer_supported = false;
|
bool hw_mixer_supported = false;
|
||||||
|
@ -299,17 +299,10 @@ int main(int argc, char** argv)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_WASAPI
|
#if defined(HAS_OBOE) || defined(HAS_WASAPI)
|
||||||
if (sharing_mode->is_set())
|
settings.player.sharing_mode = (sharing_mode->value() == "exclusive") ? ClientSettings::SharingMode::exclusive : ClientSettings::SharingMode::shared;
|
||||||
{
|
|
||||||
settings.player.sharing_mode =
|
|
||||||
(sharing_mode->value() == "exclusive") ? ClientSettings::SharingMode::exclusive : ClientSettings::SharingMode::shared;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
settings.player.mixer.mode = ClientSettings::Mixer::Mode::software;
|
|
||||||
if (mixer_mode->is_set())
|
|
||||||
{
|
|
||||||
string mode = utils::string::split_left(mixer_mode->value(), ':', settings.player.mixer.parameter);
|
string mode = utils::string::split_left(mixer_mode->value(), ':', settings.player.mixer.parameter);
|
||||||
if (mode == "software")
|
if (mode == "software")
|
||||||
settings.player.mixer.mode = ClientSettings::Mixer::Mode::software;
|
settings.player.mixer.mode = ClientSettings::Mixer::Mode::software;
|
||||||
|
@ -321,7 +314,6 @@ int main(int argc, char** argv)
|
||||||
settings.player.mixer.mode = ClientSettings::Mixer::Mode::none;
|
settings.player.mixer.mode = ClientSettings::Mixer::Mode::none;
|
||||||
else
|
else
|
||||||
throw SnapException("Mixer mode not supported: " + mode);
|
throw SnapException("Mixer mode not supported: " + mode);
|
||||||
}
|
|
||||||
|
|
||||||
boost::asio::io_context io_context;
|
boost::asio::io_context io_context;
|
||||||
// Construct a signal set registered for process termination.
|
// Construct a signal set registered for process termination.
|
||||||
|
|
|
@ -61,7 +61,7 @@ x = 1,000016667 / (1,000016667 - 1)
|
||||||
soxr_ = nullptr;
|
soxr_ = nullptr;
|
||||||
if ((format_.rate() != in_format_.rate()) || (format_.bits() != in_format_.bits()))
|
if ((format_.rate() != in_format_.rate()) || (format_.bits() != in_format_.bits()))
|
||||||
{
|
{
|
||||||
LOG(INFO, LOG_TAG) << "Resampling from " << in_format_.getFormat() << " to " << format_.getFormat() << "\n";
|
LOG(INFO, LOG_TAG) << "Resampling from " << in_format_.toString() << " to " << format_.toString() << "\n";
|
||||||
soxr_error_t error;
|
soxr_error_t error;
|
||||||
|
|
||||||
soxr_datatype_t in_type = SOXR_INT16_I;
|
soxr_datatype_t in_type = SOXR_INT16_I;
|
||||||
|
|
|
@ -49,7 +49,7 @@ SampleFormat::SampleFormat(uint32_t sampleRate, uint16_t bitsPerSample, uint16_t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string SampleFormat::getFormat() const
|
string SampleFormat::toString() const
|
||||||
{
|
{
|
||||||
stringstream ss;
|
stringstream ss;
|
||||||
ss << rate_ << ":" << bits_ << ":" << channels_;
|
ss << rate_ << ":" << bits_ << ":" << channels_;
|
||||||
|
|
|
@ -41,7 +41,7 @@ public:
|
||||||
SampleFormat(const std::string& format);
|
SampleFormat(const std::string& format);
|
||||||
SampleFormat(uint32_t rate, uint16_t bits, uint16_t channels);
|
SampleFormat(uint32_t rate, uint16_t bits, uint16_t channels);
|
||||||
|
|
||||||
std::string getFormat() const;
|
std::string toString() const;
|
||||||
|
|
||||||
void setFormat(const std::string& format);
|
void setFormat(const std::string& format);
|
||||||
void setFormat(uint32_t rate, uint16_t bits, uint16_t channels);
|
void setFormat(uint32_t rate, uint16_t bits, uint16_t channels);
|
||||||
|
|
|
@ -220,7 +220,7 @@ void OggEncoder::initEncoder()
|
||||||
vorbis_comment_init(&vc_);
|
vorbis_comment_init(&vc_);
|
||||||
vorbis_comment_add_tag(&vc_, "TITLE", "SnapStream");
|
vorbis_comment_add_tag(&vc_, "TITLE", "SnapStream");
|
||||||
vorbis_comment_add_tag(&vc_, "VERSION", VERSION);
|
vorbis_comment_add_tag(&vc_, "VERSION", VERSION);
|
||||||
vorbis_comment_add_tag(&vc_, "SAMPLE_FORMAT", sampleFormat_.getFormat().c_str());
|
vorbis_comment_add_tag(&vc_, "SAMPLE_FORMAT", sampleFormat_.toString().c_str());
|
||||||
|
|
||||||
/* set up the analysis state and auxiliary encoding storage */
|
/* set up the analysis state and auxiliary encoding storage */
|
||||||
vorbis_analysis_init(&vd_, &vi_);
|
vorbis_analysis_init(&vd_, &vi_);
|
||||||
|
|
|
@ -50,7 +50,7 @@ PcmStream::PcmStream(PcmListener* pcmListener, boost::asio::io_context& ioc, con
|
||||||
if (uri_.query.find(kUriSampleFormat) == uri_.query.end())
|
if (uri_.query.find(kUriSampleFormat) == uri_.query.end())
|
||||||
throw SnapException("Stream URI must have a sampleformat");
|
throw SnapException("Stream URI must have a sampleformat");
|
||||||
sampleFormat_ = SampleFormat(uri_.query[kUriSampleFormat]);
|
sampleFormat_ = SampleFormat(uri_.query[kUriSampleFormat]);
|
||||||
LOG(INFO, LOG_TAG) << "PcmStream sampleFormat: " << sampleFormat_.getFormat() << "\n";
|
LOG(INFO, LOG_TAG) << "PcmStream sampleFormat: " << sampleFormat_.toString() << "\n";
|
||||||
|
|
||||||
if (uri_.query.find(kUriChunkMs) != uri_.query.end())
|
if (uri_.query.find(kUriChunkMs) != uri_.query.end())
|
||||||
chunk_ms_ = cpt::stoul(uri_.query[kUriChunkMs]);
|
chunk_ms_ = cpt::stoul(uri_.query[kUriChunkMs]);
|
||||||
|
@ -97,7 +97,7 @@ const SampleFormat& PcmStream::getSampleFormat() const
|
||||||
|
|
||||||
void PcmStream::start()
|
void PcmStream::start()
|
||||||
{
|
{
|
||||||
LOG(DEBUG, LOG_TAG) << "Start, sampleformat: " << sampleFormat_.getFormat() << "\n";
|
LOG(DEBUG, LOG_TAG) << "Start, sampleformat: " << sampleFormat_.toString() << "\n";
|
||||||
encoder_->init(this, sampleFormat_);
|
encoder_->init(this, sampleFormat_);
|
||||||
active_ = true;
|
active_ = true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue