Send initial hardware mixer volume to the server

This commit is contained in:
badaix 2020-04-30 10:19:33 +02:00
parent 2217595c6b
commit e8bb8ecdba
7 changed files with 116 additions and 96 deletions

View file

@ -157,21 +157,33 @@ void Controller::getNextMessage()
throw SnapException("No audio player support"); throw SnapException("No audio player support");
player_->setVolumeCallback([this](double volume, bool muted) { player_->setVolumeCallback([this](double volume, bool muted) {
auto settings = std::make_shared<msg::ClientSettings>(); static double last_volume(-1);
settings->setVolume(static_cast<uint16_t>(volume * 100.)); static bool last_muted(true);
settings->setMuted(muted); if ((volume != last_volume) || (last_muted != muted))
clientConnection_->send(settings, [this](const boost::system::error_code& ec) { {
if (ec) last_volume = volume;
{ last_muted = muted;
LOG(ERROR, LOG_TAG) << "Failed to send client settings, error: " << ec.message() << "\n"; auto settings = std::make_shared<msg::ClientSettings>();
reconnect(); settings->setVolume(static_cast<uint16_t>(volume * 100.));
return; 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(); 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) else if (response->type == message_type::kStreamTags)
{ {
@ -194,35 +206,34 @@ void Controller::getNextMessage()
void Controller::sendTimeSyncMessage(int quick_syncs) void Controller::sendTimeSyncMessage(int quick_syncs)
{ {
auto timeReq = std::make_shared<msg::Time>(); auto timeReq = std::make_shared<msg::Time>();
clientConnection_->sendRequest<msg::Time>( clientConnection_->sendRequest<msg::Time>(timeReq, 2s, [this, quick_syncs](const boost::system::error_code& ec,
timeReq, 2s, [this, quick_syncs](const boost::system::error_code& ec, const std::unique_ptr<msg::Time>& response) mutable { const std::unique_ptr<msg::Time>& response) mutable {
if (ec) if (ec)
{ {
LOG(ERROR, LOG_TAG) << "Time sync request failed: " << ec.message() << "\n"; LOG(ERROR, LOG_TAG) << "Time sync request failed: " << ec.message() << "\n";
reconnect(); reconnect();
return; return;
} }
else else
{ {
TimeProvider::getInstance().setDiff(response->latency, response->received - response->sent); TimeProvider::getInstance().setDiff(response->latency, response->received - response->sent);
} }
std::chrono::microseconds next = 1s; std::chrono::microseconds next = 1s;
if (quick_syncs > 0) if (quick_syncs > 0)
{
if (--quick_syncs == 0)
LOG(INFO, LOG_TAG) << "diff to server [ms]: " << (float)TimeProvider::getInstance().getDiffToServer<chronos::usec>().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) sendTimeSyncMessage(quick_syncs);
LOG(INFO, LOG_TAG) << "diff to server [ms]: " << (float)TimeProvider::getInstance().getDiffToServer<chronos::usec>().count() / 1000.f
<< "\n";
next = 100us;
} }
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) void Controller::browseMdns(const MdnsHandler& handler)

View file

@ -29,7 +29,7 @@ using namespace std;
static constexpr auto LOG_TAG = "Alsa"; static constexpr auto LOG_TAG = "Alsa";
AlsaPlayer::AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream) AlsaPlayer::AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> 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) void AlsaPlayer::setVolume(double volume)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
int err = 0;
snd_mixer_elem_t* elem(nullptr);
snd_mixer_t* mixer(nullptr);
// boost::system::error_code ec; // boost::system::error_code ec;
// sd_.cancel(ec); // sd_.cancel(ec);
// if (ctl_) // if (ctl_)
@ -48,23 +45,21 @@ void AlsaPlayer::setVolume(double volume)
last_change_ = std::chrono::steady_clock::now(); last_change_ = std::chrono::steady_clock::now();
try try
{ {
openMixer(&elem, &mixer); int err = 0;
long minv, maxv; 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)); throw SnapException(std::string("Failed to get playback volume range, error: ") + snd_strerror(err));
auto mixer_volume = volume * (maxv - minv) + minv; 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 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)); throw SnapException(std::string("Failed to set playback volume, error: ") + snd_strerror(err));
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n"; LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n";
} }
if (mixer != nullptr)
snd_mixer_close(mixer);
// if (ctl_) // if (ctl_)
// { // {
// snd_ctl_subscribe_events(ctl_, 1); // snd_ctl_subscribe_events(ctl_, 1);
@ -78,68 +73,35 @@ bool AlsaPlayer::getVolume(double& volume, bool& muted)
long vol; long vol;
int err = 0; int err = 0;
snd_mixer_elem_t* elem(nullptr);
snd_mixer_t* mixer(nullptr);
try try
{ {
openMixer(&elem, &mixer); snd_mixer_handle_events(mixer_);
if ((err = snd_mixer_selem_get_playback_volume(elem_, SND_MIXER_SCHN_MONO, &vol)) < 0)
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)); throw SnapException(std::string("Failed to get playback volume, error: ") + snd_strerror(err));
// make the value bound to 1 // make the value bound to 1
long minv, maxv; 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)); throw SnapException(std::string("Failed to get playback volume range, error: ") + snd_strerror(err));
vol -= minv; vol -= minv;
maxv = maxv - minv; maxv = maxv - minv;
volume = static_cast<double>(vol) / static_cast<double>(maxv); volume = static_cast<double>(vol) / static_cast<double>(maxv);
int val; 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; return false;
muted = (val == 0); muted = (val == 0);
LOG(DEBUG, LOG_TAG) << "Get volume, mixer volume range [" << minv << ", " << maxv << "], volume: " << volume << ", muted: " << muted << "\n"; 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; return true;
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
if (mixer != nullptr)
snd_mixer_close(mixer);
LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n"; LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n";
return false; 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() void AlsaPlayer::waitForEvent()
{ {
sd_.async_wait(boost::asio::posix::stream_descriptor::wait_read, [this](const boost::system::error_code& ec) { 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<pollfd>(); fd_ = std::make_unique<pollfd>();
snd_ctl_poll_descriptors(ctl_, fd_.get(), 1); 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); sd_ = boost::asio::posix::stream_descriptor(io_context_, fd_->fd);
waitForEvent(); waitForEvent();
} }
@ -315,7 +297,7 @@ void AlsaPlayer::initAlsa()
// snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_); // snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_);
snd_pcm_sw_params(handle_, swparams); snd_pcm_sw_params(handle_, swparams);
// initMixer(); initMixer();
} }

View file

@ -49,13 +49,16 @@ private:
void initAlsa(); void initAlsa();
void uninitAlsa(); void uninitAlsa();
void initMixer(); 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(); void waitForEvent();
snd_pcm_t* handle_; snd_pcm_t* handle_;
snd_ctl_t* ctl_; snd_ctl_t* ctl_;
snd_mixer_t* mixer_;
snd_mixer_elem_t* elem_;
std::unique_ptr<pollfd> fd_; std::unique_ptr<pollfd> fd_;
std::vector<char> buffer_; std::vector<char> buffer_;
snd_pcm_uframes_t frames_; snd_pcm_uframes_t frames_;

View file

@ -25,6 +25,7 @@
using namespace std; 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> 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)
@ -43,6 +44,19 @@ void Player::start()
active_ = true; active_ = true;
if (needsThread()) if (needsThread())
playerThread_ = thread(&Player::worker, this); 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) void Player::adjustVolume(char* buffer, size_t frames)
{ {
// if (settings_.mixer.mode != ClientSettings::Mixer::Mode::software) // 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) void Player::setVolume_poly(double volume, double exp)
{ {
volume_ = std::pow(volume, 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 = M_E;
// double base = 10.; // double base = 10.;
volume_ = (pow(base, volume) - 1) / (base - 1); volume_ = (pow(base, volume) - 1) / (base - 1);
LOG(DEBUG) << "setVolume exp: " << volume << " => " << volume_ << "\n"; LOG(DEBUG, LOG_TAG) << "setVolume exp: " << volume << " => " << volume_ << "\n";
} }

View file

@ -62,6 +62,7 @@ protected:
void setVolume_poly(double volume, double exp); void setVolume_poly(double volume, double exp);
void setVolume_exp(double volume, double base); void setVolume_exp(double volume, double base);
virtual bool getVolume(double& volume, bool& muted);
void adjustVolume(char* buffer, size_t frames); void adjustVolume(char* buffer, size_t frames);
void notifyVolumeChange(double volume, bool muted) const void notifyVolumeChange(double volume, bool muted) const
{ {

View file

@ -120,7 +120,7 @@ int main(int argc, char** argv)
"", "logfilter", "log filter <tag>:<level>[,<tag>:<level>]* with tag = * or <log tag> and level = [trace,debug,info,notice,warning,error,fatal]", "", "logfilter", "log filter <tag>:<level>[,<tag>:<level>]* with tag = * or <log tag> and level = [trace,debug,info,notice,warning,error,fatal]",
settings.logging.filter); settings.logging.filter);
auto versionSwitch = op.add<Switch>("v", "version", "show version number"); auto versionSwitch = op.add<Switch>("v", "version", "show version number");
#if defined(HAS_ALSA) || defined(WINDOWS) #if defined(HAS_ALSA) || defined(HAS_WASAPI)
auto listSwitch = op.add<Switch>("l", "list", "list PCM devices"); auto listSwitch = op.add<Switch>("l", "list", "list PCM devices");
/*auto soundcardValue =*/op.add<Value<string>>("s", "soundcard", "index or name of the pcm device", "default", &pcm_device); /*auto soundcardValue =*/op.add<Value<string>>("s", "soundcard", "index or name of the pcm device", "default", &pcm_device);
#endif #endif
@ -135,16 +135,19 @@ int main(int argc, char** argv)
/*auto latencyValue =*/op.add<Value<int>>("", "latency", "latency of the PCM device", 0, &settings.player.latency); /*auto latencyValue =*/op.add<Value<int>>("", "latency", "latency of the PCM device", 0, &settings.player.latency);
/*auto instanceValue =*/op.add<Value<size_t>>("i", "instance", "instance id", 1, &settings.instance); /*auto instanceValue =*/op.add<Value<size_t>>("i", "instance", "instance id", 1, &settings.instance);
/*auto hostIdValue =*/op.add<Value<string>>("", "hostID", "unique host id", "", &settings.host_id); /*auto hostIdValue =*/op.add<Value<string>>("", "hostID", "unique host id", "", &settings.host_id);
#ifdef ANDROID #if defined(HAS_OBOE) && defined(HAS_OPENSL)
op.add<Value<string>>("", "player", "audio backend", "", &settings.player.player_name); op.add<Value<string>>("", "player", "audio backend", "", &settings.player.player_name);
#endif #endif
#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 #ifdef 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
// TODO: hardcoded
settings.player.mixer.mode = ClientSettings::Mixer::Mode::hardware;
try try
{ {
op.parse(argc, argv); op.parse(argc, argv);

View file

@ -22,10 +22,10 @@
#include "client_settings.hpp" #include "client_settings.hpp"
#include "codec_header.hpp" #include "codec_header.hpp"
#include "hello.hpp" #include "hello.hpp"
#include "pcm_chunk.hpp"
#include "server_settings.hpp" #include "server_settings.hpp"
#include "stream_tags.hpp" #include "stream_tags.hpp"
#include "time.hpp" #include "time.hpp"
#include "pcm_chunk.hpp"
#include "common/str_compat.hpp" #include "common/str_compat.hpp"
#include "common/utils.hpp" #include "common/utils.hpp"
@ -79,8 +79,8 @@ static std::unique_ptr<BaseMessage> createMessage(const BaseMessage& base_messag
case kTime: case kTime:
return createMessage<Time>(base_message, buffer); return createMessage<Time>(base_message, buffer);
case kWireChunk: case kWireChunk:
// this is kind of cheated to safe the convertion from WireChunk to PcmChunk // this is kind of cheated to safe the convertion from WireChunk to PcmChunk
// the user of the factory must be aware that a PcmChunk will be created // the user of the factory must be aware that a PcmChunk will be created
return createMessage<PcmChunk>(base_message, buffer); return createMessage<PcmChunk>(base_message, buffer);
case kClientSettings: case kClientSettings:
return createMessage<ClientSettings>(base_message, buffer); return createMessage<ClientSettings>(base_message, buffer);