diff --git a/client/client_settings.hpp b/client/client_settings.hpp index 9e44053e..bf6fa9fd 100644 --- a/client/client_settings.hpp +++ b/client/client_settings.hpp @@ -40,7 +40,8 @@ struct ClientSettings { hardware, software, - script + script, + none }; Mode mode{Mode::software}; diff --git a/client/controller.cpp b/client/controller.cpp index d80add65..f3593114 100644 --- a/client/controller.cpp +++ b/client/controller.cpp @@ -64,20 +64,18 @@ void Controller::getNextMessage() { if (stream_ && decoder_) { - // auto wireChunk = msg::message_cast(std::move(response)); + // execute on the io_context to do the (costly) decoding on another thread (if more than one thread is used) + // boost::asio::post(io_context_, [this, response = std::move(response)]() mutable { auto pcmChunk = msg::message_cast(std::move(response)); pcmChunk->format = sampleFormat_; - // std::make_unique(sampleFormat_, *wireChunk); - // pcmChunk->deserialize(baseMessage, buffer); // LOG(TRACE, LOG_TAG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.getFormat() << "\n"; if (decoder_->decode(pcmChunk.get())) { - // LOG(TRACE, LOG_TAG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->durationMs() - // << ", sec: " << pcmChunk->timestamp.sec << ", usec: " << pcmChunk->timestamp.usec / 1000 << ", type: " << - // pcmChunk->type - // << "\n"; + // LOG(TRACE, LOG_TAG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->durationMs() << ", sec: " << + // pcmChunk->timestamp.sec << ", usec: " << pcmChunk->timestamp.usec / 1000 << ", type: " << pcmChunk->type << "\n"; stream_->addChunk(std::move(pcmChunk)); } + // }); } } else if (response->type == message_type::kTime) @@ -94,8 +92,7 @@ void Controller::getNextMessage() << ", volume: " << serverSettings_->getVolume() << ", muted: " << serverSettings_->isMuted() << "\n"; if (stream_ && player_) { - player_->setVolume(serverSettings_->getVolume() / 100.); - player_->setMute(serverSettings_->isMuted()); + player_->setVolume(serverSettings_->getVolume() / 100., serverSettings_->isMuted()); stream_->setBufferLen(std::max(0, serverSettings_->getBufferMs() - serverSettings_->getLatency() - settings_.player.latency)); } } @@ -181,8 +178,7 @@ void Controller::getNextMessage() // The player class will send the device's volume to the server instead if (settings_.player.mixer.mode != ClientSettings::Mixer::Mode::hardware) { - player_->setVolume(serverSettings_->getVolume() / 100.); - player_->setMute(serverSettings_->isMuted()); + player_->setVolume(serverSettings_->getVolume() / 100., serverSettings_->isMuted()); } } else if (response->type == message_type::kStreamTags) diff --git a/client/player/alsa_player.cpp b/client/player/alsa_player.cpp index 95ec9814..9d2fe5b8 100644 --- a/client/player/alsa_player.cpp +++ b/client/player/alsa_player.cpp @@ -50,39 +50,17 @@ AlsaPlayer::AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings } -void AlsaPlayer::setMute(bool mute) +void AlsaPlayer::setHardwareVolume(double volume, bool muted) { - if (settings_.mixer.mode != ClientSettings::Mixer::Mode::hardware) - { - Player::setMute(mute); - return; - } - - int val = mute ? 0 : 1; - int err = snd_mixer_selem_set_playback_switch(elem_, SND_MIXER_SCHN_MONO, val); - if (err < 0) - LOG(ERROR, LOG_TAG) << "Failed to mute, error: " << snd_strerror(err) << "\n"; -} - - -void AlsaPlayer::setVolume(double volume) -{ - if (settings_.mixer.mode != ClientSettings::Mixer::Mode::hardware) - { - Player::setVolume(volume); - return; - } - std::lock_guard lock(mutex_); - // boost::system::error_code ec; - // sd_.cancel(ec); - // if (ctl_) - // snd_ctl_subscribe_events(ctl_, 0); - last_change_ = std::chrono::steady_clock::now(); try { - int err = 0; + int val = muted ? 0 : 1; + int err = snd_mixer_selem_set_playback_switch(elem_, SND_MIXER_SCHN_MONO, val); + if (err < 0) + LOG(ERROR, LOG_TAG) << "Failed to mute, error: " << snd_strerror(err) << "\n"; + long minv, maxv; if ((err = snd_mixer_selem_get_playback_dB_range(elem_, &minv, &maxv)) == 0) { @@ -90,7 +68,7 @@ void AlsaPlayer::setVolume(double volume) volume = volume * (1 - min_norm) + min_norm; double mixer_volume = 6000.0 * log10(volume) + maxv; - LOG(DEBUG, LOG_TAG) << "Mixer volume range [" << minv << ", " << maxv << "], volume: " << volume << ", mixer volume: " << mixer_volume << "\n"; + LOG(DEBUG, LOG_TAG) << "Mixer playback dB range [" << minv << ", " << maxv << "], volume: " << volume << ", mixer volume: " << mixer_volume << "\n"; if ((err = snd_mixer_selem_set_playback_dB_all(elem_, mixer_volume, 0)) < 0) throw SnapException(std::string("Failed to set playback volume, error: ") + snd_strerror(err)); } @@ -100,7 +78,8 @@ void AlsaPlayer::setVolume(double volume) throw SnapException(std::string("Failed to get playback volume range, error: ") + snd_strerror(err)); auto mixer_volume = volume * (maxv - minv) + minv; - LOG(DEBUG, LOG_TAG) << "Mixer volume range [" << minv << ", " << maxv << "], volume: " << volume << ", mixer volume: " << mixer_volume << "\n"; + LOG(DEBUG, LOG_TAG) << "Mixer playback volume range [" << minv << ", " << maxv << "], volume: " << volume << ", mixer volume: " << mixer_volume + << "\n"; if ((err = snd_mixer_selem_set_playback_volume_all(elem_, mixer_volume)) < 0) throw SnapException(std::string("Failed to set playback volume, error: ") + snd_strerror(err)); } @@ -109,20 +88,15 @@ void AlsaPlayer::setVolume(double volume) { LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n"; } - - // if (ctl_) - // { - // snd_ctl_subscribe_events(ctl_, 1); - // waitForEvent(); - // } } -bool AlsaPlayer::getVolume(double& volume, bool& muted) +bool AlsaPlayer::getHardwareVolume(double& volume, bool& muted) { long vol; int err = 0; + std::lock_guard lock(mutex_); try { while (snd_mixer_handle_events(mixer_) > 0) @@ -153,7 +127,7 @@ bool AlsaPlayer::getVolume(double& volume, bool& muted) } int val; if ((err = snd_mixer_selem_get_playback_switch(elem_, SND_MIXER_SCHN_MONO, &val)) < 0) - return false; + throw SnapException(std::string("Failed to get mute state, error: ") + snd_strerror(err)); muted = (val == 0); LOG(DEBUG, LOG_TAG) << "Get volume, mixer volume range [" << minv << ", " << maxv << "], volume: " << volume << ", muted: " << muted << "\n"; snd_mixer_handle_events(mixer_); @@ -203,7 +177,7 @@ void AlsaPlayer::waitForEvent() { double volume; bool muted; - if (getVolume(volume, muted)) + if (getHardwareVolume(volume, muted)) { LOG(DEBUG, LOG_TAG) << "Volume: " << volume << ", muted: " << muted << "\n"; notifyVolumeChange(volume, muted); diff --git a/client/player/alsa_player.hpp b/client/player/alsa_player.hpp index f8aaf798..50c7985b 100644 --- a/client/player/alsa_player.hpp +++ b/client/player/alsa_player.hpp @@ -39,8 +39,6 @@ public: /// List the system's audio output devices static std::vector pcm_list(void); - void setVolume(double volume) override; - void setMute(bool mute) override; protected: void worker() override; @@ -52,7 +50,9 @@ private: void initMixer(); void uninitMixer(); - bool getVolume(double& volume, bool& muted) override; + bool getHardwareVolume(double& volume, bool& muted) override; + void setHardwareVolume(double volume, bool muted) override; + void waitForEvent(); snd_pcm_t* handle_; diff --git a/client/player/player.cpp b/client/player/player.cpp index 03591f1f..7332624d 100644 --- a/client/player/player.cpp +++ b/client/player/player.cpp @@ -19,11 +19,16 @@ #include #include +#include + #include "common/aixlog.hpp" +#include "common/snap_exception.hpp" +#include "common/str_compat.hpp" #include "player.hpp" using namespace std; +using namespace boost::process; static constexpr auto LOG_TAG = "Player"; @@ -51,7 +56,7 @@ void Player::start() { double volume; bool muted; - if (getVolume(volume, muted)) + if (getHardwareVolume(volume, muted)) { LOG(DEBUG, LOG_TAG) << "Volume: " << volume << ", muted: " << muted << "\n"; notifyVolumeChange(volume, muted); @@ -76,28 +81,33 @@ void Player::worker() } -bool Player::getVolume(double& volume, bool& muted) +void Player::setHardwareVolume(double volume, bool muted) { - std::ignore = volume; - std::ignore = muted; + throw SnapException("Failed to set hardware mixer volume: not supported"); +} + + +bool Player::getHardwareVolume(double& volume, bool& muted) +{ + throw SnapException("Failed to get hardware mixer volume: not supported"); return false; } void Player::adjustVolume(char* buffer, size_t frames) { - // if (settings_.mixer.mode != ClientSettings::Mixer::Mode::software) - // return; - - double volume = volume_; - if (muted_) - volume = 0.; - - const SampleFormat& sampleFormat = stream_->getFormat(); - - if ((volume < 1.0) || (volCorrection_ != 1.)) + double volume = volCorrection_; + // apply volume changes only for software mixer + // for any other mixer, we might still have to apply the volCorrection_ + if (settings_.mixer.mode == ClientSettings::Mixer::Mode::software) { + volume = muted_ ? 0. : volume_; volume *= volCorrection_; + } + + if (volume != 1.0) + { + const SampleFormat& sampleFormat = stream_->getFormat(); if (sampleFormat.sampleSize() == 1) adjustVolume(buffer, frames * sampleFormat.channels(), volume); else if (sampleFormat.sampleSize() == 2) @@ -128,13 +138,31 @@ void Player::setVolume_exp(double volume, double base) } -void Player::setVolume(double volume) -{ - setVolume_exp(volume, 10.); -} - - -void Player::setMute(bool mute) +void Player::setVolume(double volume, bool mute) { + volume_ = volume; muted_ = mute; + if (settings_.mixer.mode == ClientSettings::Mixer::Mode::hardware) + { + setHardwareVolume(volume, muted_); + } + else if (settings_.mixer.mode == ClientSettings::Mixer::Mode::software) + { + if (settings_.mixer.parameter == "poly") + setVolume_poly(volume, 3.); + else + setVolume_exp(volume, 10.); + } + else if (settings_.mixer.mode == ClientSettings::Mixer::Mode::script) + { + try + { + child c(exe = settings_.mixer.parameter, args = {"--volume", cpt::to_string(volume), "--mute", mute ? "true" : "false"}); + c.detach(); + } + catch (const std::exception& e) + { + LOG(ERROR, LOG_TAG) << "Failed to run script '" + settings_.mixer.parameter + "', error: " << e.what() << "\n"; + } + } } diff --git a/client/player/player.hpp b/client/player/player.hpp index 5fdf95a1..fc2fb30e 100644 --- a/client/player/player.hpp +++ b/client/player/player.hpp @@ -46,8 +46,10 @@ public: virtual ~Player(); /// Set audio volume in range [0..1] - virtual void setVolume(double volume); - virtual void setMute(bool mute); + /// @param volume the volume on range [0..1] + /// @param muted muted or not + virtual void setVolume(double volume, bool mute); + /// Called on start, before the first audio sample is sent or any other function is called. /// In case of hardware mixer, it will call getVolume and notify the server about the current volume virtual void start(); @@ -68,8 +70,13 @@ protected: /// get the hardware mixer volume /// @param[out] volume the volume on range [0..1] /// @param[out] muted muted or not - /// @return true on success - virtual bool getVolume(double& volume, bool& muted); + /// @return success or not + virtual bool getHardwareVolume(double& volume, bool& muted); + + /// set the hardware mixer volume + /// @param volume the volume on range [0..1] + /// @param muted muted or not + virtual void setHardwareVolume(double volume, bool muted); void setVolume_poly(double volume, double exp); void setVolume_exp(double volume, double base); diff --git a/client/snapclient.cpp b/client/snapclient.cpp index d49d43c5..b4a7824a 100644 --- a/client/snapclient.cpp +++ b/client/snapclient.cpp @@ -150,9 +150,9 @@ int main(int argc, char** argv) #endif std::shared_ptr> mixer_mode; if (hw_mixer_supported) - mixer_mode = op.add>("", "mixer", "[:]", "software"); + mixer_mode = op.add>("", "mixer", "[:]", "software"); else - mixer_mode = op.add>("", "mixer", "[:]", "software"); + mixer_mode = op.add>("", "mixer", "[:]", "software"); try { @@ -318,6 +318,8 @@ int main(int argc, char** argv) 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); } diff --git a/server/snapserver.cpp b/server/snapserver.cpp index 8abaa9cd..f751d149 100644 --- a/server/snapserver.cpp +++ b/server/snapserver.cpp @@ -221,6 +221,7 @@ int main(int argc, char* argv[]) else throw SnapException("Invalid log sink: " + settings.logging.sink); + // TODO: op vs conf for (const auto& opt : conf.unknown_options()) LOG(WARNING) << "unknown configuration option: " << opt << "\n";