diff options
author | 2025-04-14 11:31:53 +0200 | |
---|---|---|
committer | 2025-04-27 22:23:20 +0200 | |
commit | d0f1c15a0f6df2dd51a754cb46e6175b7079c8f2 (patch) | |
tree | be35c94340442af08c316a8679089ee68e119fac /src/osd/modules/sound/pulse_sound.cpp | |
parent | ec636faeba5c5841c5a4a35b7c9dc2f06a00f538 (diff) |
New sound infrastructure.
Should be added soon:
- mute
- speaker/microphone resampling
To be added a little later:
- compression
- reverb
Needs to be added by someone else:
- coreaudio
- direct
- portaudio
- xaudio2
- js
Diffstat (limited to 'src/osd/modules/sound/pulse_sound.cpp')
-rw-r--r-- | src/osd/modules/sound/pulse_sound.cpp | 646 |
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 |