diff --git a/client/client_settings.hpp b/client/client_settings.hpp index bf6fa9fd..0963fef8 100644 --- a/client/client_settings.hpp +++ b/client/client_settings.hpp @@ -30,6 +30,7 @@ struct ClientSettings { enum class SharingMode { + unspecified, exclusive, shared }; @@ -60,7 +61,7 @@ struct ClientSettings int latency{0}; PcmDevice pcm_device; SampleFormat sample_format; - SharingMode sharing_mode{SharingMode::shared}; + SharingMode sharing_mode{SharingMode::unspecified}; Mixer mixer; }; diff --git a/client/controller.cpp b/client/controller.cpp index f3593114..8cba5bf3 100644 --- a/client/controller.cpp +++ b/client/controller.cpp @@ -55,6 +55,18 @@ Controller::Controller(boost::asio::io_context& io_context, const ClientSettings } +template +std::unique_ptr 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(io_context_, settings, stream_); + } + return nullptr; +} + + void Controller::getNextMessage() { clientConnection_->getNextMessage([this](const boost::system::error_code& ec, std::unique_ptr response) { @@ -68,7 +80,7 @@ void Controller::getNextMessage() // boost::asio::post(io_context_, [this, response = std::move(response)]() mutable { auto pcmChunk = msg::message_cast(std::move(response)); 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())) { // 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) { headerChunk_ = msg::message_cast(std::move(response)); - LOG(INFO, LOG_TAG) << "Codec: " << headerChunk_->codec << "\n"; decoder_.reset(nullptr); stream_ = nullptr; player_.reset(nullptr); @@ -122,33 +133,32 @@ void Controller::getNextMessage() throw SnapException("codec not supported: \"" + headerChunk_->codec + "\""); 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(sampleFormat_, settings_.player.sample_format); stream_->setBufferLen(std::max(0, serverSettings_->getBufferMs() - serverSettings_->getLatency() - settings_.player.latency)); - const auto& player_settings = settings_.player; - const auto& player_name = settings_.player.player_name; - player_ = nullptr; + auto& player_settings = settings_.player; #ifdef HAS_ALSA - if (!player_ && (player_name.empty() || (player_name == "alsa"))) - player_ = make_unique(io_context_, player_settings, stream_); + if (!player_) + player_ = createPlayer(settings_.player, "alsa"); #endif #ifdef HAS_OBOE - if (!player_ && (player_name.empty() || (player_name == "oboe"))) - player_ = make_unique(io_context_, player_settings, stream_); + if (!player_) + player_ = createPlayer(settings_.player, "oboe"); #endif #ifdef HAS_OPENSL - if (!player_ && (player_name.empty() || (player_name == "opensl"))) - player_ = make_unique(io_context_, player_settings, stream_); + if (!player_) + player_ = createPlayer(settings_.player, "opensl"); #endif #ifdef HAS_COREAUDIO - if (!player_ && (player_name.empty() || (player_name == "coreaudio"))) - player_ = make_unique(io_context_, player_settings, stream_); + if (!player_) + player_ = createPlayer(settings_.player, "coreaudio"); #endif #ifdef HAS_WASAPI - if (!player_ && (player_name.empty() || (player_name == "wasapi"))) - player_ = make_unique(io_context_, player_settings, stream_); + if (!player_) + player_ = createPlayer(settings_.player, "wasapi"); #endif if (!player_) throw SnapException("No audio player support"); diff --git a/client/controller.hpp b/client/controller.hpp index 51af5aae..578fb56e 100644 --- a/client/controller.hpp +++ b/client/controller.hpp @@ -67,6 +67,9 @@ private: void reconnect(); void browseMdns(const MdnsHandler& handler); + template + std::unique_ptr createPlayer(ClientSettings::Player& settings, const std::string& player_name); + void getNextMessage(); void sendTimeSyncMessage(int quick_syncs); diff --git a/client/decoder/opus_decoder.cpp b/client/decoder/opus_decoder.cpp index 16d75498..75d74020 100644 --- a/client/decoder/opus_decoder.cpp +++ b/client/decoder/opus_decoder.cpp @@ -103,7 +103,7 @@ SampleFormat OpusDecoder::setHeader(msg::CodecHeader* chunk) memcpy(&channels, chunk->payload + 10, sizeof(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 int error; diff --git a/client/player/alsa_player.cpp b/client/player/alsa_player.cpp index 688a1c0e..65b206ea 100644 --- a/client/player/alsa_player.cpp +++ b/client/player/alsa_player.cpp @@ -332,7 +332,7 @@ void AlsaPlayer::initAlsa() /* Allocate buffer to hold single period */ 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); LOG(DEBUG, LOG_TAG) << "period time: " << tmp << "\n"; diff --git a/client/player/alsa_player.hpp b/client/player/alsa_player.hpp index 50c7985b..01364c2f 100644 --- a/client/player/alsa_player.hpp +++ b/client/player/alsa_player.hpp @@ -33,7 +33,6 @@ public: AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr stream); ~AlsaPlayer() override; - /// Set audio volume in range [0..1] void start() override; void stop() override; diff --git a/client/player/oboe_player.cpp b/client/player/oboe_player.cpp index 46e4a286..425a9c69 100644 --- a/client/player/oboe_player.cpp +++ b/client/player/oboe_player.cpp @@ -45,9 +45,13 @@ OboePlayer::OboePlayer(boost::asio::io_context& io_context, const ClientSettings LOG(INFO, LOG_TAG) << "DefaultStreamValues::SampleRate: " << oboe::DefaultStreamValues::SampleRate << ", 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. oboe::AudioStreamBuilder builder; - auto result = builder.setSharingMode(oboe::SharingMode::Exclusive) + auto result = builder.setSharingMode(sharing_mode) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setChannelCount(stream->getFormat().channels()) ->setSampleRate(stream->getFormat().rate()) diff --git a/client/player/player.cpp b/client/player/player.cpp index 6972a34f..c345e331 100644 --- a/client/player/player.cpp +++ b/client/player/player.cpp @@ -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) : 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 = "") { + 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"; } diff --git a/client/snapclient.cpp b/client/snapclient.cpp index 767ead87..96935d05 100644 --- a/client/snapclient.cpp +++ b/client/snapclient.cpp @@ -141,7 +141,7 @@ int main(int argc, char** argv) #ifdef HAS_SOXR auto sample_format = op.add>("", "sampleformat", "resample audio stream to ::", ""); #endif -#ifdef HAS_WASAPI +#if defined(HAS_OBOE) || defined(HAS_WASAPI) auto sharing_mode = op.add>("", "sharingmode", "audio mode to use [shared|exclusive]", "shared"); #endif bool hw_mixer_supported = false; @@ -299,29 +299,21 @@ int main(int argc, char** argv) } #endif -#ifdef HAS_WASAPI - if (sharing_mode->is_set()) - { - settings.player.sharing_mode = - (sharing_mode->value() == "exclusive") ? ClientSettings::SharingMode::exclusive : ClientSettings::SharingMode::shared; - } +#if defined(HAS_OBOE) || defined(HAS_WASAPI) + settings.player.sharing_mode = (sharing_mode->value() == "exclusive") ? ClientSettings::SharingMode::exclusive : ClientSettings::SharingMode::shared; #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); - if (mode == "software") - settings.player.mixer.mode = ClientSettings::Mixer::Mode::software; - else if ((mode == "hardware") && hw_mixer_supported) - settings.player.mixer.mode = ClientSettings::Mixer::Mode::hardware; - else if (mode == "script") - settings.player.mixer.mode = ClientSettings::Mixer::Mode::script; - else if (mode == "none") - settings.player.mixer.mode = ClientSettings::Mixer::Mode::none; - else - throw SnapException("Mixer mode not supported: " + mode); - } + string mode = utils::string::split_left(mixer_mode->value(), ':', settings.player.mixer.parameter); + if (mode == "software") + settings.player.mixer.mode = ClientSettings::Mixer::Mode::software; + else if ((mode == "hardware") && hw_mixer_supported) + settings.player.mixer.mode = ClientSettings::Mixer::Mode::hardware; + else if (mode == "script") + settings.player.mixer.mode = ClientSettings::Mixer::Mode::script; + else if (mode == "none") + settings.player.mixer.mode = ClientSettings::Mixer::Mode::none; + else + throw SnapException("Mixer mode not supported: " + mode); boost::asio::io_context io_context; // Construct a signal set registered for process termination. diff --git a/client/stream.cpp b/client/stream.cpp index e99a277f..fa4b7115 100644 --- a/client/stream.cpp +++ b/client/stream.cpp @@ -61,7 +61,7 @@ x = 1,000016667 / (1,000016667 - 1) soxr_ = nullptr; 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_datatype_t in_type = SOXR_INT16_I; diff --git a/common/sample_format.cpp b/common/sample_format.cpp index d48f4a63..df0237ba 100644 --- a/common/sample_format.cpp +++ b/common/sample_format.cpp @@ -49,7 +49,7 @@ SampleFormat::SampleFormat(uint32_t sampleRate, uint16_t bitsPerSample, uint16_t } -string SampleFormat::getFormat() const +string SampleFormat::toString() const { stringstream ss; ss << rate_ << ":" << bits_ << ":" << channels_; diff --git a/common/sample_format.hpp b/common/sample_format.hpp index c490d166..900655f7 100644 --- a/common/sample_format.hpp +++ b/common/sample_format.hpp @@ -41,7 +41,7 @@ public: SampleFormat(const std::string& format); 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(uint32_t rate, uint16_t bits, uint16_t channels); diff --git a/server/encoder/ogg_encoder.cpp b/server/encoder/ogg_encoder.cpp index 4065a939..8cf45b7f 100644 --- a/server/encoder/ogg_encoder.cpp +++ b/server/encoder/ogg_encoder.cpp @@ -220,7 +220,7 @@ void OggEncoder::initEncoder() vorbis_comment_init(&vc_); vorbis_comment_add_tag(&vc_, "TITLE", "SnapStream"); 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 */ vorbis_analysis_init(&vd_, &vi_); diff --git a/server/streamreader/pcm_stream.cpp b/server/streamreader/pcm_stream.cpp index add4795e..ab6c9388 100644 --- a/server/streamreader/pcm_stream.cpp +++ b/server/streamreader/pcm_stream.cpp @@ -50,7 +50,7 @@ PcmStream::PcmStream(PcmListener* pcmListener, boost::asio::io_context& ioc, con if (uri_.query.find(kUriSampleFormat) == uri_.query.end()) throw SnapException("Stream URI must have a sampleformat"); 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()) chunk_ms_ = cpt::stoul(uri_.query[kUriChunkMs]); @@ -97,7 +97,7 @@ const SampleFormat& PcmStream::getSampleFormat() const 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_); active_ = true; }