mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-23 14:06:14 +02:00
Make PulseAudio server configurable
This commit is contained in:
parent
77b659824d
commit
ed9a8c6462
4 changed files with 61 additions and 33 deletions
|
@ -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 80, min 10) |
|
||||
| pulse | Linux | PulseAudio | `buffer_time=<buffer size [ms]>` (default 100, min 10)<br />`server=<PulseAudio server>` - default not-set: use the default server |
|
||||
| oboe | Android | Oboe, using OpenSL ES on Android 4.1 and AAudio on 8.1 | |
|
||||
| opensl | Android | OpenSL ES | |
|
||||
| coreaudio | macOS | Core Audio | |
|
||||
|
|
|
@ -31,7 +31,7 @@ using namespace std;
|
|||
namespace player
|
||||
{
|
||||
|
||||
static constexpr std::chrono::milliseconds BUFFER_TIME = 80ms;
|
||||
static constexpr std::chrono::milliseconds BUFFER_TIME = 100ms;
|
||||
|
||||
static constexpr auto LOG_TAG = "PulsePlayer";
|
||||
|
||||
|
@ -41,7 +41,7 @@ static constexpr auto LOG_TAG = "PulsePlayer";
|
|||
// https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/Samples/AsyncPlayback/
|
||||
|
||||
|
||||
vector<PcmDevice> PulsePlayer::pcm_list()
|
||||
vector<PcmDevice> PulsePlayer::pcm_list(const std::string& parameter)
|
||||
{
|
||||
auto pa_ml = std::shared_ptr<pa_mainloop>(pa_mainloop_new(), [](pa_mainloop* pa_ml) { pa_mainloop_free(pa_ml); });
|
||||
pa_mainloop_api* pa_mlapi = pa_mainloop_get_api(pa_ml.get());
|
||||
|
@ -49,8 +49,14 @@ vector<PcmDevice> PulsePlayer::pcm_list()
|
|||
pa_context_disconnect(pa_ctx);
|
||||
pa_context_unref(pa_ctx);
|
||||
});
|
||||
if (pa_context_connect(pa_ctx.get(), nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
|
||||
throw SnapException("Failed to connect to PulseAudio context: " + std::string(pa_strerror(pa_context_errno(pa_ctx.get()))));
|
||||
|
||||
std::string pa_server;
|
||||
auto params = utils::string::split_pairs(parameter, ',', '=');
|
||||
if (params.find("server") != params.end())
|
||||
pa_server = params["server"];
|
||||
|
||||
if (pa_context_connect(pa_ctx.get(), pa_server.empty() ? nullptr : pa_server.c_str(), PA_CONTEXT_NOFLAGS, nullptr) < 0)
|
||||
throw SnapException("Failed to connect to PulseAudio context, error: " + std::string(pa_strerror(pa_context_errno(pa_ctx.get()))));
|
||||
|
||||
static int pa_ready = 0;
|
||||
pa_context_set_state_callback(
|
||||
|
@ -82,10 +88,13 @@ vector<PcmDevice> PulsePlayer::pcm_list()
|
|||
if (now - wait_start > 5s)
|
||||
throw SnapException("Timeout while waiting for PulseAudio to become ready");
|
||||
if (pa_mainloop_iterate(pa_ml.get(), 1, nullptr) < 0)
|
||||
throw SnapException("Error while waiting for PulseAudio to become ready: " + std::string(pa_strerror(pa_context_errno(pa_ctx.get()))));
|
||||
throw SnapException("Error while waiting for PulseAudio to become ready, error: " + std::string(pa_strerror(pa_context_errno(pa_ctx.get()))));
|
||||
this_thread::sleep_for(1ms);
|
||||
}
|
||||
|
||||
if (pa_ready == 2)
|
||||
throw SnapException("PulseAudio context failed, error: " + std::string(pa_strerror(pa_context_errno(pa_ctx.get()))));
|
||||
|
||||
static std::vector<PcmDevice> devices;
|
||||
auto op = pa_context_get_sink_info_list(
|
||||
pa_ctx.get(),
|
||||
|
@ -99,6 +108,9 @@ vector<PcmDevice> PulsePlayer::pcm_list()
|
|||
},
|
||||
nullptr);
|
||||
|
||||
if (op == nullptr)
|
||||
throw SnapException("PulseAudio get sink info list failed, error: " + std::string(pa_strerror(pa_context_errno(pa_ctx.get()))));
|
||||
|
||||
wait_start = std::chrono::steady_clock::now();
|
||||
|
||||
while (pa_operation_get_state(op) != PA_OPERATION_DONE)
|
||||
|
@ -121,13 +133,15 @@ vector<PcmDevice> PulsePlayer::pcm_list()
|
|||
|
||||
|
||||
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)
|
||||
: Player(io_context, settings, stream), latency_(BUFFER_TIME), pa_ml_(nullptr), pa_ctx_(nullptr), playstream_(nullptr), server_(boost::none)
|
||||
{
|
||||
auto params = utils::string::split_pairs(settings.parameter, ',', '=');
|
||||
if (params.find("buffer_time") != params.end())
|
||||
latency_ = std::chrono::milliseconds(std::max(cpt::stoi(params["buffer_time"]), 10));
|
||||
if (params.find("server") != params.end())
|
||||
server_ = params["server"];
|
||||
|
||||
LOG(INFO, LOG_TAG) << "Using buffer_time: " << latency_.count() / 1000 << " ms\n";
|
||||
LOG(INFO, LOG_TAG) << "Using buffer_time: " << latency_.count() / 1000 << " ms, server: " << server_.value_or("default") << "\n";
|
||||
}
|
||||
|
||||
|
||||
|
@ -318,15 +332,17 @@ void PulsePlayer::start()
|
|||
else if (format.bits() == 32)
|
||||
pa_ss_.format = PA_SAMPLE_S32LE;
|
||||
else
|
||||
throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits()));
|
||||
throw SnapException("Unsupported sample format \"" + cpt::to_string(format.bits()) + "\"");
|
||||
|
||||
// Create a mainloop API and connection to the default server
|
||||
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");
|
||||
if (pa_context_connect(pa_ctx_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
|
||||
throw SnapException("Failed to connect to PulseAudio context: " + std::string(pa_strerror(pa_context_errno(pa_ctx_))));
|
||||
|
||||
const char* server = server_.has_value() ? server_.value().c_str() : nullptr;
|
||||
if (pa_context_connect(pa_ctx_, server, PA_CONTEXT_NOFLAGS, nullptr) < 0)
|
||||
throw SnapException("Failed to connect to PulseAudio context, error: " + std::string(pa_strerror(pa_context_errno(pa_ctx_))));
|
||||
|
||||
// This function defines a callback so the server will tell us it's state.
|
||||
// Our callback will wait for the state to be ready. The callback will
|
||||
|
@ -350,12 +366,12 @@ void PulsePlayer::start()
|
|||
if (now - wait_start > 5s)
|
||||
throw SnapException("Timeout while waiting for PulseAudio to become ready");
|
||||
if (pa_mainloop_iterate(pa_ml_, 1, nullptr) < 0)
|
||||
throw SnapException("Error while waiting for PulseAudio to become ready: " + std::string(pa_strerror(pa_context_errno(pa_ctx_))));
|
||||
throw SnapException("Error while waiting for PulseAudio to become ready, error: " + std::string(pa_strerror(pa_context_errno(pa_ctx_))));
|
||||
this_thread::sleep_for(1ms);
|
||||
}
|
||||
|
||||
if (pa_ready_ == 2)
|
||||
throw SnapException("PulseAudio is not ready");
|
||||
throw SnapException("PulseAudio is not ready, error: " + std::string(pa_strerror(pa_context_errno(pa_ctx_))));
|
||||
|
||||
playstream_ = pa_stream_new(pa_ctx_, "Playback", &pa_ss_, nullptr);
|
||||
if (!playstream_)
|
||||
|
|
|
@ -22,10 +22,12 @@
|
|||
#include "player.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/optional.hpp>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
|
||||
namespace player
|
||||
{
|
||||
|
||||
|
@ -44,7 +46,7 @@ public:
|
|||
void stop() override;
|
||||
|
||||
/// List the system's audio output devices
|
||||
static std::vector<PcmDevice> pcm_list();
|
||||
static std::vector<PcmDevice> pcm_list(const std::string& parameter);
|
||||
|
||||
protected:
|
||||
bool needsThread() const override;
|
||||
|
@ -71,6 +73,7 @@ protected:
|
|||
pa_mainloop* pa_ml_;
|
||||
pa_context* pa_ctx_;
|
||||
pa_stream* playstream_;
|
||||
boost::optional<std::string> server_;
|
||||
|
||||
// cache of the last volume change
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_change_;
|
||||
|
|
|
@ -55,7 +55,7 @@ using namespace std::chrono_literals;
|
|||
|
||||
static constexpr auto LOG_TAG = "Snapclient";
|
||||
|
||||
PcmDevice getPcmDevice(const std::string& player, const std::string& soundcard)
|
||||
PcmDevice getPcmDevice(const std::string& player, const std::string& parameter, const std::string& soundcard)
|
||||
{
|
||||
#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI)
|
||||
vector<PcmDevice> pcm_devices;
|
||||
|
@ -65,7 +65,7 @@ PcmDevice getPcmDevice(const std::string& player, const std::string& soundcard)
|
|||
#endif
|
||||
#if defined(HAS_PULSE)
|
||||
if (player == player::PULSE)
|
||||
pcm_devices = PulsePlayer::pcm_list();
|
||||
pcm_devices = PulsePlayer::pcm_list(parameter);
|
||||
#endif
|
||||
#if defined(HAS_WASAPI)
|
||||
if (player == player::WASAPI)
|
||||
|
@ -87,6 +87,7 @@ PcmDevice getPcmDevice(const std::string& player, const std::string& soundcard)
|
|||
return dev;
|
||||
#endif
|
||||
std::ignore = player;
|
||||
std::ignore = parameter;
|
||||
PcmDevice pcm_device;
|
||||
pcm_device.name = soundcard;
|
||||
return pcm_device;
|
||||
|
@ -208,30 +209,37 @@ int main(int argc, char** argv)
|
|||
#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI)
|
||||
if (listSwitch->is_set())
|
||||
{
|
||||
vector<PcmDevice> pcm_devices;
|
||||
try
|
||||
{
|
||||
vector<PcmDevice> pcm_devices;
|
||||
#if defined(HAS_ALSA)
|
||||
if (settings.player.player_name == player::ALSA)
|
||||
pcm_devices = AlsaPlayer::pcm_list();
|
||||
if (settings.player.player_name == player::ALSA)
|
||||
pcm_devices = AlsaPlayer::pcm_list();
|
||||
#endif
|
||||
#if defined(HAS_PULSE)
|
||||
if (settings.player.player_name == player::PULSE)
|
||||
pcm_devices = PulsePlayer::pcm_list();
|
||||
if (settings.player.player_name == player::PULSE)
|
||||
pcm_devices = PulsePlayer::pcm_list(settings.player.parameter);
|
||||
#endif
|
||||
#if defined(HAS_WASAPI)
|
||||
if (settings.player.player_name == player::WASAPI)
|
||||
pcm_devices = WASAPIPlayer::pcm_list();
|
||||
if (settings.player.player_name == player::WASAPI)
|
||||
pcm_devices = WASAPIPlayer::pcm_list();
|
||||
#endif
|
||||
#ifdef WINDOWS
|
||||
// Set console code page to UTF-8 so console known how to interpret string data
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
// Enable buffering to prevent VS from chopping up UTF-8 byte sequences
|
||||
setvbuf(stdout, nullptr, _IOFBF, 1000);
|
||||
// Set console code page to UTF-8 so console known how to interpret string data
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
// Enable buffering to prevent VS from chopping up UTF-8 byte sequences
|
||||
setvbuf(stdout, nullptr, _IOFBF, 1000);
|
||||
#endif
|
||||
for (const auto& dev : pcm_devices)
|
||||
cout << dev.idx << ": " << dev.name << "\n" << dev.description << "\n\n";
|
||||
for (const auto& dev : pcm_devices)
|
||||
cout << dev.idx << ": " << dev.name << "\n" << dev.description << "\n\n";
|
||||
|
||||
if (pcm_devices.empty())
|
||||
cout << "No PCM device available for audio backend \"" << settings.player.player_name << "\"\n";
|
||||
if (pcm_devices.empty())
|
||||
cout << "No PCM device available for audio backend \"" << settings.player.player_name << "\"\n";
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
cout << "Failed to get device list: " << e.what() << "\n";
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
#endif
|
||||
|
@ -324,7 +332,7 @@ int main(int argc, char** argv)
|
|||
}
|
||||
#endif
|
||||
|
||||
settings.player.pcm_device = getPcmDevice(settings.player.player_name, pcm_device);
|
||||
settings.player.pcm_device = getPcmDevice(settings.player.player_name, settings.player.parameter, pcm_device);
|
||||
#if defined(HAS_ALSA)
|
||||
if (settings.player.pcm_device.idx == -1)
|
||||
{
|
||||
|
@ -361,7 +369,8 @@ int main(int argc, char** argv)
|
|||
else if (settings.player.player_name == player::PULSE)
|
||||
{
|
||||
cout << "Options are a comma separated list of:\n"
|
||||
<< " \"buffer_time=<buffer size [ms]>\" - default 80, min 10\n";
|
||||
<< " \"buffer_time=<buffer size [ms]>\" - default 100, min 10\n"
|
||||
<< " \"server=<PulseAudio server>\" - default not-set: use the default server\n";
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_ALSA
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue