From 1edc7eb655644e58eb1aab3ff4a493caaa56a0bf Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 15:56:58 +0100 Subject: [PATCH] virtual sink: Factor out common code This patch moves the code for the virtual sink callbacks and initialization to a separate file. The code is re-factored, extended and built as library, so that it can be used by other virtual sinks as well. The suspend-virtual-on-master-suspend fix for the ladspa sink (!68) was incorporated into the common code as well as the sink part of !78 which fixes a crash with stacked virtual sinks. !68 has a bug which leaves a virtual sink unavailable when the master sink disappears. This bug is also fixed. Additionally, fixed block size filters, fixed window size filters and updating filter parameters are supported by the library. Fixed window size filters always deliver the same number of frames to the filter, padding new data with history frames if necessary. Rewinding can be disabled or or limited to the number of frames that the virtual sink supports. Thanks to Alexander E. Patrakov for pointing out how to rewind fixed block size filters. The library implements following command line arguments if they are enabled in valid_modargs: sink_name, sink_properties, sink_input_properties, force_flat_volume, remix, resample_method and autoloaded. --- src/modules/meson.build | 13 +- src/modules/module-virtual-sink.c | 548 +---------- src/modules/virtual-sink-common.c | 1404 +++++++++++++++++++++++++++++ src/modules/virtual-sink-common.h | 77 ++ src/pulsecore/memblockq.c | 12 + src/pulsecore/memblockq.h | 5 + 6 files changed, 1532 insertions(+), 527 deletions(-) create mode 100644 src/modules/virtual-sink-common.c create mode 100644 src/modules/virtual-sink-common.h diff --git a/src/modules/meson.build b/src/modules/meson.build index 3636ce0de..019064e98 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -2,6 +2,17 @@ if host_machine.system() != 'windows' subdir('rtp') endif +libvirtual_sink = shared_library('virtual_sink', + 'virtual-sink-common.c', + 'virtual-sink-common.h', + c_args : [pa_c_args, server_c_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep], + install_rpath : privlibdir, + install : true, + install_dir : modlibexecdir +) + # module name, sources, [headers, extra flags, extra deps, extra libs] all_modules = [ [ 'module-allow-passthrough', 'module-allow-passthrough.c' ], @@ -55,7 +66,7 @@ all_modules = [ [ 'module-tunnel-sink-new', ['module-tunnel-sink-new.c', 'restart-module.c'] ], [ 'module-tunnel-source', ['module-tunnel.c', 'restart-module.c'], [], [], [x11_dep] ], [ 'module-tunnel-source-new', ['module-tunnel-source-new.c', 'restart-module.c'] ], - [ 'module-virtual-sink', 'module-virtual-sink.c' ], + [ 'module-virtual-sink', 'module-virtual-sink.c', [], [], [], libvirtual_sink ], [ 'module-virtual-source', 'module-virtual-source.c' ], [ 'module-volume-restore', 'module-volume-restore.c' ], ] diff --git a/src/modules/module-virtual-sink.c b/src/modules/module-virtual-sink.c index 5001ce45d..add0695f9 100644 --- a/src/modules/module-virtual-sink.c +++ b/src/modules/module-virtual-sink.c @@ -22,15 +22,15 @@ #include #endif +#include + #include #include #include #include -#include #include #include -#include #include #include #include @@ -46,25 +46,17 @@ PA_MODULE_USAGE( "master= " "rate= " "channels= " + "format= " "channel_map= " "use_volume_sharing= " "force_flat_volume= " )); -#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) - struct userdata { pa_module *module; - /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ - /* bool autoloaded; */ + pa_vsink *vsink; - pa_sink *sink; - pa_sink_input *sink_input; - - pa_memblockq *memblockq; - - bool auto_desc; unsigned channels; }; @@ -74,6 +66,7 @@ static const char* const valid_modargs[] = { "master", "rate", "channels", + "format", "channel_map", "use_volume_sharing", "force_flat_volume", @@ -81,397 +74,17 @@ static const char* const valid_modargs[] = { }; /* Called from I/O thread context */ -static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SINK(o)->userdata; - - switch (code) { - - case PA_SINK_MESSAGE_GET_LATENCY: - - /* The sink is _put() before the sink input is, so let's - * make sure we don't access it in that time. Also, the - * sink input is first shut down, the sink second. */ - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { - *((int64_t*) data) = 0; - return 0; - } - - *((int64_t*) data) = - - /* Get the latency of the master sink */ - pa_sink_get_latency_within_thread(u->sink_input->sink, true) + - - /* Add the latency internal to our sink input on top */ - pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); - - /* Add resampler latency */ - *((int64_t*) data) += pa_resampler_get_delay_usec(u->sink_input->thread_info.resampler); - - return 0; - } - - return pa_sink_process_msg(o, code, data, offset, chunk); -} - -/* Called from main context */ -static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) { +static void filter_process_chunk(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata) { struct userdata *u; + unsigned buffer_size; - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); + pa_assert_se(u = userdata); + pa_assert(in_count == out_count); - if (!PA_SINK_IS_LINKED(state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->state)) - return 0; - - pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); - return 0; -} - -/* Called from the IO thread. */ -static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { - struct userdata *u; - - pa_assert(s); - pa_assert_se(u = s->userdata); - - /* When set to running or idle for the first time, request a rewind - * of the master sink to make sure we are heard immediately */ - if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) { - pa_log_debug("Requesting rewind due to state change."); - pa_sink_input_request_rewind(u->sink_input, 0, false, true, true); - } - - return 0; -} - -/* Called from I/O thread context */ -static void sink_request_rewind_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) - return; - - /* Just hand this one over to the master sink */ - pa_sink_input_request_rewind(u->sink_input, - s->thread_info.rewind_nbytes + - pa_memblockq_get_length(u->memblockq), true, false, false); -} - -/* Called from I/O thread context */ -static void sink_update_requested_latency_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) - return; - - /* Just hand this one over to the master sink */ - pa_sink_input_set_requested_latency_within_thread( - u->sink_input, - pa_sink_get_requested_latency_within_thread(s)); -} - -/* Called from main context */ -static void sink_set_volume_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(s->state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->state)) - return; - - pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, true); -} - -/* Called from main context */ -static void sink_set_mute_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(s->state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->state)) - return; - - pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); -} - -/* Called from I/O thread context */ -static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { - struct userdata *u; - float *src, *dst; - size_t fs; - unsigned n, c; - pa_memchunk tchunk; - pa_usec_t current_latency PA_GCC_UNUSED; - - pa_sink_input_assert_ref(i); - pa_assert(chunk); - pa_assert_se(u = i->userdata); - - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return -1; - - /* Hmm, process any rewind request that might be queued up */ - pa_sink_process_rewind(u->sink, 0); - - /* (1) IF YOU NEED A FIXED BLOCK SIZE USE - * pa_memblockq_peek_fixed_size() HERE INSTEAD. NOTE THAT FILTERS - * WHICH CAN DEAL WITH DYNAMIC BLOCK SIZES ARE HIGHLY - * PREFERRED. */ - while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { - pa_memchunk nchunk; - - pa_sink_render(u->sink, nbytes, &nchunk); - pa_memblockq_push(u->memblockq, &nchunk); - pa_memblock_unref(nchunk.memblock); - } - - /* (2) IF YOU NEED A FIXED BLOCK SIZE, THIS NEXT LINE IS NOT - * NECESSARY */ - tchunk.length = PA_MIN(nbytes, tchunk.length); - pa_assert(tchunk.length > 0); - - fs = pa_frame_size(&i->sample_spec); - n = (unsigned) (tchunk.length / fs); - - pa_assert(n > 0); - - chunk->index = 0; - chunk->length = n*fs; - chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); - - pa_memblockq_drop(u->memblockq, chunk->length); - - src = pa_memblock_acquire_chunk(&tchunk); - dst = pa_memblock_acquire(chunk->memblock); - - /* (3) PUT YOUR CODE HERE TO DO SOMETHING WITH THE DATA */ + buffer_size = pa_frame_size(&u->vsink->sink->sample_spec) * in_count; /* As an example, copy input to output */ - for (c = 0; c < u->channels; c++) { - pa_sample_clamp(PA_SAMPLE_FLOAT32NE, - dst+c, u->channels * sizeof(float), - src+c, u->channels * sizeof(float), - n); - } - - pa_memblock_release(tchunk.memblock); - pa_memblock_release(chunk->memblock); - - pa_memblock_unref(tchunk.memblock); - - /* (4) IF YOU NEED THE LATENCY FOR SOMETHING ACQUIRE IT LIKE THIS: */ - current_latency = - /* Get the latency of the master sink */ - pa_sink_get_latency_within_thread(i->sink, false) + - - /* Add the latency internal to our sink input on top */ - pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); - - return 0; -} - -/* Called from I/O thread context */ -static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { - struct userdata *u; - size_t amount = 0; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* If the sink is not yet linked, there is nothing to rewind */ - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - - if (u->sink->thread_info.rewind_nbytes > 0) { - size_t max_rewrite; - - max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq); - amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); - u->sink->thread_info.rewind_nbytes = 0; - - if (amount > 0) { - pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true); - - /* (5) PUT YOUR CODE HERE TO RESET YOUR FILTER */ - } - } - - pa_sink_process_rewind(u->sink, amount); - pa_memblockq_rewind(u->memblockq, nbytes); -} - -/* Called from I/O thread context */ -static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* FIXME: Too small max_rewind: - * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_memblockq_set_maxrewind(u->memblockq, nbytes); - pa_sink_set_max_rewind_within_thread(u->sink, nbytes); -} - -/* Called from I/O thread context */ -static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* (6) IF YOU NEED A FIXED BLOCK SIZE ROUND nbytes UP TO MULTIPLES - * OF IT HERE. THE PA_ROUND_UP MACRO IS USEFUL FOR THAT. */ - - pa_sink_set_max_request_within_thread(u->sink, nbytes); -} - -/* Called from I/O thread context */ -static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); -} - -/* Called from I/O thread context */ -static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* (7) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE - * BLOCK MINUS ONE SAMPLE HERE. pa_usec_to_bytes_round_up() IS - * USEFUL FOR THAT. */ - - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); -} - -/* Called from I/O thread context */ -static void sink_input_detach_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - if (PA_SINK_IS_LINKED(u->sink->thread_info.state)) - pa_sink_detach_within_thread(u->sink); - - pa_sink_set_rtpoll(u->sink, NULL); -} - -/* Called from I/O thread context */ -static void sink_input_attach_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); - - /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE - * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */ - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); - - /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND - * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT - * HERE. SEE (6) */ - pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); - - /* FIXME: Too small max_rewind: - * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); - - if (PA_SINK_IS_LINKED(u->sink->thread_info.state)) - pa_sink_attach_within_thread(u->sink); -} - -/* Called from main context */ -static void sink_input_kill_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* The order here matters! We first kill the sink so that streams - * can properly be moved away while the sink input is still connected - * to the master. */ - pa_sink_input_cork(u->sink_input, true); - pa_sink_unlink(u->sink); - pa_sink_input_unlink(u->sink_input); - - pa_sink_input_unref(u->sink_input); - u->sink_input = NULL; - - pa_sink_unref(u->sink); - u->sink = NULL; - - pa_module_unload_request(u->module, true); -} - -/* Called from main context */ -static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - if (dest) { - pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); - pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); - } else - pa_sink_set_asyncmsgq(u->sink, NULL); - - if (u->auto_desc && dest) { - const char *z; - pa_proplist *pl; - - pl = pa_proplist_new(); - z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s", - pa_proplist_gets(u->sink->proplist, "device.vsink.name"), z ? z : dest->name); - - pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); - pa_proplist_free(pl); - } -} - -/* Called from main context */ -static void sink_input_volume_changed_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - pa_sink_volume_changed(u->sink, &i->volume); -} - -/* Called from main context */ -static void sink_input_mute_changed_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - pa_sink_mute_changed(u->sink, i->muted); + memcpy(dst, src, buffer_size); } int pa__init(pa_module*m) { @@ -480,11 +93,7 @@ int pa__init(pa_module*m) { pa_channel_map map; pa_modargs *ma; pa_sink *master=NULL; - pa_sink_input_new_data sink_input_data; - pa_sink_new_data sink_data; bool use_volume_sharing = true; - bool force_flat_volume = false; - pa_memchunk silence; pa_assert(m); @@ -501,7 +110,6 @@ int pa__init(pa_module*m) { pa_assert(master); ss = master->sample_spec; - ss.format = PA_SAMPLE_FLOAT32; map = master->channel_map; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { pa_log("Invalid sample format specification or channel map"); @@ -513,118 +121,23 @@ int pa__init(pa_module*m) { goto fail; } - if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { - pa_log("force_flat_volume= expects a boolean argument"); - goto fail; - } - - if (use_volume_sharing && force_flat_volume) { - pa_log("Flat volume can't be forced when using volume sharing."); - goto fail; - } - u = pa_xnew0(struct userdata, 1); u->module = m; m->userdata = u; u->channels = ss.channels; - /* Create sink */ - pa_sink_new_data_init(&sink_data); - sink_data.driver = __FILE__; - sink_data.module = m; - if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) - sink_data.name = pa_sprintf_malloc("%s.vsink", master->name); - pa_sink_new_data_set_sample_spec(&sink_data, &ss); - pa_sink_new_data_set_channel_map(&sink_data, &map); - pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); - pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - pa_proplist_sets(sink_data.proplist, "device.vsink.name", sink_data.name); - - if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log("Invalid properties"); - pa_sink_new_data_done(&sink_data); - goto fail; - } - - if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { - const char *z; - - z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s", sink_data.name, z ? z : master->name); - } - - u->sink = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)) - | (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0)); - pa_sink_new_data_done(&sink_data); - - if (!u->sink) { - pa_log("Failed to create sink."); - goto fail; - } - - u->sink->parent.process_msg = sink_process_msg_cb; - u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb; - u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; - u->sink->update_requested_latency = sink_update_requested_latency_cb; - u->sink->request_rewind = sink_request_rewind_cb; - pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb); - if (!use_volume_sharing) { - pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); - pa_sink_enable_decibel_volume(u->sink, true); - } - /* Normally this flag would be enabled automatically be we can force it. */ - if (force_flat_volume) - u->sink->flags |= PA_SINK_FLAT_VOLUME; - u->sink->userdata = u; - - pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); - - /* Create sink input */ - pa_sink_input_new_data_init(&sink_input_data); - sink_input_data.driver = __FILE__; - sink_input_data.module = m; - pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true); - sink_input_data.origin_sink = u->sink; - pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); - pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); - pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); - pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); - sink_input_data.flags |= PA_SINK_INPUT_START_CORKED; - - pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); - pa_sink_input_new_data_done(&sink_input_data); - - if (!u->sink_input) + /* Create virtual sink */ + if (!(u->vsink = pa_virtual_sink_create(master, "vsink", "Virtual Sink", &ss, &map, + &ss, &map, m, u, ma, use_volume_sharing, true, 0))) goto fail; - u->sink_input->pop = sink_input_pop_cb; - u->sink_input->process_rewind = sink_input_process_rewind_cb; - u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; - u->sink_input->update_max_request = sink_input_update_max_request_cb; - u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; - u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; - u->sink_input->kill = sink_input_kill_cb; - u->sink_input->attach = sink_input_attach_cb; - u->sink_input->detach = sink_input_detach_cb; - u->sink_input->moving = sink_input_moving_cb; - u->sink_input->volume_changed = use_volume_sharing ? NULL : sink_input_volume_changed_cb; - u->sink_input->mute_changed = sink_input_mute_changed_cb; - u->sink_input->userdata = u; + /* Set callback for virtual sink */ + u->vsink->process_chunk = filter_process_chunk; - u->sink->input_to_master = u->sink_input; + /* INITIALIZE YOUR FILTER HERE */ - pa_sink_input_get_silence(u->sink_input, &silence); - u->memblockq = pa_memblockq_new("module-virtual-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence); - pa_memblock_unref(silence.memblock); - - /* (9) INITIALIZE ANYTHING ELSE YOU NEED HERE */ - - /* The order here is important. The input must be put first, - * otherwise streams might attach to the sink before the sink - * input is attached to the master. */ - pa_sink_input_put(u->sink_input); - pa_sink_put(u->sink); - pa_sink_input_cork(u->sink_input, false); + if (pa_virtual_sink_activate(u->vsink) < 0) + goto fail; pa_modargs_free(ma); @@ -645,7 +158,7 @@ int pa__get_n_used(pa_module *m) { pa_assert(m); pa_assert_se(u = m->userdata); - return pa_sink_linked_by(u->sink); + return pa_sink_linked_by(u->vsink->sink); } void pa__done(pa_module*m) { @@ -656,25 +169,8 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - /* See comments in sink_input_kill_cb() above regarding - * destruction order! */ - - if (u->sink_input) - pa_sink_input_cork(u->sink_input, true); - - if (u->sink) - pa_sink_unlink(u->sink); - - if (u->sink_input) { - pa_sink_input_unlink(u->sink_input); - pa_sink_input_unref(u->sink_input); - } - - if (u->sink) - pa_sink_unref(u->sink); - - if (u->memblockq) - pa_memblockq_free(u->memblockq); + if (u->vsink) + pa_virtual_sink_destroy(u->vsink); pa_xfree(u); } diff --git a/src/modules/virtual-sink-common.c b/src/modules/virtual-sink-common.c new file mode 100644 index 000000000..a80673b8c --- /dev/null +++ b/src/modules/virtual-sink-common.c @@ -0,0 +1,1404 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +PA_DEFINE_PRIVATE_CLASS(pa_vsink, pa_msgobject); +#define PA_VSINK(o) (pa_vsink_cast(o)) + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + +#define NO_REWIND_MAX_LATENCY (50 * PA_USEC_PER_MSEC) +#define MIN_BLOCK_SIZE 16 +#define LATENCY_MARGIN (5 * PA_USEC_PER_MSEC) + +enum { + SINK_MESSAGE_UPDATE_PARAMETERS = PA_SINK_MESSAGE_MAX +}; + +enum { + VSINK_MESSAGE_FREE_PARAMETERS, + VSINK_MESSAGE_INPUT_ATTACHED +}; + +/* Helper functions */ + +static inline pa_sink_input* get_input_from_sink(pa_sink *s) { + + if (!s->vsink || !s->vsink->input_to_master) + return NULL; + return s->vsink->input_to_master; +} + +static int check_block_sizes(size_t fixed_block_frames, size_t fixed_input_block_frames, size_t overlap_frames, pa_vsink *vs) { + size_t max_block_frames; + size_t max_frame_size; + + max_frame_size = PA_MAX(pa_frame_size(&vs->sink->sample_spec), pa_frame_size(&vs->input_to_master->sample_spec)); + + max_block_frames = pa_mempool_block_size_max(vs->sink->core->mempool); + max_block_frames = max_block_frames / max_frame_size; + + if (fixed_block_frames > max_block_frames || fixed_input_block_frames > max_block_frames || overlap_frames + MIN_BLOCK_SIZE > max_block_frames) { + pa_log_warn("At least one of fixed_block_size, fixed_input_block_size or overlap_frames exceeds maximum."); + return -1; + } + + if (fixed_block_frames > 0 && fixed_block_frames < MIN_BLOCK_SIZE) { + pa_log_warn("fixed_block_size too small."); + return -1; + } + + if (fixed_input_block_frames > 0 && fixed_input_block_frames < MIN_BLOCK_SIZE) { + pa_log_warn("fixed_input_block_size too small."); + return -1; + } + + if (fixed_block_frames + overlap_frames > max_block_frames) { + pa_log_warn("Sum of fixed_block_size and overlap_frames exceeds maximum."); + return -1; + } + + if (fixed_input_block_frames > max_block_frames) { + pa_log_warn("fixed_input_block_size exceeds maximum."); + return -1; + } + + if (fixed_input_block_frames != 0 && fixed_block_frames > fixed_input_block_frames) { + pa_log_warn("fixed_block_size larger than fixed_input_block_size."); + return -1; + } + + return 0; +} + +static size_t get_max_rewind_bytes(pa_vsink *vsink, bool use_master_domain) { + size_t max_rewind_frames, max_rewind_bytes; + + max_rewind_frames = pa_sink_input_get_max_rewind(vsink->input_to_master) / pa_frame_size(&vsink->input_to_master->sample_spec); + + if (vsink->max_rewind < 0) + max_rewind_frames = 0; + else if (vsink->max_rewind > 0) + max_rewind_frames = PA_MIN(max_rewind_frames, (unsigned) vsink->max_rewind); + + if (!use_master_domain) + return max_rewind_frames * pa_frame_size(&vsink->sink->sample_spec); + + /* Convert to master frames */ + max_rewind_frames = max_rewind_frames * vsink->input_to_master->sink->sample_spec.rate / vsink->sink->sample_spec.rate; + + /* Convert to bytes */ + max_rewind_bytes = max_rewind_frames * pa_frame_size(&vsink->input_to_master->sink->sample_spec); + + return max_rewind_bytes; +} + +/* This function is used to ensure that a larger memblockq max_rewind value set + * by update_max_rewind() for fixed block size filters or by a local version of + * update_max_rewind() will not be overwritten. */ +static void set_memblockq_max_rewind(pa_vsink *vsink) { + size_t max_rewind; + + if (!vsink->memblockq) + return; + + max_rewind = PA_MAX(pa_memblockq_get_maxrewind(vsink->memblockq), get_max_rewind_bytes(vsink, false)); + pa_memblockq_set_maxrewind(vsink->memblockq, max_rewind); +} + +static void set_latency_range_within_thread(pa_vsink *vsink) { + pa_usec_t min_latency, max_latency; + pa_sink_input *i = vsink->input_to_master; + pa_sink *s = vsink->sink; + + pa_assert(s); + pa_assert(i); + + min_latency = i->sink->thread_info.min_latency; + max_latency = i->sink->thread_info.max_latency; + + if (s->flags & PA_SINK_DYNAMIC_LATENCY) { + if (vsink->max_latency) + max_latency = PA_MIN(vsink->max_latency, max_latency); + + if (vsink->fixed_block_size) { + pa_usec_t latency; + + latency = pa_bytes_to_usec(vsink->fixed_block_size * pa_frame_size(&s->sample_spec), &s->sample_spec); + min_latency = PA_MAX(min_latency, latency); + } + + max_latency = PA_MAX(max_latency, min_latency); + } + + pa_sink_set_latency_range_within_thread(s, min_latency, max_latency); +} + +/* Sink callbacks */ + +/* Called from I/O thread context */ +int pa_virtual_sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i; + pa_vsink *vsink; + + pa_sink *s = PA_SINK(o); + vsink = s->vsink; + pa_assert(vsink); + i = vsink->input_to_master; + pa_assert(i); + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it in that time. Also, the + * sink input is first shut down, the sink second. */ + if (!PA_SINK_IS_LINKED(s->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) { + *((int64_t*) data) = 0; + return 0; + } + + *((int64_t*) data) = + + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(i->sink, true) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); + + /* Add the latency caused by the local memblockq */ + if (vsink->memblockq) + *((int64_t*) data) += pa_bytes_to_usec(pa_memblockq_get_length(vsink->memblockq), &s->sample_spec); + + /* Add the resampler delay */ + if (vsink->input_to_master->thread_info.resampler) + *((int64_t*) data) += pa_resampler_get_delay_usec(vsink->input_to_master->thread_info.resampler); + + /* Add additional filter latency if required. */ + if (vsink->get_extra_latency) + *((int64_t*) data) += vsink->get_extra_latency(s); + + return 0; + + case SINK_MESSAGE_UPDATE_PARAMETERS: + + /* Rewind the stream. If rewinding is disabled, the filter should handle + * parameter changes without need to rewind the filter. */ + if (vsink->max_rewind >= 0 && PA_SINK_IS_OPENED(s->thread_info.state)) { + pa_log_debug("Requesting rewind due to parameter update."); + pa_sink_request_rewind(s, (size_t) -1); + } + + /* Let the module update the filter parameters. Because the main thread + * is waiting, variables can be accessed freely in the callback. */ + if (vsink->update_filter_parameters) { + void *parameters; + size_t old_block_size, old_input_block_size, old_overlap_frames; + + /* Save old block sizes */ + old_block_size = vsink->fixed_block_size; + old_input_block_size = vsink->fixed_input_block_size; + old_overlap_frames = vsink->overlap_frames; + + parameters = vsink->update_filter_parameters(data, s->userdata); + if (parameters) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(vsink), VSINK_MESSAGE_FREE_PARAMETERS, parameters, 0, NULL, NULL); + + /* Updating the parameters may have changed the block sizes, so check them again. */ + if (check_block_sizes(vsink->fixed_block_size, vsink->fixed_input_block_size, vsink->overlap_frames, vsink) < 0) { + pa_log_warn("Invalid new block sizes, keeping old values."); + vsink->fixed_block_size = old_block_size; + vsink->fixed_input_block_size = old_input_block_size; + vsink->overlap_frames = old_overlap_frames; + } + + /* Inform the filter of the block sizes in use */ + if (vsink->update_block_sizes) + vsink->update_block_sizes(vsink->fixed_block_size, vsink->fixed_input_block_size, vsink->overlap_frames, s->userdata); + + /* If the block sizes changed, max_rewind, tlength and latency range may have changed as well. */ + set_latency_range_within_thread(vsink); + if (i->update_max_rewind) + i->update_max_rewind(i, pa_sink_input_get_max_rewind(i)); + pa_memblockq_set_tlength(vsink->memblockq, vsink->fixed_block_size * pa_frame_size(&vsink->sink->sample_spec)); + } + + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +int pa_virtual_sink_set_state_in_main_thread(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) { + pa_sink_input *i; + + pa_sink_assert_ref(s); + i = get_input_from_sink(s); + pa_assert(i); + + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(i->state)) + return 0; + + pa_sink_input_cork(i, state == PA_SINK_SUSPENDED); + return 0; +} + +/* Called from the IO thread. */ +int pa_virtual_sink_set_state_in_io_thread(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + pa_sink_input *i; + pa_vsink *vsink; + + pa_sink_assert_ref(s); + vsink = s->vsink; + pa_assert(vsink); + i = vsink->input_to_master; + pa_assert(i); + + /* When set to running, request a rewind of the master sink to make sure + * we are heard immediately. Also set max_rewind on sink and master sink */ + if (PA_SINK_IS_RUNNING(new_state) && !PA_SINK_IS_RUNNING(s->thread_info.state)) { + + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, false, true, true); + + set_latency_range_within_thread(vsink); + + pa_sink_set_max_rewind_within_thread(s, get_max_rewind_bytes(vsink, false)); + set_memblockq_max_rewind(vsink); + pa_sink_set_max_rewind_within_thread(i->sink, get_max_rewind_bytes(vsink, true)); + } + + return 0; +} + +/* Called from I/O thread context */ +void pa_virtual_sink_request_rewind(pa_sink *s) { + pa_vsink *vsink; + pa_sink_input *i; + size_t amount, in_fs, out_fs; + + pa_sink_assert_ref(s); + vsink = s->vsink; + pa_assert(vsink); + i = vsink->input_to_master; + pa_assert(i); + + if (!PA_SINK_IS_LINKED(s->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) + return; + + if (vsink->max_rewind < 0) { + pa_sink_input_request_rewind(i, 0, true, false, false); + return; + } + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + + amount = s->thread_info.rewind_nbytes; + if (vsink->memblockq) + amount += pa_memblockq_get_length(vsink->memblockq); + + /* Convert to sink input domain */ + amount = amount * out_fs / in_fs; + + /* Just hand this one over to the master sink */ + pa_sink_input_request_rewind(i, amount, true, false, false); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_update_requested_latency(pa_sink *s) { + pa_vsink *vsink; + pa_sink_input *i; + pa_usec_t latency; + + pa_sink_assert_ref(s); + vsink = s->vsink; + pa_assert(vsink); + i = vsink->input_to_master; + pa_assert(i); + + if (!PA_SINK_IS_LINKED(s->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) + return; + + latency = pa_sink_get_requested_latency_within_thread(s); + if (vsink->max_latency) + latency = PA_MIN(vsink->max_latency, latency); + + /* If we are using fixed blocksize, part of the latency is implemented + * in the virtual sink. Reduce master latency by this amount. Do not set + * the latency too small to avoid high CPU load and underruns. */ + if (vsink->fixed_block_size) { + size_t in_fs; + pa_usec_t fixed_block_latency, min_latency; + + in_fs = pa_frame_size(&s->sample_spec); + fixed_block_latency = pa_bytes_to_usec(vsink->fixed_block_size * in_fs, &s->sample_spec); + min_latency = i->sink->thread_info.min_latency; + if (min_latency < LATENCY_MARGIN) + min_latency += LATENCY_MARGIN; + + if (latency < fixed_block_latency + min_latency) + latency = min_latency; + else + latency = latency - fixed_block_latency; + } + + /* Now hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread(i, latency); +} + +/* Called from main context */ +void pa_virtual_sink_set_volume(pa_sink *s) { + pa_sink_input *i; + pa_cvolume vol; + + pa_sink_assert_ref(s); + i = get_input_from_sink(s); + pa_assert(i); + + if (!PA_SINK_IS_LINKED(s->state) || + !PA_SINK_INPUT_IS_LINKED(i->state)) + return; + + /* Remap the volume, sink and sink input may have different + * channel counts. */ + vol = s->real_volume; + pa_cvolume_remap(&vol, &s->channel_map, &i->channel_map); + pa_sink_input_set_volume(i, &vol, s->save_volume, true); +} + +/* Called from main context */ +void pa_virtual_sink_set_mute(pa_sink *s) { + pa_sink_input *i; + + pa_sink_assert_ref(s); + i = get_input_from_sink(s); + pa_assert(i); + + if (!PA_SINK_IS_LINKED(s->state) || + !PA_SINK_INPUT_IS_LINKED(i->state)) + return; + + pa_sink_input_set_mute(i, s->muted, s->save_muted); +} + +/* Sink iinput callbacks */ + +/* Called from I/O thread context */ +int pa_virtual_sink_input_pop(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + pa_sink *s; + uint8_t *src, *dst; + size_t in_fs, out_fs, in_count; + size_t overlap_frames, max_block_frames; + unsigned n; + pa_memchunk tchunk; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + pa_assert(chunk); + + if (!PA_SINK_IS_LINKED(s->thread_info.state)) + return -1; + + /* Process any rewind request that might be queued up. */ + pa_sink_process_rewind(s, 0); + + if (!vsink->process_chunk || !vsink->memblockq) { + pa_sink_render(s, nbytes, chunk); + return 0; + } + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + max_block_frames = pa_mempool_block_size_max(i->sink->core->mempool) / PA_MAX(in_fs, out_fs); + nbytes = PA_MIN(nbytes, max_block_frames * out_fs); + + /* Get new samples. */ + if (!vsink->fixed_block_size) { + + while (pa_memblockq_peek(vsink->memblockq, &tchunk) < 0) { + pa_memchunk nchunk; + + pa_sink_render(s, nbytes * in_fs / out_fs, &nchunk); + pa_memblockq_push(vsink->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + + tchunk.length = PA_MIN(nbytes * in_fs / out_fs, tchunk.length); + + } else { + size_t bytes_missing; + + /* Make sure that the memblockq contains enough data. */ + while ((bytes_missing = pa_memblockq_get_missing(vsink->memblockq)) != 0) { + pa_memchunk nchunk; + + pa_sink_render(s, bytes_missing, &nchunk); + pa_memblockq_push(vsink->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + + pa_memblockq_peek_fixed_size(vsink->memblockq, vsink->fixed_block_size * in_fs, &tchunk); + } + pa_assert(tchunk.length > 0); + + n = (unsigned) (PA_MIN(tchunk.length, vsink->max_chunk_size) / in_fs); + + pa_assert(n > 0); + + /* Release result chunk, the data will be fetched again later. */ + pa_memblock_unref(tchunk.memblock); + pa_memchunk_reset(&tchunk); + + /* Calculate size of overlap. */ + overlap_frames = vsink->overlap_frames; + if (vsink->get_current_overlap) + overlap_frames = PA_MIN(overlap_frames, vsink->get_current_overlap(i)); + + /* If fixed input block size is used, the overlap_framse value will be ignored. */ + if (vsink->fixed_input_block_size) { + overlap_frames = 0; + if (n > vsink->fixed_input_block_size) + n = vsink->fixed_input_block_size; + else + overlap_frames = vsink->fixed_input_block_size - n; + } + + /* In case of variable block size, it may be possible, that the sum of + * new samples and history data exceeds pa_mempool_block_size_max(). + * Then the number of new samples must be limited. */ + if (n + overlap_frames > max_block_frames) + n = max_block_frames - overlap_frames; + + /* Now rewind the memblockq to get some history data if required. */ + if (overlap_frames) + pa_memblockq_rewind(vsink->memblockq, overlap_frames * in_fs); + + /* Prepare output chunk */ + chunk->index = 0; + chunk->length = n * out_fs; + chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); + + /* Get input data */ + in_count = n + overlap_frames; + pa_memblockq_peek_fixed_size(vsink->memblockq, in_count * in_fs, &tchunk); + pa_memblockq_drop(vsink->memblockq, in_count * in_fs); + + src = pa_memblock_acquire_chunk(&tchunk); + dst = pa_memblock_acquire(chunk->memblock); + + /* Let the filter process the chunk */ + vsink->process_chunk(src, dst, in_count, n, i->userdata); + + /* For fixed block size filters, we may have to drop some of the data + * after a rewind (see pa_virtual_sink_input_process_rewind()). */ + chunk->index += vsink->drop_bytes; + chunk->length -= vsink->drop_bytes; + vsink->drop_bytes = 0; + + pa_memblock_release(tchunk.memblock); + pa_memblock_release(chunk->memblock); + + pa_memblock_unref(tchunk.memblock); + + return 0; +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_process_rewind(pa_sink_input *i, size_t nbytes) { + pa_sink *s; + size_t amount = 0; + size_t in_fs, out_fs; + size_t rewind_frames; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + /* If the sink is not yet linked, there is nothing to rewind */ + if (!PA_SINK_IS_LINKED(s->thread_info.state)) + return; + + /* If the sink input is corked, ignore the rewind request. */ + if (i->thread_info.state == PA_SINK_INPUT_CORKED) + return; + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + rewind_frames = nbytes / out_fs; + + /* For fixed block size filters, rewind the filter by a full number of blocks. + * This means that during the next sink_input_pop() call, the filter will + * process some old data that must be discarded after processing. */ + if (vsink->fixed_block_size) + rewind_frames = PA_ROUND_UP(rewind_frames, vsink->fixed_block_size); + + /* Rewind the filter before changing the write pointer of the memblockq. + * Because the memblockq is placed before the filter, the filter must always + * be rewound by the full amount. */ + if (vsink->rewind_filter && nbytes > 0 ) + vsink->rewind_filter(s, rewind_frames); + + if ((s->thread_info.rewind_nbytes > 0) && (vsink->max_rewind >= 0)) { + size_t max_rewrite; + + max_rewrite = nbytes * in_fs / out_fs; + if (vsink->memblockq) + max_rewrite += pa_memblockq_get_length(vsink->memblockq); + amount = PA_MIN(s->thread_info.rewind_nbytes, max_rewrite); + s->thread_info.rewind_nbytes = 0; + + /* Update write pointer if the data needs to be rewritten. */ + if (vsink->memblockq && amount > 0) + pa_memblockq_seek(vsink->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true); + } + + if (vsink->memblockq) + pa_sink_process_rewind(s, amount); + else + pa_sink_process_rewind(s, nbytes * in_fs / out_fs); + + if (vsink->memblockq) { + pa_memblockq_rewind(vsink->memblockq, rewind_frames * in_fs); + + /* Remember number of bytes to drop during next sink_input_pop(). */ + if (vsink->fixed_block_size) + vsink->drop_bytes = rewind_frames * out_fs - nbytes; + } +} + +/* Called from I/O thread context */ +size_t pa_virtual_sink_input_get_max_rewind_limit(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + size_t ret, rewind_limit; + void *state = NULL; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + ret = (size_t)(-1); + + if (!PA_SINK_IS_OPENED(s->thread_info.state)) + return ret; + + /* Disable rewinding if max_rewind = -1 */ + if (vsink->max_rewind < 0) + return 0; + + /* If a max_rewind value > 0 was specified, limit rewinding to + * the specified number of frames */ + if (vsink->max_rewind > 0) + ret = vsink->max_rewind; + + /* Calculate the number of frames we can rewind in the sink domain. */ + if (ret != (size_t)(-1)) { + /* Convert to master frames */ + ret = ret * vsink->input_to_master->sink->sample_spec.rate / vsink->sink->sample_spec.rate; + + /* Convert to bytes */ + ret *= pa_frame_size(&vsink->input_to_master->sink->sample_spec); + } + + /* Get the limit from the attached sink inputs (in vsink sample spec) */ + rewind_limit = (size_t)(-1); + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { + + if (i->get_max_rewind_limit) { + size_t limit; + + limit = i->get_max_rewind_limit(i); + if (rewind_limit == (size_t)(-1) || rewind_limit > limit) + rewind_limit = limit; + } + } + } + + if (rewind_limit != (size_t)(-1)) { + + /* Convert to frames */ + rewind_limit = rewind_limit / pa_frame_size(&vsink->sink->sample_spec); + + /* Convert to master frames */ + rewind_limit = rewind_limit * vsink->input_to_master->sink->sample_spec.rate / vsink->sink->sample_spec.rate; + + /* Convert to bytes */ + rewind_limit *= pa_frame_size(&vsink->input_to_master->sink->sample_spec); + + /* Use the minimum of the local and sink input limit */ + if (ret != (size_t)(-1)) + ret = PA_MIN(ret, rewind_limit); + else + ret = rewind_limit; + } + + return ret; +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes) { + pa_sink *s; + size_t in_fs, out_fs, max_rewind; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + + max_rewind = nbytes * in_fs / out_fs; + + if (vsink->memblockq) { + size_t add_on_bytes = 0; + + /* For fixed block size filters we have to add one block size to + * max_rewind of the memblockq to ensure we can rewind to a block + * border, even if a full rewind is requested. */ + if (max_rewind > 0) + add_on_bytes = vsink->fixed_block_size * in_fs; + + /* Add the history frames. */ + add_on_bytes += vsink->overlap_frames * in_fs; + + /* For fixed input size filters, simply use vsink->fixed_input_block_size */ + if (vsink->fixed_input_block_size) + add_on_bytes = vsink->fixed_input_block_size * in_fs; + + pa_memblockq_set_maxrewind(vsink->memblockq, max_rewind + add_on_bytes); + } + + pa_sink_set_max_rewind_within_thread(s, max_rewind); + + if (vsink->set_filter_max_rewind) { + + max_rewind = nbytes / out_fs; + if (vsink->fixed_block_size) + max_rewind = PA_ROUND_UP(max_rewind, vsink->fixed_block_size); + + vsink->set_filter_max_rewind(i, max_rewind); + } +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_update_max_request(pa_sink_input *i, size_t nbytes) { + pa_sink *s; + size_t in_fs, out_fs, max_request_frames; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + + max_request_frames = nbytes / out_fs; + + if (vsink->max_request_frames_min) + max_request_frames = PA_MAX(max_request_frames, vsink->max_request_frames_min); + + /* For a fixed block size filter, round up to the nearest multiple + * of the block size. */ + if (vsink->fixed_block_size) + max_request_frames = PA_ROUND_UP(max_request_frames, vsink->fixed_block_size); + + pa_sink_set_max_request_within_thread(s, max_request_frames * in_fs); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_update_sink_latency_range(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + set_latency_range_within_thread(vsink); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_update_sink_fixed_latency(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + pa_usec_t latency; + size_t in_fs; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + in_fs = pa_frame_size(&s->sample_spec); + + /* For filters with fixed block size we have to add the block size minus 1 sample + * to the fixed latency. */ + latency = i->sink->thread_info.fixed_latency; + if (vsink->fixed_block_size && !(s->flags & PA_SINK_DYNAMIC_LATENCY)) + latency += pa_bytes_to_usec((vsink->fixed_block_size - 1) * in_fs, &s->sample_spec); + + pa_sink_set_fixed_latency_within_thread(s, latency); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_detach(pa_sink_input *i) { + pa_sink *s; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + + if (PA_SINK_IS_LINKED(s->thread_info.state)) + pa_sink_detach_within_thread(s); + + pa_sink_set_rtpoll(s, NULL); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_attach(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + size_t in_fs, out_fs; + pa_usec_t latency; + size_t max_request_frames; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + + pa_sink_set_rtpoll(s, i->sink->thread_info.rtpoll); + + set_latency_range_within_thread(vsink); + + /* For filters with fixed block size we have to add the block size minus one + * sample to the fixed latency. */ + latency = i->sink->thread_info.fixed_latency; + if (vsink->fixed_block_size && !(s->flags & PA_SINK_DYNAMIC_LATENCY)) + latency += pa_bytes_to_usec((vsink->fixed_block_size - 1) * in_fs, &s->sample_spec); + + pa_sink_set_fixed_latency_within_thread(s, latency); + + max_request_frames = pa_sink_input_get_max_request(i) / out_fs; + + if (vsink->max_request_frames_min) + max_request_frames = PA_MAX(max_request_frames, vsink->max_request_frames_min); + + /* For filters with fixed block size, round up to the nearest multiple + * of the block size. */ + if (vsink->fixed_block_size) + max_request_frames = PA_ROUND_UP(max_request_frames, vsink->fixed_block_size); + + pa_sink_set_max_request_within_thread(s, max_request_frames * in_fs); + + pa_sink_set_max_rewind_within_thread(s, get_max_rewind_bytes(vsink, false)); + set_memblockq_max_rewind(vsink); + if (PA_SINK_IS_OPENED(s->thread_info.state)) + pa_sink_set_max_rewind_within_thread(i->sink, get_max_rewind_bytes(vsink, true)); + + /* This call is needed to remove the UNAVAILABLE suspend cause after + * a move when the previous master sink disappeared. */ + pa_virtual_sink_send_input_attached_message(vsink); + + if (PA_SINK_IS_LINKED(s->thread_info.state)) + pa_sink_attach_within_thread(s); +} + +/* Called from main context */ +void pa_virtual_sink_input_kill(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + pa_module *m; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + /* The order here matters! We first kill the sink so that streams + * can properly be moved away while the sink input is still connected + * to the master. It may be possible that the sink input is connected + * to a virtual sink which has lost its master, so do not try to cork + * if the sink has no I/O context. */ + if (i->sink && i->sink->asyncmsgq) + pa_sink_input_cork(i, true); + pa_sink_unlink(s); + pa_sink_input_unlink(i); + + pa_sink_input_unref(i); + + if (vsink->memblockq) + pa_memblockq_free(vsink->memblockq); + + /* Virtual sinks must set the module */ + m = s->module; + pa_assert(m); + pa_sink_unref(s); + + vsink->sink = NULL; + vsink->input_to_master = NULL; + vsink->memblockq = NULL; + + pa_module_unload_request(m, true); +} + +/* Called from main context */ +bool pa_virtual_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { + pa_sink *s; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + if (vsink->autoloaded) + return false; + + return s != dest; +} + +/* Called from main context */ +void pa_virtual_sink_input_moving(pa_sink_input *i, pa_sink *dest) { + pa_sink *s; + pa_vsink *vsink; + uint32_t idx; + pa_sink_input *input; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + if (dest) { + pa_sink_set_asyncmsgq(s, dest->asyncmsgq); + pa_sink_update_flags(s, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_MASTER_DEVICE, dest->name); + } else + pa_sink_set_asyncmsgq(s, NULL); + + if (dest && vsink->set_description) + vsink->set_description(i, dest); + + else { + if (vsink->auto_desc && dest) { + const char *z; + pa_proplist *pl; + char *proplist_name; + + pl = pa_proplist_new(); + proplist_name = pa_sprintf_malloc("device.%s.name", vsink->sink_type); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "%s %s on %s", vsink->desc_head, + pa_proplist_gets(s->proplist, proplist_name), z ? z : dest->name); + + pa_sink_update_proplist(s, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + pa_xfree(proplist_name); + } + + if (dest) + pa_proplist_setf(i->proplist, PA_PROP_MEDIA_NAME, "%s Stream from %s", vsink->desc_head, pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)); + } + + /* Propagate asyncmsq change to attached virtual sinks */ + PA_IDXSET_FOREACH(input, s->inputs, idx) { + if (input->origin_sink && input->moving) + input->moving(input, s); + } + + /* Propagate asyncmsq change to virtual sources attached to the monitor */ + if (s->monitor_source) { + pa_source_output *output; + + PA_IDXSET_FOREACH(output, s->monitor_source->outputs, idx) { + if (output->destination_source && output->moving) + output->moving(output, s->monitor_source); + } + } +} + +/* Called from main context */ +void pa_virtual_sink_input_volume_changed(pa_sink_input *i) { + pa_sink *s; + pa_cvolume vol; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + + /* Remap the volume, sink and sink input may have different + * channel counts. */ + vol = i->volume; + pa_cvolume_remap(&vol, &i->channel_map, &s->channel_map); + pa_sink_volume_changed(s, &vol); +} + +/* Called from main context */ +void pa_virtual_sink_input_mute_changed(pa_sink_input *i) { + pa_sink *s; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + + pa_sink_mute_changed(s, i->muted); +} + +/* Called from main context */ +void pa_virtual_sink_input_suspend(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause) { + pa_sink *s; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + + if (!PA_SINK_IS_LINKED(s->state)) + return; + + if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE) + pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE); + else + pa_sink_suspend(s, true, PA_SUSPEND_UNAVAILABLE); +} + +/* Other functions */ + +void pa_virtual_sink_set_callbacks(pa_sink *s, bool use_volume_sharing) { + + s->parent.process_msg = pa_virtual_sink_process_msg; + s->set_state_in_main_thread = pa_virtual_sink_set_state_in_main_thread; + s->set_state_in_io_thread = pa_virtual_sink_set_state_in_io_thread; + s->update_requested_latency = pa_virtual_sink_update_requested_latency; + s->request_rewind = pa_virtual_sink_request_rewind; + pa_sink_set_set_mute_callback(s, pa_virtual_sink_set_mute); + if (!use_volume_sharing) { + pa_sink_set_set_volume_callback(s, pa_virtual_sink_set_volume); + pa_sink_enable_decibel_volume(s, true); + } +} + +void pa_virtual_sink_input_set_callbacks(pa_sink_input *i, bool use_volume_sharing) { + + i->pop = pa_virtual_sink_input_pop; + i->process_rewind = pa_virtual_sink_input_process_rewind; + i->update_max_rewind = pa_virtual_sink_input_update_max_rewind; + i->update_max_request = pa_virtual_sink_input_update_max_request; + i->update_sink_latency_range = pa_virtual_sink_input_update_sink_latency_range; + i->update_sink_fixed_latency = pa_virtual_sink_input_update_sink_fixed_latency; + i->kill = pa_virtual_sink_input_kill; + i->attach = pa_virtual_sink_input_attach; + i->detach = pa_virtual_sink_input_detach; + i->may_move_to = pa_virtual_sink_input_may_move_to; + i->moving = pa_virtual_sink_input_moving; + i->volume_changed = use_volume_sharing ? NULL : pa_virtual_sink_input_volume_changed; + i->mute_changed = pa_virtual_sink_input_mute_changed; + i->suspend = pa_virtual_sink_input_suspend; + i->get_max_rewind_limit = pa_virtual_sink_input_get_max_rewind_limit; +} + +static int vsink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_vsink *vsink; + pa_sink *s; + pa_sink_input *i; + + pa_assert(o); + pa_assert_ctl_context(); + + vsink = PA_VSINK(o); + + switch (code) { + + case VSINK_MESSAGE_FREE_PARAMETERS: + + pa_assert(userdata); + pa_assert(vsink->free_filter_parameters); + vsink->free_filter_parameters(userdata); + return 0; + + case VSINK_MESSAGE_INPUT_ATTACHED: + + s = vsink->sink; + i = vsink->input_to_master; + + /* This may happen if a message is still pending after the vsink was + * destroyed. */ + if (!s || !i) + return 0; + + if (PA_SINK_IS_LINKED(s->state)) { + if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE) + pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE); + else + pa_sink_suspend(s, true, PA_SUSPEND_UNAVAILABLE); + } + return 0; + } + return 0; +} + +int pa_virtual_sink_activate(pa_vsink *vs) { + + pa_assert(vs); + pa_assert(vs->sink); + pa_assert(vs->input_to_master); + + /* Check that block sizes are plausible */ + if (check_block_sizes(vs->fixed_block_size, vs->fixed_input_block_size, vs->overlap_frames, vs) < 0) { + pa_log_warn("Invalid block sizes."); + return -1; + } + + /* For fixed block size filters, set the target length of the memblockq */ + if (vs->memblockq && vs->fixed_block_size) + pa_memblockq_set_tlength(vs->memblockq, vs->fixed_block_size * pa_frame_size(&vs->sink->sample_spec)); + + /* Set sink input latency at startup to max_latency if specified. */ + if (vs->max_latency) + pa_sink_input_set_requested_latency(vs->input_to_master, vs->max_latency); + + /* Set sink max_rewind on master sink. */ + pa_sink_set_max_rewind(vs->input_to_master->sink, get_max_rewind_bytes(vs, true)); + + /* The order here is important. The input must be put first, + * otherwise streams might attach to the sink before the sink + * input is attached to the master. */ + pa_sink_input_put(vs->input_to_master); + pa_sink_put(vs->sink); + + /* If volume sharing and flat volumes are disabled, we have to apply the sink volume to the sink input. */ + if (!(vs->sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) && !pa_sink_flat_volume_enabled(vs->input_to_master->sink)) { + pa_cvolume vol; + + vol = vs->sink->real_volume; + pa_cvolume_remap(&vol, &vs->sink->channel_map, &vs->input_to_master->channel_map); + pa_sink_input_set_volume(vs->input_to_master, &vol, vs->sink->save_volume, true); + } + + pa_sink_input_cork(vs->input_to_master, false); + + return 0; +} + +void pa_virtual_sink_destroy(pa_vsink *vs) { + + pa_assert(vs); + + /* See comments in sink_input_kill() above regarding + * destruction order! */ + if (vs->input_to_master && PA_SINK_INPUT_IS_LINKED(vs->input_to_master->state)) + pa_sink_input_cork(vs->input_to_master, true); + + if (vs->sink) + pa_sink_unlink(vs->sink); + + if (vs->input_to_master) { + pa_sink_input_unlink(vs->input_to_master); + pa_sink_input_unref(vs->input_to_master); + vs->input_to_master = NULL; + } + + if (vs->memblockq) + pa_memblockq_free(vs->memblockq); + + if (vs->sink) { + pa_sink_unref(vs->sink); + vs->sink = NULL; + } + + /* We have to use pa_msgobject_unref() here because there may still be pending + * VSINK_MESSAGE_INPUT_ATTACHED messages. */ + pa_msgobject_unref(PA_MSGOBJECT(vs)); +} + +/* Manually create a vsink structure. */ +pa_vsink* pa_virtual_sink_vsink_new(pa_sink *s, int max_rewind) { + pa_vsink *vsink; + + pa_assert(s); + + /* Create new vsink */ + vsink = pa_msgobject_new(pa_vsink); + vsink->parent.process_msg = vsink_process_msg; + + vsink->sink = s; + s->vsink = vsink; + + /* Reset virtual sink parameters */ + vsink->input_to_master = NULL; + vsink->memblockq = NULL; + vsink->auto_desc = false; + vsink->desc_head = "Unknown Sink"; + vsink->sink_type = "unknown"; + vsink->autoloaded = false; + vsink->max_chunk_size = pa_frame_align(pa_mempool_block_size_max(s->core->mempool), &s->sample_spec); + vsink->fixed_block_size = 0; + vsink->fixed_input_block_size = 0; + vsink->overlap_frames = 0; + vsink->drop_bytes = 0; + vsink->max_request_frames_min = 0; + vsink->max_latency = 0; + vsink->max_rewind = max_rewind; + vsink->rewind_filter = NULL; + vsink->process_chunk = NULL; + vsink->get_extra_latency = NULL; + vsink->set_description = NULL; + vsink->update_filter_parameters = NULL; + vsink->update_block_sizes = NULL; + + /* If rewinding is disabled, limit latency to NO_REWIND_MAX_LATENCY. + * If max_rewind is given, use the maximum of NO_REWIND_MAX_LATENCY + * and max_rewind, else use maximum latency from master sink. */ + if (max_rewind < 0) + vsink->max_latency = NO_REWIND_MAX_LATENCY; + else if (max_rewind > 0) { + vsink->max_latency = max_rewind * PA_USEC_PER_SEC / s->sample_spec.rate; + vsink->max_latency = PA_MAX(vsink->max_latency, NO_REWIND_MAX_LATENCY); + } + + return vsink; +} + +pa_vsink *pa_virtual_sink_create(pa_sink *master, const char *sink_type, const char *desc_prefix, + pa_sample_spec *sink_ss, pa_channel_map *sink_map, + pa_sample_spec *sink_input_ss, pa_channel_map *sink_input_map, + pa_module *m, void *userdata, pa_modargs *ma, + bool use_volume_sharing, bool create_memblockq, + int max_rewind) { + + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_data; + char *sink_type_property; + bool auto_desc; + bool force_flat_volume = false; + bool remix = true; + pa_resample_method_t resample_method = PA_RESAMPLER_INVALID; + pa_vsink *vsink; + pa_sink *s; + pa_sink_input *i; + + /* Make sure all necessary values are set. Only userdata and sink_name + * are allowed to be NULL. */ + pa_assert(master); + pa_assert(sink_ss); + pa_assert(sink_map); + pa_assert(sink_input_ss); + pa_assert(sink_input_map); + pa_assert(m); + pa_assert(ma); + + /* We do not support resampling in filters */ + pa_assert(sink_input_ss->rate == sink_ss->rate); + + if (!sink_type) + sink_type = "unknown"; + if (!desc_prefix) + desc_prefix = "Unknown Sink"; + + /* Get some command line arguments. Because there is no common default + * for use_volume_sharing, this value must be passed as argument to + * pa_virtual_sink_create(). */ + + if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { + pa_log("force_flat_volume= expects a boolean argument"); + return NULL; + } + + if (use_volume_sharing && force_flat_volume) { + pa_log("Flat volume can't be forced when using volume sharing."); + return NULL; + } + + if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) { + pa_log("Invalid boolean remix parameter"); + return NULL; + } + + if (pa_modargs_get_resample_method(ma, &resample_method) < 0) { + pa_log("Invalid resampling method"); + return NULL; + } + + /* Create sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = m->name; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.%s", master->name, sink_type); + pa_sink_new_data_set_sample_spec(&sink_data, sink_ss); + pa_sink_new_data_set_channel_map(&sink_data, sink_map); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid sink properties"); + pa_sink_new_data_done(&sink_data); + return NULL; + } + + s = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)) + | (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0)); + pa_sink_new_data_done(&sink_data); + + if (!s) { + pa_log("Failed to create sink."); + return NULL; + } + + /* Set name and description properties after the sink has been created, + * otherwise they may be duplicate. */ + if ((auto_desc = !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(s->proplist, PA_PROP_DEVICE_DESCRIPTION, "%s %s on %s", desc_prefix, s->name, z ? z : master->name); + } + + sink_type_property = pa_sprintf_malloc("device.%s.name", sink_type); + pa_proplist_sets(s->proplist, sink_type_property, s->name); + pa_xfree(sink_type_property); + + /* Create vsink structure. */ + vsink = pa_virtual_sink_vsink_new(s, max_rewind); + + pa_virtual_sink_set_callbacks(s, use_volume_sharing); + vsink->auto_desc = auto_desc; + vsink->desc_head = desc_prefix; + vsink->sink_type = sink_type; + + /* Normally this flag would be enabled automatically be we can force it. */ + if (force_flat_volume) + s->flags |= PA_SINK_FLAT_VOLUME; + s->userdata = userdata; + + pa_sink_set_asyncmsgq(s, master->asyncmsgq); + + /* Create sink input */ + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true); + sink_input_data.origin_sink = s; + pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "%s Stream from %s", desc_prefix, pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, sink_input_ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, sink_input_map); + sink_input_data.resample_method = resample_method; + sink_input_data.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX) | PA_SINK_INPUT_START_CORKED; + if (!pa_safe_streq(master->name, m->core->default_sink->name)) + sink_input_data.preferred_sink = pa_xstrdup(master->name); + + if (pa_modargs_get_proplist(ma, "sink_input_properties", sink_input_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid sink input properties"); + pa_sink_input_new_data_done(&sink_input_data); + pa_virtual_sink_destroy(vsink); + return NULL; + } + + pa_sink_input_new(&i, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!i) { + pa_log("Could not create sink-input"); + pa_virtual_sink_destroy(vsink); + return NULL; + } + + pa_virtual_sink_input_set_callbacks(i, use_volume_sharing); + i->userdata = userdata; + + vsink->input_to_master = i; + + vsink->autoloaded = false; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &vsink->autoloaded) < 0) { + pa_log("Failed to parse autoloaded value"); + pa_virtual_sink_destroy(vsink); + return NULL; + } + + if (create_memblockq) { + char *tmp; + pa_memchunk silence; + + tmp = pa_sprintf_malloc("%s memblockq", desc_prefix); + pa_sink_input_get_silence(i, &silence); + vsink->memblockq = pa_memblockq_new(tmp, 0, MEMBLOCKQ_MAXLENGTH, 0, sink_ss, 1, 1, 0, &silence); + pa_memblock_unref(silence.memblock); + pa_xfree(tmp); + } + + return vsink; +} + +/* Send request to update filter parameters to the I/O-thread. */ +void pa_virtual_sink_request_parameter_update(pa_vsink *vs, void *parameters) { + + pa_assert(vs); + pa_assert(vs->sink); + + /* parameters may be NULL if it is enough to have access to userdata from the + * callback. */ + pa_asyncmsgq_send(vs->sink->asyncmsgq, PA_MSGOBJECT(vs->sink), SINK_MESSAGE_UPDATE_PARAMETERS, parameters, 0, NULL); +} + +/* Called from I/O context. This is needed as a separate function + * because module-echo-cancel has to send the message from + * sink_input_attach_cb(). */ +void pa_virtual_sink_send_input_attached_message(pa_vsink *vs) { + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(vs), VSINK_MESSAGE_INPUT_ATTACHED, NULL, 0, NULL, NULL); +} diff --git a/src/modules/virtual-sink-common.h b/src/modules/virtual-sink-common.h new file mode 100644 index 000000000..04c73e457 --- /dev/null +++ b/src/modules/virtual-sink-common.h @@ -0,0 +1,77 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#include +#include + +/* Callbacks for virtual sinks. */ +int pa_virtual_sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk); + +int pa_virtual_sink_set_state_in_main_thread(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause); +int pa_virtual_sink_set_state_in_io_thread(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause); + +void pa_virtual_sink_request_rewind(pa_sink *s); +void pa_virtual_sink_update_requested_latency(pa_sink *s); +void pa_virtual_sink_set_volume(pa_sink *s); +void pa_virtual_sink_set_mute(pa_sink *s); + +int pa_virtual_sink_input_pop(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk); + +void pa_virtual_sink_input_process_rewind(pa_sink_input *i, size_t nbytes); +void pa_virtual_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes); +void pa_virtual_sink_input_update_max_request(pa_sink_input *i, size_t nbytes); +void pa_virtual_sink_input_update_sink_latency_range(pa_sink_input *i); +void pa_virtual_sink_input_update_sink_fixed_latency(pa_sink_input *i); + +void pa_virtual_sink_input_detach(pa_sink_input *i); +void pa_virtual_sink_input_attach(pa_sink_input *i); +void pa_virtual_sink_input_kill(pa_sink_input *i); +void pa_virtual_sink_input_moving(pa_sink_input *i, pa_sink *dest); +bool pa_virtual_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest); +size_t pa_virtual_sink_input_get_max_rewind_limit(pa_sink_input *i); + +void pa_virtual_sink_input_volume_changed(pa_sink_input *i); +void pa_virtual_sink_input_mute_changed(pa_sink_input *i); + +void pa_virtual_sink_input_suspend(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause); + +/* Set callbacks for virtual sink and sink input. */ +void pa_virtual_sink_set_callbacks(pa_sink *s, bool use_volume_sharing); +void pa_virtual_sink_input_set_callbacks(pa_sink_input *i, bool use_volume_sharing); + +/* Create a new virtual sink. Returns a filled vsink structure or NULL on failure. */ +pa_vsink *pa_virtual_sink_create(pa_sink *master, const char *sink_type, const char *desc_prefix, + pa_sample_spec *sink_ss, pa_channel_map *sink_map, + pa_sample_spec *sink_input_ss, pa_channel_map *sink_input_map, + pa_module *m, void *userdata, pa_modargs *ma, + bool use_volume_sharing, bool create_memblockq, + int max_rewind); + +/* Activate the new virtual sink. */ +int pa_virtual_sink_activate(pa_vsink *vs); + +/* Destroys the objects associated with the virtual sink. */ +void pa_virtual_sink_destroy(pa_vsink *vs); + +/* Create vsink structure */ +pa_vsink* pa_virtual_sink_vsink_new(pa_sink *s, int max_rewind); + +/* Update filter parameters */ +void pa_virtual_sink_request_parameter_update(pa_vsink *vs, void *parameters); + +/* Send sink input attached message (only needed for module-echo-cancel) */ +void pa_virtual_sink_send_input_attached_message(pa_vsink *vs); diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index 3aa353609..565c8b654 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -816,6 +816,18 @@ size_t pa_memblockq_get_prebuf(pa_memblockq *bq) { return bq->prebuf; } +size_t pa_memblockq_get_missing(pa_memblockq *bq) { + size_t l; + + pa_assert(bq); + + if ((l = pa_memblockq_get_length(bq)) >= bq->tlength) + return 0; + + l = bq->tlength - l; + return PA_MAX(bq->minreq, l); +} + size_t pa_memblockq_pop_missing(pa_memblockq *bq) { size_t l; diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h index 96e41cc3c..4e3cb8403 100644 --- a/src/pulsecore/memblockq.h +++ b/src/pulsecore/memblockq.h @@ -112,6 +112,11 @@ bool pa_memblockq_is_readable(pa_memblockq *bq); /* Return the length of the queue in bytes */ size_t pa_memblockq_get_length(pa_memblockq *bq); +/* Return the number of bytes missing to achieve the target length. + * If the number of missing bytes is smaller than minreq but larger + * than 0, minreq will be returned. */ +size_t pa_memblockq_get_missing(pa_memblockq *bq); + /* Return the number of bytes that are missing since the last call to * this function, reset the internal counter to 0. */ size_t pa_memblockq_pop_missing(pa_memblockq *bq);