Add support for PulseAudio properties

This commit is contained in:
badaix 2021-03-16 09:03:50 +01:00
parent be9c15cba6
commit 432e4dfad0
5 changed files with 57 additions and 11 deletions

View file

@ -116,7 +116,7 @@ Available audio backends are configured using the `--player` command line parame
| Backend | OS | Description | Parameters |
| --------- | ------- | ------------ | ---------- |
| alsa | Linux | ALSA | `buffer_time=<total buffer size [ms]>` (default 80, min 10)<br />`fragments=<number of buffers>` (default 4, min 2) |
| pulse | Linux | PulseAudio | `buffer_time=<buffer size [ms]>` (default 100, min 10)<br />`server=<PulseAudio server>` - default not-set: use the default server |
| pulse | Linux | PulseAudio | `buffer_time=<buffer size [ms]>` (default 100, min 10)<br />`server=<PulseAudio server>` - default not-set: use the default server<br />`property=<key>=<value>` set PA property, can be used multiple times (default `media.role=music`) |
| oboe | Android | Oboe, using OpenSL ES on Android 4.1 and AAudio on 8.1 | |
| opensl | Android | OpenSL ES | |
| coreaudio | macOS | Core Audio | |

View file

@ -19,6 +19,8 @@
#include <cassert>
#include <iostream>
#include <pulse/proplist.h>
#include "common/aixlog.hpp"
#include "common/snap_exception.hpp"
#include "common/str_compat.hpp"
@ -133,13 +135,30 @@ vector<PcmDevice> PulsePlayer::pcm_list(const std::string& parameter)
PulsePlayer::PulsePlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream)
: Player(io_context, settings, stream), latency_(BUFFER_TIME), pa_ml_(nullptr), pa_ctx_(nullptr), playstream_(nullptr), server_(boost::none)
: Player(io_context, settings, stream), latency_(BUFFER_TIME), pa_ml_(nullptr), pa_ctx_(nullptr), playstream_(nullptr), proplist_(nullptr),
server_(boost::none)
{
auto params = utils::string::split_pairs(settings.parameter, ',', '=');
auto params = utils::string::split_pairs_to_container<std::vector<std::string>>(settings.parameter, ',', '=');
if (params.find("buffer_time") != params.end())
latency_ = std::chrono::milliseconds(std::max(cpt::stoi(params["buffer_time"]), 10));
latency_ = std::chrono::milliseconds(std::max(cpt::stoi(params["buffer_time"].front()), 10));
if (params.find("server") != params.end())
server_ = params["server"];
server_ = params["server"].front();
properties_[PA_PROP_MEDIA_ROLE] = "music";
if (params.find("property") != params.end())
{
for (const auto& p : params["property"])
{
std::string value;
std::string key = utils::string::split_left(p, '=', value);
if (!key.empty())
properties_[key] = value;
}
}
for (const auto& property : properties_)
{
if (!property.second.empty())
LOG(INFO, LOG_TAG) << "Setting property \"" << property.first << "\" to \"" << property.second << "\"\n";
}
LOG(INFO, LOG_TAG) << "Using buffer_time: " << latency_.count() / 1000 << " ms, server: " << server_.value_or("default") << "\n";
}
@ -337,7 +356,15 @@ void PulsePlayer::start()
pa_ready_ = 0;
pa_ml_ = pa_mainloop_new();
pa_mainloop_api* pa_mlapi = pa_mainloop_get_api(pa_ml_);
pa_ctx_ = pa_context_new(pa_mlapi, "Snapcast");
proplist_ = pa_proplist_new();
for (const auto& property : properties_)
{
if (!property.second.empty())
pa_proplist_sets(proplist_, property.first.c_str(), property.second.c_str());
}
pa_ctx_ = pa_context_new_with_proplist(pa_mlapi, "Snapcast", proplist_);
const char* server = server_.has_value() ? server_.value().c_str() : nullptr;
if (pa_context_connect(pa_ctx_, server, PA_CONTEXT_NOFLAGS, nullptr) < 0)
@ -476,6 +503,12 @@ void PulsePlayer::stop()
pa_stream_unref(playstream_);
playstream_ = nullptr;
}
if (proplist_ != nullptr)
{
pa_proplist_free(proplist_);
proplist_ = nullptr;
}
}
} // namespace player

View file

@ -73,7 +73,9 @@ protected:
pa_mainloop* pa_ml_;
pa_context* pa_ctx_;
pa_stream* playstream_;
pa_proplist* proplist_;
boost::optional<std::string> server_;
std::map<std::string, std::string> properties_;
// cache of the last volume change
std::chrono::time_point<std::chrono::steady_clock> last_change_;

View file

@ -363,7 +363,8 @@ int main(int argc, char** argv)
{
cout << "Options are a comma separated list of:\n"
<< " \"buffer_time=<buffer size [ms]>\" - default 100, min 10\n"
<< " \"server=<PulseAudio server>\" - default not-set: use the default server\n";
<< " \"server=<PulseAudio server>\" - default not-set: use the default server\n"
<< " \"property=<key>=<value>\" - can be set multiple times, default 'media.role=music'\n";
}
#endif
#ifdef HAS_ALSA

View file

@ -143,10 +143,10 @@ static std::vector<std::string> split(const std::string& s, char delim)
return elems;
}
static std::map<std::string, std::string> split_pairs(const std::string& s, char pair_delim, char key_value_delim)
template <typename T>
static std::map<std::string, T> split_pairs_to_container(const std::string& s, char pair_delim, char key_value_delim)
{
std::map<std::string, std::string> result;
std::map<std::string, T> result;
auto keyValueList = split(s, pair_delim);
for (auto& kv : keyValueList)
{
@ -155,13 +155,23 @@ static std::map<std::string, std::string> split_pairs(const std::string& s, char
{
std::string key = trim_copy(kv.substr(0, pos));
std::string value = trim_copy(kv.substr(pos + 1));
result[key] = value;
result[key].push_back(std::move(value));
}
}
return result;
}
static std::map<std::string, std::string> split_pairs(const std::string& s, char pair_delim, char key_value_delim)
{
std::map<std::string, std::string> result;
auto pairs = split_pairs_to_container<std::vector<std::string>>(s, pair_delim, key_value_delim);
for (auto& pair : pairs)
result[pair.first] = *pair.second.begin();
return result;
}
} // namespace string
} // namespace utils