diff --git a/client/controller.cpp b/client/controller.cpp index 1fffa10a..8cbe80f7 100644 --- a/client/controller.cpp +++ b/client/controller.cpp @@ -157,21 +157,33 @@ void Controller::getNextMessage() throw SnapException("No audio player support"); player_->setVolumeCallback([this](double volume, bool muted) { - auto settings = std::make_shared(); - settings->setVolume(static_cast(volume * 100.)); - settings->setMuted(muted); - clientConnection_->send(settings, [this](const boost::system::error_code& ec) { - if (ec) - { - LOG(ERROR, LOG_TAG) << "Failed to send client settings, error: " << ec.message() << "\n"; - reconnect(); - return; - } - }); + static double last_volume(-1); + static bool last_muted(true); + if ((volume != last_volume) || (last_muted != muted)) + { + last_volume = volume; + last_muted = muted; + auto settings = std::make_shared(); + settings->setVolume(static_cast(volume * 100.)); + settings->setMuted(muted); + clientConnection_->send(settings, [this](const boost::system::error_code& ec) { + if (ec) + { + LOG(ERROR, LOG_TAG) << "Failed to send client settings, error: " << ec.message() << "\n"; + reconnect(); + return; + } + }); + } }); - player_->setVolume(serverSettings_->getVolume() / 100.); - player_->setMute(serverSettings_->isMuted()); player_->start(); + // Don't change the initial hardware mixer volume on the user's device. + // 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()); + } } else if (response->type == message_type::kStreamTags) { @@ -194,35 +206,34 @@ void Controller::getNextMessage() void Controller::sendTimeSyncMessage(int quick_syncs) { auto timeReq = std::make_shared(); - clientConnection_->sendRequest( - timeReq, 2s, [this, quick_syncs](const boost::system::error_code& ec, const std::unique_ptr& response) mutable { - if (ec) - { - LOG(ERROR, LOG_TAG) << "Time sync request failed: " << ec.message() << "\n"; - reconnect(); - return; - } - else - { - TimeProvider::getInstance().setDiff(response->latency, response->received - response->sent); - } + clientConnection_->sendRequest(timeReq, 2s, [this, quick_syncs](const boost::system::error_code& ec, + const std::unique_ptr& response) mutable { + if (ec) + { + LOG(ERROR, LOG_TAG) << "Time sync request failed: " << ec.message() << "\n"; + reconnect(); + return; + } + else + { + TimeProvider::getInstance().setDiff(response->latency, response->received - response->sent); + } - std::chrono::microseconds next = 1s; - if (quick_syncs > 0) + std::chrono::microseconds next = 1s; + if (quick_syncs > 0) + { + if (--quick_syncs == 0) + LOG(INFO, LOG_TAG) << "diff to server [ms]: " << (float)TimeProvider::getInstance().getDiffToServer().count() / 1000.f << "\n"; + next = 100us; + } + timer_.expires_after(next); + timer_.async_wait([this, quick_syncs](const boost::system::error_code& ec) { + if (!ec) { - if (--quick_syncs == 0) - LOG(INFO, LOG_TAG) << "diff to server [ms]: " << (float)TimeProvider::getInstance().getDiffToServer().count() / 1000.f - << "\n"; - next = 100us; + sendTimeSyncMessage(quick_syncs); } - timer_.expires_after(next); - timer_.async_wait([this, quick_syncs](const boost::system::error_code& ec) { - if (!ec) - { - sendTimeSyncMessage(quick_syncs); - } - }); }); + }); } void Controller::browseMdns(const MdnsHandler& handler) diff --git a/client/player/alsa_player.cpp b/client/player/alsa_player.cpp index 4569114d..dd1dc351 100644 --- a/client/player/alsa_player.cpp +++ b/client/player/alsa_player.cpp @@ -29,7 +29,7 @@ using namespace std; static constexpr auto LOG_TAG = "Alsa"; AlsaPlayer::AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr stream) - : Player(io_context, settings, stream), handle_(nullptr), ctl_(nullptr), sd_(io_context) + : Player(io_context, settings, stream), handle_(nullptr), ctl_(nullptr), mixer_(nullptr), elem_(nullptr), sd_(io_context) { } @@ -37,9 +37,6 @@ AlsaPlayer::AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings void AlsaPlayer::setVolume(double volume) { std::lock_guard lock(mutex_); - int err = 0; - snd_mixer_elem_t* elem(nullptr); - snd_mixer_t* mixer(nullptr); // boost::system::error_code ec; // sd_.cancel(ec); // if (ctl_) @@ -48,23 +45,21 @@ void AlsaPlayer::setVolume(double volume) last_change_ = std::chrono::steady_clock::now(); try { - openMixer(&elem, &mixer); - + int err = 0; long minv, maxv; - if ((err = snd_mixer_selem_get_playback_volume_range(elem, &minv, &maxv)) < 0) + if ((err = snd_mixer_selem_get_playback_volume_range(elem_, &minv, &maxv)) < 0) 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"; - if ((err = snd_mixer_selem_set_playback_volume_all(elem, mixer_volume)) < 0) + 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)); } catch (const std::exception& e) { LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n"; } - if (mixer != nullptr) - snd_mixer_close(mixer); + // if (ctl_) // { // snd_ctl_subscribe_events(ctl_, 1); @@ -78,68 +73,35 @@ bool AlsaPlayer::getVolume(double& volume, bool& muted) long vol; int err = 0; - snd_mixer_elem_t* elem(nullptr); - snd_mixer_t* mixer(nullptr); - try { - openMixer(&elem, &mixer); - - if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &vol)) < 0) + snd_mixer_handle_events(mixer_); + if ((err = snd_mixer_selem_get_playback_volume(elem_, SND_MIXER_SCHN_MONO, &vol)) < 0) throw SnapException(std::string("Failed to get playback volume, error: ") + snd_strerror(err)); // make the value bound to 1 long minv, maxv; - if ((err = snd_mixer_selem_get_playback_volume_range(elem, &minv, &maxv)) < 0) + if ((err = snd_mixer_selem_get_playback_volume_range(elem_, &minv, &maxv)) < 0) throw SnapException(std::string("Failed to get playback volume range, error: ") + snd_strerror(err)); vol -= minv; maxv = maxv - minv; volume = static_cast(vol) / static_cast(maxv); int val; - if ((err = snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &val)) < 0) + if ((err = snd_mixer_selem_get_playback_switch(elem_, SND_MIXER_SCHN_MONO, &val)) < 0) return false; muted = (val == 0); LOG(DEBUG, LOG_TAG) << "Get volume, mixer volume range [" << minv << ", " << maxv << "], volume: " << volume << ", muted: " << muted << "\n"; - if (mixer != nullptr) - snd_mixer_close(mixer); return true; } catch (const std::exception& e) { - if (mixer != nullptr) - snd_mixer_close(mixer); LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n"; return false; } } -void AlsaPlayer::openMixer(snd_mixer_elem_t** elem, snd_mixer_t** mixer) -{ - snd_mixer_selem_id_t* sid; - - snd_mixer_selem_id_alloca(&sid); - std::string mix_name = "Master"; - int mix_index = 0; - // sets simple-mixer index and name - snd_mixer_selem_id_set_index(sid, mix_index); - snd_mixer_selem_id_set_name(sid, mix_name.c_str()); - - int err; - if ((err = snd_mixer_open(mixer, 0)) < 0) - throw SnapException(std::string("Failed to open mixer, error: ") + snd_strerror(err)); - if ((err = snd_mixer_attach(*mixer, settings_.pcm_device.name.c_str())) < 0) - throw SnapException("Failed to attach mixer to " + settings_.pcm_device.name + ", error: " + snd_strerror(err)); - if ((err = snd_mixer_selem_register(*mixer, NULL, NULL)) < 0) - throw SnapException(std::string("Failed to register selem, error: ") + snd_strerror(err)); - if ((err = snd_mixer_load(*mixer)) < 0) - throw SnapException(std::string("Failed to load mixer, error: ") + snd_strerror(err)); - *elem = snd_mixer_find_selem(*mixer, sid); - if (!elem) - throw SnapException(std::string("Failed to find selem, error: ") + snd_strerror(err)); -} - void AlsaPlayer::waitForEvent() { sd_.async_wait(boost::asio::posix::stream_descriptor::wait_read, [this](const boost::system::error_code& ec) { @@ -192,6 +154,26 @@ void AlsaPlayer::initMixer() fd_ = std::make_unique(); snd_ctl_poll_descriptors(ctl_, fd_.get(), 1); + snd_mixer_selem_id_t* sid; + snd_mixer_selem_id_alloca(&sid); + std::string mix_name = "Master"; + int mix_index = 0; + // sets simple-mixer index and name + snd_mixer_selem_id_set_index(sid, mix_index); + snd_mixer_selem_id_set_name(sid, mix_name.c_str()); + + if ((err = snd_mixer_open(&mixer_, 0)) < 0) + throw SnapException(std::string("Failed to open mixer, error: ") + snd_strerror(err)); + if ((err = snd_mixer_attach(mixer_, settings_.pcm_device.name.c_str())) < 0) + throw SnapException("Failed to attach mixer to " + settings_.pcm_device.name + ", error: " + snd_strerror(err)); + if ((err = snd_mixer_selem_register(mixer_, NULL, NULL)) < 0) + throw SnapException(std::string("Failed to register selem, error: ") + snd_strerror(err)); + if ((err = snd_mixer_load(mixer_)) < 0) + throw SnapException(std::string("Failed to load mixer, error: ") + snd_strerror(err)); + elem_ = snd_mixer_find_selem(mixer_, sid); + if (!elem_) + throw SnapException(std::string("Failed to find selem, error: ") + snd_strerror(err)); + sd_ = boost::asio::posix::stream_descriptor(io_context_, fd_->fd); waitForEvent(); } @@ -315,7 +297,7 @@ void AlsaPlayer::initAlsa() // snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_); snd_pcm_sw_params(handle_, swparams); - // initMixer(); + initMixer(); } diff --git a/client/player/alsa_player.hpp b/client/player/alsa_player.hpp index 40bfc2f7..22b47258 100644 --- a/client/player/alsa_player.hpp +++ b/client/player/alsa_player.hpp @@ -49,13 +49,16 @@ private: void initAlsa(); void uninitAlsa(); void initMixer(); - bool getVolume(double& volume, bool& muted); - void openMixer(snd_mixer_elem_t** elem, snd_mixer_t** mixer); + + bool getVolume(double& volume, bool& muted) override; void waitForEvent(); snd_pcm_t* handle_; snd_ctl_t* ctl_; + snd_mixer_t* mixer_; + snd_mixer_elem_t* elem_; + std::unique_ptr fd_; std::vector buffer_; snd_pcm_uframes_t frames_; diff --git a/client/player/player.cpp b/client/player/player.cpp index 86888f3c..01fade3f 100644 --- a/client/player/player.cpp +++ b/client/player/player.cpp @@ -25,6 +25,7 @@ using namespace std; +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) @@ -43,6 +44,19 @@ void Player::start() active_ = true; if (needsThread()) playerThread_ = thread(&Player::worker, this); + + // If hardware mixer is used, send the initial volume to the server, because this is + // the volume that is configured by the user on his local device + if (settings_.mixer.mode == ClientSettings::Mixer::Mode::hardware) + { + double volume; + bool muted; + if (getVolume(volume, muted)) + { + LOG(DEBUG, LOG_TAG) << "Volume: " << volume << ", muted: " << muted << "\n"; + notifyVolumeChange(volume, muted); + } + } } @@ -62,6 +76,12 @@ void Player::worker() } +bool Player::getVolume(double& volume, bool& muted) +{ + return false; +} + + void Player::adjustVolume(char* buffer, size_t frames) { // if (settings_.mixer.mode != ClientSettings::Mixer::Mode::software) @@ -92,7 +112,7 @@ void Player::adjustVolume(char* buffer, size_t frames) void Player::setVolume_poly(double volume, double exp) { volume_ = std::pow(volume, exp); - LOG(DEBUG) << "setVolume poly: " << volume << " => " << volume_ << "\n"; + LOG(DEBUG, LOG_TAG) << "setVolume poly: " << volume << " => " << volume_ << "\n"; } @@ -102,7 +122,7 @@ void Player::setVolume_exp(double volume, double base) // double base = M_E; // double base = 10.; volume_ = (pow(base, volume) - 1) / (base - 1); - LOG(DEBUG) << "setVolume exp: " << volume << " => " << volume_ << "\n"; + LOG(DEBUG, LOG_TAG) << "setVolume exp: " << volume << " => " << volume_ << "\n"; } diff --git a/client/player/player.hpp b/client/player/player.hpp index 61baba85..c6b1cebb 100644 --- a/client/player/player.hpp +++ b/client/player/player.hpp @@ -62,6 +62,7 @@ protected: void setVolume_poly(double volume, double exp); void setVolume_exp(double volume, double base); + virtual bool getVolume(double& volume, bool& muted); void adjustVolume(char* buffer, size_t frames); void notifyVolumeChange(double volume, bool muted) const { diff --git a/client/snapclient.cpp b/client/snapclient.cpp index b7ed1c13..250f1222 100644 --- a/client/snapclient.cpp +++ b/client/snapclient.cpp @@ -120,7 +120,7 @@ int main(int argc, char** argv) "", "logfilter", "log filter :[,:]* with tag = * or and level = [trace,debug,info,notice,warning,error,fatal]", settings.logging.filter); auto versionSwitch = op.add("v", "version", "show version number"); -#if defined(HAS_ALSA) || defined(WINDOWS) +#if defined(HAS_ALSA) || defined(HAS_WASAPI) auto listSwitch = op.add("l", "list", "list PCM devices"); /*auto soundcardValue =*/op.add>("s", "soundcard", "index or name of the pcm device", "default", &pcm_device); #endif @@ -135,16 +135,19 @@ int main(int argc, char** argv) /*auto latencyValue =*/op.add>("", "latency", "latency of the PCM device", 0, &settings.player.latency); /*auto instanceValue =*/op.add>("i", "instance", "instance id", 1, &settings.instance); /*auto hostIdValue =*/op.add>("", "hostID", "unique host id", "", &settings.host_id); -#ifdef ANDROID +#if defined(HAS_OBOE) && defined(HAS_OPENSL) op.add>("", "player", "audio backend", "", &settings.player.player_name); #endif #ifdef HAS_SOXR auto sample_format = op.add>("", "sampleformat", "resample audio stream to ::", ""); #endif #ifdef HAS_WASAPI - auto sharing_mode = op.add>("", "sharingmode", "audio mode to use [shared/exclusive]", "shared"); + auto sharing_mode = op.add>("", "sharingmode", "audio mode to use [shared|exclusive]", "shared"); #endif + // TODO: hardcoded + settings.player.mixer.mode = ClientSettings::Mixer::Mode::hardware; + try { op.parse(argc, argv); diff --git a/common/message/factory.hpp b/common/message/factory.hpp index babc394a..b59ecd32 100644 --- a/common/message/factory.hpp +++ b/common/message/factory.hpp @@ -22,10 +22,10 @@ #include "client_settings.hpp" #include "codec_header.hpp" #include "hello.hpp" +#include "pcm_chunk.hpp" #include "server_settings.hpp" #include "stream_tags.hpp" #include "time.hpp" -#include "pcm_chunk.hpp" #include "common/str_compat.hpp" #include "common/utils.hpp" @@ -79,8 +79,8 @@ static std::unique_ptr createMessage(const BaseMessage& base_messag case kTime: return createMessage