diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index ae1bf9d68..4914666e6 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -33,6 +33,9 @@ #include "echo-cancel.h" +#include +#include + #include #include #include @@ -41,11 +44,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -222,12 +223,14 @@ struct userdata { pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write; pa_source *source; + pa_vsource *vsource; bool source_auto_desc; pa_source_output *source_output; pa_memblockq *source_memblockq; /* echo canceller needs fixed sized chunks */ size_t source_skip; pa_sink *sink; + pa_vsink *vsink; bool sink_auto_desc; pa_sink_input *sink_input; pa_memblockq *sink_memblockq; @@ -435,40 +438,6 @@ static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t return pa_source_process_msg(o, code, data, offset, chunk); } -/* Called from sink 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 delay */ - *((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 source_set_state_in_main_thread_cb(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) { struct userdata *u; @@ -519,23 +488,6 @@ static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, p 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 source I/O thread context */ static void source_update_requested_latency_cb(pa_source *s) { struct userdata *u; @@ -557,27 +509,6 @@ static void source_update_requested_latency_cb(pa_source *s) { pa_source_output_set_requested_latency_within_thread(u->source_output, latency); } -/* Called from sink I/O thread context */ -static void sink_update_requested_latency_cb(pa_sink *s) { - struct userdata *u; - pa_usec_t latency; - - 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; - - pa_log_debug("Sink update requested latency"); - - /* Cap the maximum latency so we don't have to process too large chunks */ - latency = PA_MIN(pa_sink_get_requested_latency_within_thread(s), - pa_bytes_to_usec(u->sink_blocksize, &s->sample_spec) * MAX_LATENCY_BLOCKS); - - pa_sink_input_set_requested_latency_within_thread(u->sink_input, latency); -} - /* Called from sink I/O thread context */ static void sink_request_rewind_cb(pa_sink *s) { struct userdata *u; @@ -610,20 +541,6 @@ static void source_set_volume_cb(pa_source *s) { pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, true); } -/* 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 source_get_volume_cb(pa_source *s) { struct userdata *u; @@ -660,20 +577,6 @@ static void source_set_mute_cb(pa_source *s) { pa_source_output_set_mute(u->source_output, s->muted, s->save_muted); } -/* 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 source I/O thread context. */ static void apply_diff_time(struct userdata *u, int64_t diff_time) { int64_t diff; @@ -1156,21 +1059,6 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in return pa_sink_input_process_msg(obj, code, data, offset, chunk); } -/* Called from sink 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); - - pa_log_debug("Sink input update max rewind %lld", (long long) nbytes); - - /* FIXME: Too small max_rewind: - * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_memblockq_set_maxrewind(u->sink_memblockq, nbytes); - pa_sink_set_max_rewind_within_thread(u->sink, nbytes); -} - /* Called from source I/O thread context. */ static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) { struct userdata *u; @@ -1183,18 +1071,6 @@ static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbyte pa_source_set_max_rewind_within_thread(u->source, nbytes); } -/* Called from sink 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); - - pa_log_debug("Sink input update max request %lld", (long long) nbytes); - - pa_sink_set_max_request_within_thread(u->sink, nbytes); -} - /* Called from sink I/O thread context. */ static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) { struct userdata *u; @@ -1221,20 +1097,6 @@ static void source_output_update_source_requested_latency_cb(pa_source_output *o pa_log_debug("Source output update requested latency %lld", (long long) latency); } -/* Called from sink 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_log_debug("Sink input update latency range %lld %lld", - (long long) i->sink->thread_info.min_latency, - (long long) i->sink->thread_info.max_latency); - - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); -} - /* Called from source I/O thread context. */ static void source_output_update_source_latency_range_cb(pa_source_output *o) { struct userdata *u; @@ -1249,19 +1111,6 @@ static void source_output_update_source_latency_range_cb(pa_source_output *o) { pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency); } -/* Called from sink 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); - - pa_log_debug("Sink input update fixed latency %lld", - (long long) i->sink->thread_info.fixed_latency); - - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); -} - /* Called from source I/O thread context. */ static void source_output_update_source_fixed_latency_cb(pa_source_output *o) { struct userdata *u; @@ -1329,6 +1178,10 @@ static void sink_input_attach_cb(pa_sink_input *i) { PA_RTPOLL_LATE, u->asyncmsgq); + /* 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(u->vsink); + if (PA_SINK_IS_LINKED(u->sink->thread_info.state)) pa_sink_attach_within_thread(u->sink); } @@ -1357,14 +1210,10 @@ static void source_output_detach_cb(pa_source_output *o) { static void sink_input_detach_cb(pa_sink_input *i) { struct userdata *u; - pa_sink_input_assert_ref(i); + pa_virtual_sink_input_detach(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); - pa_log_debug("Sink input %d detach", i->index); if (u->rtpoll_item_write) { @@ -1517,19 +1366,12 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { } /* Called from main context */ -static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { +static void sink_set_description_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->sink_auto_desc && dest) { + if (u->vsink->auto_desc) { const char *y, *z; pa_proplist *pl; @@ -1548,26 +1390,6 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { } } -/* 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); -} - /* Called from main context */ static int canceller_process_msg_cb(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { struct pa_echo_canceller_msg *msg; @@ -1911,6 +1733,10 @@ int pa__init(pa_module*m) { pa_source_set_asyncmsgq(u->source, source_master->asyncmsgq); + /* Create vsource structure. Only needed for output_from_master field, otherwise + * unused because the virtual source here is too different from other filters */ + u->vsource = pa_virtual_source_vsource_new(u->source); + /* Create sink */ pa_sink_new_data_init(&sink_data); sink_data.driver = __FILE__; @@ -1948,18 +1774,16 @@ int pa__init(pa_module*m) { goto fail; } - u->sink->parent.process_msg = sink_process_msg_cb; + pa_virtual_sink_set_callbacks(u->sink, u->use_volume_sharing); 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 (!u->use_volume_sharing) { - pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); - pa_sink_enable_decibel_volume(u->sink, true); - } u->sink->userdata = u; + u->vsink = pa_virtual_sink_vsink_new(u->sink, 0); + u->vsink->set_description = sink_set_description_cb; + u->vsink->auto_desc = u->sink_auto_desc; + u->vsink->max_latency = pa_bytes_to_usec(u->sink_blocksize, &u->sink->sample_spec) * MAX_LATENCY_BLOCKS; + pa_sink_set_asyncmsgq(u->sink, sink_master->asyncmsgq); /* Create source output */ @@ -1999,7 +1823,7 @@ int pa__init(pa_module*m) { u->source_output->moving = source_output_moving_cb; u->source_output->userdata = u; - u->source->output_from_master = u->source_output; + u->vsource->output_from_master = u->source_output; /* Create sink input */ pa_sink_input_new_data_init(&sink_input_data); @@ -2022,26 +1846,19 @@ int pa__init(pa_module*m) { if (!u->sink_input) goto fail; + pa_virtual_sink_input_set_callbacks(u->sink_input, u->use_volume_sharing); u->sink_input->parent.process_msg = sink_input_process_msg_cb; 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_requested_latency = sink_input_update_sink_requested_latency_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->state_change = sink_input_state_change_cb; u->sink_input->may_move_to = sink_input_may_move_to_cb; - u->sink_input->moving = sink_input_moving_cb; - if (!u->use_volume_sharing) - u->sink_input->volume_changed = sink_input_volume_changed_cb; - u->sink_input->mute_changed = sink_input_mute_changed_cb; u->sink_input->userdata = u; - u->sink->input_to_master = u->sink_input; + u->vsink->input_to_master = u->sink_input; pa_sink_input_get_silence(u->sink_input, &silence); @@ -2173,6 +1990,12 @@ void pa__done(pa_module*m) { pa_sink_input_unref(u->sink_input); } + if (u->vsink) + pa_xfree(u->vsink); + + if (u->vsource) + pa_xfree(u->vsource); + if (u->source) pa_source_unref(u->source); if (u->sink) diff --git a/src/modules/meson.build b/src/modules/meson.build index 05267c93a..59df566f3 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -2,6 +2,28 @@ 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 +) + +libvirtual_source = shared_library('virtual_source', + 'virtual-source-common.c', + 'virtual-source-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' ], @@ -28,7 +50,7 @@ all_modules = [ [ 'module-http-protocol-tcp', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_HTTP', '-DUSE_TCP_SOCKETS'], [], libprotocol_http ], [ 'module-http-protocol-unix', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_HTTP', '-DUSE_UNIX_SOCKETS'], [], libprotocol_http ], [ 'module-intended-roles', 'module-intended-roles.c' ], - [ 'module-ladspa-sink', 'module-ladspa-sink.c', 'ladspa.h', ['-DLADSPA_PATH=' + join_paths(libdir, 'ladspa') + ':/usr/local/lib/ladspa:/usr/lib/ladspa:/usr/local/lib64/ladspa:/usr/lib64/ladspa'], [dbus_dep, libm_dep, ltdl_dep] ], + [ 'module-ladspa-sink', 'module-ladspa-sink.c', 'ladspa.h', ['-DLADSPA_PATH=' + join_paths(libdir, 'ladspa') + ':/usr/local/lib/ladspa:/usr/lib/ladspa:/usr/local/lib64/ladspa:/usr/lib64/ladspa'], [dbus_dep, libm_dep, ltdl_dep], libvirtual_sink ], [ 'module-loopback', 'module-loopback.c' ], [ 'module-match', 'module-match.c' ], [ 'module-native-protocol-fd', 'module-native-protocol-fd.c', [], [], [], libprotocol_native ], @@ -37,8 +59,8 @@ all_modules = [ [ 'module-null-sink', 'module-null-sink.c' ], [ 'module-null-source', 'module-null-source.c' ], [ 'module-position-event-sounds', 'module-position-event-sounds.c' ], - [ 'module-remap-sink', 'module-remap-sink.c' ], - [ 'module-remap-source', 'module-remap-source.c' ], + [ 'module-remap-sink', 'module-remap-sink.c', [], [], [], libvirtual_sink ], + [ 'module-remap-source', 'module-remap-source.c', [], [], [], libvirtual_source ], [ 'module-rescue-streams', 'module-rescue-streams.c' ], [ 'module-role-cork', ['module-role-cork.c', 'stream-interaction.c'], 'stream-interaction.h' ], [ 'module-role-ducking', ['module-role-ducking.c', 'stream-interaction.c'], 'stream-interaction.h' ], @@ -55,8 +77,8 @@ 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-source', 'module-virtual-source.c' ], + [ 'module-virtual-sink', 'module-virtual-sink.c', [], [], [], libvirtual_sink ], + [ 'module-virtual-source', 'module-virtual-source.c', [], [], [], libvirtual_source ], [ 'module-volume-restore', 'module-volume-restore.c' ], ] @@ -163,13 +185,13 @@ endif if fftw_dep.found() all_modules += [ - [ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c', [], [], [fftw_dep, libm_dep] ], + [ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c', [], [], [fftw_dep, libm_dep], libvirtual_sink ], ] endif if dbus_dep.found() and fftw_dep.found() all_modules += [ - [ 'module-equalizer-sink', 'module-equalizer-sink.c', [], [], [dbus_dep, fftw_dep, libm_dep] ], + [ 'module-equalizer-sink', 'module-equalizer-sink.c', [], [], [dbus_dep, fftw_dep, libm_dep], libvirtual_sink ], ] endif @@ -251,7 +273,7 @@ module_echo_cancel_sources = [ module_echo_cancel_orc_sources = [] module_echo_cancel_flags = [] module_echo_cancel_deps = [libatomic_ops_dep] -module_echo_cancel_libs = [] +module_echo_cancel_libs = [libvirtual_sink, libvirtual_source] if get_option('adrian-aec') module_echo_cancel_sources += [ diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c index 2ccd9eb13..df1e25996 100644 --- a/src/modules/module-combine-sink.c +++ b/src/modules/module-combine-sink.c @@ -124,10 +124,10 @@ struct output { pa_memblockq *memblockq; /* For communication of the stream latencies to the main thread */ - pa_usec_t total_latency; + int64_t total_latency; struct { pa_usec_t timestamp; - pa_usec_t sink_latency; + int64_t sink_latency; size_t output_memblockq_size; uint64_t receive_counter; } latency_snapshot; @@ -249,8 +249,8 @@ static uint32_t rate_controller( static void adjust_rates(struct userdata *u) { struct output *o; struct sink_snapshot rdata; - pa_usec_t avg_total_latency = 0; - pa_usec_t target_latency = 0; + int64_t avg_total_latency = 0; + int64_t target_latency = 0; pa_usec_t max_sink_latency = 0; pa_usec_t min_total_latency = (pa_usec_t)-1; uint32_t base_rate; @@ -280,7 +280,7 @@ static void adjust_rates(struct userdata *u) { return; PA_IDXSET_FOREACH(o, u->outputs, idx) { - pa_usec_t snapshot_latency; + int64_t snapshot_latency; int64_t time_difference; if (!o->sink_input || !PA_SINK_IS_OPENED(o->sink->state)) @@ -319,7 +319,7 @@ static void adjust_rates(struct userdata *u) { /* Debug output */ pa_log_debug("[%s] Snapshot sink latency = %0.2fms, total snapshot latency = %0.2fms", o->sink->name, (double) o->latency_snapshot.sink_latency / PA_USEC_PER_MSEC, (double) snapshot_latency / PA_USEC_PER_MSEC); - if (o->total_latency > 10*PA_USEC_PER_SEC) + if (o->total_latency > (int64_t)(10*PA_USEC_PER_SEC)) pa_log_warn("[%s] Total latency of output is very high (%0.2fms), most likely the audio timing in one of your drivers is broken.", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC); n++; diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c index fdec9d54b..1804e040a 100644 --- a/src/modules/module-equalizer-sink.c +++ b/src/modules/module-equalizer-sink.c @@ -28,6 +28,8 @@ #include #endif +#include + #include #include #include @@ -50,10 +52,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -85,9 +85,7 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; - pa_sink *sink; - pa_sink_input *sink_input; - bool autoloaded; + pa_vsink *vsink; size_t channels; size_t fft_size;//length (res) of fft @@ -125,8 +123,6 @@ struct userdata { pa_database *database; char **base_profiles; - - bool automatic_description; }; static const char* const valid_modargs[] = { @@ -237,134 +233,12 @@ static void alloc_input_buffers(struct userdata *u, size_t min_buffer_length) { u->input_buffer_max = min_buffer_length; } -/* 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: { - //size_t fs=pa_frame_size(&u->sink->sample_spec); - - /* 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->output_q) + - pa_memblockq_get_length(u->input_q), &u->sink_input->sink->sample_spec) + - pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); - // pa_bytes_to_usec(u->samples_gathered * fs, &u->sink->sample_spec); - //+ pa_bytes_to_usec(u->latency * fs, ss) - - /* 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 pa_usec_t sink_get_extra_latency_cb(pa_sink *s) { struct userdata *u; - pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); - 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->input_q), 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); + return pa_bytes_to_usec(pa_memblockq_get_length(u->output_q), &u->vsink->input_to_master->sample_spec); } #if 1 @@ -521,14 +395,14 @@ static void dsp_logic( #endif static void flatten_to_memblockq(struct userdata *u) { - size_t mbs = pa_mempool_block_size_max(u->sink->core->mempool); + size_t mbs = pa_mempool_block_size_max(u->vsink->sink->core->mempool); pa_memchunk tchunk; char *dst; size_t i = 0; while(i < u->output_buffer_length) { tchunk.index = 0; tchunk.length = PA_MIN((u->output_buffer_length - i), mbs); - tchunk.memblock = pa_memblock_new(u->sink->core->mempool, tchunk.length); + tchunk.memblock = pa_memblock_new(u->vsink->sink->core->mempool, tchunk.length); //pa_log_debug("pushing %ld into the q", tchunk.length); dst = pa_memblock_acquire(tchunk.memblock); memcpy(dst, u->output_buffer + i, tchunk.length); @@ -540,7 +414,7 @@ static void flatten_to_memblockq(struct userdata *u) { } static void process_samples(struct userdata *u) { - size_t fs = pa_frame_size(&(u->sink->sample_spec)); + size_t fs = pa_frame_size(&(u->vsink->sink->sample_spec)); unsigned a_i; float *H, X; size_t iterations, offset; @@ -590,7 +464,7 @@ static void process_samples(struct userdata *u) { } static void input_buffer(struct userdata *u, pa_memchunk *in) { - size_t fs = pa_frame_size(&(u->sink->sample_spec)); + size_t fs = pa_frame_size(&(u->vsink->sink->sample_spec)); size_t samples = in->length/fs; float *src = pa_memblock_acquire_chunk(in); pa_assert(u->samples_gathered + samples <= u->input_buffer_max); @@ -617,22 +491,22 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); pa_assert(chunk); - pa_assert(u->sink); + pa_assert(u->vsink->sink); - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) + if (!PA_SINK_IS_LINKED(u->vsink->sink->thread_info.state)) return -1; /* FIXME: Please clean this up. I see more commented code lines * than uncommented code lines. I am sorry, but I am too dumb to * understand this. */ - fs = pa_frame_size(&(u->sink->sample_spec)); - mbs = pa_mempool_block_size_max(u->sink->core->mempool); + fs = pa_frame_size(&(u->vsink->sink->sample_spec)); + mbs = pa_mempool_block_size_max(u->vsink->sink->core->mempool); if (pa_memblockq_get_length(u->output_q) > 0) { //pa_log_debug("qsize is %ld", pa_memblockq_get_length(u->output_q)); goto END; } - //nbytes = PA_MIN(nbytes, pa_mempool_block_size_max(u->sink->core->mempool)); + //nbytes = PA_MIN(nbytes, pa_mempool_block_size_max(u->vsink->sink->core->mempool)); target_samples = PA_ROUND_UP(nbytes / fs, u->R); ////pa_log_debug("vanilla mbs = %ld",mbs); //mbs = PA_ROUND_DOWN(mbs / fs, u->R); @@ -651,7 +525,7 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk chunk->memblock = NULL; /* Hmm, process any rewind request that might be queued up */ - pa_sink_process_rewind(u->sink, 0); + pa_sink_process_rewind(u->vsink->sink, 0); //pa_log_debug("start output-buffered %ld, input-buffered %ld, requested %ld",buffered_samples,u->samples_gathered,samples_requested); //pa_rtclock_get(&start); @@ -661,7 +535,7 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk pa_assert(input_remaining > 0); while (pa_memblockq_peek(u->input_q, &tchunk) < 0) { //pa_sink_render(u->sink, input_remaining * fs, &tchunk); - pa_sink_render_full(u->sink, PA_MIN(input_remaining * fs, mbs), &tchunk); + pa_sink_render_full(u->vsink->sink, PA_MIN(input_remaining * fs, mbs), &tchunk); pa_memblockq_push(u->input_q, &tchunk); pa_memblock_unref(tchunk.memblock); } @@ -700,26 +574,6 @@ END: return 0; } -/* 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); -} - #if 0 static void reset_filter(struct userdata *u) { size_t fs = pa_frame_size(&u->sink->sample_spec); @@ -738,148 +592,6 @@ static void reset_filter(struct userdata *u) { } #endif -/* 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_log_debug("Rewind callback!"); - 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; - max_rewrite = nbytes + pa_memblockq_get_length(u->input_q); - //PA_MIN(pa_memblockq_get_length(u->input_q), nbytes); - amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); - u->sink->thread_info.rewind_nbytes = 0; - - if (amount > 0) { - //invalidate the output q - pa_memblockq_seek(u->input_q, - (int64_t) amount, PA_SEEK_RELATIVE, true); - pa_log("Resetting filter"); - //reset_filter(u); //this is the "proper" thing to do... - } - } - - pa_sink_process_rewind(u->sink, amount); - pa_memblockq_rewind(u->input_q, 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->input_q, 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; - size_t fs; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - fs = pa_frame_size(&u->sink_input->sample_spec); - pa_sink_set_max_request_within_thread(u->sink, PA_ROUND_UP(nbytes / fs, u->R) * fs); -} - -/* 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); - - 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; - size_t fs, max_request; - - 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); - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); - - fs = pa_frame_size(&u->sink_input->sample_spec); - /* set buffer size to max request, no overlap copy */ - max_request = PA_ROUND_UP(pa_sink_input_get_max_request(u->sink_input) / fs, u->R); - max_request = PA_MAX(max_request, u->window_size); - - pa_sink_set_max_request_within_thread(u->sink, max_request * fs); - - /* 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; - - /* Leave u->sink alone for now, it will be cleaned up on module - * unload (and it is needed during unload as well). */ - - pa_module_unload_request(u->module, true); -} - static void pack(char **strs, size_t len, char **packed, size_t *length) { size_t t_len = 0; size_t headers = (1+len) * sizeof(uint16_t); @@ -967,7 +679,7 @@ static void save_state(struct userdata *u) { pa_aupdate_read_end(u->a_H[c]); } - key.data = u->sink->name; + key.data = u->vsink->sink->name; key.size = strlen(key.data); data.data = state; data.size = filter_state_size + packed_length; @@ -1032,7 +744,7 @@ static void load_state(struct userdata *u) { return; } - key.data = u->sink->name; + key.data = u->vsink->sink->name; key.size = strlen(key.data); if (pa_database_get(database, &key, &value) != NULL) { @@ -1062,55 +774,12 @@ static void load_state(struct userdata *u) { pa_database_close(database); } -/* Called from main context */ -static bool sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - return u->sink != dest; -} - -/* 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 (u->autoloaded) { - /* We were autoloaded, and don't support moving. Let's unload ourselves. */ - pa_log_debug("Can't move autoloaded stream, unloading"); - pa_module_unload_request(u->module, true); - } - - 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); - - if (u->automatic_description) { - const char *master_description; - char *new_description; - - master_description = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); - new_description = pa_sprintf_malloc(_("FFT based equalizer on %s"), - master_description ? master_description : dest->name); - pa_sink_set_description(u->sink, new_description); - pa_xfree(new_description); - } - } else - pa_sink_set_asyncmsgq(u->sink, NULL); -} - int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma; pa_sink *master; - pa_sink_input_new_data sink_input_data; - pa_sink_new_data sink_data; size_t i; unsigned c; float *H; @@ -1194,104 +863,23 @@ int pa__init(pa_module*m) { for (c = 0; c < u->channels; ++c) u->base_profiles[c] = pa_xstrdup("default"); - /* 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.equalizer", 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"); - - 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); + /* Create virtual sink */ + if (!(u->vsink = pa_virtual_sink_create(master, "equalizer", "FFT based equalizer Sink", &ss, &map, + &ss, &map, m, u, ma, use_volume_sharing, false, 0))) goto fail; - } - if (!pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION)) { - const char *master_description; + u->vsink->get_extra_latency = sink_get_extra_latency_cb; + u->vsink->fixed_block_size = u->R; + u->vsink->max_request_frames_min = u->window_size; + u->vsink->input_to_master->pop = sink_input_pop_cb; - master_description = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, - _("FFT based equalizer on %s"), master_description ? master_description : master->name); - u->automatic_description = true; - } - - u->autoloaded = DEFAULT_AUTOLOADED; - if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) { - pa_log("Failed to parse autoloaded value"); - goto fail; - } - - 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); - } - u->sink->userdata = u; - - u->input_q = pa_memblockq_new("module-equalizer-sink input_q", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &u->sink->silence); + u->input_q = pa_memblockq_new("module-equalizer-sink input_q", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &u->vsink->sink->silence); u->output_q = pa_memblockq_new("module-equalizer-sink output_q", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, NULL); u->output_buffer = NULL; u->output_buffer_length = 0; u->output_buffer_max_length = 0; - pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); - //pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->R*fs, &ss)); - - /* 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_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Equalized Stream"); - 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) - 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->may_move_to = sink_input_may_move_to_cb; - u->sink_input->moving = sink_input_moving_cb; - if (!use_volume_sharing) - u->sink_input->volume_changed = sink_input_volume_changed_cb; - u->sink_input->mute_changed = sink_input_mute_changed_cb; - u->sink_input->userdata = u; - - u->sink->input_to_master = u->sink_input; + u->vsink->memblockq = u->input_q; dbus_init(u); @@ -1311,12 +899,8 @@ int pa__init(pa_module*m) { /* load old parameters */ load_state(u); - /* 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); @@ -1337,7 +921,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) { @@ -1349,7 +933,8 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - save_state(u); + if (u->vsink && u->vsink->sink) + save_state(u); dbus_done(u); @@ -1357,26 +942,14 @@ void pa__done(pa_module*m) { pa_xfree(u->base_profiles[c]); pa_xfree(u->base_profiles); - /* 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); pa_xfree(u->output_buffer); - pa_memblockq_free(u->output_q); - pa_memblockq_free(u->input_q); + + if (u->vsink) + pa_virtual_sink_destroy(u->vsink); + + if (u->output_q) + pa_memblockq_free(u->output_q); fftwf_destroy_plan(u->inverse_plan); fftwf_destroy_plan(u->forward_plan); @@ -1622,21 +1195,21 @@ void dbus_init(struct userdata *u) { uint32_t dummy; DBusMessage *message = NULL; pa_idxset *sink_list = NULL; - u->dbus_protocol=pa_dbus_protocol_get(u->sink->core); - u->dbus_path=pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index); + u->dbus_protocol=pa_dbus_protocol_get(u->vsink->sink->core); + u->dbus_path=pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->vsink->sink->index); pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, u->dbus_path, &equalizer_info, u) >= 0); - sink_list = pa_shared_get(u->sink->core, SINKLIST); - u->database = pa_shared_get(u->sink->core, EQDB); + sink_list = pa_shared_get(u->vsink->sink->core, SINKLIST); + u->database = pa_shared_get(u->vsink->sink->core, EQDB); if (sink_list == NULL) { char *state_path; sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func); - pa_shared_set(u->sink->core, SINKLIST, sink_list); + pa_shared_set(u->vsink->sink->core, SINKLIST, sink_list); pa_assert_se(state_path = pa_state_path(NULL, false)); pa_assert_se(u->database = pa_database_open(state_path, "equalizer-presets", false, true)); pa_xfree(state_path); - pa_shared_set(u->sink->core, EQDB, u->database); - pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core); + pa_shared_set(u->vsink->sink->core, EQDB, u->database); + pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->vsink->sink->core); pa_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME); } pa_idxset_put(sink_list, u, &dummy); @@ -1657,14 +1230,14 @@ void dbus_done(struct userdata *u) { pa_dbus_protocol_send_signal(u->dbus_protocol, message); dbus_message_unref(message); - pa_assert_se(sink_list=pa_shared_get(u->sink->core,SINKLIST)); + pa_assert_se(sink_list=pa_shared_get(u->module->core,SINKLIST)); pa_idxset_remove_by_data(sink_list,u,&dummy); if (pa_idxset_size(sink_list) == 0) { pa_dbus_protocol_unregister_extension(u->dbus_protocol, EXTNAME); pa_dbus_protocol_remove_interface(u->dbus_protocol, MANAGER_PATH, manager_info.name); - pa_shared_remove(u->sink->core, EQDB); + pa_shared_remove(u->module->core, EQDB); pa_database_close(u->database); - pa_shared_remove(u->sink->core, SINKLIST); + pa_shared_remove(u->module->core, SINKLIST); pa_xfree(sink_list); } pa_dbus_protocol_remove_interface(u->dbus_protocol, u->dbus_path, equalizer_info.name); @@ -2234,7 +1807,7 @@ void equalizer_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *_u) pa_assert(conn); pa_assert(msg); - rate = (uint32_t) u->sink->sample_spec.rate; + rate = (uint32_t) u->vsink->sink->sample_spec.rate; pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &rate); } @@ -2260,7 +1833,7 @@ void equalizer_get_all(DBusConnection *conn, DBusMessage *msg, void *_u) { rev = 1; n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE(u); - rate = (uint32_t) u->sink->sample_spec.rate; + rate = (uint32_t) u->vsink->sink->sample_spec.rate; fft_size = (uint32_t) u->fft_size; channels = (uint32_t) u->channels; diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c index 1c1278218..39e825c96 100644 --- a/src/modules/module-filter-apply.c +++ b/src/modules/module-filter-apply.c @@ -272,7 +272,7 @@ static bool find_paired_master(struct userdata *u, struct filter *filter, pa_obj } /* Make sure we're not routing to another instance of * the same filter. */ - filter->source_master = so->source->output_from_master->source; + filter->source_master = so->source->vsource->output_from_master->source; } else { filter->source_master = so->source; } @@ -293,7 +293,7 @@ static bool find_paired_master(struct userdata *u, struct filter *filter, pa_obj if (pa_streq(module_name, si->sink->module->name)) { /* Make sure we're not routing to another instance of * the same filter. */ - filter->sink_master = si->sink->input_to_master->sink; + filter->sink_master = si->sink->vsink->input_to_master->sink; } else { filter->sink_master = si->sink; } @@ -461,7 +461,7 @@ static void find_filters_for_module(struct userdata *u, pa_module *m, const char if (sink->module == m) { pa_assert(pa_sink_is_filter(sink)); - fltr = filter_new(name, parameters, sink->input_to_master->sink, NULL); + fltr = filter_new(name, parameters, sink->vsink->input_to_master->sink, NULL); fltr->module_index = m->index; fltr->sink = sink; @@ -474,12 +474,12 @@ static void find_filters_for_module(struct userdata *u, pa_module *m, const char pa_assert(pa_source_is_filter(source)); if (!fltr) { - fltr = filter_new(name, parameters, NULL, source->output_from_master->source); + fltr = filter_new(name, parameters, NULL, source->vsource->output_from_master->source); fltr->module_index = m->index; fltr->source = source; } else { fltr->source = source; - fltr->source_master = source->output_from_master->source; + fltr->source_master = source->vsource->output_from_master->source; } break; diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index b68300d31..4127538c7 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -24,16 +24,16 @@ #include #endif +#include + #include #include #include #include -#include #include #include -#include #include #include #include @@ -76,8 +76,7 @@ They are not related and where possible the names of the LADSPA port variables c struct userdata { pa_module *module; - pa_sink *sink; - pa_sink_input *sink_input; + pa_vsink *vsink; const LADSPA_Descriptor *descriptor; LADSPA_Handle handle[PA_CHANNELS_MAX]; @@ -91,8 +90,6 @@ struct userdata { about control out ports. We connect them all to this single buffer. */ LADSPA_Data control_out; - pa_memblockq *memblockq; - bool *use_default; pa_sample_spec ss; @@ -100,9 +97,6 @@ struct userdata { pa_dbus_protocol *dbus_protocol; char *dbus_path; #endif - - bool auto_desc; - bool autoloaded; }; static const char* const valid_modargs[] = { @@ -124,13 +118,7 @@ static const char* const valid_modargs[] = { NULL }; -/* The PA_SINK_MESSAGE types that extend the predefined messages. */ -enum { - LADSPA_SINK_MESSAGE_UPDATE_PARAMETERS = PA_SINK_MESSAGE_MAX -}; - static int write_control_parameters(struct userdata *u, double *control_values, bool *use_default); -static void connect_control_ports(struct userdata *u); #ifdef HAVE_DBUS @@ -230,7 +218,7 @@ static void set_algorithm_parameters(DBusConnection *conn, DBusMessage *msg, DBu goto error; } - pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), LADSPA_SINK_MESSAGE_UPDATE_PARAMETERS, NULL, 0, NULL); + pa_virtual_sink_request_parameter_update(u->vsink, NULL); pa_dbus_send_empty_reply(conn, msg); @@ -312,8 +300,8 @@ static pa_dbus_interface_info ladspa_info = { static void dbus_init(struct userdata *u) { pa_assert_se(u); - u->dbus_protocol = pa_dbus_protocol_get(u->sink->core); - u->dbus_path = pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index); + u->dbus_protocol = pa_dbus_protocol_get(u->vsink->sink->core); + u->dbus_path = pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->vsink->sink->index); pa_dbus_protocol_add_interface(u->dbus_protocol, u->dbus_path, &ladspa_info, u); } @@ -337,393 +325,41 @@ static void dbus_done(struct userdata *u) { #endif /* HAVE_DBUS */ /* 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; - - case LADSPA_SINK_MESSAGE_UPDATE_PARAMETERS: - - /* rewind the stream to throw away the previously rendered data */ - - pa_log_debug("Requesting rewind due to parameter update."); - pa_sink_request_rewind(u->sink, -1); - - /* change the sink parameters */ - connect_control_ports(u); - - 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) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - 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_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) { +static void filter_process_chunk(uint8_t *src_p, uint8_t *dst_p, unsigned in_count, unsigned out_count, void *userdata) { struct userdata *u; + unsigned h, c; float *src, *dst; - size_t fs; - unsigned n, h, c; - pa_memchunk tchunk; - 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); - - 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); - } - - tchunk.length = PA_MIN(nbytes, tchunk.length); - pa_assert(tchunk.length > 0); - - fs = pa_frame_size(&i->sample_spec); - n = (unsigned) (PA_MIN(tchunk.length, u->block_size) / 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); + pa_assert_se(u = userdata); + pa_assert(in_count == out_count); + src = (float *)src_p; + dst = (float *)dst_p; for (h = 0; h < (u->channels / u->max_ladspaport_count); h++) { for (c = 0; c < u->input_count; c++) - pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c], sizeof(float), src+ h*u->max_ladspaport_count + c, u->channels*sizeof(float), n); - u->descriptor->run(u->handle[h], n); + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c], sizeof(float), src + h * u->max_ladspaport_count + c, u->channels * sizeof(float), in_count); + u->descriptor->run(u->handle[h], in_count); for (c = 0; c < u->output_count; c++) - pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst + h*u->max_ladspaport_count + c, u->channels*sizeof(float), u->output[c], sizeof(float), n); - } - - pa_memblock_release(tchunk.memblock); - pa_memblock_release(chunk->memblock); - - pa_memblock_unref(tchunk.memblock); - - 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) { - unsigned c; - - pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true); - - pa_log_debug("Resetting plugin"); - - /* Reset the plugin */ - if (u->descriptor->deactivate) - for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) - u->descriptor->deactivate(u->handle[c]); - if (u->descriptor->activate) - for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) - u->descriptor->activate(u->handle[c]); - } - } - - 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); - - 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); - - 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); - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); - 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 bool sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - if (u->autoloaded) - return false; - - return u->sink != dest; -} - -/* 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, "LADSPA Plugin %s on %s", - pa_proplist_gets(u->sink->proplist, "device.ladspa.name"), z ? z : dest->name); - - pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); - pa_proplist_free(pl); + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst + h * u->max_ladspaport_count + c, u->channels * sizeof(float), u->output[c], sizeof(float), in_count); } } -/* Called from main context */ -static void sink_input_mute_changed_cb(pa_sink_input *i) { +/* Called from I/O thread context */ +static void reset_filter(pa_sink *s, size_t amount) { struct userdata *u; + unsigned c; - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); + pa_assert_se(u = s->userdata); - pa_sink_mute_changed(u->sink, i->muted); -} + pa_log_debug("Resetting plugin"); -/* Called from main context */ -static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - if (!PA_SINK_IS_LINKED(u->sink->state)) - return; - - if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE) - pa_sink_suspend(u->sink, false, PA_SUSPEND_UNAVAILABLE); - else - pa_sink_suspend(u->sink, true, PA_SUSPEND_UNAVAILABLE); + /* Reset the plugin */ + if (u->descriptor->deactivate) + for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) + u->descriptor->deactivate(u->handle[c]); + if (u->descriptor->activate) + for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) + u->descriptor->activate(u->handle[c]); } static int parse_control_parameters(struct userdata *u, const char *cdata, double *read_values, bool *use_default) { @@ -791,11 +427,12 @@ fail: return -1; } -static void connect_control_ports(struct userdata *u) { +static void connect_control_ports(void *userdata) { + struct userdata *u; unsigned long p = 0, h = 0, c; const LADSPA_Descriptor *d; - pa_assert(u); + pa_assert_se(u = userdata); pa_assert_se(d = u->descriptor); for (p = 0; p < d->PortCount; p++) { @@ -819,6 +456,12 @@ static void connect_control_ports(struct userdata *u) { } } +static void *update_filter_parameters(void *parameters, void *userdata) { + + connect_control_ports(userdata); + return NULL; +} + static int validate_control_parameters(struct userdata *u, double *control_values, bool *use_default) { unsigned long p = 0, h = 0; const LADSPA_Descriptor *d; @@ -1000,15 +643,12 @@ int pa__init(pa_module*m) { char *t; const char *master_name; pa_sink *master; - pa_sink_input_new_data sink_input_data; - pa_sink_new_data sink_data; const char *plugin, *label, *input_ladspaport_map, *output_ladspaport_map; LADSPA_Descriptor_Function descriptor_func; unsigned long input_ladspaport[PA_CHANNELS_MAX], output_ladspaport[PA_CHANNELS_MAX]; const char *e, *cdata; const LADSPA_Descriptor *d; unsigned long p, h, j, n_control, c; - pa_memchunk silence; pa_assert(m); @@ -1273,113 +913,25 @@ int pa__init(pa_module*m) { for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) d->activate(u->handle[c]); - /* 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.ladspa", 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.ladspa.module", plugin); - pa_proplist_sets(sink_data.proplist, "device.ladspa.label", d->Label); - pa_proplist_sets(sink_data.proplist, "device.ladspa.name", d->Name); - pa_proplist_sets(sink_data.proplist, "device.ladspa.maker", d->Maker); - pa_proplist_sets(sink_data.proplist, "device.ladspa.copyright", d->Copyright); - pa_proplist_setf(sink_data.proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID); - - 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; - } - - u->autoloaded = DEFAULT_AUTOLOADED; - if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) { - pa_log("Failed to parse autoloaded value"); - 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, "LADSPA Plugin %s on %s", d->Name, z ? z : master->name); - } - - u->sink = pa_sink_new(m->core, &sink_data, - (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)) | PA_SINK_SHARE_VOLUME_WITH_MASTER); - 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); - 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_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream"); - pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); - - if (pa_modargs_get_proplist(ma, "sink_input_properties", sink_input_data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log("Invalid properties"); - pa_sink_input_new_data_done(&sink_input_data); - goto fail; - } - - 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, "ladspa", "LADSPA Sink", &ss, &map, + &ss, &map, m, u, ma, true, 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->may_move_to = sink_input_may_move_to_cb; - u->sink_input->moving = sink_input_moving_cb; - u->sink_input->mute_changed = sink_input_mute_changed_cb; - u->sink_input->suspend = sink_input_suspend_cb; - u->sink_input->userdata = u; + u->vsink->process_chunk = filter_process_chunk; + u->vsink->rewind_filter = reset_filter; + u->vsink->update_filter_parameters = update_filter_parameters; + u->vsink->max_chunk_size = u->block_size; - u->sink->input_to_master = u->sink_input; + pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.module", plugin); + pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.label", d->Label); + pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.name", d->Name); + pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.maker", d->Maker); + pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.copyright", d->Copyright); + pa_proplist_setf(u->vsink->sink->proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID); - pa_sink_input_get_silence(u->sink_input, &silence); - u->memblockq = pa_memblockq_new("module-ladspa-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence); - pa_memblock_unref(silence.memblock); - - /* 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; #ifdef HAVE_DBUS dbus_init(u); @@ -1404,7 +956,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) { @@ -1416,26 +968,12 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - /* See comments in sink_input_kill_cb() above regarding - * destruction order! */ - #ifdef HAVE_DBUS dbus_done(u); #endif - 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->vsink) + pa_virtual_sink_destroy(u->vsink); for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) { if (u->handle[c]) { @@ -1464,9 +1002,6 @@ void pa__done(pa_module*m) { } } - if (u->memblockq) - pa_memblockq_free(u->memblockq); - pa_xfree(u->control); pa_xfree(u->use_default); pa_xfree(u); diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index 4ff6cd57a..6b0563d7e 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -21,13 +21,13 @@ #include #endif +#include + #include #include -#include #include #include -#include #include #include @@ -50,10 +50,7 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; - pa_sink *sink; - pa_sink_input *sink_input; - - bool auto_desc; + pa_vsink *vsink; }; static const char* const valid_modargs[] = { @@ -70,273 +67,12 @@ static const char* const valid_modargs[] = { NULL }; -/* Called from I/O thread context */ -static int sink_process_msg(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 yet */ - 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(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - 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(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; - - pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, true, false, false); -} - -/* Called from I/O thread context */ -static void sink_update_requested_latency(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 I/O thread context */ -static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { - struct userdata *u; - - 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); - - pa_sink_render(u->sink, nbytes, chunk); - return 0; -} - -/* Called from I/O thread context */ -static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { - size_t amount = 0; - struct userdata *u; - - 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) { - amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes); - u->sink->thread_info.rewind_nbytes = 0; - } - - pa_sink_process_rewind(u->sink, amount); -} - -/* 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_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); - - 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); - - 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); - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); - 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 *k; - pa_proplist *pl; - - pl = pa_proplist_new(); - k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name); - - pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); - pa_proplist_free(pl); - } -} - int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; - pa_resample_method_t resample_method = PA_RESAMPLER_INVALID; pa_channel_map sink_map, stream_map; pa_modargs *ma; pa_sink *master; - pa_sink_input_new_data sink_input_data; - pa_sink_new_data sink_data; - bool remix = true; pa_assert(m); @@ -371,100 +107,17 @@ int pa__init(pa_module*m) { if (pa_channel_map_equal(&stream_map, &master->channel_map)) pa_log_warn("No remapping configured, proceeding nonetheless!"); - if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) { - pa_log("Invalid boolean remix parameter"); - goto fail; - } - - if (pa_modargs_get_resample_method(ma, &resample_method) < 0) { - pa_log("Invalid resampling method"); - goto fail; - } - u = pa_xnew0(struct userdata, 1); u->module = m; m->userdata = u; - /* 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.remapped", master->name); - pa_sink_new_data_set_sample_spec(&sink_data, &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"); + /* Create virtual sink */ + if (!(u->vsink = pa_virtual_sink_create(master, "remapped", "Remapped Sink", &ss, &sink_map, + &ss, &stream_map, m, u, ma, false, false, 0))) + goto fail; - 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); + if (pa_virtual_sink_activate(u->vsink) < 0) goto fail; - } - - if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { - const char *k; - - k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); - } - - u->sink = pa_sink_new(m->core, &sink_data, master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)); - 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; - u->sink->set_state_in_main_thread = sink_set_state_in_main_thread; - u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; - u->sink->update_requested_latency = sink_update_requested_latency; - u->sink->request_rewind = sink_request_rewind; - 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_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream"); - 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, &stream_map); - sink_input_data.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX) | PA_SINK_INPUT_START_CORKED; - sink_input_data.resample_method = resample_method; - - 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) - 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->attach = sink_input_attach_cb; - u->sink_input->detach = sink_input_detach_cb; - u->sink_input->kill = sink_input_kill_cb; - u->sink_input->moving = sink_input_moving_cb; - u->sink_input->userdata = u; - - u->sink->input_to_master = u->sink_input; - - /* 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); pa_modargs_free(ma); @@ -485,7 +138,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) { @@ -496,22 +149,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->vsink) + pa_virtual_sink_destroy(u->vsink); pa_xfree(u); } diff --git a/src/modules/module-remap-source.c b/src/modules/module-remap-source.c index 993700b59..3d1d2dddb 100644 --- a/src/modules/module-remap-source.c +++ b/src/modules/module-remap-source.c @@ -22,6 +22,8 @@ #include #endif +#include + #include #include @@ -47,6 +49,7 @@ PA_MODULE_USAGE( "source_properties= " "master= " "master_channel_map= " + "uplink_sink= (optional)" "format= " "rate= " "channels= " @@ -57,10 +60,7 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; - pa_source *source; - pa_source_output *source_output; - - bool auto_desc; + pa_vsource *vsource; }; static const char* const valid_modargs[] = { @@ -68,6 +68,7 @@ static const char* const valid_modargs[] = { "source_properties", "master", "master_channel_map", + "uplink_sink", "format", "rate", "channels", @@ -77,226 +78,12 @@ static const char* const valid_modargs[] = { NULL }; -/* Called from I/O thread context */ -static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SOURCE(o)->userdata; - - switch (code) { - - case PA_SOURCE_MESSAGE_GET_LATENCY: - - /* The source is _put() before the source output is, so let's - * make sure we don't access it in that time. Also, the - * source output is first shut down, the source second. */ - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) { - *((int64_t*) data) = 0; - return 0; - } - - *((int64_t*) data) = - - /* Get the latency of the master source */ - pa_source_get_latency_within_thread(u->source_output->source, true) + - /* Add the latency internal to our source output on top */ - pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec); - - /* Add resampler delay */ - *((int64_t*) data) += pa_resampler_get_delay_usec(u->source_output->thread_info.resampler); - - return 0; - } - - return pa_source_process_msg(o, code, data, offset, chunk); -} - -/* Called from main context */ -static int source_set_state_in_main_thread_cb(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state)) - return 0; - - pa_source_output_cork(u->source_output, state == PA_SOURCE_SUSPENDED); - return 0; -} - -/* Called from I/O thread context */ -static void source_update_requested_latency_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) - return; - - pa_log_debug("Source update requested latency."); - - /* Just hand this one over to the master source */ - pa_source_output_set_requested_latency_within_thread( - u->source_output, - pa_source_get_requested_latency_within_thread(s)); -} - -/* Called from output thread context */ -static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - return; - - if (!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) { - pa_log("push when no link?"); - return; - } - - pa_source_post(u->source, chunk); -} - -/* Called from output thread context */ -static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - /* If the source is not yet linked, there is nothing to rewind */ - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_process_rewind(u->source, nbytes); -} - -/* Called from output thread context */ -static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - pa_source_set_max_rewind_within_thread(u->source, nbytes); -} - -/* Called from output thread context */ -static void source_output_detach_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_detach_within_thread(u->source); - - pa_source_set_rtpoll(u->source, NULL); -} - -/* Called from output thread context */ -static void source_output_attach_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll); - pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency); - pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency); - pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o)); - - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_attach_within_thread(u->source); -} - -/* Called from main thread */ -static void source_output_kill_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_assert_ctl_context(); - pa_assert_se(u = o->userdata); - - /* The order here matters! We first kill the source so that streams - * can properly be moved away while the source output is still connected - * to the master. */ - pa_source_output_cork(u->source_output, true); - pa_source_unlink(u->source); - pa_source_output_unlink(u->source_output); - - pa_source_output_unref(u->source_output); - u->source_output = NULL; - - pa_source_unref(u->source); - u->source = NULL; - - pa_module_unload_request(u->module, true); -} - -/* Called from output thread context except when cork() is called without valid source. */ -static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_assert_se(u = o->userdata); - - pa_log_debug("Source output %d state %d.", o->index, state); -} - -/* Called from main thread */ -static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { - struct userdata *u; - uint32_t idx; - pa_source_output *output; - - pa_source_output_assert_ref(o); - pa_assert_ctl_context(); - pa_assert_se(u = o->userdata); - - if (dest) { - pa_source_set_asyncmsgq(u->source, dest->asyncmsgq); - pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags); - } else - pa_source_set_asyncmsgq(u->source, NULL); - - /* Propagate asyncmsq change to attached virtual sources */ - PA_IDXSET_FOREACH(output, u->source->outputs, idx) { - if (output->destination_source && output->moving) - output->moving(output, u->source); - } - - if (u->auto_desc && dest) { - const char *k; - pa_proplist *pl; - - pl = pa_proplist_new(); - k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name); - - pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl); - pa_proplist_free(pl); - } -} - int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; - pa_resample_method_t resample_method = PA_RESAMPLER_INVALID; pa_channel_map source_map, stream_map; pa_modargs *ma; pa_source *master; - pa_source_output_new_data source_output_data; - pa_source_new_data source_data; - bool remix = true; pa_assert(m); @@ -331,98 +118,17 @@ int pa__init(pa_module*m) { if (pa_channel_map_equal(&stream_map, &master->channel_map)) pa_log_warn("No remapping configured, proceeding nonetheless!"); - if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) { - pa_log("Invalid boolean remix parameter."); - goto fail; - } - - if (pa_modargs_get_resample_method(ma, &resample_method) < 0) { - pa_log("Invalid resampling method"); - goto fail; - } - u = pa_xnew0(struct userdata, 1); u->module = m; m->userdata = u; - /* Create source */ - pa_source_new_data_init(&source_data); - source_data.driver = __FILE__; - source_data.module = m; - if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) - source_data.name = pa_sprintf_malloc("%s.remapped", master->name); - pa_source_new_data_set_sample_spec(&source_data, &ss); - pa_source_new_data_set_channel_map(&source_data, &source_map); - pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); - pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + /* Create virtual sink */ + if (!(u->vsource = pa_virtual_source_create(master, "remapped", "Remapped Source", &ss, &source_map, + &ss, &stream_map, m, u, ma, false, false))) + goto fail; - if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log("Invalid properties."); - pa_source_new_data_done(&source_data); + if (pa_virtual_source_activate(u->vsource) < 0) goto fail; - } - - if ((u->auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { - const char *k; - - k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); - } - - u->source = pa_source_new(m->core, &source_data, master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)); - pa_source_new_data_done(&source_data); - - if (!u->source) { - pa_log("Failed to create source."); - goto fail; - } - - u->source->parent.process_msg = source_process_msg_cb; - u->source->set_state_in_main_thread = source_set_state_in_main_thread_cb; - u->source->update_requested_latency = source_update_requested_latency_cb; - - u->source->userdata = u; - - pa_source_set_asyncmsgq(u->source, master->asyncmsgq); - - /* Create source output */ - pa_source_output_new_data_init(&source_output_data); - source_output_data.driver = __FILE__; - source_output_data.module = m; - pa_source_output_new_data_set_source(&source_output_data, master, false, true); - source_output_data.destination_source = u->source; - - pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream"); - pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); - pa_source_output_new_data_set_sample_spec(&source_output_data, &ss); - pa_source_output_new_data_set_channel_map(&source_output_data, &stream_map); - source_output_data.flags = (remix ? 0 : PA_SOURCE_OUTPUT_NO_REMIX) | PA_SOURCE_OUTPUT_START_CORKED; - source_output_data.resample_method = resample_method; - - pa_source_output_new(&u->source_output, m->core, &source_output_data); - pa_source_output_new_data_done(&source_output_data); - - if (!u->source_output) - goto fail; - - u->source_output->push = source_output_push_cb; - u->source_output->process_rewind = source_output_process_rewind_cb; - u->source_output->update_max_rewind = source_output_update_max_rewind_cb; - u->source_output->kill = source_output_kill_cb; - u->source_output->attach = source_output_attach_cb; - u->source_output->detach = source_output_detach_cb; - u->source_output->state_change = source_output_state_change_cb; - u->source_output->moving = source_output_moving_cb; - u->source_output->userdata = u; - - u->source->output_from_master = u->source_output; - - /* The order here is important. The output must be put first, - * otherwise streams might attach to the source before the - * source output is attached to the master. */ - pa_source_output_put(u->source_output); - pa_source_put(u->source); - pa_source_output_cork(u->source_output, false); pa_modargs_free(ma); @@ -443,7 +149,7 @@ int pa__get_n_used(pa_module *m) { pa_assert(m); pa_assert_se(u = m->userdata); - return pa_source_linked_by(u->source); + return pa_source_linked_by(u->vsource->source); } void pa__done(pa_module*m) { @@ -454,22 +160,8 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - /* See comments in source_output_kill_cb() above regarding - * destruction order! */ - - if (u->source_output) - pa_source_output_cork(u->source_output, true); - - if (u->source) - pa_source_unlink(u->source); - - if (u->source_output) { - pa_source_output_unlink(u->source_output); - pa_source_output_unref(u->source_output); - } - - if (u->source) - pa_source_unref(u->source); + if (u->vsource) + pa_virtual_source_destroy(u->vsource); pa_xfree(u); } diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 498b09449..b793d7e6e 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -71,7 +71,7 @@ static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval pa_core_maybe_vacuum(d->userdata->core); } - if (d->source && pa_source_check_suspend(d->source, NULL) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) { + if (d->source && pa_source_check_suspend(d->source, NULL, NULL) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Source %s idle for too long, suspending ...", d->source->name); pa_source_suspend(d->source, true, PA_SUSPEND_IDLE); pa_core_maybe_vacuum(d->userdata->core); @@ -109,6 +109,29 @@ static void resume(struct device_info *d) { } } +/* If the monitor source of a sink becomes idle and the sink is + * idle as well, we have to check if it is an uplink sink because + * the underlying virtual source might also have become idle. */ +static void restart_check_uplink(struct device_info *d, pa_source_output *ignore, struct userdata *u) { + struct device_info *d_master; + + pa_assert(d); + pa_assert(u); + + restart(d); + + if (!d->sink || (d->sink && !d->sink->uplink_of)) + return; + + if (!d->sink->uplink_of->source) + return; + + if ((d_master = pa_hashmap_get(u->device_infos, d->sink->uplink_of->source))) { + if (pa_source_check_suspend(d_master->source, NULL, ignore) <= 0) + restart(d_master); + } +} + static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { struct device_info *d; @@ -145,7 +168,7 @@ static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_outpu if (d) { resume(d); if (d->source) { - if (pa_source_check_suspend(d->source, NULL) <= 0) + if (pa_source_check_suspend(d->source, NULL, NULL) <= 0) restart(d); } else { /* The source output is connected to a monitor source. */ @@ -170,6 +193,13 @@ static pa_hook_result_t sink_input_unlink_hook_cb(pa_core *c, pa_sink_input *s, struct device_info *d; if ((d = pa_hashmap_get(u->device_infos, s->sink))) restart(d); + + if (s->sink->uplink_of && s->sink->uplink_of->source) { + if (pa_source_check_suspend(s->sink->uplink_of->source, s, NULL) <= 0) { + if ((d = pa_hashmap_get(u->device_infos, s->sink->uplink_of->source))) + restart(d); + } + } } return PA_HOOK_OK; @@ -189,12 +219,12 @@ static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_outpu if (pa_sink_check_suspend(s->source->monitor_of, NULL, s) <= 0) d = pa_hashmap_get(u->device_infos, s->source->monitor_of); } else { - if (pa_source_check_suspend(s->source, s) <= 0) + if (pa_source_check_suspend(s->source, NULL, s) <= 0) d = pa_hashmap_get(u->device_infos, s->source); } if (d) - restart(d); + restart_check_uplink(d, s, u); return PA_HOOK_OK; } @@ -206,10 +236,18 @@ static pa_hook_result_t sink_input_move_start_hook_cb(pa_core *c, pa_sink_input pa_sink_input_assert_ref(s); pa_assert(u); - if (pa_sink_check_suspend(s->sink, s, NULL) <= 0) + if (pa_sink_check_suspend(s->sink, s, NULL) <= 0) { if ((d = pa_hashmap_get(u->device_infos, s->sink))) restart(d); + if (s->sink->uplink_of && s->sink->uplink_of->source) { + if (pa_source_check_suspend(s->sink->uplink_of->source, s, NULL) <= 0) { + if ((d = pa_hashmap_get(u->device_infos, s->sink->uplink_of->source))) + restart(d); + } + } + } + return PA_HOOK_OK; } @@ -240,12 +278,12 @@ static pa_hook_result_t source_output_move_start_hook_cb(pa_core *c, pa_source_o if (pa_sink_check_suspend(s->source->monitor_of, NULL, s) <= 0) d = pa_hashmap_get(u->device_infos, s->source->monitor_of); } else { - if (pa_source_check_suspend(s->source, s) <= 0) + if (pa_source_check_suspend(s->source, NULL, s) <= 0) d = pa_hashmap_get(u->device_infos, s->source); } if (d) - restart(d); + restart_check_uplink(d, s, u); return PA_HOOK_OK; } @@ -278,9 +316,14 @@ static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_inp pa_sink_input_assert_ref(s); pa_assert(u); - if (s->state == PA_SINK_INPUT_RUNNING && s->sink) - if ((d = pa_hashmap_get(u->device_infos, s->sink))) - resume(d); + if (s->sink) { + if ((d = pa_hashmap_get(u->device_infos, s->sink))) { + if (s->state == PA_SINK_INPUT_RUNNING) + resume(d); + else if (s->state == PA_SINK_INPUT_CORKED && pa_sink_check_suspend(s->sink, NULL, NULL) <= 0) + restart(d); + } + } return PA_HOOK_OK; } @@ -289,9 +332,9 @@ static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_sourc pa_assert(c); pa_source_output_assert_ref(s); pa_assert(u); + struct device_info *d = NULL; if (s->state == PA_SOURCE_OUTPUT_RUNNING && s->source) { - struct device_info *d; if (s->source->monitor_of) d = pa_hashmap_get(u->device_infos, s->source->monitor_of); @@ -300,6 +343,15 @@ static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_sourc if (d) resume(d); + } else if (s->state == PA_SOURCE_OUTPUT_CORKED && s->source) { + + if (s->source->monitor_of && pa_sink_check_suspend(s->source->monitor_of, NULL, NULL) <= 0) + d = pa_hashmap_get(u->device_infos, s->source->monitor_of); + else if (pa_source_check_suspend(s->source, NULL, NULL) <= 0) + d = pa_hashmap_get(u->device_infos, s->source); + + if (d) + restart_check_uplink(d, NULL, u); } return PA_HOOK_OK; @@ -349,7 +401,7 @@ static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct user pa_hashmap_put(u->device_infos, o, d); if ((d->sink && pa_sink_check_suspend(d->sink, NULL, NULL) <= 0) || - (d->source && pa_source_check_suspend(d->source, NULL) <= 0)) + (d->source && pa_source_check_suspend(d->source, NULL, NULL) <= 0)) restart(d); return PA_HOOK_OK; @@ -407,7 +459,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s } else if (pa_source_isinstance(o)) { pa_source *s = PA_SOURCE(o); - if (pa_source_check_suspend(s, NULL) <= 0) + if (pa_source_check_suspend(s, NULL, NULL) <= 0) if (PA_SOURCE_IS_OPENED(s->state)) restart(d); } 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/module-virtual-source.c b/src/modules/module-virtual-source.c index 8dd7fc90f..c7a39650a 100644 --- a/src/modules/module-virtual-source.c +++ b/src/modules/module-virtual-source.c @@ -26,6 +26,8 @@ #include +#include + #include #include #include @@ -63,23 +65,8 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; - /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ - /* bool autoloaded; */ - - pa_source *source; - pa_source_output *source_output; - - pa_memblockq *memblockq; - - bool auto_desc; + pa_vsource *vsource; unsigned channels; - - /* optional fields for uplink sink */ - pa_sink *sink; - pa_usec_t block_usec; - pa_memblockq *sink_memblockq; - pa_rtpoll *rtpoll; - }; static const char* const valid_modargs[] = { @@ -93,393 +80,21 @@ static const char* const valid_modargs[] = { "channel_map", "use_volume_sharing", "force_flat_volume", + "autoloaded", NULL }; -/* 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) { - - switch (code) { - - case PA_SINK_MESSAGE_GET_LATENCY: - - /* there's no real latency here */ - *((int64_t*) data) = 0; - - 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; + size_t nbytes; - 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)) { - return 0; - } + nbytes = in_count * pa_frame_size(&u->vsource->source->sample_spec); - if (state == PA_SINK_RUNNING) { - /* need to wake-up source if it was suspended */ - pa_log_debug("Resuming source %s, because its uplink sink became active.", u->source->name); - pa_source_suspend(u->source, false, PA_SUSPEND_ALL); - - /* FIXME: if there's no client connected, the source will suspend - and playback will be stuck. You'd want to prevent the source from - sleeping when the uplink sink is active; even if the audio is - discarded at least the app isn't stuck */ - - } else { - /* nothing to do, if the sink becomes idle or suspended let - module-suspend-idle handle the sources later */ - } - - return 0; -} - -static void sink_update_requested_latency_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - /* FIXME: there's no latency support */ - -} - -/* Called from I/O thread context */ -static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SOURCE(o)->userdata; - - switch (code) { - - case PA_SOURCE_MESSAGE_GET_LATENCY: - - /* The source is _put() before the source output is, so let's - * make sure we don't access it in that time. Also, the - * source output is first shut down, the source second. */ - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) { - *((pa_usec_t*) data) = 0; - return 0; - } - - *((pa_usec_t*) data) = - - /* Get the latency of the master source */ - pa_source_get_latency_within_thread(u->source_output->source, true) + - - /* Add the latency internal to our source output on top */ - /* FIXME, no idea what I am doing here */ - pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec); - - /* Add resampler delay */ - *((int64_t*) data) += pa_resampler_get_delay_usec(u->source_output->thread_info.resampler); - - return 0; - } - - return pa_source_process_msg(o, code, data, offset, chunk); -} - -/* Called from main context */ -static int source_set_state_in_main_thread_cb(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state)) - return 0; - - pa_source_output_cork(u->source_output, state == PA_SOURCE_SUSPENDED); - return 0; -} - -/* Called from I/O thread context */ -static void source_update_requested_latency_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) - return; - - /* Just hand this one over to the master source */ - pa_source_output_set_requested_latency_within_thread( - u->source_output, - pa_source_get_requested_latency_within_thread(s)); -} - -/* Called from main context */ -static void source_set_volume_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(s->state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state)) - return; - - pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, true); -} - -/* Called from main context */ -static void source_set_mute_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(s->state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state)) - return; - - pa_source_output_set_mute(u->source_output, s->muted, s->save_muted); -} - -/* Called from input thread context */ -static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - return; - - if (!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) { - pa_log("push when no link?"); - return; - } - - /* PUT YOUR CODE HERE TO DO SOMETHING WITH THE SOURCE DATA */ - - /* if uplink sink exists, pull data from there; simplify by using - same length as chunk provided by source */ - if (u->sink && (u->sink->thread_info.state == PA_SINK_RUNNING)) { - pa_memchunk tchunk; - size_t nbytes = chunk->length; - pa_mix_info streams[2]; - pa_memchunk target_chunk; - void *target; - int ch; - - /* Hmm, process any rewind request that might be queued up */ - pa_sink_process_rewind(u->sink, 0); - - /* get data from the sink */ - while (pa_memblockq_peek(u->sink_memblockq, &tchunk) < 0) { - pa_memchunk nchunk; - - /* make sure we get nbytes from the sink with render_full, - otherwise we cannot mix with the uplink */ - pa_sink_render_full(u->sink, nbytes, &nchunk); - pa_memblockq_push(u->sink_memblockq, &nchunk); - pa_memblock_unref(nchunk.memblock); - } - pa_assert(tchunk.length == chunk->length); - - /* move the read pointer for sink memblockq */ - pa_memblockq_drop(u->sink_memblockq, tchunk.length); - - /* allocate target chunk */ - /* this could probably be done in-place, but having chunk as both - the input and output creates issues with reference counts */ - target_chunk.index = 0; - target_chunk.length = chunk->length; - pa_assert(target_chunk.length == chunk->length); - - target_chunk.memblock = pa_memblock_new(o->source->core->mempool, - target_chunk.length); - pa_assert( target_chunk.memblock ); - - /* get target pointer */ - target = pa_memblock_acquire_chunk(&target_chunk); - - /* set-up mixing structure - volume was taken care of in sink and source already */ - streams[0].chunk = *chunk; - for(ch=0;chsample_spec.channels;ch++) - streams[0].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ - streams[0].volume.channels = o->sample_spec.channels; - - streams[1].chunk = tchunk; - for(ch=0;chsample_spec.channels;ch++) - streams[1].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ - streams[1].volume.channels = o->sample_spec.channels; - - /* do mixing */ - pa_mix(streams, /* 2 streams to be mixed */ - 2, - target, /* put result in target chunk */ - chunk->length, /* same length as input */ - (const pa_sample_spec *)&o->sample_spec, /* same sample spec for input and output */ - NULL, /* no volume information */ - false); /* no mute */ - - pa_memblock_release(target_chunk.memblock); - pa_memblock_unref(tchunk.memblock); /* clean-up */ - - /* forward the data to the virtual source */ - pa_source_post(u->source, &target_chunk); - - pa_memblock_unref(target_chunk.memblock); /* clean-up */ - - } else { - /* forward the data to the virtual source */ - pa_source_post(u->source, chunk); - } - -} - -/* Called from input thread context */ -static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - /* If the source is not yet linked, there is nothing to rewind */ - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_process_rewind(u->source, nbytes); - - /* FIXME, no idea what I am doing here */ -#if 0 - pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL); - u->send_counter -= (int64_t) nbytes; -#endif -} - -/* Called from output thread context */ -static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - pa_source_set_max_rewind_within_thread(u->source, nbytes); -} - -/* Called from output thread context */ -static void source_output_attach_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll); - pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency); - pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency); - pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o)); - - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_attach_within_thread(u->source); -} - -/* Called from output thread context */ -static void source_output_detach_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_detach_within_thread(u->source); - pa_source_set_rtpoll(u->source, NULL); -} - -/* Called from output thread context except when cork() is called without valid source.*/ -static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_assert_se(u = o->userdata); - - /* FIXME */ -#if 0 - if (PA_SOURCE_OUTPUT_IS_LINKED(state) && o->thread_info.state == PA_SOURCE_OUTPUT_INIT && o->source) { - - u->skip = pa_usec_to_bytes(PA_CLIP_SUB(pa_source_get_latency_within_thread(o->source, false), - u->latency), - &o->sample_spec); - - pa_log_info("Skipping %lu bytes", (unsigned long) u->skip); - } -#endif -} - -/* Called from main thread */ -static void source_output_kill_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_assert_ctl_context(); - pa_assert_se(u = o->userdata); - - /* The order here matters! We first kill the source so that streams - * can properly be moved away while the source output is still connected - * to the master. */ - pa_source_output_cork(u->source_output, true); - pa_source_unlink(u->source); - pa_source_output_unlink(u->source_output); - - pa_source_output_unref(u->source_output); - u->source_output = NULL; - - pa_source_unref(u->source); - u->source = NULL; - - pa_module_unload_request(u->module, true); -} - -/* Called from main thread */ -static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { - struct userdata *u; - uint32_t idx; - pa_source_output *output; - - pa_source_output_assert_ref(o); - pa_assert_ctl_context(); - pa_assert_se(u = o->userdata); - - if (dest) { - pa_source_set_asyncmsgq(u->source, dest->asyncmsgq); - pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags); - } else - pa_source_set_asyncmsgq(u->source, NULL); - - /* Propagate asyncmsq change to attached virtual sources */ - PA_IDXSET_FOREACH(output, u->source->outputs, idx) { - if (output->destination_source && output->moving) - output->moving(output, u->source); - } - - 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 Source %s on %s", - pa_proplist_gets(u->source->proplist, "device.vsource.name"), z ? z : dest->name); - - pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl); - pa_proplist_free(pl); - } + /* Copy input to output */ + memcpy(dst, src, nbytes); } int pa__init(pa_module*m) { @@ -488,14 +103,7 @@ int pa__init(pa_module*m) { pa_channel_map map; pa_modargs *ma; pa_source *master=NULL; - pa_source_output_new_data source_output_data; - pa_source_new_data source_data; bool use_volume_sharing = true; - bool force_flat_volume = false; - - /* optional for uplink_sink */ - pa_sink_new_data sink_data; - size_t nbytes; pa_assert(m); @@ -512,7 +120,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"); @@ -524,174 +131,21 @@ 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->memblockq = pa_memblockq_new("module-virtual-source memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, NULL); - if (!u->memblockq) { - pa_log("Failed to create source memblockq."); - goto fail; - } u->channels = ss.channels; - /* The rtpoll created here is never run. It is only necessary to avoid crashes - * when module-virtual-source is used together with module-loopback or - * module-combine-sink. Both modules base their asyncmsq on the rtpoll provided - * by the sink. module-loopback and combine-sink only work because they - * call pa_asyncmsq_process_one() themselves. */ - u->rtpoll = pa_rtpoll_new(); - - /* Create source */ - pa_source_new_data_init(&source_data); - source_data.driver = __FILE__; - source_data.module = m; - if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) - source_data.name = pa_sprintf_malloc("%s.vsource", master->name); - pa_source_new_data_set_sample_spec(&source_data, &ss); - pa_source_new_data_set_channel_map(&source_data, &map); - pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); - pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - pa_proplist_sets(source_data.proplist, "device.vsource.name", source_data.name); - - if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log("Invalid properties"); - pa_source_new_data_done(&source_data); - goto fail; - } - - if ((u->auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { - const char *z; - - z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Source %s on %s", source_data.name, z ? z : master->name); - } - - u->source = pa_source_new(m->core, &source_data, (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)) - | (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0)); - - pa_source_new_data_done(&source_data); - - if (!u->source) { - pa_log("Failed to create source."); - goto fail; - } - - u->source->parent.process_msg = source_process_msg_cb; - u->source->set_state_in_main_thread = source_set_state_in_main_thread_cb; - u->source->update_requested_latency = source_update_requested_latency_cb; - pa_source_set_set_mute_callback(u->source, source_set_mute_cb); - if (!use_volume_sharing) { - pa_source_set_set_volume_callback(u->source, source_set_volume_cb); - pa_source_enable_decibel_volume(u->source, true); - } - /* Normally this flag would be enabled automatically be we can force it. */ - if (force_flat_volume) - u->source->flags |= PA_SOURCE_FLAT_VOLUME; - u->source->userdata = u; - - pa_source_set_asyncmsgq(u->source, master->asyncmsgq); - - /* Create source output */ - pa_source_output_new_data_init(&source_output_data); - source_output_data.driver = __FILE__; - source_output_data.module = m; - pa_source_output_new_data_set_source(&source_output_data, master, false, true); - source_output_data.destination_source = u->source; - - pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Source Stream of %s", pa_proplist_gets(u->source->proplist, PA_PROP_DEVICE_DESCRIPTION)); - pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); - pa_source_output_new_data_set_sample_spec(&source_output_data, &ss); - pa_source_output_new_data_set_channel_map(&source_output_data, &map); - source_output_data.flags |= PA_SOURCE_OUTPUT_START_CORKED; - - pa_source_output_new(&u->source_output, m->core, &source_output_data); - pa_source_output_new_data_done(&source_output_data); - - if (!u->source_output) + /* Create virtual source */ + if (!(u->vsource = pa_virtual_source_create(master, "vsource", "Virtual Source", &ss, &map, + &ss, &map, m, u, ma, use_volume_sharing, true))) goto fail; - u->source_output->push = source_output_push_cb; - u->source_output->process_rewind = source_output_process_rewind_cb; - u->source_output->update_max_rewind = source_output_update_max_rewind_cb; - u->source_output->kill = source_output_kill_cb; - u->source_output->attach = source_output_attach_cb; - u->source_output->detach = source_output_detach_cb; - u->source_output->state_change = source_output_state_change_cb; - u->source_output->moving = source_output_moving_cb; - u->source_output->userdata = u; + /* Set callback for virtual source */ + u->vsource->process_chunk = filter_process_chunk; - u->source->output_from_master = u->source_output; - - /* The order here is important. The output must be put first, - * otherwise streams might attach to the source before the - * source output is attached to the master. */ - pa_source_output_put(u->source_output); - pa_source_put(u->source); - pa_source_output_cork(u->source_output, false); - - /* Create optional uplink 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, "uplink_sink", NULL)))) { - 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, "uplink sink"); - pa_proplist_sets(sink_data.proplist, "device.uplink_sink.name", sink_data.name); - - 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, "Uplink Sink %s on %s", sink_data.name, z ? z : master->name); - } - - u->sink_memblockq = pa_memblockq_new("module-virtual-source sink_memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, NULL); - if (!u->sink_memblockq) { - pa_sink_new_data_done(&sink_data); - pa_log("Failed to create sink memblockq."); - goto fail; - } - - u->sink = pa_sink_new(m->core, &sink_data, 0); /* FIXME, sink has no capabilities */ - 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->update_requested_latency = sink_update_requested_latency_cb; - u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb; - u->sink->userdata = u; - - pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); - pa_sink_set_rtpoll(u->sink, u->rtpoll); - - /* FIXME: no idea what I am doing here */ - u->block_usec = BLOCK_USEC; - nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); - pa_sink_set_max_rewind(u->sink, 0); - pa_sink_set_max_request(u->sink, nbytes); - - pa_sink_put(u->sink); - } else { - pa_sink_new_data_done(&sink_data); - /* optional uplink sink not enabled */ - u->sink = NULL; - } + if (pa_virtual_source_activate(u->vsource) < 0) + goto fail; pa_modargs_free(ma); @@ -712,7 +166,7 @@ int pa__get_n_used(pa_module *m) { pa_assert(m); pa_assert_se(u = m->userdata); - return pa_source_linked_by(u->source); + return pa_source_linked_by(u->vsource->source); } void pa__done(pa_module*m) { @@ -723,36 +177,8 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - /* See comments in source_output_kill_cb() above regarding - * destruction order! */ - - if (u->source_output) - pa_source_output_cork(u->source_output, true); - - if (u->source) - pa_source_unlink(u->source); - - if (u->source_output) { - pa_source_output_unlink(u->source_output); - pa_source_output_unref(u->source_output); - } - - if (u->source) - pa_source_unref(u->source); - - if (u->sink) { - pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - } - - if (u->memblockq) - pa_memblockq_free(u->memblockq); - - if (u->sink_memblockq) - pa_memblockq_free(u->sink_memblockq); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); + if (u->vsource) + pa_virtual_source_destroy(u->vsource); pa_xfree(u); } diff --git a/src/modules/module-virtual-surround-sink.c b/src/modules/module-virtual-surround-sink.c index 395146c02..2733556af 100644 --- a/src/modules/module-virtual-surround-sink.c +++ b/src/modules/module-virtual-surround-sink.c @@ -29,6 +29,8 @@ #include +#include + #include #include @@ -73,14 +75,7 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; - bool autoloaded; - - pa_sink *sink; - pa_sink_input *sink_input; - - pa_memblockq *memblockq_sink; - - bool auto_desc; + pa_vsink *vsink; size_t fftlen; size_t hrir_samples; @@ -111,6 +106,76 @@ static const char* const valid_modargs[] = { NULL }; +static void filter_process_chunk(uint8_t *src_p, uint8_t *dst_p, unsigned in_count, unsigned out_count, void *userdata) { + struct userdata *u; + int ear; + unsigned c; + size_t s, fftlen; + float fftlen_if, *revspace; + float *src, *dst; + + pa_assert_se(u = userdata); + pa_assert(in_count == u->fftlen); + pa_assert(out_count == BLOCK_SIZE); + + src = (float *)src_p; + dst = (float *)dst_p; + + for (c = 0; c < u->inputs; c++) { + for (s = 0, fftlen = u->fftlen; s < fftlen; s++) { + u->inspace[c][s] = src[s * u->inputs + c]; + } + } + + fftlen_if = 1.0f / (float)u->fftlen; + revspace = u->revspace + u->fftlen - BLOCK_SIZE; + + pa_memzero(u->outspace[0], BLOCK_SIZE * 4); + pa_memzero(u->outspace[1], BLOCK_SIZE * 4); + + for (c = 0; c < u->inputs; c++) { + fftwf_complex *f_in = u->f_in; + fftwf_complex *f_out = u->f_out; + + fftwf_execute(u->p_fw[c]); + + for (ear = 0; ear < 2; ear++) { + fftwf_complex *f_ir = u->f_ir[c * 2 + ear]; + float *outspace = u->outspace[ear]; + + for (s = 0, fftlen = u->fftlen / 2 + 1; s < fftlen; s++) { + float re = f_ir[s][0] * f_in[s][0] - f_ir[s][1] * f_in[s][1]; + float im = f_ir[s][1] * f_in[s][0] + f_ir[s][0] * f_in[s][1]; + f_out[s][0] = re; + f_out[s][1] = im; + } + + fftwf_execute(u->p_bw); + + for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; ++s) + outspace[s] += revspace[s] * fftlen_if; + } + } + + for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; s++) { + float output; + float *outspace = u->outspace[0]; + + output = outspace[s]; + if (output < -1.0) output = -1.0; + if (output > 1.0) output = 1.0; + dst[s * 2 + 0] = output; + + outspace = u->outspace[1]; + + output = outspace[s]; + if (output < -1.0) output = -1.0; + if (output > 1.0) output = 1.0; + dst[s * 2 + 1] = output; + } +} + + /* Vector size of 4 floats */ #define v_size 4 static void * alloc(size_t x, size_t s) { @@ -124,26 +189,6 @@ static void * alloc(size_t x, size_t s) { return t; } -static size_t sink_input_samples(size_t nbytes) -{ - return nbytes / 8; -} - -static size_t sink_input_bytes(size_t nsamples) -{ - return nsamples * 8; -} - -static size_t sink_samples(const struct userdata *u, size_t nbytes) -{ - return nbytes / (u->inputs * 4); -} - -static size_t sink_bytes(const struct userdata *u, size_t nsamples) -{ - return nsamples * (u->inputs * 4); -} - /* Mirror channels for symmetrical impulse */ static pa_channel_position_t mirror_channel(pa_channel_position_t channel) { switch (channel) { @@ -255,449 +300,6 @@ static void normalize_hrir_stereo(float * hrir_data, float * hrir_right_data, un } } -/* 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)) { - *((pa_usec_t*) data) = 0; - return 0; - } - - *((pa_usec_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) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - 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; - size_t nbytes_sink, nbytes_input; - - 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; - - nbytes_sink = s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq_sink); - nbytes_input = sink_input_bytes(sink_samples(u, nbytes_sink)); - - /* Just hand this one over to the master sink */ - pa_sink_input_request_rewind(u->sink_input, nbytes_input, 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); -} - -static size_t memblockq_missing(pa_memblockq *bq) { - size_t l, tlength; - pa_assert(bq); - - tlength = pa_memblockq_get_tlength(bq); - if ((l = pa_memblockq_get_length(bq)) >= tlength) - return 0; - - l = tlength - l; - return l >= pa_memblockq_get_minreq(bq) ? l : 0; -} - -/* Called from I/O thread context */ -static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes_input, pa_memchunk *chunk) { - struct userdata *u; - float *src, *dst; - int c, ear; - size_t s, bytes_missing, fftlen; - pa_memchunk tchunk; - float fftlen_if, *revspace; - - pa_sink_input_assert_ref(i); - pa_assert(chunk); - pa_assert_se(u = i->userdata); - - /* Hmm, process any rewind request that might be queued up */ - pa_sink_process_rewind(u->sink, 0); - - while ((bytes_missing = memblockq_missing(u->memblockq_sink)) != 0) { - pa_memchunk nchunk; - - pa_sink_render(u->sink, bytes_missing, &nchunk); - pa_memblockq_push(u->memblockq_sink, &nchunk); - pa_memblock_unref(nchunk.memblock); - } - - pa_memblockq_rewind(u->memblockq_sink, sink_bytes(u, u->fftlen - BLOCK_SIZE)); - pa_memblockq_peek_fixed_size(u->memblockq_sink, sink_bytes(u, u->fftlen), &tchunk); - - pa_memblockq_drop(u->memblockq_sink, tchunk.length); - - /* Now tchunk contains enough data to perform the FFT - * This should be equal to u->fftlen */ - - chunk->index = 0; - chunk->length = sink_input_bytes(BLOCK_SIZE); - chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); - - src = pa_memblock_acquire_chunk(&tchunk); - - for (c = 0; c < u->inputs; c++) { - for (s = 0, fftlen = u->fftlen; s < fftlen; s++) { - u->inspace[c][s] = src[s * u->inputs + c]; - } - } - - pa_memblock_release(tchunk.memblock); - pa_memblock_unref(tchunk.memblock); - - fftlen_if = 1.0f / (float)u->fftlen; - revspace = u->revspace + u->fftlen - BLOCK_SIZE; - - pa_memzero(u->outspace[0], BLOCK_SIZE * 4); - pa_memzero(u->outspace[1], BLOCK_SIZE * 4); - - for (c = 0; c < u->inputs; c++) { - fftwf_complex *f_in = u->f_in; - fftwf_complex *f_out = u->f_out; - - fftwf_execute(u->p_fw[c]); - - for (ear = 0; ear < 2; ear++) { - fftwf_complex *f_ir = u->f_ir[c * 2 + ear]; - float *outspace = u->outspace[ear]; - - for (s = 0, fftlen = u->fftlen / 2 + 1; s < fftlen; s++) { - float re = f_ir[s][0] * f_in[s][0] - f_ir[s][1] * f_in[s][1]; - float im = f_ir[s][1] * f_in[s][0] + f_ir[s][0] * f_in[s][1]; - f_out[s][0] = re; - f_out[s][1] = im; - } - - fftwf_execute(u->p_bw); - - for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; ++s) - outspace[s] += revspace[s] * fftlen_if; - } - } - - dst = pa_memblock_acquire_chunk(chunk); - - for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; s++) { - float output; - float *outspace = u->outspace[0]; - - output = outspace[s]; - if (output < -1.0) output = -1.0; - if (output > 1.0) output = 1.0; - dst[s * 2 + 0] = output; - - outspace = u->outspace[1]; - - output = outspace[s]; - if (output < -1.0) output = -1.0; - if (output > 1.0) output = 1.0; - dst[s * 2 + 1] = output; - } - - pa_memblock_release(chunk->memblock); - - return 0; -} - -/* Called from I/O thread context */ -static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes_input) { - struct userdata *u; - size_t amount = 0; - size_t nbytes_sink; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input)); - - if (u->sink->thread_info.rewind_nbytes > 0) { - size_t max_rewrite; - - max_rewrite = nbytes_sink + pa_memblockq_get_length(u->memblockq_sink); - 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_sink, - (int64_t) amount, PA_SEEK_RELATIVE, true); - } - } - - pa_sink_process_rewind(u->sink, amount); - - pa_memblockq_rewind(u->memblockq_sink, nbytes_sink); -} - -/* Called from I/O thread context */ -static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes_input) { - struct userdata *u; - size_t nbytes_sink, nbytes_memblockq; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input)); - nbytes_memblockq = sink_bytes(u, sink_input_samples(nbytes_input) + u->fftlen); - - /* FIXME: Too small max_rewind: - * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_memblockq_set_maxrewind(u->memblockq_sink, nbytes_memblockq); - pa_sink_set_max_rewind_within_thread(u->sink, nbytes_sink); -} - -/* Called from I/O thread context */ -static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes_input) { - struct userdata *u; - - size_t nbytes_sink; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input)); - - nbytes_sink = PA_ROUND_UP(nbytes_sink, sink_bytes(u, BLOCK_SIZE)); - pa_sink_set_max_request_within_thread(u->sink, nbytes_sink); -} - -/* 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); - - 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; - size_t max_request; - - 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); - - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); - - max_request = sink_bytes(u, sink_input_samples(pa_sink_input_get_max_request(i))); - max_request = PA_ROUND_UP(max_request, sink_bytes(u, BLOCK_SIZE)); - pa_sink_set_max_request_within_thread(u->sink, max_request); - - /* FIXME: Too small max_rewind: - * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_sink_set_max_rewind_within_thread(u->sink, sink_bytes(u, sink_input_samples(pa_sink_input_get_max_rewind(i)))); - - 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 input, followed - * by the sink. That means the sink callbacks must be protected - * against an unconnected sink input! */ - pa_sink_input_cork(u->sink_input, true); - pa_sink_input_unlink(u->sink_input); - pa_sink_unlink(u->sink); - - 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 bool sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - if (u->autoloaded) - return false; - - return u->sink != dest; -} - -/* 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 Surround Sink %s on %s", - pa_proplist_gets(u->sink->proplist, "device.vsurroundsink.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); -} - int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss_input, ss_output; @@ -707,12 +309,7 @@ int pa__init(pa_module*m) { const char *hrir_left_file; const char *hrir_right_file; 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; - const char* z; unsigned i, j, ear, found_channel_left, found_channel_right; pa_sample_spec ss; @@ -722,7 +319,7 @@ int pa__init(pa_module*m) { float *hrir_temp_data; size_t hrir_samples; size_t hrir_copied_length, hrir_total_length; - int hrir_channels; + unsigned hrir_channels; int fftlen; float *impulse_temp=NULL; @@ -823,114 +420,20 @@ 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; - } - pa_channel_map_init_stereo(&map_output); u = pa_xnew0(struct userdata, 1); u->module = m; m->userdata = u; - /* 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.vsurroundsink", master->name); - pa_sink_new_data_set_sample_spec(&sink_data, &ss_input); - 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.vsurroundsink.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; - } - - u->autoloaded = DEFAULT_AUTOLOADED; - if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) { - pa_log("Failed to parse autoloaded value"); - goto fail; - } - - if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { - z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Surround 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 but 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 Surround 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_output); - pa_sink_input_new_data_set_channel_map(&sink_input_data, &map_output); - - 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, "vsurroundsink", "Virtual Surround Sink", &ss_input, &map, + &ss_output, &map_output, 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->may_move_to = sink_input_may_move_to_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; + u->vsink->process_chunk = filter_process_chunk; - u->sink->input_to_master = u->sink_input; - - pa_sink_input_get_silence(u->sink_input, &silence); - - resampler = pa_resampler_new(u->sink->core->mempool, &hrir_left_temp_ss, &hrir_map, &ss_input, &hrir_map, u->sink->core->lfe_crossover_freq, + resampler = pa_resampler_new(u->vsink->sink->core->mempool, &hrir_left_temp_ss, &hrir_map, &ss_input, &hrir_map, u->vsink->sink->core->lfe_crossover_freq, PA_RESAMPLER_SRC_SINC_BEST_QUALITY, PA_RESAMPLER_NO_REMAP); hrir_samples = hrir_left_temp_chunk.length / pa_frame_size(&hrir_left_temp_ss) * ss_input.rate / hrir_left_temp_ss.rate; @@ -1128,14 +631,11 @@ int pa__init(pa_module*m) { pa_xfree(mapping_left); pa_xfree(mapping_right); - u->memblockq_sink = pa_memblockq_new("module-virtual-surround-sink memblockq (input)", 0, MEMBLOCKQ_MAXLENGTH, sink_bytes(u, BLOCK_SIZE), &ss_input, 0, 0, sink_bytes(u, u->fftlen), &silence); - pa_memblock_unref(silence.memblock); + u->vsink->fixed_block_size = BLOCK_SIZE; + u->vsink->overlap_frames = u->fftlen - BLOCK_SIZE; - pa_memblockq_seek(u->memblockq_sink, sink_bytes(u, u->fftlen - BLOCK_SIZE), PA_SEEK_RELATIVE, false); - pa_memblockq_flush_read(u->memblockq_sink); - - pa_sink_put(u->sink); - pa_sink_input_put(u->sink_input); + if (pa_virtual_sink_activate(u->vsink) < 0) + goto fail; pa_modargs_free(ma); @@ -1183,7 +683,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) { @@ -1195,23 +695,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_unlink(u->sink_input); - - if (u->sink) - pa_sink_unlink(u->sink); - - if (u->sink_input) - pa_sink_input_unref(u->sink_input); - - if (u->sink) - pa_sink_unref(u->sink); - - if (u->memblockq_sink) - pa_memblockq_free(u->memblockq_sink); + if (u->vsink) + pa_virtual_sink_destroy(u->vsink); if (u->p_fw) { for (i = 0, j = u->inputs; i < j; i++) { 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/modules/virtual-source-common.c b/src/modules/virtual-source-common.c new file mode 100644 index 000000000..3f9cf61d5 --- /dev/null +++ b/src/modules/virtual-source-common.c @@ -0,0 +1,1515 @@ +/*** + 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 + +#include +#include + +PA_DEFINE_PRIVATE_CLASS(pa_vsource, pa_msgobject); +#define PA_VSOURCE(o) (pa_vsource_cast(o)) + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + +#define MIN_BLOCK_SIZE 16 +#define LATENCY_MARGIN (5 * PA_USEC_PER_MSEC) + +enum { + SOURCE_MESSAGE_UPDATE_PARAMETERS = PA_SOURCE_MESSAGE_MAX +}; + +enum { + VSOURCE_MESSAGE_FREE_PARAMETERS, + VSOURCE_MESSAGE_OUTPUT_ATTACHED +}; + +struct uplink_data { + pa_vsource *vsource; + pa_memblockq *memblockq; +}; + +/* Helper functions */ + +static inline pa_source_output* get_output_from_source(pa_source *s) { + + if (!s->vsource || !s->vsource->output_from_master) + return NULL; + return s->vsource->output_from_master; +} + +static int check_block_sizes(size_t fixed_block_frames, size_t fixed_input_block_frames, size_t overlap_frames, pa_vsource *vs) { + size_t max_block_frames; + size_t max_frame_size; + + max_frame_size = PA_MAX(pa_frame_size(&vs->source->sample_spec), pa_frame_size(&vs->output_from_master->sample_spec)); + + max_block_frames = pa_mempool_block_size_max(vs->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 void set_latency_range_within_thread(pa_vsource *vsource) { + pa_usec_t min_latency, max_latency; + pa_source_output *o; + pa_source *s; + + s = vsource->source; + pa_assert(s); + o = vsource->output_from_master; + pa_assert(o); + + min_latency = o->source->thread_info.min_latency; + max_latency = o->source->thread_info.max_latency; + + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) { + if (vsource->max_latency) + max_latency = PA_MIN(vsource->max_latency, max_latency); + + if (vsource->fixed_block_size) { + pa_usec_t latency; + + latency = pa_bytes_to_usec(vsource->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_source_set_latency_range_within_thread(s, min_latency, max_latency); + if (vsource->uplink_sink) + pa_sink_set_latency_range_within_thread(vsource->uplink_sink, min_latency, max_latency); +} + +/* Called from I/O thread context */ +static void set_memblockq_rewind(pa_vsource *vsource) { + + if (vsource->memblockq) { + size_t rewind_size; + size_t in_fs; + + in_fs = pa_frame_size(&vsource->output_from_master->sample_spec); + rewind_size = PA_MAX(vsource->fixed_input_block_size, vsource->overlap_frames) * in_fs; + pa_memblockq_set_maxrewind(vsource->memblockq, rewind_size); + } +} + +/* Uplink sink callbacks */ + +/* Called from I/O thread context */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + pa_sink *s; + struct uplink_data *uplink; + + s = PA_SINK(o); + uplink = s->userdata; + pa_assert(uplink); + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: + + /* While the sink is not opened or if we have not received any data yet, + * simply return 0 as latency */ + if (!PA_SINK_IS_OPENED(s->thread_info.state)) { + *((int64_t*) data) = 0; + return 0; + } + + *((int64_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(uplink->memblockq), &s->sample_spec); + *((int64_t*) data) -= pa_source_get_latency_within_thread(uplink->vsource->source, true); + + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int sink_set_state_in_main_thread(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) { + pa_vsource *vsource; + struct uplink_data *uplink; + + pa_sink_assert_ref(s); + uplink = s->userdata; + pa_assert(uplink); + vsource = uplink->vsource; + pa_assert(vsource); + + if (!PA_SINK_IS_LINKED(state)) { + return 0; + } + + /* need to wake-up source if it was suspended */ + if (!PA_SINK_IS_OPENED(s->state) && PA_SINK_IS_OPENED(state) && !PA_SOURCE_IS_OPENED(vsource->source->state) && PA_SOURCE_IS_LINKED(vsource->source->state)) { + pa_log_debug("Resuming source %s, because its uplink sink became active.", vsource->source->name); + pa_source_suspend(vsource->source, false, PA_SUSPEND_IDLE); + } + + return 0; +} + +/* Called from the IO thread. */ +static int sink_set_state_in_io_thread(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + struct uplink_data *uplink; + + pa_sink_assert_ref(s); + uplink = s->userdata; + pa_assert(uplink); + + if (!PA_SINK_IS_OPENED(new_state) && PA_SINK_IS_OPENED(s->thread_info.state)) { + pa_memblockq_flush_write(uplink->memblockq, true); + pa_sink_set_max_request_within_thread(s, 0); + pa_sink_set_max_rewind_within_thread(s, 0); + } + + return 0; +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency(pa_sink *s) { + struct uplink_data *uplink; + pa_usec_t latency; + size_t rewind_size; + + pa_sink_assert_ref(s); + uplink = s->userdata; + pa_assert(uplink); + + if (!PA_SINK_IS_LINKED(s->thread_info.state)) + return; + + latency = pa_sink_get_requested_latency_within_thread(s); + if (latency == (pa_usec_t) -1) + latency = s->thread_info.max_latency; + rewind_size = pa_usec_to_bytes(latency, &s->sample_spec); + pa_memblockq_set_maxrewind(uplink->memblockq, rewind_size); + + pa_sink_set_max_request_within_thread(s, rewind_size); + pa_sink_set_max_rewind_within_thread(s, rewind_size); +} + +static void sink_process_rewind(pa_sink *s) { + struct uplink_data *uplink; + size_t rewind_nbytes, in_buffer; + + uplink = s->userdata; + pa_assert(uplink); + + rewind_nbytes = s->thread_info.rewind_nbytes; + + if (!PA_SINK_IS_OPENED(s->thread_info.state) || rewind_nbytes <= 0) + goto finish; + + pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); + + in_buffer = pa_memblockq_get_length(uplink->memblockq); + if (in_buffer == 0) { + pa_log_debug("Memblockq empty, cannot rewind"); + goto finish; + } + + if (rewind_nbytes > in_buffer) + rewind_nbytes = in_buffer; + + pa_memblockq_seek(uplink->memblockq, -rewind_nbytes, PA_SEEK_RELATIVE, true); + pa_sink_process_rewind(s, rewind_nbytes); + + pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); + return; + +finish: + pa_sink_process_rewind(s, 0); +} + +/* Source callbacks */ + +/* Called from I/O thread context */ +int pa_virtual_source_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + pa_source_output *o; + pa_vsource *vsource; + + pa_source *s = PA_SOURCE(obj); + vsource = s->vsource; + pa_assert(vsource); + o = vsource->output_from_master; + pa_assert(o); + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: + + /* The source is _put() before the source output is, so let's + * make sure we don't access it in that time. Also, the + * source output is first shut down, the source second. */ + if (!PA_SOURCE_IS_LINKED(s->thread_info.state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = + + /* Get the latency of the master source */ + pa_source_get_latency_within_thread(o->source, true) + + + /* Add the latency internal to our source output on top */ + pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec); + + /* Add latenccy caused by the local memblockq */ + if (vsource->memblockq) + *((int64_t*) data) += pa_bytes_to_usec(pa_memblockq_get_length(vsource->memblockq), &o->sample_spec); + + /* Add resampler delay */ + *((int64_t*) data) += pa_resampler_get_delay_usec(o->thread_info.resampler); + + + /* Add additional filter latency if required. */ + if (vsource->get_extra_latency) + *((int64_t*) data) += vsource->get_extra_latency(s); + + return 0; + + case SOURCE_MESSAGE_UPDATE_PARAMETERS: + + /* Let the module update the filter parameters. Because the main thread + * is waiting, variables can be accessed freely in the callback. */ + if (vsource->update_filter_parameters) { + void *parameters; + size_t old_block_size, old_input_block_size, old_overlap_frames; + + /* Save old block sizes */ + old_block_size = vsource->fixed_block_size; + old_input_block_size = vsource->fixed_input_block_size; + old_overlap_frames = vsource->overlap_frames; + + parameters = vsource->update_filter_parameters(data, s->userdata); + if (parameters) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(vsource), VSOURCE_MESSAGE_FREE_PARAMETERS, parameters, 0, NULL, NULL); + + /* Updating the parameters may have changed the block sizes, so check them again. */ + if (check_block_sizes(vsource->fixed_block_size, vsource->fixed_input_block_size, vsource->overlap_frames, vsource) < 0) { + pa_log_warn("Invalid new block sizes, keeping old values."); + vsource->fixed_block_size = old_block_size; + vsource->fixed_input_block_size = old_input_block_size; + vsource->overlap_frames = old_overlap_frames; + } + + /* Set rewind of memblockq */ + set_memblockq_rewind(vsource); + + /* Inform the filter of the block sizes in use */ + if (vsource->update_block_sizes) + vsource->update_block_sizes(vsource->fixed_block_size, vsource->fixed_input_block_size, vsource->overlap_frames, s->userdata); + + /* If the block sizes changed the latency range may have changed as well. */ + set_latency_range_within_thread(vsource); + } + + return 0; + + } + + return pa_source_process_msg(obj, code, data, offset, chunk); +} + +/* Called from main context */ +int pa_virtual_source_set_state_in_main_thread(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) { + pa_source_output *o; + pa_vsource *vsource; + bool suspend_cause_changed; + + pa_source_assert_ref(s); + o = get_output_from_source(s); + pa_assert(o); + vsource = s->vsource; + pa_assert(vsource); + + if (!PA_SOURCE_IS_LINKED(state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + return 0; + + suspend_cause_changed = (suspend_cause != s->suspend_cause); + if (vsource->uplink_sink && PA_SINK_IS_LINKED(vsource->uplink_sink->state) && suspend_cause_changed) { + /* If the source is suspended for other reasons than being idle, the uplink sink + * should be suspended using the same reasons */ + if (suspend_cause != PA_SUSPEND_IDLE && state == PA_SOURCE_SUSPENDED) { + suspend_cause = suspend_cause & ~PA_SUSPEND_IDLE; + pa_sink_suspend(vsource->uplink_sink, true, suspend_cause); + } else if (PA_SOURCE_IS_OPENED(state) && s->suspend_cause != PA_SUSPEND_IDLE) { + /* If the source is resuming, the old suspend cause of the source should + * be removed from the sink unless the old suspend cause was idle. */ + suspend_cause = s->suspend_cause & ~PA_SUSPEND_IDLE; + pa_sink_suspend(vsource->uplink_sink, false, suspend_cause); + } + } + + pa_source_output_cork(o, state == PA_SOURCE_SUSPENDED); + return 0; +} + +/* Called from the IO thread. */ +int pa_virtual_source_set_state_in_io_thread(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + pa_vsource *vsource; + + pa_source_assert_ref(s); + vsource = s->vsource; + pa_assert(vsource); + + if (PA_SOURCE_IS_OPENED(new_state) && !PA_SOURCE_IS_OPENED(s->thread_info.state)) + set_latency_range_within_thread(vsource); + + return 0; +} + +/* Called from I/O thread context */ +void pa_virtual_source_update_requested_latency(pa_source *s) { + pa_vsource *vsource; + pa_source_output *o; + pa_usec_t latency; + + pa_source_assert_ref(s); + vsource = s->vsource; + pa_assert(vsource); + o = vsource->output_from_master; + pa_assert(o); + + if (!PA_SOURCE_IS_LINKED(s->thread_info.state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)) + return; + + latency = pa_source_get_requested_latency_within_thread(s); + if (vsource->max_latency) + latency = PA_MIN(vsource->max_latency, latency); + + /* If we are using fixed blocksize, part of the latency is implemented + * in the virtual source. Reduce master latency by this amount. Do not set + * the latency too small to avoid high CPU load and underruns. */ + if (vsource->fixed_block_size) { + size_t in_fs; + pa_usec_t fixed_block_latency, min_latency; + + in_fs = pa_frame_size(&o->sample_spec); + fixed_block_latency = pa_bytes_to_usec(vsource->fixed_block_size * in_fs, &o->sample_spec); + min_latency = o->source->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 source */ + pa_source_output_set_requested_latency_within_thread(o, latency); +} + +/* Called from main context */ +void pa_virtual_source_set_volume(pa_source *s) { + pa_source_output *o; + pa_cvolume vol; + + pa_source_assert_ref(s); + o = get_output_from_source(s); + pa_assert(o); + + if (!PA_SOURCE_IS_LINKED(s->state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + return; + + /* Remap the volume, source and source output may have different + * channel counts. */ + vol = s->real_volume; + pa_cvolume_remap(&vol, &s->channel_map, &o->channel_map); + pa_source_output_set_volume(o, &vol, s->save_volume, true); +} + +/* Called from main context */ +void pa_virtual_source_set_mute(pa_source *s) { + pa_source_output *o; + + pa_source_assert_ref(s); + o = get_output_from_source(s); + pa_assert(o); + + if (!PA_SOURCE_IS_LINKED(s->state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + return; + + pa_source_output_set_mute(o, s->muted, s->save_muted); +} + +/* Post data, mix in uplink sink */ +void pa_virtual_source_post(pa_source *s, const pa_memchunk *chunk) { + pa_vsource *vsource; + + vsource = s->vsource; + pa_assert(vsource); + + /* if uplink sink exists, pull data from there; simplify by using + same length as chunk provided by source */ + if (vsource->uplink_sink && PA_SINK_IS_OPENED(vsource->uplink_sink->thread_info.state)) { + pa_memchunk tchunk; + pa_mix_info streams[2]; + int ch; + uint8_t *dst; + pa_memchunk dst_chunk; + size_t nbytes; + struct uplink_data *uplink; + + uplink = vsource->uplink_sink->userdata; + pa_assert(uplink); + + /* Hmm, process any rewind request that might be queued up */ + if (PA_UNLIKELY(vsource->uplink_sink->thread_info.rewind_requested)) + sink_process_rewind(vsource->uplink_sink); + + nbytes = chunk->length; + + /* get data from the sink */ + while (pa_memblockq_get_length(uplink->memblockq) < nbytes) { + pa_memchunk nchunk; + size_t missing; + + missing = nbytes - pa_memblockq_get_length(uplink->memblockq); + pa_sink_render(vsource->uplink_sink, missing, &nchunk); + pa_memblockq_push(uplink->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + pa_memblockq_peek_fixed_size(uplink->memblockq, nbytes, &tchunk); + pa_assert(tchunk.length == nbytes); + + /* move the read pointer for sink memblockq */ + pa_memblockq_drop(uplink->memblockq, tchunk.length); + + /* Prepare output chunk */ + dst_chunk.index = 0; + dst_chunk.length = nbytes; + dst_chunk.memblock = pa_memblock_new(vsource->core->mempool, dst_chunk.length); + dst = pa_memblock_acquire_chunk(&dst_chunk); + + /* set-up mixing structure + volume was taken care of in sink and source already */ + streams[0].chunk = *chunk; + for(ch=0; ch < s->sample_spec.channels; ch++) + streams[0].volume.values[ch] = PA_VOLUME_NORM; + streams[0].volume.channels = s->sample_spec.channels; + + streams[1].chunk = tchunk; + for(ch=0; ch < s->sample_spec.channels;ch++) + streams[1].volume.values[ch] = PA_VOLUME_NORM; + streams[1].volume.channels = s->sample_spec.channels; + + /* do mixing */ + pa_mix(streams, /* 2 streams to be mixed */ + 2, + dst, /* put result in dst */ + nbytes, /* same length as input */ + (const pa_sample_spec *)&s->sample_spec, /* same sample spec for input and output */ + NULL, /* no volume information */ + false); /* no mute */ + + pa_memblock_release(dst_chunk.memblock); + + pa_source_post(s, &dst_chunk); + + pa_memblock_unref(tchunk.memblock); + pa_memblock_unref(dst_chunk.memblock); + } else + pa_source_post(s, chunk); +} + +/* Source output callbacks */ + +/* Called from output thread context */ +void pa_virtual_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { + pa_source *s; + size_t length, in_fs, out_fs; + pa_vsource *vsource; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + pa_assert(chunk); + + if (!PA_SOURCE_IS_LINKED(s->thread_info.state) || !PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)) + return; + + if (!vsource->process_chunk || !vsource->memblockq) { + pa_virtual_source_post(s, chunk); + return; + } + + out_fs = pa_frame_size(&s->sample_spec); + in_fs = pa_frame_size(&o->sample_spec); + + pa_memblockq_push_align(vsource->memblockq, chunk); + length = pa_memblockq_get_length(vsource->memblockq); + + while (length > vsource->fixed_block_size * in_fs || (vsource->fixed_block_size > 0 && length == vsource->fixed_block_size * in_fs)) { + uint8_t *src, *dst; + size_t in_count; + size_t overlap_frames, max_block_frames; + unsigned n; + pa_memchunk tchunk, schunk; + + /* Determine number of output samples */ + n = length / in_fs; + if (vsource->fixed_input_block_size && n > vsource->fixed_input_block_size) + n = vsource->fixed_input_block_size; + if (vsource->fixed_block_size && n > vsource->fixed_block_size) + n = vsource->fixed_block_size; + + n = PA_MIN(n, vsource->max_chunk_size / in_fs); + + pa_assert(n > 0); + + /* Determine number of overlap frames */ + overlap_frames = vsource->overlap_frames; + if (vsource->get_current_overlap) + overlap_frames = PA_MIN(overlap_frames, vsource->get_current_overlap(o)); + + /* For fixed input block size ignore overlap frames */ + if (vsource->fixed_input_block_size) { + overlap_frames = 0; + if (n > vsource->fixed_input_block_size) + n = vsource->fixed_input_block_size; + else + overlap_frames = vsource->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. */ + max_block_frames = pa_mempool_block_size_max(o->source->core->mempool) / PA_MAX(in_fs, out_fs); + if (n + overlap_frames > max_block_frames) + n = max_block_frames - overlap_frames; + + /* Get input data */ + in_count = n + overlap_frames; + if (overlap_frames) + pa_memblockq_rewind(vsource->memblockq, overlap_frames * in_fs); + pa_memblockq_peek_fixed_size(vsource->memblockq, in_count * in_fs, &schunk); + pa_memblockq_drop(vsource->memblockq, in_count * in_fs); + + /* Prepare output chunk */ + tchunk.index = 0; + tchunk.length = n * out_fs; + tchunk.memblock = pa_memblock_new(o->source->core->mempool, tchunk.length); + + src = pa_memblock_acquire_chunk(&schunk); + dst = pa_memblock_acquire(tchunk.memblock); + + /* Let the filter process the chunk */ + vsource->process_chunk(src, dst, in_count, n, o->userdata); + + pa_memblock_release(tchunk.memblock); + pa_memblock_release(schunk.memblock); + pa_memblock_unref(schunk.memblock); + + /* Post data */ + pa_virtual_source_post(s, &tchunk); + + pa_memblock_unref(tchunk.memblock); + length = pa_memblockq_get_length(vsource->memblockq); + } +} + + /* Called from I/O thread context */ +void pa_virtual_source_output_process_rewind(pa_source_output *o, size_t nbytes) { + pa_source *s; + pa_vsource *vsource; + size_t in_fs, out_fs; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + out_fs = pa_frame_size(&s->sample_spec); + in_fs = pa_frame_size(&o->sample_spec); + + /* If the source is not yet linked, there is nothing to rewind */ + if (!PA_SOURCE_IS_LINKED(s->thread_info.state)) + return; + + /* If the source output is corked, ignore the rewind request. */ + if (o->thread_info.state == PA_SOURCE_OUTPUT_CORKED) + return; + + /* If we have a memblockq, the source is not rewindable, else + * pass the rewind on to the source */ + if (vsource->memblockq) + pa_memblockq_seek(vsource->memblockq, - nbytes, PA_SEEK_RELATIVE, true); + else { + pa_source_process_rewind(s, nbytes * out_fs / in_fs); + if (vsource->uplink_sink && PA_SINK_IS_OPENED(vsource->uplink_sink->thread_info.state)) { + struct uplink_data *uplink; + + uplink = vsource->uplink_sink->userdata; + pa_assert(uplink); + pa_memblockq_rewind(uplink->memblockq, nbytes * out_fs / in_fs); + } + } +} + +/* Called from source I/O thread context. */ +void pa_virtual_source_output_update_max_rewind(pa_source_output *o, size_t nbytes) { + pa_source *s; + pa_vsource *vsource; + size_t in_fs, out_fs; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + out_fs = pa_frame_size(&s->sample_spec); + in_fs = pa_frame_size(&o->sample_spec); + + /* Set rewind of memblockq */ + set_memblockq_rewind(vsource); + + if (!vsource->memblockq) + pa_source_set_max_rewind_within_thread(s, nbytes * out_fs / in_fs); +} + +/* Called from I/O thread context */ +void pa_virtual_source_output_update_source_latency_range(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + set_latency_range_within_thread(vsource); +} + +/* Called from I/O thread context */ +void pa_virtual_source_output_update_source_fixed_latency(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + pa_usec_t latency; + size_t out_fs; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + out_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 = o->source->thread_info.fixed_latency; + if (vsource->fixed_block_size && !(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + latency += pa_bytes_to_usec((vsource->fixed_block_size - 1) * out_fs, &s->sample_spec); + + pa_source_set_fixed_latency_within_thread(s, latency); +} + +/* Called from I/O thread context */ +void pa_virtual_source_output_attach(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + size_t out_fs, master_fs; + pa_usec_t latency; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + out_fs = pa_frame_size(&s->sample_spec); + master_fs = pa_frame_size(&o->source->sample_spec); + + pa_source_set_rtpoll(s, o->source->thread_info.rtpoll); + if (vsource->uplink_sink) + pa_sink_set_rtpoll(vsource->uplink_sink, o->source->thread_info.rtpoll); + + set_latency_range_within_thread(vsource); + + /* For filters with fixed block size we have to add the block size minus 1 sample + * to the fixed latency. */ + latency = o->source->thread_info.fixed_latency; + if (vsource->fixed_block_size && !(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + latency += pa_bytes_to_usec((vsource->fixed_block_size - 1) * out_fs, &s->sample_spec); + + pa_source_set_fixed_latency_within_thread(s, latency); + + /* Set max_rewind, virtual sources can only rewind when there is no memblockq */ + if (vsource->memblockq) + pa_source_set_max_rewind_within_thread(s, 0); + else + pa_source_set_max_rewind_within_thread(s, o->source->thread_info.max_rewind * out_fs / master_fs); + + /* Set rewind of memblockq */ + set_memblockq_rewind(vsource); + + /* This call is needed to remove the UNAVAILABLE suspend cause after + * a move when the previous master source disappeared. */ + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(vsource), VSOURCE_MESSAGE_OUTPUT_ATTACHED, NULL, 0, NULL, NULL); + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) + pa_source_attach_within_thread(s); +} + +/* Called from output thread context */ +void pa_virtual_source_output_detach(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) + pa_source_detach_within_thread(s); + + pa_source_set_rtpoll(s, NULL); + if (vsource->uplink_sink) + pa_sink_set_rtpoll(vsource->uplink_sink, NULL); +} + +/* Called from main thread */ +void pa_virtual_source_output_kill(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + pa_module *m; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + /* The order here matters! We first kill the source so that streams + * can properly be moved away while the source output is still connected + * to the master. It may be possible that the source output is connected + * to a virtual source which has lost its master, so do not try to cork + * if the source has no I/O context. */ + if (o->source && o->source->asyncmsgq) + pa_source_output_cork(o, true); + pa_source_unlink(s); + pa_source_output_unlink(o); + + pa_source_output_unref(o); + + if (vsource->memblockq) + pa_memblockq_free(vsource->memblockq); + + /* Destroy uplink sink if present */ + if (vsource->uplink_sink) { + struct uplink_data *uplink; + + uplink = vsource->uplink_sink->userdata; + pa_sink_unlink(vsource->uplink_sink); + pa_sink_unref(vsource->uplink_sink); + + if (uplink) { + if (uplink->memblockq) + pa_memblockq_free(uplink->memblockq); + + pa_xfree(uplink); + } + vsource->uplink_sink = NULL; + } + + /* Virtual sources must set the module */ + m = s->module; + pa_assert(m); + pa_source_unref(s); + + vsource->source = NULL; + vsource->output_from_master = NULL; + vsource->memblockq = NULL; + + pa_module_unload_request(m, true); +} + +/* Called from main context */ +bool pa_virtual_source_output_may_move_to(pa_source_output *o, pa_source *dest) { + pa_source *s; + pa_vsource *vsource; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + if (vsource->autoloaded) + return false; + + if (s == dest) + return false; + + if (vsource->uplink_sink) { + pa_source *chain_master; + + chain_master = dest; + while (chain_master->vsource && chain_master->vsource->output_from_master) + chain_master = chain_master->vsource->output_from_master->source; + + if (chain_master == vsource->uplink_sink->monitor_source) + return false; + } + + return true; +} + +/* Called from main thread */ +void pa_virtual_source_output_moving(pa_source_output *o, pa_source *dest) { + pa_source *s; + pa_vsource *vsource; + uint32_t idx; + pa_source_output *output; + pa_sink_input *input; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + if (dest) { + pa_source_set_asyncmsgq(s, dest->asyncmsgq); + pa_source_update_flags(s, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags); + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_MASTER_DEVICE, dest->name); + vsource->source_moving = true; + if (vsource->uplink_sink) { + pa_sink_flags_t flags = 0; + + if (dest->flags & PA_SOURCE_LATENCY) + flags |= PA_SINK_LATENCY; + if (dest->flags & PA_SOURCE_DYNAMIC_LATENCY) + flags |= PA_SINK_DYNAMIC_LATENCY; + pa_sink_set_asyncmsgq(vsource->uplink_sink, dest->asyncmsgq); + pa_sink_update_flags(vsource->uplink_sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, flags); + pa_proplist_sets(vsource->uplink_sink->proplist, PA_PROP_DEVICE_MASTER_DEVICE, dest->name); + } + } else { + pa_source_set_asyncmsgq(s, NULL); + if (vsource->uplink_sink) + pa_sink_set_asyncmsgq(vsource->uplink_sink, NULL); + } + + if (dest && vsource->set_description) + vsource->set_description(o, dest); + + else { + if (vsource->auto_desc && dest) { + const char *z; + pa_proplist *pl; + char *proplist_name; + + pl = pa_proplist_new(); + proplist_name = pa_sprintf_malloc("device.%s.name", vsource->source_type); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "%s %s on %s", vsource->desc_head, + pa_proplist_gets(s->proplist, proplist_name), z ? z : dest->name); + + pa_source_update_proplist(s, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + pa_xfree(proplist_name); + } + + if (dest) + pa_proplist_setf(o->proplist, PA_PROP_MEDIA_NAME, "%s Stream from %s", vsource->desc_head, pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)); + } + + if (vsource->uplink_sink && 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, "Uplink sink %s on %s", + pa_proplist_gets(vsource->uplink_sink->proplist, "device.uplink_sink.name"), z ? z : dest->name); + + pa_sink_update_proplist(vsource->uplink_sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } + + /* Propagate asyncmsq change to attached virtual sources */ + PA_IDXSET_FOREACH(output, s->outputs, idx) { + if (output->destination_source && output->moving) + output->moving(output, s); + } + + /* Propagate asyncmsq change to virtual sinks attached to the uplink sink */ + if (vsource->uplink_sink) { + PA_IDXSET_FOREACH(input, vsource->uplink_sink->inputs, idx) { + if (input->origin_sink && input->moving) + input->moving(input, vsource->uplink_sink); + } + } + +} + +/* Called from main context */ +void pa_virtual_source_output_volume_changed(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + pa_cvolume vol; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + /* Preserve source volume if the master source is changing */ + if (vsource->source_moving) { + vsource->source_moving = false; + return; + } + + /* Remap the volume, source and source output may have different + * channel counts. */ + vol = o->volume; + pa_cvolume_remap(&vol, &o->channel_map, &s->channel_map); + pa_source_volume_changed(s, &vol); +} + +/* Called from main context */ +void pa_virtual_source_output_mute_changed(pa_source_output *o) { + pa_source *s; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + + pa_source_mute_changed(s, o->muted); +} + +/* Called from main context */ +void pa_virtual_source_output_suspend(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause) { + pa_source *s; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + + if (!PA_SOURCE_IS_LINKED(s->state)) + return; + + if (o->source->state != PA_SOURCE_SUSPENDED || o->source->suspend_cause == PA_SUSPEND_IDLE) + pa_source_suspend(s, false, PA_SUSPEND_UNAVAILABLE); + else + pa_source_suspend(s, true, PA_SUSPEND_UNAVAILABLE); +} + +/* Other functions */ + +void pa_virtual_source_set_callbacks(pa_source *s, bool use_volume_sharing) { + + s->parent.process_msg = pa_virtual_source_process_msg; + s->set_state_in_main_thread = pa_virtual_source_set_state_in_main_thread; + s->set_state_in_io_thread = pa_virtual_source_set_state_in_io_thread; + s->update_requested_latency = pa_virtual_source_update_requested_latency; + pa_source_set_set_mute_callback(s, pa_virtual_source_set_mute); + if (!use_volume_sharing) { + pa_source_set_set_volume_callback(s, pa_virtual_source_set_volume); + pa_source_enable_decibel_volume(s, true); + } +} + +void pa_virtual_source_output_set_callbacks(pa_source_output *o, bool use_volume_sharing) { + + o->push = pa_virtual_source_output_push; + o->update_source_latency_range = pa_virtual_source_output_update_source_latency_range; + o->update_source_fixed_latency = pa_virtual_source_output_update_source_fixed_latency; + o->kill = pa_virtual_source_output_kill; + o->attach = pa_virtual_source_output_attach; + o->detach = pa_virtual_source_output_detach; + o->may_move_to = pa_virtual_source_output_may_move_to; + o->moving = pa_virtual_source_output_moving; + o->volume_changed = use_volume_sharing ? NULL : pa_virtual_source_output_volume_changed; + o->mute_changed = pa_virtual_source_output_mute_changed; + o->suspend = pa_virtual_source_output_suspend; + o->update_max_rewind = pa_virtual_source_output_update_max_rewind; + o->process_rewind = pa_virtual_source_output_process_rewind; +} + +static int vsource_process_msg(pa_msgobject *obj, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_vsource *vsource; + pa_source *s; + pa_source_output *o; + + pa_assert(obj); + pa_assert_ctl_context(); + + vsource = PA_VSOURCE(obj); + + switch (code) { + + case VSOURCE_MESSAGE_FREE_PARAMETERS: + + pa_assert(userdata); + pa_assert(vsource->free_filter_parameters); + vsource->free_filter_parameters(userdata); + return 0; + + case VSOURCE_MESSAGE_OUTPUT_ATTACHED: + + s = vsource->source; + o = vsource->output_from_master; + + /* This may happen if a message is still pending after the vsink was + * destroyed. */ + if (!s || !o) + return 0; + + if (PA_SOURCE_IS_LINKED(s->state)) { + if (o->source->state != PA_SOURCE_SUSPENDED || o->source->suspend_cause == PA_SUSPEND_IDLE) + pa_source_suspend(s, false, PA_SUSPEND_UNAVAILABLE); + else + pa_source_suspend(s, true, PA_SUSPEND_UNAVAILABLE); + } + return 0; + } + return 0; +} + +int pa_virtual_source_activate(pa_vsource *vs) { + + pa_assert(vs); + pa_assert(vs->source); + pa_assert(vs->output_from_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; + } + + /* Activate uplink sink */ + if (vs->uplink_sink) + pa_sink_put(vs->uplink_sink); + + /* Set source output latency at startup to max_latency if specified. */ + if (vs->max_latency) + pa_source_output_set_requested_latency(vs->output_from_master, vs->max_latency); + + /* The order here is important. The output must be put first, + * otherwise streams might attach to the source before the source + * output is attached to the master. */ + pa_source_output_put(vs->output_from_master); + pa_source_put(vs->source); + + /* If volume sharing and flat volumes are disabled, we have to apply the source volume to the source output. */ + if (!(vs->source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) && !pa_source_flat_volume_enabled(vs->output_from_master->source)) { + pa_cvolume vol; + + vol = vs->source->real_volume; + pa_cvolume_remap(&vol, &vs->source->channel_map, &vs->output_from_master->channel_map); + pa_source_output_set_volume(vs->output_from_master, &vol, vs->source->save_volume, true); + } + + pa_source_output_cork(vs->output_from_master, false); + + return 0; +} + +void pa_virtual_source_destroy(pa_vsource *vs) { + + pa_assert(vs); + + /* See comments in source_output_kill() above regarding + * destruction order! */ + if (vs->output_from_master && PA_SOURCE_OUTPUT_IS_LINKED(vs->output_from_master->state)) + pa_source_output_cork(vs->output_from_master, true); + + if (vs->source) + pa_source_unlink(vs->source); + + if (vs->output_from_master) { + pa_source_output_unlink(vs->output_from_master); + pa_source_output_unref(vs->output_from_master); + vs->output_from_master = NULL; + } + + if (vs->memblockq) + pa_memblockq_free(vs->memblockq); + + if (vs->source) { + pa_source_unref(vs->source); + vs->source = NULL; + } + + /* Destroy uplink sink if present */ + if (vs->uplink_sink) { + struct uplink_data *uplink; + + uplink = vs->uplink_sink->userdata; + pa_sink_unlink(vs->uplink_sink); + pa_sink_unref(vs->uplink_sink); + + if (uplink) { + if (uplink->memblockq) + pa_memblockq_free(uplink->memblockq); + + pa_xfree(uplink); + } + } + + /* We have to use pa_msgobject_unref() here because there may still be pending + * VSOURCE_MESSAGE_OUTPUT_ATTACHED messages. */ + pa_msgobject_unref(PA_MSGOBJECT(vs)); +} + +/* Manually create a vsource structure. */ +pa_vsource* pa_virtual_source_vsource_new(pa_source *s) { + pa_vsource *vsource; + + pa_assert(s); + + /* Create new vource */ + vsource = pa_msgobject_new(pa_vsource); + vsource->parent.process_msg = vsource_process_msg; + + vsource->source = s; + vsource->core = s->core; + s->vsource = vsource; + + /* Reset virtual source parameters */ + vsource->output_from_master = NULL; + vsource->memblockq = NULL; + vsource->auto_desc = false; + vsource->source_moving = false; + vsource->desc_head = "Unknown Sink"; + vsource->source_type = "unknown"; + vsource->autoloaded = false; + vsource->max_chunk_size = pa_frame_align(pa_mempool_block_size_max(s->core->mempool), &s->sample_spec); + vsource->fixed_block_size = 0; + vsource->fixed_input_block_size = 0; + vsource->overlap_frames = 0; + vsource->max_latency = 0; + vsource->process_chunk = NULL; + vsource->get_extra_latency = NULL; + vsource->set_description = NULL; + vsource->update_filter_parameters = NULL; + vsource->update_block_sizes = NULL; + vsource->free_filter_parameters = NULL; + vsource->uplink_sink = NULL; + + return vsource; +} + +pa_vsource *pa_virtual_source_create(pa_source *master, const char *source_type, const char *desc_prefix, + pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *source_output_ss, pa_channel_map *source_output_map, + pa_module *m, void *userdata, pa_modargs *ma, + bool use_volume_sharing, bool create_memblockq) { + + pa_source_output_new_data source_output_data; + pa_source_new_data source_data; + char *source_type_property; + bool auto_desc; + bool force_flat_volume = false; + bool remix = true; + pa_resample_method_t resample_method = PA_RESAMPLER_INVALID; + pa_vsource *vsource; + pa_source *s; + pa_source_output *o; + const char *uplink_sink; + pa_sink_new_data sink_data; + + /* Make sure all necessary values are set. Only userdata and source description + * are allowed to be NULL. */ + pa_assert(master); + pa_assert(source_ss); + pa_assert(source_map); + pa_assert(source_output_ss); + pa_assert(source_output_map); + pa_assert(m); + pa_assert(ma); + + /* We do not support resampling in filters */ + pa_assert(source_output_ss->rate == source_ss->rate); + + if (!source_type) + source_type = "unknown"; + if (!desc_prefix) + desc_prefix = "Unknown Source"; + + /* 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_source_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 source */ + pa_source_new_data_init(&source_data); + source_data.driver = m->name; + source_data.module = m; + if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) + source_data.name = pa_sprintf_malloc("%s.%s", master->name, source_type); + pa_source_new_data_set_sample_spec(&source_data, source_ss); + pa_source_new_data_set_channel_map(&source_data, source_map); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&source_data); + return NULL; + } + + s = pa_source_new(m->core, &source_data, (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)) + | (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0)); + + pa_source_new_data_done(&source_data); + + if (!s) { + pa_log("Failed to create source."); + return NULL; + } + + /* Set name and description properties after the source 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); + } + + source_type_property = pa_sprintf_malloc("device.%s.name", source_type); + pa_proplist_sets(s->proplist, source_type_property, s->name); + pa_xfree(source_type_property); + + /* Create vsource structure. */ + vsource = pa_virtual_source_vsource_new(s); + + pa_virtual_source_set_callbacks(s, use_volume_sharing); + vsource->auto_desc = auto_desc; + vsource->desc_head = desc_prefix; + vsource->source_type = source_type; + + /* Normally this flag would be enabled automatically be we can force it. */ + if (force_flat_volume) + s->flags |= PA_SOURCE_FLAT_VOLUME; + s->userdata = userdata; + + pa_source_set_asyncmsgq(s, master->asyncmsgq); + + /* Create source output */ + pa_source_output_new_data_init(&source_output_data); + source_output_data.driver = __FILE__; + source_output_data.module = m; + pa_source_output_new_data_set_source(&source_output_data, master, false, true); + source_output_data.destination_source = s; + + pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "%s Stream of %s", desc_prefix, pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)); + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_source_output_new_data_set_sample_spec(&source_output_data, source_output_ss); + pa_source_output_new_data_set_channel_map(&source_output_data, source_output_map); + source_output_data.resample_method = resample_method; + source_output_data.flags = (remix ? 0 : PA_SOURCE_OUTPUT_NO_REMIX) | PA_SOURCE_OUTPUT_START_CORKED; + if (!pa_safe_streq(master->name, m->core->default_source->name)) + source_output_data.preferred_source = pa_xstrdup(master->name); + + if (pa_modargs_get_proplist(ma, "source_output_properties", source_output_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid source output properties"); + pa_source_output_new_data_done(&source_output_data); + pa_virtual_source_destroy(vsource); + return NULL; + } + + pa_source_output_new(&o, m->core, &source_output_data); + pa_source_output_new_data_done(&source_output_data); + + if (!o) { + pa_log("Could not create source-output"); + pa_virtual_source_destroy(vsource); + return NULL; + } + + pa_virtual_source_output_set_callbacks(o, use_volume_sharing); + o->userdata = userdata; + + vsource->output_from_master = o; + + vsource->autoloaded = false; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &vsource->autoloaded) < 0) { + pa_log("Failed to parse autoloaded value"); + pa_virtual_source_destroy(vsource); + return NULL; + } + + if (create_memblockq) { + char *tmp; + pa_memchunk silence; + + tmp = pa_sprintf_malloc("%s memblockq", desc_prefix); + pa_silence_memchunk_get(&s->core->silence_cache, s->core->mempool, &silence, &o->sample_spec, 0); + vsource->memblockq = pa_memblockq_new(tmp, 0, MEMBLOCKQ_MAXLENGTH, 0, source_output_ss, 1, 1, 0, &silence); + pa_memblock_unref(silence.memblock); + pa_xfree(tmp); + if (!vsource->memblockq) { + pa_log("Failed to create memblockq"); + pa_virtual_source_destroy(vsource); + return NULL; + } + } + + /* Set up uplink sink */ + uplink_sink = pa_modargs_get_value(ma, "uplink_sink", NULL); + if (uplink_sink) { + const char *z; + char *tmp; + pa_memchunk silence; + pa_sink_flags_t flags; + struct uplink_data *uplink; + + pa_sink_new_data_init(&sink_data); + sink_data.driver = m->name; + sink_data.module = m; + sink_data.name = pa_xstrdup(uplink_sink); + pa_sink_new_data_set_sample_spec(&sink_data, source_ss); + pa_sink_new_data_set_channel_map(&sink_data, source_map); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "uplink sink"); + pa_proplist_sets(sink_data.proplist, "device.uplink_sink.name", sink_data.name); + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Uplink Sink %s on %s", sink_data.name, z ? z : master->name); + + flags = 0; + if (master->flags & PA_SOURCE_LATENCY) + flags = PA_SINK_LATENCY; + if (master->flags & PA_SOURCE_DYNAMIC_LATENCY) + flags |= PA_SINK_DYNAMIC_LATENCY; + vsource->uplink_sink = pa_sink_new(m->core, &sink_data, flags); + pa_sink_new_data_done(&sink_data); + + if (!vsource->uplink_sink) { + pa_log("Failed to create uplink sink"); + pa_virtual_source_destroy(vsource); + return NULL; + } + + uplink = pa_xnew0(struct uplink_data, 1); + vsource->uplink_sink->userdata = uplink; + + tmp = pa_sprintf_malloc("%s uplink sink memblockq", desc_prefix); + pa_silence_memchunk_get(&s->core->silence_cache, s->core->mempool, &silence, &s->sample_spec, 0); + uplink->memblockq = pa_memblockq_new(tmp, 0, MEMBLOCKQ_MAXLENGTH, 0, source_ss, 1, 1, 0, &silence); + pa_memblock_unref(silence.memblock); + pa_xfree(tmp); + if (!uplink->memblockq) { + pa_log("Failed to create sink memblockq"); + pa_virtual_source_destroy(vsource); + return NULL; + } + + vsource->uplink_sink->parent.process_msg = sink_process_msg; + vsource->uplink_sink->update_requested_latency = sink_update_requested_latency; + vsource->uplink_sink->set_state_in_main_thread = sink_set_state_in_main_thread; + vsource->uplink_sink->set_state_in_io_thread = sink_set_state_in_io_thread; + vsource->uplink_sink->uplink_of = vsource; + uplink->vsource = vsource; + + pa_sink_set_asyncmsgq(vsource->uplink_sink, master->asyncmsgq); + } + + return vsource; +} + +/* Send request to update filter parameters to the I/O-thread. */ +void pa_virtual_source_request_parameter_update(pa_vsource *vs, void *parameters) { + + pa_assert(vs); + pa_assert(vs->source); + + /* parameters may be NULL if it is enough to have access to userdata from the + * callback. */ + pa_asyncmsgq_send(vs->source->asyncmsgq, PA_MSGOBJECT(vs->source), SOURCE_MESSAGE_UPDATE_PARAMETERS, parameters, 0, NULL); +} diff --git a/src/modules/virtual-source-common.h b/src/modules/virtual-source-common.h new file mode 100644 index 000000000..f48a60018 --- /dev/null +++ b/src/modules/virtual-source-common.h @@ -0,0 +1,74 @@ +/*** + 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 sources. */ +int pa_virtual_source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk); + +int pa_virtual_source_set_state_in_main_thread(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause); +int pa_virtual_source_set_state_in_io_thread(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause); + +void pa_virtual_source_update_requested_latency(pa_source *s); +void pa_virtual_source_set_volume(pa_source *s); +void pa_virtual_source_set_mute(pa_source *s); + +void pa_virtual_source_output_push(pa_source_output *o, const pa_memchunk *chunk); + +void pa_virtual_source_output_update_source_latency_range(pa_source_output *o); +void pa_virtual_source_output_update_source_fixed_latency(pa_source_output *o); + +void pa_virtual_source_output_process_rewind(pa_source_output *o, size_t nbytes); +void pa_virtual_source_output_update_max_rewind(pa_source_output *o, size_t nbytes); + +void pa_virtual_source_output_detach(pa_source_output *o); +void pa_virtual_source_output_attach(pa_source_output *o); +void pa_virtual_source_output_kill(pa_source_output *o); +void pa_virtual_source_output_moving(pa_source_output *o, pa_source *dest); +bool pa_virtual_source_output_may_move_to(pa_source_output *o, pa_source *dest); + +void pa_virtual_source_output_volume_changed(pa_source_output *o); +void pa_virtual_source_output_mute_changed(pa_source_output *o); + +void pa_virtual_source_output_suspend(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause); + +/* Set callbacks for virtual source and source output. */ +void pa_virtual_source_set_callbacks(pa_source *s, bool use_volume_sharing); +void pa_virtual_source_output_set_callbacks(pa_source_output *o, bool use_volume_sharing); + +/* Create a new virtual source. Returns a filled vsource structure or NULL on failure. */ +pa_vsource *pa_virtual_source_create(pa_source *master, const char *source_type, const char *desc_prefix, + pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *source_output_ss, pa_channel_map *source_output_map, + pa_module *m, void *userdata, pa_modargs *ma, + bool use_volume_sharing, bool create_memblockq); + +/* Activate the new virtual source. */ +int pa_virtual_source_activate(pa_vsource *vs); + +/* Destroys the objects associated with the virtual source. */ +void pa_virtual_source_destroy(pa_vsource *vs); + +/* Create vsource structure */ +pa_vsource* pa_virtual_source_vsource_new(pa_source *s); + +/* Update filter parameters */ +void pa_virtual_source_request_parameter_update(pa_vsource *vs, void *parameters); + +/* Post data, mix in uplink sink */ +void pa_virtual_source_post(pa_source *s, const pa_memchunk *chunk); diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index df7b72461..248d953f6 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -415,7 +415,7 @@ finish: /* a < b -> return -1 * a == b -> return 0 * a > b -> return 1 */ -static int compare_sinks(pa_sink *a, pa_sink *b) { +static int compare_sinks(pa_sink *a, pa_sink *b, bool ignore_configured_virtual_default) { pa_core *core; core = a->core; @@ -429,23 +429,37 @@ static int compare_sinks(pa_sink *a, pa_sink *b) { return 1; /* The policy default sink is preferred over any other sink. */ - if (pa_safe_streq(b->name, core->policy_default_sink)) - return -1; - if (pa_safe_streq(a->name, core->policy_default_sink)) - return 1; + if (pa_safe_streq(b->name, core->policy_default_sink)) { + if (!ignore_configured_virtual_default || !pa_sink_is_filter(b)) + return -1; + } + if (pa_safe_streq(a->name, core->policy_default_sink)) { + if (!ignore_configured_virtual_default || !pa_sink_is_filter(a)) + return 1; + } /* The configured default sink is preferred over any other sink * except the policy default sink. */ - if (pa_safe_streq(b->name, core->configured_default_sink)) - return -1; - if (pa_safe_streq(a->name, core->configured_default_sink)) - return 1; + if (pa_safe_streq(b->name, core->configured_default_sink)) { + if (!ignore_configured_virtual_default || !pa_sink_is_filter(b)) + return -1; + } + if (pa_safe_streq(a->name, core->configured_default_sink)) { + if (!ignore_configured_virtual_default || !pa_sink_is_filter(a)) + return 1; + } if (a->priority < b->priority) return -1; if (a->priority > b->priority) return 1; + /* Let sinks like pipe sink or null sink win against filter sinks */ + if (a->vsink && !b->vsink) + return -1; + if (!a->vsink && b->vsink) + return 1; + /* It's hard to find any difference between these sinks, but maybe one of * them is already the default sink? If so, it's best to keep it as the * default to avoid changing the routing for no good reason. */ @@ -457,11 +471,10 @@ static int compare_sinks(pa_sink *a, pa_sink *b) { return 0; } -void pa_core_update_default_sink(pa_core *core) { +pa_sink *pa_core_find_best_sink(pa_core *core, bool ignore_configured_virtual_default) { pa_sink *best = NULL; pa_sink *sink; uint32_t idx; - pa_sink *old_default_sink; pa_assert(core); @@ -474,10 +487,21 @@ void pa_core_update_default_sink(pa_core *core) { continue; } - if (compare_sinks(sink, best) > 0) + if (compare_sinks(sink, best, ignore_configured_virtual_default) > 0) best = sink; } + return best; +} + +void pa_core_update_default_sink(pa_core *core) { + pa_sink *best; + pa_sink *old_default_sink; + + pa_assert(core); + + best = pa_core_find_best_sink(core, false); + old_default_sink = core->default_sink; if (best == old_default_sink) @@ -503,7 +527,7 @@ void pa_core_update_default_sink(pa_core *core) { /* a < b -> return -1 * a == b -> return 0 * a > b -> return 1 */ -static int compare_sources(pa_source *a, pa_source *b) { +static int compare_sources(pa_source *a, pa_source *b, bool ignore_configured_virtual_default) { pa_core *core; core = a->core; @@ -517,17 +541,25 @@ static int compare_sources(pa_source *a, pa_source *b) { return 1; /* The policy default source is preferred over any other source. */ - if (pa_safe_streq(b->name, core->policy_default_source)) - return -1; - if (pa_safe_streq(a->name, core->policy_default_source)) - return 1; + if (pa_safe_streq(b->name, core->policy_default_source)) { + if (!ignore_configured_virtual_default || !pa_source_is_filter(b)) + return -1; + } + if (pa_safe_streq(a->name, core->policy_default_source)) { + if (!ignore_configured_virtual_default || !pa_source_is_filter(a)) + return 1; + } /* The configured default source is preferred over any other source * except the policy default source. */ - if (pa_safe_streq(b->name, core->configured_default_source)) - return -1; - if (pa_safe_streq(a->name, core->configured_default_source)) - return 1; + if (pa_safe_streq(b->name, core->configured_default_source)) { + if (!ignore_configured_virtual_default || !pa_source_is_filter(b)) + return -1; + } + if (pa_safe_streq(a->name, core->configured_default_source)) { + if (!ignore_configured_virtual_default || !pa_source_is_filter(a)) + return 1; + } /* Monitor sources lose to non-monitor sources. */ if (a->monitor_of && !b->monitor_of) @@ -540,9 +572,15 @@ static int compare_sources(pa_source *a, pa_source *b) { if (a->priority > b->priority) return 1; + /* Let sources like pipe source or null source win against filter sources */ + if (a->vsource && !b->vsource) + return -1; + if (!a->vsource && b->vsource) + return 1; + /* If the sources are monitors, we can compare the monitored sinks. */ if (a->monitor_of) - return compare_sinks(a->monitor_of, b->monitor_of); + return compare_sinks(a->monitor_of, b->monitor_of, false); /* It's hard to find any difference between these sources, but maybe one of * them is already the default source? If so, it's best to keep it as the @@ -555,11 +593,10 @@ static int compare_sources(pa_source *a, pa_source *b) { return 0; } -void pa_core_update_default_source(pa_core *core) { +pa_source *pa_core_find_best_source(pa_core *core, bool ignore_configured_virtual_default) { pa_source *best = NULL; pa_source *source; uint32_t idx; - pa_source *old_default_source; pa_assert(core); @@ -572,10 +609,21 @@ void pa_core_update_default_source(pa_core *core) { continue; } - if (compare_sources(source, best) > 0) + if (compare_sources(source, best, ignore_configured_virtual_default) > 0) best = source; } + return best; +} + +void pa_core_update_default_source(pa_core *core) { + pa_source *best; + pa_source *old_default_source; + + pa_assert(core); + + best = pa_core_find_best_source(core, false); + old_default_source = core->default_source; if (best == old_default_source) @@ -693,11 +741,6 @@ void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink if (!si->sink) continue; - /* Skip this sink input if it is connecting a filter sink to - * the master */ - if (si->origin_sink) - continue; - /* It might happen that a stream and a sink are set up at the same time, in which case we want to make sure we don't interfere with that */ @@ -727,11 +770,6 @@ void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_sou if (!so->source) continue; - /* Skip this source output if it is connecting a filter source to - * the master */ - if (so->destination_source) - continue; - /* It might happen that a stream and a source are set up at the same time, in which case we want to make sure we don't interfere with that */ diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index 7ac06f5d7..11011a744 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -271,6 +271,9 @@ void pa_core_set_policy_default_source(pa_core *core, const char *source); void pa_core_update_default_sink(pa_core *core); void pa_core_update_default_source(pa_core *core); +pa_sink *pa_core_find_best_sink(pa_core *core, bool ignore_configured_virtual_default); +pa_source *pa_core_find_best_source(pa_core *core, bool ignore_configured_virtual_default); + void pa_core_set_exit_idle_time(pa_core *core, int time); /* Check whether no one is connected to this core */ 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); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 4380087ca..1e625a677 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1782,12 +1782,13 @@ bool pa_sink_input_may_move(pa_sink_input *i) { return true; } -static bool find_filter_sink_input(pa_sink_input *target, pa_sink *s) { +bool pa_sink_input_is_filter_loop(pa_sink_input *target, pa_sink *s) { unsigned PA_UNUSED i = 0; - while (s && s->input_to_master) { - if (s->input_to_master == target) + + while (s && (s->vsink && s->vsink->input_to_master)) { + if (s->vsink->input_to_master == target) return true; - s = s->input_to_master->sink; + s = s->vsink->input_to_master->sink; pa_assert(i++ < 100); } return false; @@ -1799,8 +1800,8 @@ static bool is_filter_sink_moving(pa_sink_input *i) { if (!sink) return false; - while (sink->input_to_master) { - sink = sink->input_to_master->sink; + while (sink->vsink && sink->vsink->input_to_master) { + sink = sink->vsink->input_to_master->sink; if (!sink) return true; @@ -1826,7 +1827,7 @@ bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { return false; /* Make sure we're not creating a filter sink cycle */ - if (find_filter_sink_input(i, dest)) { + if (pa_sink_input_is_filter_loop(i, dest)) { pa_log_debug("Can't connect input to %s, as that would create a cycle.", dest->name); return false; } @@ -2239,10 +2240,28 @@ void pa_sink_input_fail_move(pa_sink_input *i) { if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP) return; - /* Can we move the sink input to the default sink? */ - if (i->core->rescue_streams && pa_sink_input_may_move_to(i, i->core->default_sink)) { - if (pa_sink_input_finish_move(i, i->core->default_sink, false) >= 0) - return; + /* Try to rescue stream if configured */ + if (i->core->rescue_streams) { + + /* Can we move the sink input to the default sink? */ + if (pa_sink_input_may_move_to(i, i->core->default_sink)) { + if (pa_sink_input_finish_move(i, i->core->default_sink, false) >= 0) + return; + } + + /* If this is a filter stream and the default sink is set to a filter sink within + * the same filter chain, we would create a loop and therefore have to find another + * sink to move to. */ + if (i->origin_sink && pa_sink_input_is_filter_loop(i, i->core->default_sink)) { + pa_sink *best; + + best = pa_core_find_best_sink(i->core, true); + + if (best && pa_sink_input_may_move_to(i, best)) { + if (pa_sink_input_finish_move(i, best, false) >= 0) + return; + } + } } if (i->moving) diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 7a75c0f09..58b086dbf 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -422,6 +422,8 @@ int pa_sink_input_start_move(pa_sink_input *i); int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save); void pa_sink_input_fail_move(pa_sink_input *i); +bool pa_sink_input_is_filter_loop(pa_sink_input *target, pa_sink *s); + pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i); /* To be used exclusively by the sink driver IO thread */ diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 0f0dc56fc..1ed24b786 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -286,7 +286,8 @@ pa_sink* pa_sink_new( s->inputs = pa_idxset_new(NULL, NULL); s->n_corked = 0; - s->input_to_master = NULL; + s->vsink = NULL; + s->uplink_of = NULL; s->reference_volume = s->real_volume = data->volume; pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); @@ -1693,10 +1694,10 @@ pa_sink *pa_sink_get_master(pa_sink *s) { pa_sink_assert_ref(s); while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { - if (PA_UNLIKELY(!s->input_to_master)) + if (PA_UNLIKELY(!s->vsink || (s->vsink && !s->vsink->input_to_master))) return NULL; - s = s->input_to_master->sink; + s = s->vsink->input_to_master->sink; } return s; @@ -1706,7 +1707,7 @@ pa_sink *pa_sink_get_master(pa_sink *s) { bool pa_sink_is_filter(pa_sink *s) { pa_sink_assert_ref(s); - return (s->input_to_master != NULL); + return ((s->vsink != NULL) && (s->vsink->input_to_master != NULL)); } /* Called from main context */ @@ -2552,7 +2553,7 @@ unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_sourc } if (s->monitor_source) - ret += pa_source_check_suspend(s->monitor_source, ignore_output); + ret += pa_source_check_suspend(s->monitor_source, ignore_input, ignore_output); return ret; } @@ -3258,6 +3259,9 @@ void pa_sink_invalidate_requested_latency(pa_sink *s, bool dynamic) { if (PA_SINK_IS_LINKED(s->thread_info.state)) { + if (s->uplink_of && s->uplink_of->source) + pa_source_invalidate_requested_latency(s->uplink_of->source, dynamic); + if (s->update_requested_latency) s->update_requested_latency(s); @@ -4064,9 +4068,32 @@ void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool if (!i->sink) continue; - /* Don't move sink-inputs which connect filter sinks to their target sinks */ - if (i->origin_sink) + /* If this is a filter stream and the default sink is set to a filter sink within + * the same filter chain, we would create a loop and therefore have to find another + * sink to move to. */ + if (i->origin_sink && pa_sink_input_is_filter_loop(i, core->default_sink)) { + pa_sink *best; + + /* If the default sink changed to our filter chain, lets make the current + * master the preferred sink. */ + if (default_sink_changed) { + pa_xfree(i->preferred_sink); + i->preferred_sink = pa_xstrdup(i->sink->name); + + continue; + } + + best = pa_core_find_best_sink(core, true); + + if (!best || !pa_sink_input_may_move_to(i, best)) + continue; + + pa_log_info("Moving sink input %u \"%s\" to the default sink would create a filter loop, moving to %s instead.", + i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), best->name); + + pa_sink_input_move_to(i, best, false); continue; + } /* If default_sink_changed is false, the old sink became unavailable, so all streams must be moved. */ if (pa_safe_streq(old_sink->name, i->preferred_sink) && default_sink_changed) diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 383edacb5..9e9c9ee9c 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -56,6 +56,90 @@ typedef void(*pa_sink_cb_t)(pa_sink *s); typedef int (*pa_sink_get_mute_cb_t)(pa_sink *s, bool *mute); +/* Virtual sink structure */ +typedef struct pa_vsink { + pa_msgobject parent; /* Message object */ + pa_sink *sink; /* A pointer to the virtual sink */ + pa_sink_input *input_to_master; /* Sink input to the master sink */ + pa_memblockq *memblockq; /* Memblockq of the virtual sink, may be NULL */ + size_t drop_bytes; /* Number of bytes to drop during sink_input_pop() + * in sink input sample speci. Used during rewind + * of fixed block size filters */ + + bool auto_desc; /* Automatically adapt description on move */ + const char *desc_head; /* Leading part of description string used for the + * sink and sink input when auto_desc is true */ + const char *sink_type; /* Name for the type of sink, used as suffix for + * the sink name if the name is derived from the + * master sink. */ + bool autoloaded; /* True if the sink was not loaded manually */ + size_t max_chunk_size; /* Maximum chunk size in bytes that the filter will + * accept, set to pa_mempool_block_size_max() by default */ + size_t fixed_block_size; /* Block size in frames for fixed block size filters, + * 0 if block size is controlled by pulseaudio. */ + size_t fixed_input_block_size; /* Input block size in frames. If not 0, input data for + * process_chunk() will always have the same size. + * If not enough new data is available, the remaining + * samples will be filled with history. */ + size_t overlap_frames; /* Some filters require old input samples in addtion to + * the current data. The variable contains the number of + * previous frames that will be passed to process_chunk(). + * The actual number of history frames may be variable if + * the filter defines the get_current_overlap() function. + * In this case, overlap_frames contains the maximum + * number of history frames. */ + size_t max_request_frames_min; /* Minimum value for max_request in frames, 0 if unused */ + pa_usec_t max_latency; /* Maximum latency allowed for the sink, 0 if unused */ + int max_rewind; /* Maximum number of frames that the sink can rewind. + * 0 means unlimited, -1 disables rewinding */ + + /* Callback to rewind the filter when pulseaudio requests it. Called from + * I/O thread context. May be NULL */ + void (*rewind_filter)(pa_sink *s, size_t amount); + + /* Callback to process a chunk of data by the filter. Called from I/O thread + * context. May be NULL */ + void (*process_chunk)(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata); + + /* Callback to communicate the max_rewind value to the filter. Called from + * I/O thread context whenever the max_rewind value changes. May be NULL */ + void (*set_filter_max_rewind)(pa_sink_input *i, size_t amount); + + /* Callback to retrieve additional latency caused by the filter. Called from + * I/O thread context. May be NULL */ + pa_usec_t (*get_extra_latency)(pa_sink *s); + + /* If defined, this function is called from the sink-input pop() callback + * to retrieve the current number of history frames to include in the next + * chunk. Called from I/O thread. */ + size_t (*get_current_overlap)(pa_sink_input *i); + + /* If set and dest is valid, this function is called in the moving() callback + * to change the description of sink and sink_input. Called from main context. + * May be NULL */ + void (*set_description)(pa_sink_input *i, pa_sink *dest); + + /* If set, this function will be called after update_filter_parameters() to + * inform the filter of the block sizes that will be used. These may differ + * from the sizes set in update_filter_parameters() if the function tries to + * set an invalid combination of block sizes. Called from I/O thread. */ + void (*update_block_sizes)(size_t fixed_block_size, size_t fixed_input_block_size, size_t overlap_frames, void *userdata); + + /* If set, this function is called in I/O thread context when an update of the + * filter parameters is requested. May be NULL. The function must replace + * the currently used parameter structure by the new structure in parameters + * and return a pointer to the old structure so that it can be freed in the + * main thread using free_filter_parameters(). If the old structure can be + * re-used, the function may return NULL. update_filter_parameters() may + * also modify the block sizes. */ + void *(*update_filter_parameters)(void *parameters, void *userdata); + + /* Frees a parameter structure. May only be NULL, if update_filter_parameters() + * is also NULL or if update_filter_parameters() always returns NULL. Called + * from main thread. */ + void (*free_filter_parameters)(void *parameters); +} pa_vsink; + struct pa_sink { pa_msgobject parent; @@ -88,7 +172,8 @@ struct pa_sink { pa_idxset *inputs; unsigned n_corked; pa_source *monitor_source; - pa_sink_input *input_to_master; /* non-NULL only for filter sinks */ + pa_vsink *vsink; /* non-NULL only for filter sinks */ + pa_vsource *uplink_of; /* non-NULL only for uplink sinks */ pa_volume_t base_volume; /* shall be constant */ unsigned n_volume_steps; /* shall be constant */ diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 2e2b7a274..f4537d355 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -1304,12 +1304,13 @@ bool pa_source_output_may_move(pa_source_output *o) { return true; } -static bool find_filter_source_output(pa_source_output *target, pa_source *s) { +bool pa_source_output_is_filter_loop(pa_source_output *target, pa_source *s) { unsigned PA_UNUSED i = 0; - while (s && s->output_from_master) { - if (s->output_from_master == target) + + while (s && (s->vsource && s->vsource->output_from_master)) { + if (s->vsource->output_from_master == target) return true; - s = s->output_from_master->source; + s = s->vsource->output_from_master->source; pa_assert(i++ < 100); } return false; @@ -1321,8 +1322,8 @@ static bool is_filter_source_moving(pa_source_output *o) { if (!source) return false; - while (source->output_from_master) { - source = source->output_from_master->source; + while (source->vsource && source->vsource->output_from_master) { + source = source->vsource->output_from_master->source; if (!source) return true; @@ -1347,7 +1348,7 @@ bool pa_source_output_may_move_to(pa_source_output *o, pa_source *dest) { return false; /* Make sure we're not creating a filter source cycle */ - if (find_filter_source_output(o, dest)) { + if (pa_source_output_is_filter_loop(o, dest)) { pa_log_debug("Can't connect output to %s, as that would create a cycle.", dest->name); return false; } @@ -1660,10 +1661,28 @@ void pa_source_output_fail_move(pa_source_output *o) { if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_STOP) return; - /* Can we move the source output to the default source? */ - if (o->core->rescue_streams && pa_source_output_may_move_to(o, o->core->default_source)) { - if (pa_source_output_finish_move(o, o->core->default_source, false) >= 0) - return; + /* Try to rescue stream if configured */ + if (o->core->rescue_streams) { + + /* Can we move the source output to the default source? */ + if (pa_source_output_may_move_to(o, o->core->default_source)) { + if (pa_source_output_finish_move(o, o->core->default_source, false) >= 0) + return; + } + + /* If this is a filter stream and the default source is set to a filter source within + * the same filter chain, we would create a loop and therefore have to find another + * source to move to. */ + if (o->destination_source && pa_source_output_is_filter_loop(o, o->core->default_source)) { + pa_source *best; + + best = pa_core_find_best_source(o->core, true); + + if (best && pa_source_output_may_move_to(o, best)) { + if (pa_source_output_finish_move(o, best, false) >= 0) + return; + } + } } if (o->moving) diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index 2bf56820b..0515a080e 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -350,6 +350,8 @@ int pa_source_output_start_move(pa_source_output *o); int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save); void pa_source_output_fail_move(pa_source_output *o); +bool pa_source_output_is_filter_loop(pa_source_output *target, pa_source *s); + pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o); /* To be used exclusively by the source driver thread */ diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 99d8dde6e..28c7819fa 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -273,7 +273,7 @@ pa_source* pa_source_new( s->outputs = pa_idxset_new(NULL, NULL); s->n_corked = 0; s->monitor_of = NULL; - s->output_from_master = NULL; + s->vsource = NULL; s->reference_volume = s->real_volume = data->volume; pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); @@ -1235,10 +1235,10 @@ pa_source *pa_source_get_master(pa_source *s) { pa_source_assert_ref(s); while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { - if (PA_UNLIKELY(!s->output_from_master)) + if (PA_UNLIKELY(!s->vsource || (s->vsource && !s->vsource->output_from_master))) return NULL; - s = s->output_from_master->source; + s = s->vsource->output_from_master->source; } return s; @@ -1248,7 +1248,7 @@ pa_source *pa_source_get_master(pa_source *s) { bool pa_source_is_filter(pa_source *s) { pa_source_assert_ref(s); - return (s->output_from_master != NULL); + return (s->vsource->output_from_master != NULL); } /* Called from main context */ @@ -2019,7 +2019,7 @@ unsigned pa_source_used_by(pa_source *s) { } /* Called from main thread */ -unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) { +unsigned pa_source_check_suspend(pa_source *s, pa_sink_input *ignore_input, pa_source_output *ignore_output) { unsigned ret; pa_source_output *o; uint32_t idx; @@ -2033,7 +2033,7 @@ unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) { ret = 0; PA_IDXSET_FOREACH(o, s->outputs, idx) { - if (o == ignore) + if (o == ignore_output) continue; /* We do not assert here. It is perfectly valid for a source output to @@ -2053,6 +2053,9 @@ unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) { ret ++; } + if (s->vsource && s->vsource->uplink_sink) + ret += pa_sink_check_suspend(s->vsource->uplink_sink, ignore_input, ignore_output); + return ret; } @@ -2400,6 +2403,16 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency)) result = o->thread_info.requested_source_latency; + if (s->vsource && s->vsource->uplink_sink) { + pa_usec_t uplink_sink_latency; + + uplink_sink_latency = pa_sink_get_requested_latency_within_thread(s->vsource->uplink_sink); + + if (uplink_sink_latency != (pa_usec_t) -1 && + (result == (pa_usec_t) -1 || result > uplink_sink_latency)) + result = uplink_sink_latency; + } + if (result != (pa_usec_t) -1) result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); @@ -3033,9 +3046,32 @@ void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_sour if (!o->source) continue; - /* Don't move source-outputs which connect sources to filter sources */ - if (o->destination_source) + /* If this is a filter stream and the default source is set to a filter source within + * the same filter chain, we would create a loop and therefore have to find another + * source to move to. */ + if (o->destination_source && pa_source_output_is_filter_loop(o, core->default_source)) { + pa_source *best; + + /* If the default source changed to our filter chain, lets make the current + * master the preferred source. */ + if (default_source_changed) { + pa_xfree(o->preferred_source); + o->preferred_source = pa_xstrdup(o->source->name); + + continue; + } + + best = pa_core_find_best_source(core, true); + + if (!best || !pa_source_output_may_move_to(o, best)) + continue; + + pa_log_info("Moving source output %u \"%s\" to the default source would create a filter loop, moving to %s instead.", + o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), best->name); + + pa_source_output_move_to(o, best, false); continue; + } /* If default_source_changed is false, the old source became unavailable, so all streams must be moved. */ if (pa_safe_streq(old_source->name, o->preferred_source) && default_source_changed) diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index aa71ee829..4ec0b327e 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -57,6 +57,79 @@ typedef void(*pa_source_cb_t)(pa_source *s); typedef int (*pa_source_get_mute_cb_t)(pa_source *s, bool *mute); +/* Virtual source structure */ +typedef struct pa_vsource { + pa_msgobject parent; /* Message object */ + pa_core *core; /* Pointer to core */ + pa_source *source; /* A pointer to the virtual source */ + pa_source_output *output_from_master; /* source output from the master source */ + pa_memblockq *memblockq; /* Memblockq of the virtual source, may be NULL */ + + bool auto_desc; /* Automatically adapt description on move */ + bool source_moving; /* Set when master source changes to preserve volume */ + const char *desc_head; /* Leading part of description string used for the + * source and source input when auto_desc is true */ + const char *source_type; /* Name for the type of source, used as suffix for + * the source name if the name is derived from the + * master source. */ + bool autoloaded; /* True if the source was not loaded manually */ + size_t max_chunk_size; /* Maximum chunk size in bytes that the filter will + * accept, set to pa_mempool_block_size_max() by default */ + size_t fixed_block_size; /* Block size in frames for fixed block size filters, + * 0 if block size is controlled by pulseaudio. */ + size_t fixed_input_block_size; /* Input block size in frames. If not 0, input data for + * process_chunk() will always have the same size. + * If not enough new data is available, the remaining + * samples will be filled with history. */ + size_t overlap_frames; /* Some filters require old input samples in addtion to + * the current data. The variable contains the number of + * previous frames that will be passed to process_chunk(). + * The actual number of history frames may be variable if + * the filter defines the get_current_overlap() function. + * In this case, overlap_frames contains the maximum + * number of history frames. */ + pa_usec_t max_latency; /* Maximum latency allowed for the source, 0 if unused */ + pa_sink *uplink_sink; /* Uplink sink if present, otherwise NULL */ + + /* Callback to process a chunk of data by the filter. Called from I/O thread + * context. May be NULL */ + void (*process_chunk)(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata); + + /* Callback to retrieve additional latency caused by the filter. Called from + * I/O thread context. May be NULL */ + pa_usec_t (*get_extra_latency)(pa_source *s); + + /* If defined, this function is called from the source-output push() callback + * to retrieve the current number of history frames to include in the next + * chunk. Called from I/O thread. */ + size_t (*get_current_overlap)(pa_source_output *o); + + /* If set and dest is valid, this function is called in the moving() callback + * to change the description of source and source-output. Called from main context. + * May be NULL */ + void (*set_description)(pa_source_output *o, pa_source *dest); + + /* If set, this function will be called after update_filter_parameters() to + * inform the filter of the block sizes that will be used. These may differ + * from the sizes set in update_filter_parameters() if the function tries to + * set an invalid combination of block sizes. Called from I/O thread. */ + void (*update_block_sizes)(size_t fixed_block_size, size_t fixed_input_block_size, size_t overlap_frames, void *userdata); + + /* If set, this function is called in I/O thread context when an update of the + * filter parameters is requested. May be NULL. The function must replace + * the currently used parameter structure by the new structure in parameters + * and return a pointer to the old structure so that it can be freed in the + * main thread using free_filter_parameters(). If the old structure can be + * re-used, the function may return NULL. update_filter_parameters() may + * also modify the block sizes. */ + void *(*update_filter_parameters)(void *parameters, void *userdata); + + /* Frees a parameter structure. May only be NULL, if update_filter_parameters() + * is also NULL or if update_filter_parameters() always returns NULL. Called + * from main thread. */ + void (*free_filter_parameters)(void *parameters); +} pa_vsource; + struct pa_source { pa_msgobject parent; @@ -89,7 +162,7 @@ struct pa_source { pa_idxset *outputs; unsigned n_corked; pa_sink *monitor_of; /* may be NULL */ - pa_source_output *output_from_master; /* non-NULL only for filter sources */ + pa_vsource *vsource; /* non-NULL only for filter sources */ pa_volume_t base_volume; /* shall be constant */ unsigned n_volume_steps; /* shall be constant */ @@ -426,7 +499,7 @@ unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that ar /* Returns how many streams are active that don't allow suspensions. If * "ignore" is non-NULL, that stream is not included in the count. */ -unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore); +unsigned pa_source_check_suspend(pa_source *s, pa_sink_input *ignore_input, pa_source_output *ignore_output); const char *pa_source_state_to_string(pa_source_state_t state); diff --git a/src/pulsecore/typedefs.h b/src/pulsecore/typedefs.h index 3652f8f76..a80117c8d 100644 --- a/src/pulsecore/typedefs.h +++ b/src/pulsecore/typedefs.h @@ -32,6 +32,7 @@ typedef struct pa_sink_input pa_sink_input; typedef struct pa_source pa_source; typedef struct pa_source_volume_change pa_source_volume_change; typedef struct pa_source_output pa_source_output; +typedef struct pa_vsource pa_vsource; #endif