mirror of
https://github.com/badaix/snapcast.git
synced 2025-06-06 04:41:44 +02:00
Add support for PulseAudio properties
This commit is contained in:
parent
be9c15cba6
commit
432e4dfad0
5 changed files with 57 additions and 11 deletions
|
@ -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 | |
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue