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);