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,6 +157,12 @@ void Controller::getNextMessage()
throw SnapException("No audio player support");
player_->setVolumeCallback([this](double volume, bool muted) {
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<msg::ClientSettings>();
settings->setVolume(static_cast<uint16_t>(volume * 100.));
settings->setMuted(muted);
@ -168,10 +174,16 @@ void Controller::getNextMessage()
return;
}
});
}
});
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());
player_->start();
}
}
else if (response->type == message_type::kStreamTags)
{
@ -194,8 +206,8 @@ void Controller::getNextMessage()
void Controller::sendTimeSyncMessage(int quick_syncs)
{
auto timeReq = std::make_shared<msg::Time>();
clientConnection_->sendRequest<msg::Time>(
timeReq, 2s, [this, quick_syncs](const boost::system::error_code& ec, const std::unique_ptr<msg::Time>& response) mutable {
clientConnection_->sendRequest<msg::Time>(timeReq, 2s, [this, quick_syncs](const boost::system::error_code& ec,
const std::unique_ptr<msg::Time>& response) mutable {
if (ec)
{
LOG(ERROR, LOG_TAG) << "Time sync request failed: " << ec.message() << "\n";
@ -211,8 +223,7 @@ void Controller::sendTimeSyncMessage(int quick_syncs)
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";
LOG(INFO, LOG_TAG) << "diff to server [ms]: " << (float)TimeProvider::getInstance().getDiffToServer<chronos::usec>().count() / 1000.f << "\n";
next = 100us;
}
timer_.expires_after(next);

View file

@ -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> 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<std::mutex> 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<double>(vol) / static_cast<double>(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<pollfd>();
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();
}

View file

@ -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<pollfd> fd_;
std::vector<char> buffer_;
snd_pcm_uframes_t frames_;

View file

@ -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> 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";
}

View file

@ -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
{

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]",
settings.logging.filter);
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 soundcardValue =*/op.add<Value<string>>("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<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 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);
#endif
#ifdef HAS_SOXR
auto sample_format = op.add<Value<string>>("", "sampleformat", "resample audio stream to <rate>:<bits>:<channels>", "");
#endif
#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
// TODO: hardcoded
settings.player.mixer.mode = ClientSettings::Mixer::Mode::hardware;
try
{
op.parse(argc, argv);

View file

@ -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"