summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd/modules/sound/pulse_sound.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/osd/modules/sound/pulse_sound.cpp')
-rw-r--r--src/osd/modules/sound/pulse_sound.cpp646
1 files changed, 388 insertions, 258 deletions
diff --git a/src/osd/modules/sound/pulse_sound.cpp b/src/osd/modules/sound/pulse_sound.cpp
index 373913f9f91..4b7c87b6b9b 100644
--- a/src/osd/modules/sound/pulse_sound.cpp
+++ b/src/osd/modules/sound/pulse_sound.cpp
@@ -14,21 +14,12 @@
#ifndef NO_USE_PULSEAUDIO
#define GNU_SOURCE
-#include <fcntl.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <poll.h>
-
-#include <mutex>
-#include <thread>
+
#include <pulse/pulseaudio.h>
+#include <map>
#include "modules/lib/osdobj_common.h"
-using osd::s16;
-using osd::u32;
-
class sound_pulse : public osd_module, public sound_module
{
public:
@@ -40,52 +31,113 @@ public:
virtual int init(osd_interface &osd, osd_options const &options) override;
virtual void exit() override;
- virtual void update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) override;
- virtual void set_mastervolume(int attenuation) override;
+
+ virtual bool external_per_channel_volume() override { return false; }
+ virtual bool split_streams_per_source() override { return true; }
+
+ virtual uint32_t get_generation() override;
+ virtual osd::audio_info get_information() override;
+ virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override;
+ virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) override;
+ virtual void stream_close(uint32_t id) override;
+ virtual void stream_sink_update(uint32_t, const int16_t *buffer, int samples_this_frame) override;
private:
- struct abuffer {
- size_t cpos;
- std::vector<u32> data;
+ struct position_info {
+ pa_channel_position_t m_position;
+ std::array<double, 3> m_coords;
};
- std::thread *m_thread;
- pa_mainloop *m_mainloop;
- pa_context *m_context;
- pa_stream *m_stream;
- std::mutex m_mutex;
+ static const position_info position_infos[];
+ static const char *const typenames[];
+ enum { AREC, APLAY };
+
+ struct node_info {
+ sound_pulse *m_pulse;
+ uint32_t m_id, m_osdid;
+ int m_type;
+ std::string m_name, m_desc;
+
+ // Audio node info
+ std::vector<pa_channel_position_t> m_position_codes;
+ std::vector<std::string> m_position_names;
+ std::vector<std::array<double, 3>> m_positions;
+ uint32_t m_sink_port_count, m_source_port_count;
+
+ osd::audio_rate_range m_rate;
+
+ node_info(sound_pulse *pulse, uint32_t id, uint32_t osdid, int type, std::string name, std::string desc) : m_pulse(pulse), m_id(id), m_osdid(osdid), m_type(type), m_name(name), m_desc(desc), m_sink_port_count(0), m_source_port_count(0), m_rate{0, 0, 0} {
+ }
+ };
+
+ struct stream_info {
+ sound_pulse *m_pulse;
+ uint32_t m_osdid;
+ uint32_t m_pulse_id;
+ node_info *m_target_node;
+ uint32_t m_channels;
+ pa_stream *m_stream;
+ std::vector<float> m_volumes;
+ abuffer m_buffer;
+
+ stream_info(sound_pulse *pulse, uint32_t osdid, uint32_t channels) : m_pulse(pulse), m_osdid(osdid), m_pulse_id(0), m_channels(channels), m_stream(nullptr), m_volumes(channels), m_buffer(channels) {}
+ };
- std::vector<abuffer> m_buffers;
+ std::map<uint32_t, node_info> m_nodes;
+ std::map<uint32_t, uint32_t> m_node_osdid_to_id;
- u32 m_last_sample;
- int m_new_volume_value;
- bool m_setting_volume;
- bool m_new_volume;
+ std::map<uint32_t, stream_info> m_streams;
+ std::map<uint32_t, uint32_t> m_stream_pulse_id_to_osdid;
- int m_pipe_to_sub[2];
- int m_pipe_to_main[2];
+ pa_threaded_mainloop *m_mainloop;
+ pa_context *m_context;
+ uint32_t m_node_current_id, m_stream_current_id;
+ uint32_t m_generation;
+ bool m_wait_stream, m_wait_init;
+
+ std::string m_default_audio_sink;
+ std::string m_default_audio_source;
- static void i_volume_set_notify(pa_context *, int success, void *self);
- void volume_set_notify(int success);
+ static void i_server_info(pa_context *, const pa_server_info *i, void *self);
+ void server_info(const pa_server_info *i);
static void i_context_notify(pa_context *, void *self);
void context_notify();
+ static void i_context_subscribe(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void *self);
+ void context_subscribe(pa_subscription_event_type_t t, uint32_t idx);
static void i_stream_notify(pa_stream *, void *self);
- void stream_notify();
+ void stream_notify(stream_info *stream);
static void i_stream_write_request(pa_stream *, size_t size, void *self);
- void stream_write_request(size_t size);
+ void stream_write_request(stream_info *stream, size_t size);
+ static void i_source_info(pa_context *, const pa_source_info *i, int eol, void *self);
+ void source_info(const pa_source_info *i, int eol);
+ static void i_sink_info_new(pa_context *, const pa_sink_info *i, int eol, void *self);
+ void sink_info_new(const pa_sink_info *i, int eol);
+ static void i_sink_input_info_change(pa_context *, const pa_sink_input_info *i, int eol, void *self);
+ void sink_input_info_change(stream_info *stream, const pa_sink_input_info *i, int eol);
- void make_pipes();
-
- void mainloop_thread();
- void send_main(char c);
- void send_sub(char c);
- char get_main();
- char peek_main();
- void stop_mainloop(int err);
void generic_error(const char *msg);
void generic_pa_error(const char *msg, int err);
};
+// Try to more or less map to speaker.h positions
+
+const sound_pulse::position_info sound_pulse::position_infos[] = {
+ { PA_CHANNEL_POSITION_MONO, { 0.0, 0.0, 1.0 } },
+ { PA_CHANNEL_POSITION_FRONT_LEFT, { -0.2, 0.0, 1.0 } },
+ { PA_CHANNEL_POSITION_FRONT_RIGHT, { 0.2, 0.0, 1.0 } },
+ { PA_CHANNEL_POSITION_FRONT_CENTER, { 0.0, 0.0, 1.0 } },
+ { PA_CHANNEL_POSITION_LFE, { 0.0, -0.5, 1.0 } },
+ { PA_CHANNEL_POSITION_REAR_LEFT, { -0.2, 0.0, -0.5 } },
+ { PA_CHANNEL_POSITION_REAR_RIGHT, { 0.2, 0.0, -0.5 } },
+ { PA_CHANNEL_POSITION_REAR_CENTER, { 0.0, 0.0, -0.5 } },
+ { PA_CHANNEL_POSITION_MAX, { 0.0, 0.0, 0.0 } }
+};
+
+
+const char *const sound_pulse::typenames[] = {
+ "Audio recorder", "Speaker"
+};
+
void sound_pulse::generic_error(const char *msg)
{
perror(msg);
@@ -98,205 +150,312 @@ void sound_pulse::generic_pa_error(const char *msg, int err)
::exit(1);
}
-char sound_pulse::peek_main()
+void sound_pulse::context_notify()
{
- char c;
- int err = read(m_pipe_to_main[0], &c, 1);
- if(err != 1) {
- if(err >= 0) {
- fprintf(stderr, "peek_main: read returned %d, that's supposedly impossible\n", err);
- ::exit(1);
- }
- if(errno == EAGAIN || errno == EWOULDBLOCK) {
- // No data, no problem
- return -1;
- }
- generic_error("peek_main: read");
+ fprintf(stderr, "context notify\n");
+ pa_context_state state = pa_context_get_state(m_context);
+ if(state == PA_CONTEXT_READY) {
+ pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_ALL, nullptr, this);
+ pa_context_get_sink_info_list(m_context, i_sink_info_new, (void *)this);
+
+ } else if(state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) {
+ m_generation = 0x80000000;
+ pa_threaded_mainloop_signal(m_mainloop, 0);
}
- return c;
}
-char sound_pulse::get_main()
+void sound_pulse::i_context_notify(pa_context *, void *self)
{
- pollfd pfds[1];
- pfds[0].fd = m_pipe_to_main[0];
- pfds[0].events = POLLIN;
- pfds[0].revents = 0;
- int err = poll(pfds, 1, -1);
- if(err < 0)
- generic_error("get_main: poll");
-
- char c;
- err = read(m_pipe_to_main[0], &c, 1);
- if(err != 1) {
- if(err >= 0) {
- fprintf(stderr, "get_main: read returned %d, that's supposedly impossible\n", err);
- ::exit(1);
- }
- generic_error("get_main: read");
- }
- return c;
+ static_cast<sound_pulse *>(self)->context_notify();
}
-void sound_pulse::send_main(char c)
+void sound_pulse::stream_notify(stream_info *stream)
{
- int err = write(m_pipe_to_main[1], &c, 1);
- if(err != 1) {
- if(err >= 0) {
- fprintf(stderr, "send_main: write returned %d, that's supposedly impossible\n", err);
- ::exit(1);
- }
- if(errno == EAGAIN || errno == EWOULDBLOCK) {
- fprintf(stderr, "send_main: write would block, pipe buffer overflowed, something is going very badly\n");
- ::exit(1);
- }
- generic_error("send_main: write");
+ pa_stream_state state = pa_stream_get_state(stream->m_stream);
+
+ fprintf(stderr, "stream notify\n");
+ if(state == PA_STREAM_READY || state == PA_STREAM_FAILED || state == PA_STREAM_TERMINATED) {
+ m_wait_stream = false;
+ pa_threaded_mainloop_signal(m_mainloop, 0);
}
}
-void sound_pulse::send_sub(char c)
+void sound_pulse::i_stream_notify(pa_stream *, void *self)
{
- int err = write(m_pipe_to_sub[1], &c, 1);
- if(err != 1) {
- if(err >= 0) {
- fprintf(stderr, "send_sub: write returned %d, that's supposedly impossible\n", err);
- ::exit(1);
- }
- if(errno == EAGAIN || errno == EWOULDBLOCK) {
- fprintf(stderr, "send_sub: write would block, pipe buffer overflowed, something is going very badly\n");
- ::exit(1);
- }
- generic_error("send_sub: write");
- }
+ stream_info *si = static_cast<stream_info *>(self);
+ si->m_pulse->stream_notify(si);
}
-void sound_pulse::make_pipes()
+void sound_pulse::stream_write_request(stream_info *stream, size_t size)
{
- if(pipe2(m_pipe_to_sub, O_NONBLOCK))
- generic_error("pipe2 pipe_to_sub");
- if(pipe2(m_pipe_to_main, O_NONBLOCK))
- generic_error("pipe2 pipe_to_main");
+ // This is called with the thread locked
+ while(size) {
+ void *buffer;
+ size_t bsize = size;
+ int err = pa_stream_begin_write(stream->m_stream, &buffer, &bsize);
+ if(err)
+ generic_pa_error("stream begin write", err);
+ uint32_t frames = bsize/2/stream->m_channels;
+ uint32_t bytes = frames*2*stream->m_channels;
+ stream->m_buffer.get((int16_t *)buffer, frames);
+ err = pa_stream_write(stream->m_stream, buffer, bytes, nullptr, 0, PA_SEEK_RELATIVE);
+ if(err)
+ generic_pa_error("stream write", err);
+ size -= bytes;
+ }
}
-void sound_pulse::context_notify()
+void sound_pulse::i_stream_write_request(pa_stream *, size_t size, void *self)
{
- pa_context_state state = pa_context_get_state(m_context);
- if(state == PA_CONTEXT_READY)
- send_main('r');
+ stream_info *si = static_cast<stream_info *>(self);
+ si->m_pulse->stream_write_request(si, size);
+}
- else if(state == PA_CONTEXT_FAILED) {
- send_main('f');
- stop_mainloop(pa_context_errno(m_context));
- } else if(state == PA_CONTEXT_TERMINATED) {
- send_main('t');
- stop_mainloop(0);
+void sound_pulse::server_info(const pa_server_info *i)
+{
+ m_default_audio_sink = i->default_sink_name;
+ m_default_audio_source = i->default_source_name;
+ fprintf(stderr, "defaults %s %s\n", i->default_sink_name, i->default_source_name);
+ m_generation++;
+ if(m_wait_init) {
+ m_wait_init = false;
+ pa_threaded_mainloop_signal(m_mainloop, 0);
}
}
-void sound_pulse::i_context_notify(pa_context *, void *self)
+void sound_pulse::i_server_info(pa_context *, const pa_server_info *i, void *self)
{
- static_cast<sound_pulse *>(self)->context_notify();
+ static_cast<sound_pulse *>(self)->server_info(i);
}
-void sound_pulse::stream_notify()
+void sound_pulse::source_info(const pa_source_info *i, int eol)
{
- pa_stream_state state = pa_stream_get_state(m_stream);
-
- if(state == PA_STREAM_READY)
- send_main('r');
-
- else if(state == PA_STREAM_FAILED) {
- send_main('f');
- stop_mainloop(pa_context_errno(m_context));
+ if(eol) {
+ if(m_wait_init)
+ pa_context_get_server_info(m_context, i_server_info, (void *)this);
+ return;
+ }
+ auto ni = m_nodes.find(i->index);
+ if(ni != m_nodes.end()) {
+ // Add the monitoring sources to the node
+ ni->second.m_source_port_count = i->channel_map.channels;
+ return;
+ }
- } else if(state == PA_STREAM_TERMINATED)
- pa_context_disconnect(m_context);
+ fprintf(stderr, "new source %d (%s)\n", i->index, i->description);
+ m_node_osdid_to_id[m_node_current_id] = i->index;
+ auto &node = m_nodes.emplace(i->index, node_info(this, i->index, m_node_current_id++, AREC, i->name, i->description)).first->second;
+
+ node.m_source_port_count = i->channel_map.channels;
+ for(int chan=0; chan != i->channel_map.channels; chan++) {
+ pa_channel_position_t pos = i->channel_map.map[chan];
+ node.m_position_codes.push_back(pos);
+ node.m_position_names.push_back(pa_channel_position_to_pretty_string(pos));
+ for(uint32_t j = 0;; j++) {
+ if((position_infos[j].m_position == pos) || (position_infos[j].m_position == PA_CHANNEL_POSITION_MAX)) {
+ node.m_positions.push_back(position_infos[j].m_coords);
+ break;
+ }
+ }
+ }
}
-void sound_pulse::i_stream_notify(pa_stream *, void *self)
+void sound_pulse::i_source_info(pa_context *, const pa_source_info *i, int eol, void *self)
{
- static_cast<sound_pulse *>(self)->stream_notify();
+ static_cast<sound_pulse *>(self)->source_info(i, eol);
}
-void sound_pulse::stream_write_request(size_t size)
+void sound_pulse::sink_info_new(const pa_sink_info *i, int eol)
{
- if(size & 3) {
- fprintf(stderr, "stream request with size %d not a multiple of 4.\n", int(size));
- ::exit(1);
+ if(eol) {
+ if(m_wait_init)
+ pa_context_get_source_info_list(m_context, i_source_info, (void *)this);
+ return;
}
- size >>= 2;
- std::unique_lock<std::mutex> lock(m_mutex);
- while(size) {
- if(m_buffers.empty()) {
- std::vector<u32> zero(size, m_last_sample);
- int err = pa_stream_write(m_stream, zero.data(), size << 2, nullptr, 0, PA_SEEK_RELATIVE);
- if(err)
- generic_pa_error("stream write", err);
- size = 0;
-
- } else {
- auto &buf = m_buffers[0];
- size_t csz = size;
- size_t cur = buf.data.size() - buf.cpos;
- if(csz > cur)
- csz = cur;
- int err = pa_stream_write(m_stream, buf.data.data() + buf.cpos, csz << 2, nullptr, 0, PA_SEEK_RELATIVE);
- if(err)
- generic_pa_error("stream write", err);
- if(csz == cur)
- m_buffers.erase(m_buffers.begin());
- else
- buf.cpos += csz;
- size -= csz;
+ fprintf(stderr, "new sink %d (%s)\n", i->index, i->description);
+ fprintf(stderr, "rate %d\n", i->sample_spec.rate);
+ m_node_osdid_to_id[m_node_current_id] = i->index;
+ auto &node = m_nodes.emplace(i->index, node_info(this, i->index, m_node_current_id++, APLAY, i->name, i->description)).first->second;
+
+ node.m_sink_port_count = i->channel_map.channels;
+ for(int chan=0; chan != i->channel_map.channels; chan++) {
+ pa_channel_position_t pos = i->channel_map.map[chan];
+ node.m_position_codes.push_back(pos);
+ node.m_position_names.push_back(pa_channel_position_to_pretty_string(pos));
+ for(uint32_t j = 0;; j++) {
+ if((position_infos[j].m_position == pos) || (position_infos[j].m_position == PA_CHANNEL_POSITION_MAX)) {
+ node.m_positions.push_back(position_infos[j].m_coords);
+ break;
+ }
}
}
+ m_generation++;
}
-void sound_pulse::i_stream_write_request(pa_stream *, size_t size, void *self)
+void sound_pulse::i_sink_info_new(pa_context *, const pa_sink_info *i, int eol, void *self)
{
- static_cast<sound_pulse *>(self)->stream_write_request(size);
+ static_cast<sound_pulse *>(self)->sink_info_new(i, eol);
}
-void sound_pulse::mainloop_thread()
+void sound_pulse::sink_input_info_change(stream_info *stream, const pa_sink_input_info *i, int eol)
{
- int err = 0;
- pa_mainloop_run(m_mainloop, &err);
- if(err)
- generic_pa_error("mainloop stopped", err);
+ if(eol)
+ return;
+
+ auto ni = m_nodes.find(i->sink);
+ if(ni != m_nodes.end())
+ stream->m_target_node = &ni->second;
+
+ for(uint32_t port = 0; port != stream->m_channels; port++)
+ stream->m_volumes[port] = pa_sw_volume_to_dB(i->volume.values[port]);
+
+ fprintf(stderr, "change stream %d/%d sink=%s [%f %f]\n", stream->m_osdid, stream->m_pulse_id, stream->m_target_node->m_desc.c_str(), stream->m_volumes[0], stream->m_volumes[1]);
+ m_generation++;
}
-void sound_pulse::stop_mainloop(int err)
+void sound_pulse::i_sink_input_info_change(pa_context *, const pa_sink_input_info *i, int eol, void *self)
{
- pa_mainloop_quit(m_mainloop, err);
+ fprintf(stderr, "i_sink_input_info_change %p %d\n", i, eol);
+ stream_info *stream = static_cast<stream_info *>(self);
+ stream->m_pulse->sink_input_info_change(stream, i, eol);
}
-int sound_pulse::init(osd_interface &osd, osd_options const &options)
+void sound_pulse::context_subscribe(pa_subscription_event_type_t t, uint32_t idx)
{
- m_last_sample = 0;
- m_setting_volume = false;
- m_new_volume = false;
- m_new_volume_value = 0;
+ // This is called with the thread locked
+ static const char *const evt[] = { "sink", "source", "sink-input", "source-output", "module", "client", "cache", "server", "autoload", "card" };
+ static const char *const evt2[] = { "new", "change", "remove" };
+ switch(int(t)) {
+ case PA_SUBSCRIPTION_EVENT_REMOVE | PA_SUBSCRIPTION_EVENT_SINK:
+ case PA_SUBSCRIPTION_EVENT_REMOVE | PA_SUBSCRIPTION_EVENT_SOURCE: {
+ auto si = m_nodes.find(idx);
+ if(si == m_nodes.end())
+ break;
+ fprintf(stderr, "removing %s\n", si->second.m_desc.c_str());
+ for(auto &istream : m_streams)
+ if(istream.second.m_target_node == &si->second)
+ istream.second.m_target_node = nullptr;
+ m_nodes.erase(si);
+ m_generation++;
+ break;
+ }
+
+ case PA_SUBSCRIPTION_EVENT_NEW | PA_SUBSCRIPTION_EVENT_SINK:
+ pa_context_get_sink_info_by_index(m_context, idx, i_sink_info_new, this);
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_NEW | PA_SUBSCRIPTION_EVENT_SOURCE:
+ pa_context_get_source_info_by_index(m_context, idx, i_source_info, this);
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CHANGE | PA_SUBSCRIPTION_EVENT_SERVER:
+ pa_context_get_server_info(m_context, i_server_info, (void *)this);
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CHANGE | PA_SUBSCRIPTION_EVENT_SINK_INPUT: {
+ auto si1 = m_stream_pulse_id_to_osdid.find(idx);
+ if(si1 == m_stream_pulse_id_to_osdid.end())
+ break;
+ auto si = m_streams.find(si1->second);
+ if(si == m_streams.end())
+ break;
+ pa_context_get_sink_input_info(m_context, idx, i_sink_input_info_change, (void *)&si->second);
+ break;
+ }
- m_mainloop = pa_mainloop_new();
- m_context = pa_context_new(pa_mainloop_get_api(m_mainloop), "MAME");
- pa_context_set_state_callback(m_context, i_context_notify, this);
- int err = pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
+ default:
+ fprintf(stderr, "event %s %s %d\n", evt2[t>>4], evt[t&15], idx);
+ }
+}
- if(err)
- generic_pa_error("pa_connect", err);
+void sound_pulse::i_context_subscribe(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void *self)
+{
+ static_cast<sound_pulse *>(self)->context_subscribe(t, idx);
+}
- make_pipes();
- m_thread = new std::thread(&sound_pulse::mainloop_thread, this);
- char res = get_main();
- if(res != 'r')
+int sound_pulse::init(osd_interface &osd, osd_options const &options)
+{
+ m_node_current_id = 1;
+ m_stream_current_id = 1;
+ m_generation = 0;
+ m_wait_stream = false;
+ m_wait_init = true;
+
+ m_mainloop = pa_threaded_mainloop_new();
+ m_context = pa_context_new(pa_threaded_mainloop_get_api(m_mainloop), "MAME");
+ pa_context_set_state_callback(m_context, i_context_notify, this);
+ pa_context_set_subscribe_callback(m_context, i_context_subscribe, this);
+ pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
+ pa_threaded_mainloop_start(m_mainloop);
+
+ pa_threaded_mainloop_lock(m_mainloop);
+ while(m_wait_init)
+ pa_threaded_mainloop_wait(m_mainloop);
+ pa_threaded_mainloop_unlock(m_mainloop);
+ if(m_generation >= 0x80000000)
return 1;
- const int sample_rate = options.sample_rate();
+ return 0;
+}
+
+uint32_t sound_pulse::get_generation()
+{
+ pa_threaded_mainloop_lock(m_mainloop);
+ uint32_t result = m_generation;
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return result;
+}
+
+osd::audio_info sound_pulse::get_information()
+{
+ osd::audio_info result;
+ pa_threaded_mainloop_lock(m_mainloop);
+ result.m_nodes.resize(m_nodes.size());
+ result.m_default_sink = 0;
+ result.m_default_source = 0;
+ result.m_generation = m_generation;
+ uint32_t node = 0;
+ for(auto &inode : m_nodes) {
+ result.m_nodes[node].m_name = inode.second.m_desc;
+ result.m_nodes[node].m_id = inode.second.m_osdid;
+ result.m_nodes[node].m_rate = inode.second.m_rate;
+ result.m_nodes[node].m_sinks = inode.second.m_sink_port_count;
+ result.m_nodes[node].m_sources = inode.second.m_source_port_count;
+ result.m_nodes[node].m_port_names = inode.second.m_position_names;
+ result.m_nodes[node].m_port_positions = inode.second.m_positions;
+
+ if(inode.second.m_name == m_default_audio_sink)
+ result.m_default_sink = inode.second.m_osdid;
+ if(inode.second.m_name == m_default_audio_source)
+ result.m_default_source = inode.second.m_osdid;
+ node ++;
+ }
+
+ for(auto &istream : m_streams)
+ if(istream.second.m_target_node)
+ result.m_streams.emplace_back(osd::audio_info::stream_info { istream.second.m_osdid, istream.second.m_target_node->m_osdid, istream.second.m_volumes });
+
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return result;
+}
+
+uint32_t sound_pulse::stream_sink_open(uint32_t node, std::string name, uint32_t rate)
+{
+ pa_threaded_mainloop_lock(m_mainloop);
+ auto ni = m_node_osdid_to_id.find(node);
+ if(ni == m_node_osdid_to_id.end()) {
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return 0;
+ }
+ node_info &snode = m_nodes.find(ni->second)->second;
+
+ uint32_t id = m_stream_current_id++;
+ auto &stream = m_streams.emplace(id, stream_info(this, id, snode.m_sink_port_count)).first->second;
pa_sample_spec ss;
#ifdef LSB_FIRST
@@ -304,108 +463,79 @@ int sound_pulse::init(osd_interface &osd, osd_options const &options)
#else
ss.format = PA_SAMPLE_S16BE;
#endif
- ss.rate = sample_rate;
- ss.channels = 2;
- m_stream = pa_stream_new(m_context, "main output", &ss, nullptr);
- pa_stream_set_state_callback(m_stream, i_stream_notify, this);
- pa_stream_set_write_callback(m_stream, i_stream_write_request, this);
+ ss.rate = rate;
+ ss.channels = stream.m_channels;
+ stream.m_stream = pa_stream_new(m_context, name.c_str(), &ss, nullptr);
+ pa_stream_set_state_callback(stream.m_stream, i_stream_notify, &stream);
+ pa_stream_set_write_callback(stream.m_stream, i_stream_write_request, &stream);
pa_buffer_attr battr;
- battr.fragsize = sample_rate / 1000;
- battr.maxlength = uint32_t(-1);
- battr.minreq = sample_rate / 1000;
+ battr.fragsize = uint32_t(-1);
+ battr.maxlength = 1024;
+ battr.minreq = uint32_t(-1);
battr.prebuf = uint32_t(-1);
- battr.tlength = sample_rate / 1000;
+ battr.tlength = uint32_t(-1);
- err = pa_stream_connect_playback(m_stream, nullptr, &battr, PA_STREAM_ADJUST_LATENCY, nullptr, nullptr);
+ int err = pa_stream_connect_playback(stream.m_stream, snode.m_name.c_str(), &battr, pa_stream_flags_t(PA_STREAM_ADJUST_LATENCY|PA_STREAM_START_UNMUTED), nullptr, nullptr);
if(err)
generic_pa_error("stream connect playback", err);
- res = get_main();
- if(res != 'r')
- return 1;
- return 0;
-}
+ stream.m_target_node = &snode;
-void sound_pulse::update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame)
-{
- std::unique_lock<std::mutex> lock(m_mutex);
- m_buffers.resize(m_buffers.size() + 1);
- auto &buf = m_buffers.back();
- buf.cpos = 0;
- buf.data.resize(samples_this_frame);
- memcpy(buf.data.data(), buffer, samples_this_frame*4);
- m_last_sample = buf.data.back();
-
- if(m_buffers.size() > 10)
- // If there way too many buffers, drop some so only 10 are left (roughly 0.2s)
- m_buffers.erase(m_buffers.begin(), m_buffers.begin() + m_buffers.size() - 10);
-
- else if(m_buffers.size() >= 5)
- // If there are too many buffers, remove five sample per buffer
- // to slowly resync to reduce latency (4 seconds to
- // compensate one buffer roughly)
- buf.cpos = 5;
+ m_wait_stream = true;
+ while(m_wait_stream)
+ pa_threaded_mainloop_wait(m_mainloop);
+
+ stream.m_pulse_id = pa_stream_get_index(stream.m_stream);
+ m_stream_pulse_id_to_osdid[stream.m_pulse_id] = id;
+
+ fprintf(stderr, "stream id %d\n", stream.m_pulse_id);
+ pa_threaded_mainloop_unlock(m_mainloop);
+
+ return id;
}
-void sound_pulse::volume_set_notify(int success)
+void sound_pulse::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame)
{
- std::unique_lock<std::mutex> lock(m_mutex);
- if(m_new_volume) {
- m_new_volume = false;
- pa_cvolume vol;
- pa_cvolume_set(&vol, 2, pa_sw_volume_from_dB(m_new_volume_value));
- pa_context_set_sink_input_volume(m_context, pa_stream_get_index(m_stream), &vol, i_volume_set_notify, this);
- } else
- m_setting_volume = false;
+ pa_threaded_mainloop_lock(m_mainloop);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end()) {
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return;
+ }
+ si->second.m_buffer.push(buffer, samples_this_frame);
+ pa_threaded_mainloop_unlock(m_mainloop);
}
-void sound_pulse::i_volume_set_notify(pa_context *, int success, void *self)
+void sound_pulse::stream_set_volumes(uint32_t id, const std::vector<float> &db)
{
- static_cast<sound_pulse *>(self)->volume_set_notify(success);
}
-void sound_pulse::set_mastervolume(int attenuation)
+void sound_pulse::stream_close(uint32_t id)
{
- if(!m_stream)
+ pa_threaded_mainloop_lock(m_mainloop);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end()) {
+ pa_threaded_mainloop_unlock(m_mainloop);
return;
-
-
- std::unique_lock<std::mutex> lock(m_mutex);
- if(m_setting_volume) {
- m_new_volume = true;
- m_new_volume_value = attenuation;
- } else {
- m_setting_volume = true;
- pa_cvolume vol;
- pa_cvolume_set(&vol, 2, pa_sw_volume_from_dB(attenuation));
- pa_context_set_sink_input_volume(m_context, pa_stream_get_index(m_stream), &vol, i_volume_set_notify, this);
}
+ stream_info &stream = si->second;
+ pa_stream_set_state_callback(stream.m_stream, nullptr, &stream);
+ pa_stream_set_write_callback(stream.m_stream, nullptr, &stream);
+ pa_stream_disconnect(stream.m_stream);
+ m_streams.erase(si);
+ pa_threaded_mainloop_unlock(m_mainloop);
}
void sound_pulse::exit()
{
- if(!m_stream)
- return;
+ for(const auto &si : m_streams) {
+ pa_stream_disconnect(si.second.m_stream);
+ pa_stream_unref(si.second.m_stream);
+ }
- pa_stream_disconnect(m_stream);
- while(get_main() != 't') {}
- pa_stream_unref(m_stream);
pa_context_unref(m_context);
- m_thread->join();
- pa_mainloop_free(m_mainloop);
- delete m_thread;
-
- close(m_pipe_to_sub[0]);
- close(m_pipe_to_sub[1]);
- close(m_pipe_to_main[0]);
- close(m_pipe_to_main[1]);
-
- m_thread = nullptr;
- m_mainloop = nullptr;
- m_context = nullptr;
- m_stream = nullptr;
- m_buffers.clear();
+ pa_threaded_mainloop_free(m_mainloop);
}
#else