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 | | 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) | | 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 | | | oboe | Android | Oboe, using OpenSL ES on Android 4.1 and AAudio on 8.1 | |
| opensl | Android | OpenSL ES | | | opensl | Android | OpenSL ES | |
| coreaudio | macOS | Core Audio | | | coreaudio | macOS | Core Audio | |

View file

@ -19,6 +19,8 @@
#include <cassert> #include <cassert>
#include <iostream> #include <iostream>
#include <pulse/proplist.h>
#include "common/aixlog.hpp" #include "common/aixlog.hpp"
#include "common/snap_exception.hpp" #include "common/snap_exception.hpp"
#include "common/str_compat.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) 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()) 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()) 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"; 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_ready_ = 0;
pa_ml_ = pa_mainloop_new(); pa_ml_ = pa_mainloop_new();
pa_mainloop_api* pa_mlapi = pa_mainloop_get_api(pa_ml_); 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; const char* server = server_.has_value() ? server_.value().c_str() : nullptr;
if (pa_context_connect(pa_ctx_, server, PA_CONTEXT_NOFLAGS, nullptr) < 0) if (pa_context_connect(pa_ctx_, server, PA_CONTEXT_NOFLAGS, nullptr) < 0)
@ -476,6 +503,12 @@ void PulsePlayer::stop()
pa_stream_unref(playstream_); pa_stream_unref(playstream_);
playstream_ = nullptr; playstream_ = nullptr;
} }
if (proplist_ != nullptr)
{
pa_proplist_free(proplist_);
proplist_ = nullptr;
}
} }
} // namespace player } // namespace player

View file

@ -73,7 +73,9 @@ protected:
pa_mainloop* pa_ml_; pa_mainloop* pa_ml_;
pa_context* pa_ctx_; pa_context* pa_ctx_;
pa_stream* playstream_; pa_stream* playstream_;
pa_proplist* proplist_;
boost::optional<std::string> server_; boost::optional<std::string> server_;
std::map<std::string, std::string> properties_;
// cache of the last volume change // cache of the last volume change
std::chrono::time_point<std::chrono::steady_clock> last_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" cout << "Options are a comma separated list of:\n"
<< " \"buffer_time=<buffer size [ms]>\" - default 100, min 10\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 #endif
#ifdef HAS_ALSA #ifdef HAS_ALSA

View file

@ -143,10 +143,10 @@ static std::vector<std::string> split(const std::string& s, char delim)
return elems; return elems;
} }
template <typename T>
static std::map<std::string, std::string> split_pairs(const std::string& s, char pair_delim, char key_value_delim) 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); auto keyValueList = split(s, pair_delim);
for (auto& kv : keyValueList) 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 key = trim_copy(kv.substr(0, pos));
std::string value = trim_copy(kv.substr(pos + 1)); std::string value = trim_copy(kv.substr(pos + 1));
result[key] = value; result[key].push_back(std::move(value));
} }
} }
return result; 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 string
} // namespace utils } // namespace utils