From d18756fda99211f80653d7c65106d47b4f975879 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 15:54:26 +0100 Subject: [PATCH 01/14] sink: Introduce structure needed to consolidate virtual sink code This patch introduces the vsink structure which holds all data needed by virtual sinks. This is in preparation of the consolidation of the virtual sink code. The input_to_master field of the sink will be moved to the vsink structure after the consolidation. Until all virtual sinks are converted, sink->input_to_master and sink->vsink->input_to_master must both be supported. --- src/modules/module-filter-apply.c | 10 +++- src/pulsecore/sink-input.c | 25 ++++++--- src/pulsecore/sink.c | 15 ++++-- src/pulsecore/sink.h | 85 +++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 11 deletions(-) diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c index 1c1278218..bd4a9d176 100644 --- a/src/modules/module-filter-apply.c +++ b/src/modules/module-filter-apply.c @@ -293,7 +293,10 @@ 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; + if (si->sink->vsink) + filter->sink_master = si->sink->vsink->input_to_master->sink; + else + filter->sink_master = si->sink->input_to_master->sink; } else { filter->sink_master = si->sink; } @@ -461,7 +464,10 @@ 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); + if (sink->vsink) + fltr = filter_new(name, parameters, sink->vsink->input_to_master->sink, NULL); + else + fltr = filter_new(name, parameters, sink->input_to_master->sink, NULL); fltr->module_index = m->index; fltr->sink = sink; diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 4380087ca..c780d7b37 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1784,10 +1784,20 @@ bool pa_sink_input_may_move(pa_sink_input *i) { static bool find_filter_sink_input(pa_sink_input *target, pa_sink *s) { unsigned PA_UNUSED i = 0; - while (s && s->input_to_master) { - if (s->input_to_master == target) - return true; - s = s->input_to_master->sink; + + /* During consolidation, we have to support s->input_to_master and + * s->vsink->input_to_master. The first will disappear after all + * virtual sinks use the new code. */ + while (s && (s->input_to_master || (s->vsink && s->vsink->input_to_master))) { + if (s->vsink) { + if (s->vsink->input_to_master == target) + return true; + s = s->vsink->input_to_master->sink; + } else { + if (s->input_to_master == target) + return true; + s = s->input_to_master->sink; + } pa_assert(i++ < 100); } return false; @@ -1799,8 +1809,11 @@ 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->input_to_master || (sink->vsink && sink->vsink->input_to_master)) { + if (sink->vsink) + sink = sink->vsink->input_to_master->sink; + else + sink = sink->input_to_master->sink; if (!sink) return true; diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 0f0dc56fc..e87a16a04 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -287,6 +287,7 @@ 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->reference_volume = s->real_volume = data->volume; pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); @@ -1692,11 +1693,19 @@ bool pa_sink_has_filter_attached(pa_sink *s) { pa_sink *pa_sink_get_master(pa_sink *s) { pa_sink_assert_ref(s); + /* During consolidation, we have to support s->input_to_master and + * s->vsink->input_to_master. The first will disappear after all + * virtual sinks use the new code. */ while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { - if (PA_UNLIKELY(!s->input_to_master)) + if (PA_UNLIKELY(s->vsink && !s->vsink->input_to_master)) + return NULL; + if (PA_UNLIKELY(!s->vsink && !s->input_to_master)) return NULL; - s = s->input_to_master->sink; + if (s->input_to_master) + s = s->input_to_master->sink; + else + s = s->vsink->input_to_master->sink; } return s; @@ -1706,7 +1715,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->input_to_master != NULL)); } /* Called from main context */ diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 383edacb5..f4db8158d 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,6 +172,7 @@ struct pa_sink { pa_idxset *inputs; unsigned n_corked; pa_source *monitor_source; + pa_vsink *vsink; /* non-NULL only for filter sinks */ pa_sink_input *input_to_master; /* non-NULL only for filter sinks */ pa_volume_t base_volume; /* shall be constant */ From 1edc7eb655644e58eb1aab3ff4a493caaa56a0bf Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 15:56:58 +0100 Subject: [PATCH 02/14] virtual sink: Factor out common code This patch moves the code for the virtual sink callbacks and initialization to a separate file. The code is re-factored, extended and built as library, so that it can be used by other virtual sinks as well. The suspend-virtual-on-master-suspend fix for the ladspa sink (!68) was incorporated into the common code as well as the sink part of !78 which fixes a crash with stacked virtual sinks. !68 has a bug which leaves a virtual sink unavailable when the master sink disappears. This bug is also fixed. Additionally, fixed block size filters, fixed window size filters and updating filter parameters are supported by the library. Fixed window size filters always deliver the same number of frames to the filter, padding new data with history frames if necessary. Rewinding can be disabled or or limited to the number of frames that the virtual sink supports. Thanks to Alexander E. Patrakov for pointing out how to rewind fixed block size filters. The library implements following command line arguments if they are enabled in valid_modargs: sink_name, sink_properties, sink_input_properties, force_flat_volume, remix, resample_method and autoloaded. --- src/modules/meson.build | 13 +- src/modules/module-virtual-sink.c | 548 +---------- src/modules/virtual-sink-common.c | 1404 +++++++++++++++++++++++++++++ src/modules/virtual-sink-common.h | 77 ++ src/pulsecore/memblockq.c | 12 + src/pulsecore/memblockq.h | 5 + 6 files changed, 1532 insertions(+), 527 deletions(-) create mode 100644 src/modules/virtual-sink-common.c create mode 100644 src/modules/virtual-sink-common.h diff --git a/src/modules/meson.build b/src/modules/meson.build index 3636ce0de..019064e98 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -2,6 +2,17 @@ if host_machine.system() != 'windows' subdir('rtp') endif +libvirtual_sink = shared_library('virtual_sink', + 'virtual-sink-common.c', + 'virtual-sink-common.h', + c_args : [pa_c_args, server_c_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep], + install_rpath : privlibdir, + install : true, + install_dir : modlibexecdir +) + # module name, sources, [headers, extra flags, extra deps, extra libs] all_modules = [ [ 'module-allow-passthrough', 'module-allow-passthrough.c' ], @@ -55,7 +66,7 @@ all_modules = [ [ 'module-tunnel-sink-new', ['module-tunnel-sink-new.c', 'restart-module.c'] ], [ 'module-tunnel-source', ['module-tunnel.c', 'restart-module.c'], [], [], [x11_dep] ], [ 'module-tunnel-source-new', ['module-tunnel-source-new.c', 'restart-module.c'] ], - [ 'module-virtual-sink', 'module-virtual-sink.c' ], + [ 'module-virtual-sink', 'module-virtual-sink.c', [], [], [], libvirtual_sink ], [ 'module-virtual-source', 'module-virtual-source.c' ], [ 'module-volume-restore', 'module-volume-restore.c' ], ] diff --git a/src/modules/module-virtual-sink.c b/src/modules/module-virtual-sink.c index 5001ce45d..add0695f9 100644 --- a/src/modules/module-virtual-sink.c +++ b/src/modules/module-virtual-sink.c @@ -22,15 +22,15 @@ #include #endif +#include + #include #include #include #include -#include #include #include -#include #include #include #include @@ -46,25 +46,17 @@ PA_MODULE_USAGE( "master= " "rate= " "channels= " + "format= " "channel_map= " "use_volume_sharing= " "force_flat_volume= " )); -#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) - struct userdata { pa_module *module; - /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ - /* bool autoloaded; */ + pa_vsink *vsink; - pa_sink *sink; - pa_sink_input *sink_input; - - pa_memblockq *memblockq; - - bool auto_desc; unsigned channels; }; @@ -74,6 +66,7 @@ static const char* const valid_modargs[] = { "master", "rate", "channels", + "format", "channel_map", "use_volume_sharing", "force_flat_volume", @@ -81,397 +74,17 @@ static const char* const valid_modargs[] = { }; /* Called from I/O thread context */ -static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SINK(o)->userdata; - - switch (code) { - - case PA_SINK_MESSAGE_GET_LATENCY: - - /* The sink is _put() before the sink input is, so let's - * make sure we don't access it in that time. Also, the - * sink input is first shut down, the sink second. */ - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { - *((int64_t*) data) = 0; - return 0; - } - - *((int64_t*) data) = - - /* Get the latency of the master sink */ - pa_sink_get_latency_within_thread(u->sink_input->sink, true) + - - /* Add the latency internal to our sink input on top */ - pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); - - /* Add resampler latency */ - *((int64_t*) data) += pa_resampler_get_delay_usec(u->sink_input->thread_info.resampler); - - return 0; - } - - return pa_sink_process_msg(o, code, data, offset, chunk); -} - -/* Called from main context */ -static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) { +static void filter_process_chunk(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata) { struct userdata *u; + unsigned buffer_size; - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); + pa_assert_se(u = userdata); + pa_assert(in_count == out_count); - if (!PA_SINK_IS_LINKED(state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->state)) - return 0; - - pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); - return 0; -} - -/* Called from the IO thread. */ -static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { - struct userdata *u; - - pa_assert(s); - pa_assert_se(u = s->userdata); - - /* When set to running or idle for the first time, request a rewind - * of the master sink to make sure we are heard immediately */ - if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) { - pa_log_debug("Requesting rewind due to state change."); - pa_sink_input_request_rewind(u->sink_input, 0, false, true, true); - } - - return 0; -} - -/* Called from I/O thread context */ -static void sink_request_rewind_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) - return; - - /* Just hand this one over to the master sink */ - pa_sink_input_request_rewind(u->sink_input, - s->thread_info.rewind_nbytes + - pa_memblockq_get_length(u->memblockq), true, false, false); -} - -/* Called from I/O thread context */ -static void sink_update_requested_latency_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) - return; - - /* Just hand this one over to the master sink */ - pa_sink_input_set_requested_latency_within_thread( - u->sink_input, - pa_sink_get_requested_latency_within_thread(s)); -} - -/* Called from main context */ -static void sink_set_volume_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(s->state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->state)) - return; - - pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, true); -} - -/* Called from main context */ -static void sink_set_mute_cb(pa_sink *s) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(s->state) || - !PA_SINK_INPUT_IS_LINKED(u->sink_input->state)) - return; - - pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); -} - -/* Called from I/O thread context */ -static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { - struct userdata *u; - float *src, *dst; - size_t fs; - unsigned n, c; - pa_memchunk tchunk; - pa_usec_t current_latency PA_GCC_UNUSED; - - pa_sink_input_assert_ref(i); - pa_assert(chunk); - pa_assert_se(u = i->userdata); - - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return -1; - - /* Hmm, process any rewind request that might be queued up */ - pa_sink_process_rewind(u->sink, 0); - - /* (1) IF YOU NEED A FIXED BLOCK SIZE USE - * pa_memblockq_peek_fixed_size() HERE INSTEAD. NOTE THAT FILTERS - * WHICH CAN DEAL WITH DYNAMIC BLOCK SIZES ARE HIGHLY - * PREFERRED. */ - while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { - pa_memchunk nchunk; - - pa_sink_render(u->sink, nbytes, &nchunk); - pa_memblockq_push(u->memblockq, &nchunk); - pa_memblock_unref(nchunk.memblock); - } - - /* (2) IF YOU NEED A FIXED BLOCK SIZE, THIS NEXT LINE IS NOT - * NECESSARY */ - tchunk.length = PA_MIN(nbytes, tchunk.length); - pa_assert(tchunk.length > 0); - - fs = pa_frame_size(&i->sample_spec); - n = (unsigned) (tchunk.length / fs); - - pa_assert(n > 0); - - chunk->index = 0; - chunk->length = n*fs; - chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); - - pa_memblockq_drop(u->memblockq, chunk->length); - - src = pa_memblock_acquire_chunk(&tchunk); - dst = pa_memblock_acquire(chunk->memblock); - - /* (3) PUT YOUR CODE HERE TO DO SOMETHING WITH THE DATA */ + buffer_size = pa_frame_size(&u->vsink->sink->sample_spec) * in_count; /* As an example, copy input to output */ - for (c = 0; c < u->channels; c++) { - pa_sample_clamp(PA_SAMPLE_FLOAT32NE, - dst+c, u->channels * sizeof(float), - src+c, u->channels * sizeof(float), - n); - } - - pa_memblock_release(tchunk.memblock); - pa_memblock_release(chunk->memblock); - - pa_memblock_unref(tchunk.memblock); - - /* (4) IF YOU NEED THE LATENCY FOR SOMETHING ACQUIRE IT LIKE THIS: */ - current_latency = - /* Get the latency of the master sink */ - pa_sink_get_latency_within_thread(i->sink, false) + - - /* Add the latency internal to our sink input on top */ - pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); - - return 0; -} - -/* Called from I/O thread context */ -static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { - struct userdata *u; - size_t amount = 0; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* If the sink is not yet linked, there is nothing to rewind */ - if (!PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - - if (u->sink->thread_info.rewind_nbytes > 0) { - size_t max_rewrite; - - max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq); - amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); - u->sink->thread_info.rewind_nbytes = 0; - - if (amount > 0) { - pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true); - - /* (5) PUT YOUR CODE HERE TO RESET YOUR FILTER */ - } - } - - pa_sink_process_rewind(u->sink, amount); - pa_memblockq_rewind(u->memblockq, nbytes); -} - -/* Called from I/O thread context */ -static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* FIXME: Too small max_rewind: - * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_memblockq_set_maxrewind(u->memblockq, nbytes); - pa_sink_set_max_rewind_within_thread(u->sink, nbytes); -} - -/* Called from I/O thread context */ -static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* (6) IF YOU NEED A FIXED BLOCK SIZE ROUND nbytes UP TO MULTIPLES - * OF IT HERE. THE PA_ROUND_UP MACRO IS USEFUL FOR THAT. */ - - pa_sink_set_max_request_within_thread(u->sink, nbytes); -} - -/* Called from I/O thread context */ -static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); -} - -/* Called from I/O thread context */ -static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* (7) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE - * BLOCK MINUS ONE SAMPLE HERE. pa_usec_to_bytes_round_up() IS - * USEFUL FOR THAT. */ - - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); -} - -/* Called from I/O thread context */ -static void sink_input_detach_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - if (PA_SINK_IS_LINKED(u->sink->thread_info.state)) - pa_sink_detach_within_thread(u->sink); - - pa_sink_set_rtpoll(u->sink, NULL); -} - -/* Called from I/O thread context */ -static void sink_input_attach_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); - - /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE - * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */ - pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); - - /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND - * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT - * HERE. SEE (6) */ - pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); - - /* FIXME: Too small max_rewind: - * https://bugs.freedesktop.org/show_bug.cgi?id=53709 */ - pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); - - if (PA_SINK_IS_LINKED(u->sink->thread_info.state)) - pa_sink_attach_within_thread(u->sink); -} - -/* Called from main context */ -static void sink_input_kill_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* The order here matters! We first kill the sink so that streams - * can properly be moved away while the sink input is still connected - * to the master. */ - pa_sink_input_cork(u->sink_input, true); - pa_sink_unlink(u->sink); - pa_sink_input_unlink(u->sink_input); - - pa_sink_input_unref(u->sink_input); - u->sink_input = NULL; - - pa_sink_unref(u->sink); - u->sink = NULL; - - pa_module_unload_request(u->module, true); -} - -/* Called from main context */ -static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - if (dest) { - pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); - pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); - } else - pa_sink_set_asyncmsgq(u->sink, NULL); - - if (u->auto_desc && dest) { - const char *z; - pa_proplist *pl; - - pl = pa_proplist_new(); - z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s", - pa_proplist_gets(u->sink->proplist, "device.vsink.name"), z ? z : dest->name); - - pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); - pa_proplist_free(pl); - } -} - -/* Called from main context */ -static void sink_input_volume_changed_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - pa_sink_volume_changed(u->sink, &i->volume); -} - -/* Called from main context */ -static void sink_input_mute_changed_cb(pa_sink_input *i) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - pa_sink_mute_changed(u->sink, i->muted); + memcpy(dst, src, buffer_size); } int pa__init(pa_module*m) { @@ -480,11 +93,7 @@ int pa__init(pa_module*m) { pa_channel_map map; pa_modargs *ma; pa_sink *master=NULL; - pa_sink_input_new_data sink_input_data; - pa_sink_new_data sink_data; bool use_volume_sharing = true; - bool force_flat_volume = false; - pa_memchunk silence; pa_assert(m); @@ -501,7 +110,6 @@ int pa__init(pa_module*m) { pa_assert(master); ss = master->sample_spec; - ss.format = PA_SAMPLE_FLOAT32; map = master->channel_map; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { pa_log("Invalid sample format specification or channel map"); @@ -513,118 +121,23 @@ int pa__init(pa_module*m) { goto fail; } - if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { - pa_log("force_flat_volume= expects a boolean argument"); - goto fail; - } - - if (use_volume_sharing && force_flat_volume) { - pa_log("Flat volume can't be forced when using volume sharing."); - goto fail; - } - u = pa_xnew0(struct userdata, 1); u->module = m; m->userdata = u; u->channels = ss.channels; - /* Create sink */ - pa_sink_new_data_init(&sink_data); - sink_data.driver = __FILE__; - sink_data.module = m; - if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) - sink_data.name = pa_sprintf_malloc("%s.vsink", master->name); - pa_sink_new_data_set_sample_spec(&sink_data, &ss); - pa_sink_new_data_set_channel_map(&sink_data, &map); - pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); - pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - pa_proplist_sets(sink_data.proplist, "device.vsink.name", sink_data.name); - - if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log("Invalid properties"); - pa_sink_new_data_done(&sink_data); - goto fail; - } - - if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { - const char *z; - - z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s", sink_data.name, z ? z : master->name); - } - - u->sink = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)) - | (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0)); - pa_sink_new_data_done(&sink_data); - - if (!u->sink) { - pa_log("Failed to create sink."); - goto fail; - } - - u->sink->parent.process_msg = sink_process_msg_cb; - u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb; - u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; - u->sink->update_requested_latency = sink_update_requested_latency_cb; - u->sink->request_rewind = sink_request_rewind_cb; - pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb); - if (!use_volume_sharing) { - pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); - pa_sink_enable_decibel_volume(u->sink, true); - } - /* Normally this flag would be enabled automatically be we can force it. */ - if (force_flat_volume) - u->sink->flags |= PA_SINK_FLAT_VOLUME; - u->sink->userdata = u; - - pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); - - /* Create sink input */ - pa_sink_input_new_data_init(&sink_input_data); - sink_input_data.driver = __FILE__; - sink_input_data.module = m; - pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true); - sink_input_data.origin_sink = u->sink; - pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); - pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); - pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); - pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); - sink_input_data.flags |= PA_SINK_INPUT_START_CORKED; - - pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); - pa_sink_input_new_data_done(&sink_input_data); - - if (!u->sink_input) + /* Create virtual sink */ + if (!(u->vsink = pa_virtual_sink_create(master, "vsink", "Virtual Sink", &ss, &map, + &ss, &map, m, u, ma, use_volume_sharing, true, 0))) goto fail; - u->sink_input->pop = sink_input_pop_cb; - u->sink_input->process_rewind = sink_input_process_rewind_cb; - u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; - u->sink_input->update_max_request = sink_input_update_max_request_cb; - u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; - u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; - u->sink_input->kill = sink_input_kill_cb; - u->sink_input->attach = sink_input_attach_cb; - u->sink_input->detach = sink_input_detach_cb; - u->sink_input->moving = sink_input_moving_cb; - u->sink_input->volume_changed = use_volume_sharing ? NULL : sink_input_volume_changed_cb; - u->sink_input->mute_changed = sink_input_mute_changed_cb; - u->sink_input->userdata = u; + /* Set callback for virtual sink */ + u->vsink->process_chunk = filter_process_chunk; - u->sink->input_to_master = u->sink_input; + /* INITIALIZE YOUR FILTER HERE */ - pa_sink_input_get_silence(u->sink_input, &silence); - u->memblockq = pa_memblockq_new("module-virtual-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence); - pa_memblock_unref(silence.memblock); - - /* (9) INITIALIZE ANYTHING ELSE YOU NEED HERE */ - - /* The order here is important. The input must be put first, - * otherwise streams might attach to the sink before the sink - * input is attached to the master. */ - pa_sink_input_put(u->sink_input); - pa_sink_put(u->sink); - pa_sink_input_cork(u->sink_input, false); + if (pa_virtual_sink_activate(u->vsink) < 0) + goto fail; pa_modargs_free(ma); @@ -645,7 +158,7 @@ int pa__get_n_used(pa_module *m) { pa_assert(m); pa_assert_se(u = m->userdata); - return pa_sink_linked_by(u->sink); + return pa_sink_linked_by(u->vsink->sink); } void pa__done(pa_module*m) { @@ -656,25 +169,8 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - /* See comments in sink_input_kill_cb() above regarding - * destruction order! */ - - if (u->sink_input) - pa_sink_input_cork(u->sink_input, true); - - if (u->sink) - pa_sink_unlink(u->sink); - - if (u->sink_input) { - pa_sink_input_unlink(u->sink_input); - pa_sink_input_unref(u->sink_input); - } - - if (u->sink) - pa_sink_unref(u->sink); - - if (u->memblockq) - pa_memblockq_free(u->memblockq); + if (u->vsink) + pa_virtual_sink_destroy(u->vsink); pa_xfree(u); } diff --git a/src/modules/virtual-sink-common.c b/src/modules/virtual-sink-common.c new file mode 100644 index 000000000..a80673b8c --- /dev/null +++ b/src/modules/virtual-sink-common.c @@ -0,0 +1,1404 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +PA_DEFINE_PRIVATE_CLASS(pa_vsink, pa_msgobject); +#define PA_VSINK(o) (pa_vsink_cast(o)) + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + +#define NO_REWIND_MAX_LATENCY (50 * PA_USEC_PER_MSEC) +#define MIN_BLOCK_SIZE 16 +#define LATENCY_MARGIN (5 * PA_USEC_PER_MSEC) + +enum { + SINK_MESSAGE_UPDATE_PARAMETERS = PA_SINK_MESSAGE_MAX +}; + +enum { + VSINK_MESSAGE_FREE_PARAMETERS, + VSINK_MESSAGE_INPUT_ATTACHED +}; + +/* Helper functions */ + +static inline pa_sink_input* get_input_from_sink(pa_sink *s) { + + if (!s->vsink || !s->vsink->input_to_master) + return NULL; + return s->vsink->input_to_master; +} + +static int check_block_sizes(size_t fixed_block_frames, size_t fixed_input_block_frames, size_t overlap_frames, pa_vsink *vs) { + size_t max_block_frames; + size_t max_frame_size; + + max_frame_size = PA_MAX(pa_frame_size(&vs->sink->sample_spec), pa_frame_size(&vs->input_to_master->sample_spec)); + + max_block_frames = pa_mempool_block_size_max(vs->sink->core->mempool); + max_block_frames = max_block_frames / max_frame_size; + + if (fixed_block_frames > max_block_frames || fixed_input_block_frames > max_block_frames || overlap_frames + MIN_BLOCK_SIZE > max_block_frames) { + pa_log_warn("At least one of fixed_block_size, fixed_input_block_size or overlap_frames exceeds maximum."); + return -1; + } + + if (fixed_block_frames > 0 && fixed_block_frames < MIN_BLOCK_SIZE) { + pa_log_warn("fixed_block_size too small."); + return -1; + } + + if (fixed_input_block_frames > 0 && fixed_input_block_frames < MIN_BLOCK_SIZE) { + pa_log_warn("fixed_input_block_size too small."); + return -1; + } + + if (fixed_block_frames + overlap_frames > max_block_frames) { + pa_log_warn("Sum of fixed_block_size and overlap_frames exceeds maximum."); + return -1; + } + + if (fixed_input_block_frames > max_block_frames) { + pa_log_warn("fixed_input_block_size exceeds maximum."); + return -1; + } + + if (fixed_input_block_frames != 0 && fixed_block_frames > fixed_input_block_frames) { + pa_log_warn("fixed_block_size larger than fixed_input_block_size."); + return -1; + } + + return 0; +} + +static size_t get_max_rewind_bytes(pa_vsink *vsink, bool use_master_domain) { + size_t max_rewind_frames, max_rewind_bytes; + + max_rewind_frames = pa_sink_input_get_max_rewind(vsink->input_to_master) / pa_frame_size(&vsink->input_to_master->sample_spec); + + if (vsink->max_rewind < 0) + max_rewind_frames = 0; + else if (vsink->max_rewind > 0) + max_rewind_frames = PA_MIN(max_rewind_frames, (unsigned) vsink->max_rewind); + + if (!use_master_domain) + return max_rewind_frames * pa_frame_size(&vsink->sink->sample_spec); + + /* Convert to master frames */ + max_rewind_frames = max_rewind_frames * vsink->input_to_master->sink->sample_spec.rate / vsink->sink->sample_spec.rate; + + /* Convert to bytes */ + max_rewind_bytes = max_rewind_frames * pa_frame_size(&vsink->input_to_master->sink->sample_spec); + + return max_rewind_bytes; +} + +/* This function is used to ensure that a larger memblockq max_rewind value set + * by update_max_rewind() for fixed block size filters or by a local version of + * update_max_rewind() will not be overwritten. */ +static void set_memblockq_max_rewind(pa_vsink *vsink) { + size_t max_rewind; + + if (!vsink->memblockq) + return; + + max_rewind = PA_MAX(pa_memblockq_get_maxrewind(vsink->memblockq), get_max_rewind_bytes(vsink, false)); + pa_memblockq_set_maxrewind(vsink->memblockq, max_rewind); +} + +static void set_latency_range_within_thread(pa_vsink *vsink) { + pa_usec_t min_latency, max_latency; + pa_sink_input *i = vsink->input_to_master; + pa_sink *s = vsink->sink; + + pa_assert(s); + pa_assert(i); + + min_latency = i->sink->thread_info.min_latency; + max_latency = i->sink->thread_info.max_latency; + + if (s->flags & PA_SINK_DYNAMIC_LATENCY) { + if (vsink->max_latency) + max_latency = PA_MIN(vsink->max_latency, max_latency); + + if (vsink->fixed_block_size) { + pa_usec_t latency; + + latency = pa_bytes_to_usec(vsink->fixed_block_size * pa_frame_size(&s->sample_spec), &s->sample_spec); + min_latency = PA_MAX(min_latency, latency); + } + + max_latency = PA_MAX(max_latency, min_latency); + } + + pa_sink_set_latency_range_within_thread(s, min_latency, max_latency); +} + +/* Sink callbacks */ + +/* Called from I/O thread context */ +int pa_virtual_sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i; + pa_vsink *vsink; + + pa_sink *s = PA_SINK(o); + vsink = s->vsink; + pa_assert(vsink); + i = vsink->input_to_master; + pa_assert(i); + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it in that time. Also, the + * sink input is first shut down, the sink second. */ + if (!PA_SINK_IS_LINKED(s->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) { + *((int64_t*) data) = 0; + return 0; + } + + *((int64_t*) data) = + + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(i->sink, true) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); + + /* Add the latency caused by the local memblockq */ + if (vsink->memblockq) + *((int64_t*) data) += pa_bytes_to_usec(pa_memblockq_get_length(vsink->memblockq), &s->sample_spec); + + /* Add the resampler delay */ + if (vsink->input_to_master->thread_info.resampler) + *((int64_t*) data) += pa_resampler_get_delay_usec(vsink->input_to_master->thread_info.resampler); + + /* Add additional filter latency if required. */ + if (vsink->get_extra_latency) + *((int64_t*) data) += vsink->get_extra_latency(s); + + return 0; + + case SINK_MESSAGE_UPDATE_PARAMETERS: + + /* Rewind the stream. If rewinding is disabled, the filter should handle + * parameter changes without need to rewind the filter. */ + if (vsink->max_rewind >= 0 && PA_SINK_IS_OPENED(s->thread_info.state)) { + pa_log_debug("Requesting rewind due to parameter update."); + pa_sink_request_rewind(s, (size_t) -1); + } + + /* Let the module update the filter parameters. Because the main thread + * is waiting, variables can be accessed freely in the callback. */ + if (vsink->update_filter_parameters) { + void *parameters; + size_t old_block_size, old_input_block_size, old_overlap_frames; + + /* Save old block sizes */ + old_block_size = vsink->fixed_block_size; + old_input_block_size = vsink->fixed_input_block_size; + old_overlap_frames = vsink->overlap_frames; + + parameters = vsink->update_filter_parameters(data, s->userdata); + if (parameters) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(vsink), VSINK_MESSAGE_FREE_PARAMETERS, parameters, 0, NULL, NULL); + + /* Updating the parameters may have changed the block sizes, so check them again. */ + if (check_block_sizes(vsink->fixed_block_size, vsink->fixed_input_block_size, vsink->overlap_frames, vsink) < 0) { + pa_log_warn("Invalid new block sizes, keeping old values."); + vsink->fixed_block_size = old_block_size; + vsink->fixed_input_block_size = old_input_block_size; + vsink->overlap_frames = old_overlap_frames; + } + + /* Inform the filter of the block sizes in use */ + if (vsink->update_block_sizes) + vsink->update_block_sizes(vsink->fixed_block_size, vsink->fixed_input_block_size, vsink->overlap_frames, s->userdata); + + /* If the block sizes changed, max_rewind, tlength and latency range may have changed as well. */ + set_latency_range_within_thread(vsink); + if (i->update_max_rewind) + i->update_max_rewind(i, pa_sink_input_get_max_rewind(i)); + pa_memblockq_set_tlength(vsink->memblockq, vsink->fixed_block_size * pa_frame_size(&vsink->sink->sample_spec)); + } + + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +int pa_virtual_sink_set_state_in_main_thread(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) { + pa_sink_input *i; + + pa_sink_assert_ref(s); + i = get_input_from_sink(s); + pa_assert(i); + + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(i->state)) + return 0; + + pa_sink_input_cork(i, state == PA_SINK_SUSPENDED); + return 0; +} + +/* Called from the IO thread. */ +int pa_virtual_sink_set_state_in_io_thread(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + pa_sink_input *i; + pa_vsink *vsink; + + pa_sink_assert_ref(s); + vsink = s->vsink; + pa_assert(vsink); + i = vsink->input_to_master; + pa_assert(i); + + /* When set to running, request a rewind of the master sink to make sure + * we are heard immediately. Also set max_rewind on sink and master sink */ + if (PA_SINK_IS_RUNNING(new_state) && !PA_SINK_IS_RUNNING(s->thread_info.state)) { + + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, false, true, true); + + set_latency_range_within_thread(vsink); + + pa_sink_set_max_rewind_within_thread(s, get_max_rewind_bytes(vsink, false)); + set_memblockq_max_rewind(vsink); + pa_sink_set_max_rewind_within_thread(i->sink, get_max_rewind_bytes(vsink, true)); + } + + return 0; +} + +/* Called from I/O thread context */ +void pa_virtual_sink_request_rewind(pa_sink *s) { + pa_vsink *vsink; + pa_sink_input *i; + size_t amount, in_fs, out_fs; + + pa_sink_assert_ref(s); + vsink = s->vsink; + pa_assert(vsink); + i = vsink->input_to_master; + pa_assert(i); + + if (!PA_SINK_IS_LINKED(s->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) + return; + + if (vsink->max_rewind < 0) { + pa_sink_input_request_rewind(i, 0, true, false, false); + return; + } + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + + amount = s->thread_info.rewind_nbytes; + if (vsink->memblockq) + amount += pa_memblockq_get_length(vsink->memblockq); + + /* Convert to sink input domain */ + amount = amount * out_fs / in_fs; + + /* Just hand this one over to the master sink */ + pa_sink_input_request_rewind(i, amount, true, false, false); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_update_requested_latency(pa_sink *s) { + pa_vsink *vsink; + pa_sink_input *i; + pa_usec_t latency; + + pa_sink_assert_ref(s); + vsink = s->vsink; + pa_assert(vsink); + i = vsink->input_to_master; + pa_assert(i); + + if (!PA_SINK_IS_LINKED(s->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) + return; + + latency = pa_sink_get_requested_latency_within_thread(s); + if (vsink->max_latency) + latency = PA_MIN(vsink->max_latency, latency); + + /* If we are using fixed blocksize, part of the latency is implemented + * in the virtual sink. Reduce master latency by this amount. Do not set + * the latency too small to avoid high CPU load and underruns. */ + if (vsink->fixed_block_size) { + size_t in_fs; + pa_usec_t fixed_block_latency, min_latency; + + in_fs = pa_frame_size(&s->sample_spec); + fixed_block_latency = pa_bytes_to_usec(vsink->fixed_block_size * in_fs, &s->sample_spec); + min_latency = i->sink->thread_info.min_latency; + if (min_latency < LATENCY_MARGIN) + min_latency += LATENCY_MARGIN; + + if (latency < fixed_block_latency + min_latency) + latency = min_latency; + else + latency = latency - fixed_block_latency; + } + + /* Now hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread(i, latency); +} + +/* Called from main context */ +void pa_virtual_sink_set_volume(pa_sink *s) { + pa_sink_input *i; + pa_cvolume vol; + + pa_sink_assert_ref(s); + i = get_input_from_sink(s); + pa_assert(i); + + if (!PA_SINK_IS_LINKED(s->state) || + !PA_SINK_INPUT_IS_LINKED(i->state)) + return; + + /* Remap the volume, sink and sink input may have different + * channel counts. */ + vol = s->real_volume; + pa_cvolume_remap(&vol, &s->channel_map, &i->channel_map); + pa_sink_input_set_volume(i, &vol, s->save_volume, true); +} + +/* Called from main context */ +void pa_virtual_sink_set_mute(pa_sink *s) { + pa_sink_input *i; + + pa_sink_assert_ref(s); + i = get_input_from_sink(s); + pa_assert(i); + + if (!PA_SINK_IS_LINKED(s->state) || + !PA_SINK_INPUT_IS_LINKED(i->state)) + return; + + pa_sink_input_set_mute(i, s->muted, s->save_muted); +} + +/* Sink iinput callbacks */ + +/* Called from I/O thread context */ +int pa_virtual_sink_input_pop(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + pa_sink *s; + uint8_t *src, *dst; + size_t in_fs, out_fs, in_count; + size_t overlap_frames, max_block_frames; + unsigned n; + pa_memchunk tchunk; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + pa_assert(chunk); + + if (!PA_SINK_IS_LINKED(s->thread_info.state)) + return -1; + + /* Process any rewind request that might be queued up. */ + pa_sink_process_rewind(s, 0); + + if (!vsink->process_chunk || !vsink->memblockq) { + pa_sink_render(s, nbytes, chunk); + return 0; + } + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + max_block_frames = pa_mempool_block_size_max(i->sink->core->mempool) / PA_MAX(in_fs, out_fs); + nbytes = PA_MIN(nbytes, max_block_frames * out_fs); + + /* Get new samples. */ + if (!vsink->fixed_block_size) { + + while (pa_memblockq_peek(vsink->memblockq, &tchunk) < 0) { + pa_memchunk nchunk; + + pa_sink_render(s, nbytes * in_fs / out_fs, &nchunk); + pa_memblockq_push(vsink->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + + tchunk.length = PA_MIN(nbytes * in_fs / out_fs, tchunk.length); + + } else { + size_t bytes_missing; + + /* Make sure that the memblockq contains enough data. */ + while ((bytes_missing = pa_memblockq_get_missing(vsink->memblockq)) != 0) { + pa_memchunk nchunk; + + pa_sink_render(s, bytes_missing, &nchunk); + pa_memblockq_push(vsink->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + + pa_memblockq_peek_fixed_size(vsink->memblockq, vsink->fixed_block_size * in_fs, &tchunk); + } + pa_assert(tchunk.length > 0); + + n = (unsigned) (PA_MIN(tchunk.length, vsink->max_chunk_size) / in_fs); + + pa_assert(n > 0); + + /* Release result chunk, the data will be fetched again later. */ + pa_memblock_unref(tchunk.memblock); + pa_memchunk_reset(&tchunk); + + /* Calculate size of overlap. */ + overlap_frames = vsink->overlap_frames; + if (vsink->get_current_overlap) + overlap_frames = PA_MIN(overlap_frames, vsink->get_current_overlap(i)); + + /* If fixed input block size is used, the overlap_framse value will be ignored. */ + if (vsink->fixed_input_block_size) { + overlap_frames = 0; + if (n > vsink->fixed_input_block_size) + n = vsink->fixed_input_block_size; + else + overlap_frames = vsink->fixed_input_block_size - n; + } + + /* In case of variable block size, it may be possible, that the sum of + * new samples and history data exceeds pa_mempool_block_size_max(). + * Then the number of new samples must be limited. */ + if (n + overlap_frames > max_block_frames) + n = max_block_frames - overlap_frames; + + /* Now rewind the memblockq to get some history data if required. */ + if (overlap_frames) + pa_memblockq_rewind(vsink->memblockq, overlap_frames * in_fs); + + /* Prepare output chunk */ + chunk->index = 0; + chunk->length = n * out_fs; + chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); + + /* Get input data */ + in_count = n + overlap_frames; + pa_memblockq_peek_fixed_size(vsink->memblockq, in_count * in_fs, &tchunk); + pa_memblockq_drop(vsink->memblockq, in_count * in_fs); + + src = pa_memblock_acquire_chunk(&tchunk); + dst = pa_memblock_acquire(chunk->memblock); + + /* Let the filter process the chunk */ + vsink->process_chunk(src, dst, in_count, n, i->userdata); + + /* For fixed block size filters, we may have to drop some of the data + * after a rewind (see pa_virtual_sink_input_process_rewind()). */ + chunk->index += vsink->drop_bytes; + chunk->length -= vsink->drop_bytes; + vsink->drop_bytes = 0; + + pa_memblock_release(tchunk.memblock); + pa_memblock_release(chunk->memblock); + + pa_memblock_unref(tchunk.memblock); + + return 0; +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_process_rewind(pa_sink_input *i, size_t nbytes) { + pa_sink *s; + size_t amount = 0; + size_t in_fs, out_fs; + size_t rewind_frames; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + /* If the sink is not yet linked, there is nothing to rewind */ + if (!PA_SINK_IS_LINKED(s->thread_info.state)) + return; + + /* If the sink input is corked, ignore the rewind request. */ + if (i->thread_info.state == PA_SINK_INPUT_CORKED) + return; + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + rewind_frames = nbytes / out_fs; + + /* For fixed block size filters, rewind the filter by a full number of blocks. + * This means that during the next sink_input_pop() call, the filter will + * process some old data that must be discarded after processing. */ + if (vsink->fixed_block_size) + rewind_frames = PA_ROUND_UP(rewind_frames, vsink->fixed_block_size); + + /* Rewind the filter before changing the write pointer of the memblockq. + * Because the memblockq is placed before the filter, the filter must always + * be rewound by the full amount. */ + if (vsink->rewind_filter && nbytes > 0 ) + vsink->rewind_filter(s, rewind_frames); + + if ((s->thread_info.rewind_nbytes > 0) && (vsink->max_rewind >= 0)) { + size_t max_rewrite; + + max_rewrite = nbytes * in_fs / out_fs; + if (vsink->memblockq) + max_rewrite += pa_memblockq_get_length(vsink->memblockq); + amount = PA_MIN(s->thread_info.rewind_nbytes, max_rewrite); + s->thread_info.rewind_nbytes = 0; + + /* Update write pointer if the data needs to be rewritten. */ + if (vsink->memblockq && amount > 0) + pa_memblockq_seek(vsink->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true); + } + + if (vsink->memblockq) + pa_sink_process_rewind(s, amount); + else + pa_sink_process_rewind(s, nbytes * in_fs / out_fs); + + if (vsink->memblockq) { + pa_memblockq_rewind(vsink->memblockq, rewind_frames * in_fs); + + /* Remember number of bytes to drop during next sink_input_pop(). */ + if (vsink->fixed_block_size) + vsink->drop_bytes = rewind_frames * out_fs - nbytes; + } +} + +/* Called from I/O thread context */ +size_t pa_virtual_sink_input_get_max_rewind_limit(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + size_t ret, rewind_limit; + void *state = NULL; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + ret = (size_t)(-1); + + if (!PA_SINK_IS_OPENED(s->thread_info.state)) + return ret; + + /* Disable rewinding if max_rewind = -1 */ + if (vsink->max_rewind < 0) + return 0; + + /* If a max_rewind value > 0 was specified, limit rewinding to + * the specified number of frames */ + if (vsink->max_rewind > 0) + ret = vsink->max_rewind; + + /* Calculate the number of frames we can rewind in the sink domain. */ + if (ret != (size_t)(-1)) { + /* Convert to master frames */ + ret = ret * vsink->input_to_master->sink->sample_spec.rate / vsink->sink->sample_spec.rate; + + /* Convert to bytes */ + ret *= pa_frame_size(&vsink->input_to_master->sink->sample_spec); + } + + /* Get the limit from the attached sink inputs (in vsink sample spec) */ + rewind_limit = (size_t)(-1); + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { + + if (i->get_max_rewind_limit) { + size_t limit; + + limit = i->get_max_rewind_limit(i); + if (rewind_limit == (size_t)(-1) || rewind_limit > limit) + rewind_limit = limit; + } + } + } + + if (rewind_limit != (size_t)(-1)) { + + /* Convert to frames */ + rewind_limit = rewind_limit / pa_frame_size(&vsink->sink->sample_spec); + + /* Convert to master frames */ + rewind_limit = rewind_limit * vsink->input_to_master->sink->sample_spec.rate / vsink->sink->sample_spec.rate; + + /* Convert to bytes */ + rewind_limit *= pa_frame_size(&vsink->input_to_master->sink->sample_spec); + + /* Use the minimum of the local and sink input limit */ + if (ret != (size_t)(-1)) + ret = PA_MIN(ret, rewind_limit); + else + ret = rewind_limit; + } + + return ret; +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes) { + pa_sink *s; + size_t in_fs, out_fs, max_rewind; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + + max_rewind = nbytes * in_fs / out_fs; + + if (vsink->memblockq) { + size_t add_on_bytes = 0; + + /* For fixed block size filters we have to add one block size to + * max_rewind of the memblockq to ensure we can rewind to a block + * border, even if a full rewind is requested. */ + if (max_rewind > 0) + add_on_bytes = vsink->fixed_block_size * in_fs; + + /* Add the history frames. */ + add_on_bytes += vsink->overlap_frames * in_fs; + + /* For fixed input size filters, simply use vsink->fixed_input_block_size */ + if (vsink->fixed_input_block_size) + add_on_bytes = vsink->fixed_input_block_size * in_fs; + + pa_memblockq_set_maxrewind(vsink->memblockq, max_rewind + add_on_bytes); + } + + pa_sink_set_max_rewind_within_thread(s, max_rewind); + + if (vsink->set_filter_max_rewind) { + + max_rewind = nbytes / out_fs; + if (vsink->fixed_block_size) + max_rewind = PA_ROUND_UP(max_rewind, vsink->fixed_block_size); + + vsink->set_filter_max_rewind(i, max_rewind); + } +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_update_max_request(pa_sink_input *i, size_t nbytes) { + pa_sink *s; + size_t in_fs, out_fs, max_request_frames; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + + max_request_frames = nbytes / out_fs; + + if (vsink->max_request_frames_min) + max_request_frames = PA_MAX(max_request_frames, vsink->max_request_frames_min); + + /* For a fixed block size filter, round up to the nearest multiple + * of the block size. */ + if (vsink->fixed_block_size) + max_request_frames = PA_ROUND_UP(max_request_frames, vsink->fixed_block_size); + + pa_sink_set_max_request_within_thread(s, max_request_frames * in_fs); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_update_sink_latency_range(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + set_latency_range_within_thread(vsink); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_update_sink_fixed_latency(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + pa_usec_t latency; + size_t in_fs; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + in_fs = pa_frame_size(&s->sample_spec); + + /* For filters with fixed block size we have to add the block size minus 1 sample + * to the fixed latency. */ + latency = i->sink->thread_info.fixed_latency; + if (vsink->fixed_block_size && !(s->flags & PA_SINK_DYNAMIC_LATENCY)) + latency += pa_bytes_to_usec((vsink->fixed_block_size - 1) * in_fs, &s->sample_spec); + + pa_sink_set_fixed_latency_within_thread(s, latency); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_detach(pa_sink_input *i) { + pa_sink *s; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + + if (PA_SINK_IS_LINKED(s->thread_info.state)) + pa_sink_detach_within_thread(s); + + pa_sink_set_rtpoll(s, NULL); +} + +/* Called from I/O thread context */ +void pa_virtual_sink_input_attach(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + size_t in_fs, out_fs; + pa_usec_t latency; + size_t max_request_frames; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + out_fs = pa_frame_size(&i->sample_spec); + in_fs = pa_frame_size(&s->sample_spec); + + pa_sink_set_rtpoll(s, i->sink->thread_info.rtpoll); + + set_latency_range_within_thread(vsink); + + /* For filters with fixed block size we have to add the block size minus one + * sample to the fixed latency. */ + latency = i->sink->thread_info.fixed_latency; + if (vsink->fixed_block_size && !(s->flags & PA_SINK_DYNAMIC_LATENCY)) + latency += pa_bytes_to_usec((vsink->fixed_block_size - 1) * in_fs, &s->sample_spec); + + pa_sink_set_fixed_latency_within_thread(s, latency); + + max_request_frames = pa_sink_input_get_max_request(i) / out_fs; + + if (vsink->max_request_frames_min) + max_request_frames = PA_MAX(max_request_frames, vsink->max_request_frames_min); + + /* For filters with fixed block size, round up to the nearest multiple + * of the block size. */ + if (vsink->fixed_block_size) + max_request_frames = PA_ROUND_UP(max_request_frames, vsink->fixed_block_size); + + pa_sink_set_max_request_within_thread(s, max_request_frames * in_fs); + + pa_sink_set_max_rewind_within_thread(s, get_max_rewind_bytes(vsink, false)); + set_memblockq_max_rewind(vsink); + if (PA_SINK_IS_OPENED(s->thread_info.state)) + pa_sink_set_max_rewind_within_thread(i->sink, get_max_rewind_bytes(vsink, true)); + + /* This call is needed to remove the UNAVAILABLE suspend cause after + * a move when the previous master sink disappeared. */ + pa_virtual_sink_send_input_attached_message(vsink); + + if (PA_SINK_IS_LINKED(s->thread_info.state)) + pa_sink_attach_within_thread(s); +} + +/* Called from main context */ +void pa_virtual_sink_input_kill(pa_sink_input *i) { + pa_sink *s; + pa_vsink *vsink; + pa_module *m; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + /* The order here matters! We first kill the sink so that streams + * can properly be moved away while the sink input is still connected + * to the master. It may be possible that the sink input is connected + * to a virtual sink which has lost its master, so do not try to cork + * if the sink has no I/O context. */ + if (i->sink && i->sink->asyncmsgq) + pa_sink_input_cork(i, true); + pa_sink_unlink(s); + pa_sink_input_unlink(i); + + pa_sink_input_unref(i); + + if (vsink->memblockq) + pa_memblockq_free(vsink->memblockq); + + /* Virtual sinks must set the module */ + m = s->module; + pa_assert(m); + pa_sink_unref(s); + + vsink->sink = NULL; + vsink->input_to_master = NULL; + vsink->memblockq = NULL; + + pa_module_unload_request(m, true); +} + +/* Called from main context */ +bool pa_virtual_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { + pa_sink *s; + pa_vsink *vsink; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + if (vsink->autoloaded) + return false; + + return s != dest; +} + +/* Called from main context */ +void pa_virtual_sink_input_moving(pa_sink_input *i, pa_sink *dest) { + pa_sink *s; + pa_vsink *vsink; + uint32_t idx; + pa_sink_input *input; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + vsink = s->vsink; + pa_assert(vsink); + + if (dest) { + pa_sink_set_asyncmsgq(s, dest->asyncmsgq); + pa_sink_update_flags(s, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_MASTER_DEVICE, dest->name); + } else + pa_sink_set_asyncmsgq(s, NULL); + + if (dest && vsink->set_description) + vsink->set_description(i, dest); + + else { + if (vsink->auto_desc && dest) { + const char *z; + pa_proplist *pl; + char *proplist_name; + + pl = pa_proplist_new(); + proplist_name = pa_sprintf_malloc("device.%s.name", vsink->sink_type); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "%s %s on %s", vsink->desc_head, + pa_proplist_gets(s->proplist, proplist_name), z ? z : dest->name); + + pa_sink_update_proplist(s, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + pa_xfree(proplist_name); + } + + if (dest) + pa_proplist_setf(i->proplist, PA_PROP_MEDIA_NAME, "%s Stream from %s", vsink->desc_head, pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)); + } + + /* Propagate asyncmsq change to attached virtual sinks */ + PA_IDXSET_FOREACH(input, s->inputs, idx) { + if (input->origin_sink && input->moving) + input->moving(input, s); + } + + /* Propagate asyncmsq change to virtual sources attached to the monitor */ + if (s->monitor_source) { + pa_source_output *output; + + PA_IDXSET_FOREACH(output, s->monitor_source->outputs, idx) { + if (output->destination_source && output->moving) + output->moving(output, s->monitor_source); + } + } +} + +/* Called from main context */ +void pa_virtual_sink_input_volume_changed(pa_sink_input *i) { + pa_sink *s; + pa_cvolume vol; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + + /* Remap the volume, sink and sink input may have different + * channel counts. */ + vol = i->volume; + pa_cvolume_remap(&vol, &i->channel_map, &s->channel_map); + pa_sink_volume_changed(s, &vol); +} + +/* Called from main context */ +void pa_virtual_sink_input_mute_changed(pa_sink_input *i) { + pa_sink *s; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + + pa_sink_mute_changed(s, i->muted); +} + +/* Called from main context */ +void pa_virtual_sink_input_suspend(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause) { + pa_sink *s; + + pa_sink_input_assert_ref(i); + s = i->origin_sink; + pa_assert(s); + + if (!PA_SINK_IS_LINKED(s->state)) + return; + + if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE) + pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE); + else + pa_sink_suspend(s, true, PA_SUSPEND_UNAVAILABLE); +} + +/* Other functions */ + +void pa_virtual_sink_set_callbacks(pa_sink *s, bool use_volume_sharing) { + + s->parent.process_msg = pa_virtual_sink_process_msg; + s->set_state_in_main_thread = pa_virtual_sink_set_state_in_main_thread; + s->set_state_in_io_thread = pa_virtual_sink_set_state_in_io_thread; + s->update_requested_latency = pa_virtual_sink_update_requested_latency; + s->request_rewind = pa_virtual_sink_request_rewind; + pa_sink_set_set_mute_callback(s, pa_virtual_sink_set_mute); + if (!use_volume_sharing) { + pa_sink_set_set_volume_callback(s, pa_virtual_sink_set_volume); + pa_sink_enable_decibel_volume(s, true); + } +} + +void pa_virtual_sink_input_set_callbacks(pa_sink_input *i, bool use_volume_sharing) { + + i->pop = pa_virtual_sink_input_pop; + i->process_rewind = pa_virtual_sink_input_process_rewind; + i->update_max_rewind = pa_virtual_sink_input_update_max_rewind; + i->update_max_request = pa_virtual_sink_input_update_max_request; + i->update_sink_latency_range = pa_virtual_sink_input_update_sink_latency_range; + i->update_sink_fixed_latency = pa_virtual_sink_input_update_sink_fixed_latency; + i->kill = pa_virtual_sink_input_kill; + i->attach = pa_virtual_sink_input_attach; + i->detach = pa_virtual_sink_input_detach; + i->may_move_to = pa_virtual_sink_input_may_move_to; + i->moving = pa_virtual_sink_input_moving; + i->volume_changed = use_volume_sharing ? NULL : pa_virtual_sink_input_volume_changed; + i->mute_changed = pa_virtual_sink_input_mute_changed; + i->suspend = pa_virtual_sink_input_suspend; + i->get_max_rewind_limit = pa_virtual_sink_input_get_max_rewind_limit; +} + +static int vsink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_vsink *vsink; + pa_sink *s; + pa_sink_input *i; + + pa_assert(o); + pa_assert_ctl_context(); + + vsink = PA_VSINK(o); + + switch (code) { + + case VSINK_MESSAGE_FREE_PARAMETERS: + + pa_assert(userdata); + pa_assert(vsink->free_filter_parameters); + vsink->free_filter_parameters(userdata); + return 0; + + case VSINK_MESSAGE_INPUT_ATTACHED: + + s = vsink->sink; + i = vsink->input_to_master; + + /* This may happen if a message is still pending after the vsink was + * destroyed. */ + if (!s || !i) + return 0; + + if (PA_SINK_IS_LINKED(s->state)) { + if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE) + pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE); + else + pa_sink_suspend(s, true, PA_SUSPEND_UNAVAILABLE); + } + return 0; + } + return 0; +} + +int pa_virtual_sink_activate(pa_vsink *vs) { + + pa_assert(vs); + pa_assert(vs->sink); + pa_assert(vs->input_to_master); + + /* Check that block sizes are plausible */ + if (check_block_sizes(vs->fixed_block_size, vs->fixed_input_block_size, vs->overlap_frames, vs) < 0) { + pa_log_warn("Invalid block sizes."); + return -1; + } + + /* For fixed block size filters, set the target length of the memblockq */ + if (vs->memblockq && vs->fixed_block_size) + pa_memblockq_set_tlength(vs->memblockq, vs->fixed_block_size * pa_frame_size(&vs->sink->sample_spec)); + + /* Set sink input latency at startup to max_latency if specified. */ + if (vs->max_latency) + pa_sink_input_set_requested_latency(vs->input_to_master, vs->max_latency); + + /* Set sink max_rewind on master sink. */ + pa_sink_set_max_rewind(vs->input_to_master->sink, get_max_rewind_bytes(vs, true)); + + /* The order here is important. The input must be put first, + * otherwise streams might attach to the sink before the sink + * input is attached to the master. */ + pa_sink_input_put(vs->input_to_master); + pa_sink_put(vs->sink); + + /* If volume sharing and flat volumes are disabled, we have to apply the sink volume to the sink input. */ + if (!(vs->sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) && !pa_sink_flat_volume_enabled(vs->input_to_master->sink)) { + pa_cvolume vol; + + vol = vs->sink->real_volume; + pa_cvolume_remap(&vol, &vs->sink->channel_map, &vs->input_to_master->channel_map); + pa_sink_input_set_volume(vs->input_to_master, &vol, vs->sink->save_volume, true); + } + + pa_sink_input_cork(vs->input_to_master, false); + + return 0; +} + +void pa_virtual_sink_destroy(pa_vsink *vs) { + + pa_assert(vs); + + /* See comments in sink_input_kill() above regarding + * destruction order! */ + if (vs->input_to_master && PA_SINK_INPUT_IS_LINKED(vs->input_to_master->state)) + pa_sink_input_cork(vs->input_to_master, true); + + if (vs->sink) + pa_sink_unlink(vs->sink); + + if (vs->input_to_master) { + pa_sink_input_unlink(vs->input_to_master); + pa_sink_input_unref(vs->input_to_master); + vs->input_to_master = NULL; + } + + if (vs->memblockq) + pa_memblockq_free(vs->memblockq); + + if (vs->sink) { + pa_sink_unref(vs->sink); + vs->sink = NULL; + } + + /* We have to use pa_msgobject_unref() here because there may still be pending + * VSINK_MESSAGE_INPUT_ATTACHED messages. */ + pa_msgobject_unref(PA_MSGOBJECT(vs)); +} + +/* Manually create a vsink structure. */ +pa_vsink* pa_virtual_sink_vsink_new(pa_sink *s, int max_rewind) { + pa_vsink *vsink; + + pa_assert(s); + + /* Create new vsink */ + vsink = pa_msgobject_new(pa_vsink); + vsink->parent.process_msg = vsink_process_msg; + + vsink->sink = s; + s->vsink = vsink; + + /* Reset virtual sink parameters */ + vsink->input_to_master = NULL; + vsink->memblockq = NULL; + vsink->auto_desc = false; + vsink->desc_head = "Unknown Sink"; + vsink->sink_type = "unknown"; + vsink->autoloaded = false; + vsink->max_chunk_size = pa_frame_align(pa_mempool_block_size_max(s->core->mempool), &s->sample_spec); + vsink->fixed_block_size = 0; + vsink->fixed_input_block_size = 0; + vsink->overlap_frames = 0; + vsink->drop_bytes = 0; + vsink->max_request_frames_min = 0; + vsink->max_latency = 0; + vsink->max_rewind = max_rewind; + vsink->rewind_filter = NULL; + vsink->process_chunk = NULL; + vsink->get_extra_latency = NULL; + vsink->set_description = NULL; + vsink->update_filter_parameters = NULL; + vsink->update_block_sizes = NULL; + + /* If rewinding is disabled, limit latency to NO_REWIND_MAX_LATENCY. + * If max_rewind is given, use the maximum of NO_REWIND_MAX_LATENCY + * and max_rewind, else use maximum latency from master sink. */ + if (max_rewind < 0) + vsink->max_latency = NO_REWIND_MAX_LATENCY; + else if (max_rewind > 0) { + vsink->max_latency = max_rewind * PA_USEC_PER_SEC / s->sample_spec.rate; + vsink->max_latency = PA_MAX(vsink->max_latency, NO_REWIND_MAX_LATENCY); + } + + return vsink; +} + +pa_vsink *pa_virtual_sink_create(pa_sink *master, const char *sink_type, const char *desc_prefix, + pa_sample_spec *sink_ss, pa_channel_map *sink_map, + pa_sample_spec *sink_input_ss, pa_channel_map *sink_input_map, + pa_module *m, void *userdata, pa_modargs *ma, + bool use_volume_sharing, bool create_memblockq, + int max_rewind) { + + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_data; + char *sink_type_property; + bool auto_desc; + bool force_flat_volume = false; + bool remix = true; + pa_resample_method_t resample_method = PA_RESAMPLER_INVALID; + pa_vsink *vsink; + pa_sink *s; + pa_sink_input *i; + + /* Make sure all necessary values are set. Only userdata and sink_name + * are allowed to be NULL. */ + pa_assert(master); + pa_assert(sink_ss); + pa_assert(sink_map); + pa_assert(sink_input_ss); + pa_assert(sink_input_map); + pa_assert(m); + pa_assert(ma); + + /* We do not support resampling in filters */ + pa_assert(sink_input_ss->rate == sink_ss->rate); + + if (!sink_type) + sink_type = "unknown"; + if (!desc_prefix) + desc_prefix = "Unknown Sink"; + + /* Get some command line arguments. Because there is no common default + * for use_volume_sharing, this value must be passed as argument to + * pa_virtual_sink_create(). */ + + if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { + pa_log("force_flat_volume= expects a boolean argument"); + return NULL; + } + + if (use_volume_sharing && force_flat_volume) { + pa_log("Flat volume can't be forced when using volume sharing."); + return NULL; + } + + if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) { + pa_log("Invalid boolean remix parameter"); + return NULL; + } + + if (pa_modargs_get_resample_method(ma, &resample_method) < 0) { + pa_log("Invalid resampling method"); + return NULL; + } + + /* Create sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = m->name; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.%s", master->name, sink_type); + pa_sink_new_data_set_sample_spec(&sink_data, sink_ss); + pa_sink_new_data_set_channel_map(&sink_data, sink_map); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid sink properties"); + pa_sink_new_data_done(&sink_data); + return NULL; + } + + s = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)) + | (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0)); + pa_sink_new_data_done(&sink_data); + + if (!s) { + pa_log("Failed to create sink."); + return NULL; + } + + /* Set name and description properties after the sink has been created, + * otherwise they may be duplicate. */ + if ((auto_desc = !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(s->proplist, PA_PROP_DEVICE_DESCRIPTION, "%s %s on %s", desc_prefix, s->name, z ? z : master->name); + } + + sink_type_property = pa_sprintf_malloc("device.%s.name", sink_type); + pa_proplist_sets(s->proplist, sink_type_property, s->name); + pa_xfree(sink_type_property); + + /* Create vsink structure. */ + vsink = pa_virtual_sink_vsink_new(s, max_rewind); + + pa_virtual_sink_set_callbacks(s, use_volume_sharing); + vsink->auto_desc = auto_desc; + vsink->desc_head = desc_prefix; + vsink->sink_type = sink_type; + + /* Normally this flag would be enabled automatically be we can force it. */ + if (force_flat_volume) + s->flags |= PA_SINK_FLAT_VOLUME; + s->userdata = userdata; + + pa_sink_set_asyncmsgq(s, master->asyncmsgq); + + /* Create sink input */ + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true); + sink_input_data.origin_sink = s; + pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "%s Stream from %s", desc_prefix, pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, sink_input_ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, sink_input_map); + sink_input_data.resample_method = resample_method; + sink_input_data.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX) | PA_SINK_INPUT_START_CORKED; + if (!pa_safe_streq(master->name, m->core->default_sink->name)) + sink_input_data.preferred_sink = pa_xstrdup(master->name); + + if (pa_modargs_get_proplist(ma, "sink_input_properties", sink_input_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid sink input properties"); + pa_sink_input_new_data_done(&sink_input_data); + pa_virtual_sink_destroy(vsink); + return NULL; + } + + pa_sink_input_new(&i, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!i) { + pa_log("Could not create sink-input"); + pa_virtual_sink_destroy(vsink); + return NULL; + } + + pa_virtual_sink_input_set_callbacks(i, use_volume_sharing); + i->userdata = userdata; + + vsink->input_to_master = i; + + vsink->autoloaded = false; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &vsink->autoloaded) < 0) { + pa_log("Failed to parse autoloaded value"); + pa_virtual_sink_destroy(vsink); + return NULL; + } + + if (create_memblockq) { + char *tmp; + pa_memchunk silence; + + tmp = pa_sprintf_malloc("%s memblockq", desc_prefix); + pa_sink_input_get_silence(i, &silence); + vsink->memblockq = pa_memblockq_new(tmp, 0, MEMBLOCKQ_MAXLENGTH, 0, sink_ss, 1, 1, 0, &silence); + pa_memblock_unref(silence.memblock); + pa_xfree(tmp); + } + + return vsink; +} + +/* Send request to update filter parameters to the I/O-thread. */ +void pa_virtual_sink_request_parameter_update(pa_vsink *vs, void *parameters) { + + pa_assert(vs); + pa_assert(vs->sink); + + /* parameters may be NULL if it is enough to have access to userdata from the + * callback. */ + pa_asyncmsgq_send(vs->sink->asyncmsgq, PA_MSGOBJECT(vs->sink), SINK_MESSAGE_UPDATE_PARAMETERS, parameters, 0, NULL); +} + +/* Called from I/O context. This is needed as a separate function + * because module-echo-cancel has to send the message from + * sink_input_attach_cb(). */ +void pa_virtual_sink_send_input_attached_message(pa_vsink *vs) { + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(vs), VSINK_MESSAGE_INPUT_ATTACHED, NULL, 0, NULL, NULL); +} diff --git a/src/modules/virtual-sink-common.h b/src/modules/virtual-sink-common.h new file mode 100644 index 000000000..04c73e457 --- /dev/null +++ b/src/modules/virtual-sink-common.h @@ -0,0 +1,77 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#include +#include + +/* Callbacks for virtual sinks. */ +int pa_virtual_sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk); + +int pa_virtual_sink_set_state_in_main_thread(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause); +int pa_virtual_sink_set_state_in_io_thread(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause); + +void pa_virtual_sink_request_rewind(pa_sink *s); +void pa_virtual_sink_update_requested_latency(pa_sink *s); +void pa_virtual_sink_set_volume(pa_sink *s); +void pa_virtual_sink_set_mute(pa_sink *s); + +int pa_virtual_sink_input_pop(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk); + +void pa_virtual_sink_input_process_rewind(pa_sink_input *i, size_t nbytes); +void pa_virtual_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes); +void pa_virtual_sink_input_update_max_request(pa_sink_input *i, size_t nbytes); +void pa_virtual_sink_input_update_sink_latency_range(pa_sink_input *i); +void pa_virtual_sink_input_update_sink_fixed_latency(pa_sink_input *i); + +void pa_virtual_sink_input_detach(pa_sink_input *i); +void pa_virtual_sink_input_attach(pa_sink_input *i); +void pa_virtual_sink_input_kill(pa_sink_input *i); +void pa_virtual_sink_input_moving(pa_sink_input *i, pa_sink *dest); +bool pa_virtual_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest); +size_t pa_virtual_sink_input_get_max_rewind_limit(pa_sink_input *i); + +void pa_virtual_sink_input_volume_changed(pa_sink_input *i); +void pa_virtual_sink_input_mute_changed(pa_sink_input *i); + +void pa_virtual_sink_input_suspend(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause); + +/* Set callbacks for virtual sink and sink input. */ +void pa_virtual_sink_set_callbacks(pa_sink *s, bool use_volume_sharing); +void pa_virtual_sink_input_set_callbacks(pa_sink_input *i, bool use_volume_sharing); + +/* Create a new virtual sink. Returns a filled vsink structure or NULL on failure. */ +pa_vsink *pa_virtual_sink_create(pa_sink *master, const char *sink_type, const char *desc_prefix, + pa_sample_spec *sink_ss, pa_channel_map *sink_map, + pa_sample_spec *sink_input_ss, pa_channel_map *sink_input_map, + pa_module *m, void *userdata, pa_modargs *ma, + bool use_volume_sharing, bool create_memblockq, + int max_rewind); + +/* Activate the new virtual sink. */ +int pa_virtual_sink_activate(pa_vsink *vs); + +/* Destroys the objects associated with the virtual sink. */ +void pa_virtual_sink_destroy(pa_vsink *vs); + +/* Create vsink structure */ +pa_vsink* pa_virtual_sink_vsink_new(pa_sink *s, int max_rewind); + +/* Update filter parameters */ +void pa_virtual_sink_request_parameter_update(pa_vsink *vs, void *parameters); + +/* Send sink input attached message (only needed for module-echo-cancel) */ +void pa_virtual_sink_send_input_attached_message(pa_vsink *vs); diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index 3aa353609..565c8b654 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -816,6 +816,18 @@ size_t pa_memblockq_get_prebuf(pa_memblockq *bq) { return bq->prebuf; } +size_t pa_memblockq_get_missing(pa_memblockq *bq) { + size_t l; + + pa_assert(bq); + + if ((l = pa_memblockq_get_length(bq)) >= bq->tlength) + return 0; + + l = bq->tlength - l; + return PA_MAX(bq->minreq, l); +} + size_t pa_memblockq_pop_missing(pa_memblockq *bq) { size_t l; diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h index 96e41cc3c..4e3cb8403 100644 --- a/src/pulsecore/memblockq.h +++ b/src/pulsecore/memblockq.h @@ -112,6 +112,11 @@ bool pa_memblockq_is_readable(pa_memblockq *bq); /* Return the length of the queue in bytes */ size_t pa_memblockq_get_length(pa_memblockq *bq); +/* Return the number of bytes missing to achieve the target length. + * If the number of missing bytes is smaller than minreq but larger + * than 0, minreq will be returned. */ +size_t pa_memblockq_get_missing(pa_memblockq *bq); + /* Return the number of bytes that are missing since the last call to * this function, reset the internal counter to 0. */ size_t pa_memblockq_pop_missing(pa_memblockq *bq); From bbbf7baa5acec096608e8ed7bff3cfe1e4d41a78 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 15:58:16 +0100 Subject: [PATCH 03/14] remap-sink: Use common code --- src/modules/meson.build | 2 +- src/modules/module-remap-sink.c | 383 +------------------------------- 2 files changed, 12 insertions(+), 373 deletions(-) diff --git a/src/modules/meson.build b/src/modules/meson.build index 019064e98..80788c320 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -48,7 +48,7 @@ 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-sink', 'module-remap-sink.c', [], [], [], libvirtual_sink ], [ 'module-remap-source', 'module-remap-source.c' ], [ 'module-rescue-streams', 'module-rescue-streams.c' ], [ 'module-role-cork', ['module-role-cork.c', 'stream-interaction.c'], 'stream-interaction.h' ], 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); } From e5f852ab7b5d317da911693292f551cbec1ded85 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 15:58:52 +0100 Subject: [PATCH 04/14] ladspa-sink: Use common code --- src/modules/meson.build | 2 +- src/modules/module-ladspa-sink.c | 573 +++---------------------------- 2 files changed, 55 insertions(+), 520 deletions(-) diff --git a/src/modules/meson.build b/src/modules/meson.build index 80788c320..ad2456ed2 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -39,7 +39,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 ], 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); From 3aaa764de771616e45908d4a9eb5c7c0e081e428 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 15:59:18 +0100 Subject: [PATCH 05/14] equalizer-sink: Use common code This patch also fixes a couple of crash bugs that can happen if the sink cannot be initialized properly. --- src/modules/meson.build | 2 +- src/modules/module-equalizer-sink.c | 527 +++------------------------- 2 files changed, 51 insertions(+), 478 deletions(-) diff --git a/src/modules/meson.build b/src/modules/meson.build index ad2456ed2..4eca12358 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -180,7 +180,7 @@ 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 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; From 8098bfde4aa4ddd9cdb4c934ff79b2b31de30839 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 15:59:56 +0100 Subject: [PATCH 06/14] echo-cancel: Use common code This module cannot be fully consolidated, but still there are significant savings. With some re-ordering, probably even more of the code could be converted. --- src/modules/echo-cancel/module-echo-cancel.c | 230 ++----------------- src/modules/meson.build | 2 +- 2 files changed, 23 insertions(+), 209 deletions(-) diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index ae1bf9d68..36ba6fed5 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -33,6 +33,8 @@ #include "echo-cancel.h" +#include + #include #include #include @@ -41,11 +43,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -228,6 +228,7 @@ struct userdata { 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 +436,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 +486,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 +507,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 +539,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 +575,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 +1057,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 +1069,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 +1095,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 +1109,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 +1176,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 +1208,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 +1364,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 +1388,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; @@ -1948,18 +1768,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 */ @@ -2022,26 +1840,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 +1984,9 @@ void pa__done(pa_module*m) { pa_sink_input_unref(u->sink_input); } + if (u->vsink) + pa_xfree(u->vsink); + if (u->source) pa_source_unref(u->source); if (u->sink) diff --git a/src/modules/meson.build b/src/modules/meson.build index 4eca12358..6e26899db 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -262,7 +262,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] if get_option('adrian-aec') module_echo_cancel_sources += [ From 668a22e90291a4a88d4dba55420e9698d719b88b Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 20:12:13 +0100 Subject: [PATCH 07/14] virtual-surround-sink: Use common code Using the library fixes a crash bug in module-virtual-surround sink when used with use_volume_sharing=0. The old code did not remap the volumes. --- src/modules/meson.build | 2 +- src/modules/module-virtual-surround-sink.c | 687 +++------------------ 2 files changed, 87 insertions(+), 602 deletions(-) diff --git a/src/modules/meson.build b/src/modules/meson.build index 6e26899db..dbf330204 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -174,7 +174,7 @@ 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 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++) { From ed4074ae08ad1315e8beeb407d70dccbdfba38ce Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Fri, 1 Jan 2021 20:15:49 +0100 Subject: [PATCH 08/14] sink: Remove input_to_master variable from sink structure The variable is now unused. --- src/modules/module-filter-apply.c | 10 ++-------- src/pulsecore/sink-input.c | 24 ++++++------------------ src/pulsecore/sink.c | 15 +++------------ src/pulsecore/sink.h | 1 - 4 files changed, 11 insertions(+), 39 deletions(-) diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c index bd4a9d176..f6c6929c7 100644 --- a/src/modules/module-filter-apply.c +++ b/src/modules/module-filter-apply.c @@ -293,10 +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. */ - if (si->sink->vsink) - filter->sink_master = si->sink->vsink->input_to_master->sink; - else - 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; } @@ -464,10 +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)); - if (sink->vsink) - fltr = filter_new(name, parameters, sink->vsink->input_to_master->sink, NULL); - else - 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; diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index c780d7b37..9824719c5 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1785,19 +1785,10 @@ bool pa_sink_input_may_move(pa_sink_input *i) { static bool find_filter_sink_input(pa_sink_input *target, pa_sink *s) { unsigned PA_UNUSED i = 0; - /* During consolidation, we have to support s->input_to_master and - * s->vsink->input_to_master. The first will disappear after all - * virtual sinks use the new code. */ - while (s && (s->input_to_master || (s->vsink && s->vsink->input_to_master))) { - if (s->vsink) { - if (s->vsink->input_to_master == target) - return true; - s = s->vsink->input_to_master->sink; - } else { - if (s->input_to_master == target) - return true; - s = s->input_to_master->sink; - } + while (s && (s->vsink && s->vsink->input_to_master)) { + if (s->vsink->input_to_master == target) + return true; + s = s->vsink->input_to_master->sink; pa_assert(i++ < 100); } return false; @@ -1809,11 +1800,8 @@ static bool is_filter_sink_moving(pa_sink_input *i) { if (!sink) return false; - while (sink->input_to_master || (sink->vsink && sink->vsink->input_to_master)) { - if (sink->vsink) - sink = sink->vsink->input_to_master->sink; - else - 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; diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index e87a16a04..072f87aeb 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -286,7 +286,6 @@ 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->reference_volume = s->real_volume = data->volume; @@ -1693,19 +1692,11 @@ bool pa_sink_has_filter_attached(pa_sink *s) { pa_sink *pa_sink_get_master(pa_sink *s) { pa_sink_assert_ref(s); - /* During consolidation, we have to support s->input_to_master and - * s->vsink->input_to_master. The first will disappear after all - * virtual sinks use the new code. */ while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { - if (PA_UNLIKELY(s->vsink && !s->vsink->input_to_master)) - return NULL; - if (PA_UNLIKELY(!s->vsink && !s->input_to_master)) + if (PA_UNLIKELY(!s->vsink || (s->vsink && !s->vsink->input_to_master))) return NULL; - if (s->input_to_master) - s = s->input_to_master->sink; - else - s = s->vsink->input_to_master->sink; + s = s->vsink->input_to_master->sink; } return s; @@ -1715,7 +1706,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->vsink != NULL) || (s->input_to_master != NULL)); + return ((s->vsink != NULL) && (s->vsink->input_to_master != NULL)); } /* Called from main context */ diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index f4db8158d..1cc44fefc 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -173,7 +173,6 @@ struct pa_sink { unsigned n_corked; pa_source *monitor_source; pa_vsink *vsink; /* non-NULL only for filter sinks */ - pa_sink_input *input_to_master; /* non-NULL only for filter sinks */ pa_volume_t base_volume; /* shall be constant */ unsigned n_volume_steps; /* shall be constant */ From 95be21c7363d63d8583ad6bcd5521a8a5525b207 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Thu, 4 Feb 2021 15:14:01 +0100 Subject: [PATCH 09/14] core: Allow rescue of filter streams Since 14.0, filter streams would be killed if the master sink or source disappeared. This patch changes the behavior to allow rescuing of filter streams. --- src/pulsecore/core.c | 108 +++++++++++++++++++++++----------- src/pulsecore/core.h | 3 + src/pulsecore/sink-input.c | 30 ++++++++-- src/pulsecore/sink-input.h | 2 + src/pulsecore/sink.c | 27 ++++++++- src/pulsecore/source-output.c | 30 ++++++++-- src/pulsecore/source-output.h | 2 + src/pulsecore/source.c | 27 ++++++++- 8 files changed, 178 insertions(+), 51 deletions(-) diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index df7b72461..5e84493e7 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->output_from_master && !b->output_from_master) + return -1; + if (!a->output_from_master && b->output_from_master) + 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/sink-input.c b/src/pulsecore/sink-input.c index 9824719c5..1e625a677 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1782,7 +1782,7 @@ 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->vsink && s->vsink->input_to_master)) { @@ -1827,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; } @@ -2240,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 072f87aeb..21d7dae68 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -4064,9 +4064,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/source-output.c b/src/pulsecore/source-output.c index 2e2b7a274..7e0a925fd 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -1304,7 +1304,7 @@ 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) @@ -1347,7 +1347,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 +1660,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..226a717ca 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -3033,9 +3033,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) From b6ccb7e76d885072b37c008e5f36d25e43b65555 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Mon, 25 Jan 2021 21:57:07 +0100 Subject: [PATCH 10/14] source: Introduce vsource structure This patch introduces a vsource structure analog to the vsink structure. The structure will be used for virtual source consolidation. --- src/modules/module-filter-apply.c | 15 +++++-- src/pulsecore/core.c | 22 ++++++++-- src/pulsecore/source-output.c | 25 ++++++++--- src/pulsecore/source.c | 14 ++++-- src/pulsecore/source.h | 73 +++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 15 deletions(-) diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c index f6c6929c7..480d3614f 100644 --- a/src/modules/module-filter-apply.c +++ b/src/modules/module-filter-apply.c @@ -272,7 +272,10 @@ 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; + if (so->source->vsource) + filter->source_master = so->source->vsource->output_from_master->source; + else + filter->source_master = so->source->output_from_master->source; } else { filter->source_master = so->source; } @@ -474,12 +477,18 @@ 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); + if (source->vsource) + fltr = filter_new(name, parameters, NULL, source->vsource->output_from_master->source); + else + fltr = filter_new(name, parameters, NULL, source->output_from_master->source); fltr->module_index = m->index; fltr->source = source; } else { fltr->source = source; - fltr->source_master = source->output_from_master->source; + if (source->vsource) + fltr->source_master = source->vsource->output_from_master->source; + else + fltr->source_master = source->output_from_master->source; } break; diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 5e84493e7..cfe322e82 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -529,6 +529,7 @@ void pa_core_update_default_sink(pa_core *core) { * a > b -> return 1 */ static int compare_sources(pa_source *a, pa_source *b, bool ignore_configured_virtual_default) { pa_core *core; + bool a_is_vsource, b_is_vsource; core = a->core; @@ -572,10 +573,25 @@ static int compare_sources(pa_source *a, pa_source *b, bool ignore_configured_vi if (a->priority > b->priority) return 1; - /* Let sources like pipe source or null source win against filter sources */ - if (a->output_from_master && !b->output_from_master) + /* Let sources like pipe source or null source win against filter sources + During consolidation, we have to detect the presence of the vsource or + output_to_master variable. When the virtual sources have been migrated, + this will simplify. */ + a_is_vsource = false; + if (a->vsource) + a_is_vsource = true; + else if (a->output_from_master) + a_is_vsource = true; + + b_is_vsource = false; + if (b->vsource) + b_is_vsource = true; + else if (b->output_from_master) + b_is_vsource = true; + + if (a_is_vsource && !b_is_vsource) return -1; - if (!a->output_from_master && b->output_from_master) + if (!a_is_vsource && b_is_vsource) return 1; /* If the sources are monitors, we can compare the monitored sinks. */ diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 7e0a925fd..98f50fd80 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -1306,10 +1306,20 @@ bool pa_source_output_may_move(pa_source_output *o) { 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) - return true; - s = s->output_from_master->source; + + /* During consolidation, we have to support s->output_from_master and + * s->vsource->output_from_master. The first will disappear after all + * virtual sources use the new code. */ + while (s && (s->output_from_master || (s->vsource && s->vsource->output_from_master))) { + if (s->vsource) { + if (s->vsource->output_from_master == target) + return true; + s = s->vsource->output_from_master->source; + } else { + if (s->output_from_master == target) + return true; + s = s->output_from_master->source; + } pa_assert(i++ < 100); } return false; @@ -1321,8 +1331,11 @@ 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->output_from_master || (source->vsource && source->vsource->output_from_master)) { + if (source->vsource) + source = source->vsource->output_from_master->source; + else + source = source->output_from_master->source; if (!source) return true; diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 226a717ca..cbffc9b93 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -1234,11 +1234,19 @@ bool pa_source_flat_volume_enabled(pa_source *s) { pa_source *pa_source_get_master(pa_source *s) { pa_source_assert_ref(s); + /* During consolidation, we have to support s->output_from_master and + * s->vsource->output_from_master. The first will disappear after all + * virtual sources use the new code. */ while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { - if (PA_UNLIKELY(!s->output_from_master)) + if (PA_UNLIKELY(s->vsource && !s->vsource->output_from_master)) return NULL; + if (PA_UNLIKELY(!s->vsource && !s->output_from_master)) + return NULL; - s = s->output_from_master->source; + if (s->output_from_master) + s = s->output_from_master->source; + else + s = s->vsource->output_from_master->source; } return s; @@ -1248,7 +1256,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->output_from_master != NULL || s->vsource->output_from_master != NULL)); } /* Called from main context */ diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index aa71ee829..41dab0046 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -57,6 +57,78 @@ 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 */ + + /* 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; @@ -90,6 +162,7 @@ struct pa_source { 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 */ From 0b26ed9b0c029ab09183b4606084a41c2d336c00 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Sun, 31 Jan 2021 17:02:57 +0100 Subject: [PATCH 11/14] virtual source: Factor out common code Analog to the virtual sink consolidation series, the common code of virtual sources is moved to a library. The library adds support for fixed block size filters and parameter changing. Rewinding has been dropped because virtual sources do not need it. --- src/modules/meson.build | 13 +- src/modules/module-virtual-source.c | 575 +++----------- src/modules/virtual-source-common.c | 1110 +++++++++++++++++++++++++++ src/modules/virtual-source-common.h | 71 ++ 4 files changed, 1302 insertions(+), 467 deletions(-) create mode 100644 src/modules/virtual-source-common.c create mode 100644 src/modules/virtual-source-common.h diff --git a/src/modules/meson.build b/src/modules/meson.build index dbf330204..21466c82c 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -13,6 +13,17 @@ libvirtual_sink = shared_library('virtual_sink', 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' ], @@ -67,7 +78,7 @@ all_modules = [ [ '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', [], [], [], libvirtual_sink ], - [ 'module-virtual-source', 'module-virtual-source.c' ], + [ 'module-virtual-source', 'module-virtual-source.c', [], [], [], libvirtual_source ], [ 'module-volume-restore', 'module-volume-restore.c' ], ] diff --git a/src/modules/module-virtual-source.c b/src/modules/module-virtual-source.c index 8dd7fc90f..4e39a53ea 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,15 +65,7 @@ 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 */ @@ -79,6 +73,7 @@ struct userdata { pa_usec_t block_usec; pa_memblockq *sink_memblockq; pa_rtpoll *rtpoll; + bool auto_desc; }; @@ -96,6 +91,97 @@ static const char* const valid_modargs[] = { NULL }; +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_assert_se(u = userdata); + pa_assert(in_count == out_count); + + nbytes = in_count * pa_frame_size(&u->vsource->source->sample_spec); + + /* 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; + pa_mix_info streams[2]; + pa_memchunk chunk; + void *src_copy; + int ch; + pa_source_output *o; + + /* 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 == nbytes); + + /* move the read pointer for sink memblockq */ + pa_memblockq_drop(u->sink_memblockq, tchunk.length); + + o = u->vsource->output_from_master; + + /* allocate source chunk */ + chunk.index = 0; + chunk.length = nbytes; + chunk.memblock = pa_memblock_new(o->source->core->mempool, nbytes); + pa_assert(chunk.memblock); + + /* Copy source data to chunk */ + src_copy = pa_memblock_acquire_chunk(&chunk); + memcpy(src_copy, src, nbytes); + + /* 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, + dst, /* put result in dst */ + nbytes, /* 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(chunk.memblock); + pa_memblock_unref(tchunk.memblock); + pa_memblock_unref(chunk.memblock); + } else + /* Copy input to output */ + memcpy(dst, src, nbytes); +} + +/* When the source output moves, the asyncmsgq of the uplink sink has + * to change as well */ +static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_assert(u = o->userdata); + + pa_virtual_source_output_moving(o, dest); + if (dest && u->sink) { + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); + } +} + /* 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) { @@ -125,8 +211,8 @@ static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, p 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); + pa_log_debug("Resuming source %s, because its uplink sink became active.", u->vsource->source->name); + pa_source_suspend(u->vsource->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 @@ -151,347 +237,13 @@ static void sink_update_requested_latency_cb(pa_sink *s) { } -/* 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); - } -} - int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; 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; @@ -512,7 +264,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,24 +275,9 @@ 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 @@ -551,93 +287,14 @@ int pa__init(pa_module*m) { * 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; - - 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); + /* Set callback for virtual source */ + u->vsource->process_chunk = filter_process_chunk; + u->vsource->output_from_master->moving = source_output_moving_cb; /* Create optional uplink sink */ pa_sink_new_data_init(&sink_data); @@ -693,6 +350,9 @@ int pa__init(pa_module*m) { u->sink = NULL; } + if (pa_virtual_source_activate(u->vsource) < 0) + goto fail; + pa_modargs_free(ma); return 0; @@ -712,7 +372,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,31 +383,14 @@ 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); 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); diff --git a/src/modules/virtual-source-common.c b/src/modules/virtual-source-common.c new file mode 100644 index 000000000..cbbbadd56 --- /dev/null +++ b/src/modules/virtual-source-common.c @@ -0,0 +1,1110 @@ +/*** + 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_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 +}; + +/* 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); +} + +/* 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); + } +} + +/* 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_source_assert_ref(s); + o = get_output_from_source(s); + pa_assert(o); + + if (!PA_SOURCE_IS_LINKED(state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + return 0; + + 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); +} + +/* 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_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_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); +} + +/* 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); + + 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_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + s = o->destination_source; + pa_assert(s); + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) + pa_source_detach_within_thread(s); + + pa_source_set_rtpoll(s, 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); + + /* 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; + + return s != dest; +} + +/* 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_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; + } else + pa_source_set_asyncmsgq(s, 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)); + } + + /* Propagate asyncmsq change to attached virtual sources */ + PA_IDXSET_FOREACH(output, s->outputs, idx) { + if (output->destination_source && output->moving) + output->moving(output, s); + } + +} + +/* 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; + } + + /* 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; + } + + /* 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; + + 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; + + /* 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); + } + + 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..ff07a2ee4 --- /dev/null +++ b/src/modules/virtual-source-common.h @@ -0,0 +1,71 @@ +/*** + 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); From 9f70ad3c953ffe55a0aabae5527fbe5c038b84e5 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Sat, 1 May 2021 14:47:43 +0200 Subject: [PATCH 12/14] virtual-source-common: Integrate uplink sink into virtual source library This patch integrates the uplink sink feature of module-virtual-source into the virtual source library, so that every virtual source can use it. The patch also introduces latency handling and rewinding for the uplink sink. Similar to the monitor source, the only useful definition of the latency appears to be the negative of the master source latency. Rewinding will not be possible in most situations, because the underlying memblockq is nearly always empty. module-combine-sink and module-suspend-on-idle required some changes to deal correctly with this type of sink. --- src/modules/module-combine-sink.c | 12 +- src/modules/module-suspend-on-idle.c | 78 ++++- src/modules/module-virtual-source.c | 225 +-------------- src/modules/virtual-source-common.c | 415 ++++++++++++++++++++++++++- src/modules/virtual-source-common.h | 3 + src/pulsecore/sink.c | 6 +- src/pulsecore/sink.h | 1 + src/pulsecore/source.c | 17 +- src/pulsecore/source.h | 3 +- src/pulsecore/typedefs.h | 1 + 10 files changed, 512 insertions(+), 249 deletions(-) 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-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-source.c b/src/modules/module-virtual-source.c index 4e39a53ea..c7a39650a 100644 --- a/src/modules/module-virtual-source.c +++ b/src/modules/module-virtual-source.c @@ -67,14 +67,6 @@ struct userdata { 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; - bool auto_desc; - }; static const char* const valid_modargs[] = { @@ -88,6 +80,7 @@ static const char* const valid_modargs[] = { "channel_map", "use_volume_sharing", "force_flat_volume", + "autoloaded", NULL }; @@ -100,141 +93,8 @@ static void filter_process_chunk(uint8_t *src, uint8_t *dst, unsigned in_count, nbytes = in_count * pa_frame_size(&u->vsource->source->sample_spec); - /* 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; - pa_mix_info streams[2]; - pa_memchunk chunk; - void *src_copy; - int ch; - pa_source_output *o; - - /* 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 == nbytes); - - /* move the read pointer for sink memblockq */ - pa_memblockq_drop(u->sink_memblockq, tchunk.length); - - o = u->vsource->output_from_master; - - /* allocate source chunk */ - chunk.index = 0; - chunk.length = nbytes; - chunk.memblock = pa_memblock_new(o->source->core->mempool, nbytes); - pa_assert(chunk.memblock); - - /* Copy source data to chunk */ - src_copy = pa_memblock_acquire_chunk(&chunk); - memcpy(src_copy, src, nbytes); - - /* 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, - dst, /* put result in dst */ - nbytes, /* 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(chunk.memblock); - pa_memblock_unref(tchunk.memblock); - pa_memblock_unref(chunk.memblock); - } else - /* Copy input to output */ - memcpy(dst, src, nbytes); -} - -/* When the source output moves, the asyncmsgq of the uplink sink has - * to change as well */ -static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { - struct userdata *u; - - pa_assert(u = o->userdata); - - pa_virtual_source_output_moving(o, dest); - if (dest && u->sink) { - pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); - } -} - -/* 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) { - struct userdata *u; - - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SINK_IS_LINKED(state)) { - return 0; - } - - 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->vsource->source->name); - pa_source_suspend(u->vsource->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 */ - + /* Copy input to output */ + memcpy(dst, src, nbytes); } int pa__init(pa_module*m) { @@ -245,10 +105,6 @@ int pa__init(pa_module*m) { pa_source *master=NULL; bool use_volume_sharing = true; - /* optional for uplink_sink */ - pa_sink_new_data sink_data; - size_t nbytes; - pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { @@ -280,75 +136,13 @@ int pa__init(pa_module*m) { m->userdata = u; 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 virtual source */ + /* 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; /* Set callback for virtual source */ u->vsource->process_chunk = filter_process_chunk; - u->vsource->output_from_master->moving = source_output_moving_cb; - - /* 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; @@ -386,16 +180,5 @@ void pa__done(pa_module*m) { if (u->vsource) pa_virtual_source_destroy(u->vsource); - if (u->sink) { - pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - } - - if (u->sink_memblockq) - pa_memblockq_free(u->sink_memblockq); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - pa_xfree(u); } diff --git a/src/modules/virtual-source-common.c b/src/modules/virtual-source-common.c index cbbbadd56..3f9cf61d5 100644 --- a/src/modules/virtual-source-common.c +++ b/src/modules/virtual-source-common.c @@ -22,8 +22,10 @@ #include #include +#include #include +#include PA_DEFINE_PRIVATE_CLASS(pa_vsource, pa_msgobject); #define PA_VSOURCE(o) (pa_vsource_cast(o)) @@ -42,6 +44,11 @@ enum { 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) { @@ -121,6 +128,8 @@ static void set_latency_range_within_thread(pa_vsource *vsource) { } 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 */ @@ -136,6 +145,134 @@ static void set_memblockq_rewind(pa_vsource *vsource) { } } +/* 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 */ @@ -230,15 +367,34 @@ int pa_virtual_source_process_msg(pa_msgobject *obj, int code, void *data, int64 /* 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; } @@ -335,6 +491,86 @@ void pa_virtual_source_set_mute(pa_source *s) { 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 */ @@ -355,7 +591,7 @@ void pa_virtual_source_output_push(pa_source_output *o, const pa_memchunk *chunk return; if (!vsource->process_chunk || !vsource->memblockq) { - pa_source_post(s, chunk); + pa_virtual_source_post(s, chunk); return; } @@ -427,7 +663,7 @@ void pa_virtual_source_output_push(pa_source_output *o, const pa_memchunk *chunk pa_memblock_unref(schunk.memblock); /* Post data */ - pa_source_post(s, &tchunk); + pa_virtual_source_post(s, &tchunk); pa_memblock_unref(tchunk.memblock); length = pa_memblockq_get_length(vsource->memblockq); @@ -461,8 +697,16 @@ void pa_virtual_source_output_process_rewind(pa_source_output *o, size_t nbytes) * pass the rewind on to the source */ if (vsource->memblockq) pa_memblockq_seek(vsource->memblockq, - nbytes, PA_SEEK_RELATIVE, true); - else + 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. */ @@ -543,6 +787,8 @@ void pa_virtual_source_output_attach(pa_source_output *o) { 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); @@ -574,16 +820,21 @@ void pa_virtual_source_output_attach(pa_source_output *o) { /* 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 */ @@ -614,6 +865,23 @@ void pa_virtual_source_output_kill(pa_source_output *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); @@ -640,7 +908,21 @@ bool pa_virtual_source_output_may_move_to(pa_source_output *o, pa_source *dest) if (vsource->autoloaded) return false; - return s != dest; + 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 */ @@ -649,6 +931,7 @@ void pa_virtual_source_output_moving(pa_source_output *o, pa_source *dest) { pa_vsource *vsource; uint32_t idx; pa_source_output *output; + pa_sink_input *input; pa_source_output_assert_ref(o); pa_assert_ctl_context(); @@ -662,8 +945,22 @@ void pa_virtual_source_output_moving(pa_source_output *o, pa_source *dest) { 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; - } else + 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); @@ -689,12 +986,33 @@ void pa_virtual_source_output_moving(pa_source_output *o, pa_source *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 */ @@ -834,6 +1152,10 @@ int pa_virtual_source_activate(pa_vsource *vs) { 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); @@ -884,6 +1206,22 @@ void pa_virtual_source_destroy(pa_vsource *vs) { 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)); @@ -922,6 +1260,7 @@ pa_vsource* pa_virtual_source_vsource_new(pa_source *s) { vsource->update_filter_parameters = NULL; vsource->update_block_sizes = NULL; vsource->free_filter_parameters = NULL; + vsource->uplink_sink = NULL; return vsource; } @@ -942,6 +1281,8 @@ pa_vsource *pa_virtual_source_create(pa_source *master, const char *source_type, 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. */ @@ -1093,6 +1434,70 @@ pa_vsource *pa_virtual_source_create(pa_source *master, const char *source_type, 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; diff --git a/src/modules/virtual-source-common.h b/src/modules/virtual-source-common.h index ff07a2ee4..f48a60018 100644 --- a/src/modules/virtual-source-common.h +++ b/src/modules/virtual-source-common.h @@ -69,3 +69,6 @@ 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/sink.c b/src/pulsecore/sink.c index 21d7dae68..1ed24b786 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -287,6 +287,7 @@ pa_sink* pa_sink_new( s->inputs = pa_idxset_new(NULL, NULL); s->n_corked = 0; 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); @@ -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); diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 1cc44fefc..9e9c9ee9c 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -173,6 +173,7 @@ struct pa_sink { unsigned n_corked; pa_source *monitor_source; 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.c b/src/pulsecore/source.c index cbffc9b93..969142984 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -2027,7 +2027,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; @@ -2041,7 +2041,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 @@ -2061,6 +2061,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; } @@ -2408,6 +2411,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); diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 41dab0046..52464ee16 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -89,6 +89,7 @@ typedef struct pa_vsource { * 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 */ @@ -499,7 +500,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 From 5d05301663e625678570bea4fcb73016a7472c21 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Sun, 31 Jan 2021 21:27:47 +0100 Subject: [PATCH 13/14] remap-source: Use common code --- src/modules/meson.build | 2 +- src/modules/module-remap-source.c | 334 ++---------------------------- 2 files changed, 14 insertions(+), 322 deletions(-) diff --git a/src/modules/meson.build b/src/modules/meson.build index 21466c82c..3d3c7ba17 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -60,7 +60,7 @@ all_modules = [ [ 'module-null-source', 'module-null-source.c' ], [ 'module-position-event-sounds', 'module-position-event-sounds.c' ], [ 'module-remap-sink', 'module-remap-sink.c', [], [], [], libvirtual_sink ], - [ 'module-remap-source', 'module-remap-source.c' ], + [ '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' ], 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); } From 664d175da89f816518c029ffd61088b48f6fef65 Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Mon, 1 Feb 2021 18:57:36 +0100 Subject: [PATCH 14/14] source: Remove output_from_master field The only source still using the output_from_master field of the source structure was module-echo-cancel. Due to the differences between the echo-cancel source and the other virtual sources, consolidation did not seem useful. To faciliate removing the output_from_master field from the source, a dummy vsource structure was created for module-echo-cancel. After that, the output_from_master field was removed. --- src/modules/echo-cancel/module-echo-cancel.c | 11 ++++++++- src/modules/meson.build | 2 +- src/modules/module-filter-apply.c | 15 +++--------- src/pulsecore/core.c | 22 +++--------------- src/pulsecore/source-output.c | 24 +++++--------------- src/pulsecore/source.c | 16 ++++--------- src/pulsecore/source.h | 1 - 7 files changed, 27 insertions(+), 64 deletions(-) diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index 36ba6fed5..4914666e6 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -34,6 +34,7 @@ #include "echo-cancel.h" #include +#include #include #include @@ -222,6 +223,7 @@ 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 */ @@ -1731,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__; @@ -1817,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); @@ -1987,6 +1993,9 @@ void pa__done(pa_module*m) { 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 3d3c7ba17..5932a0255 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -273,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 = [libvirtual_sink] +module_echo_cancel_libs = [libvirtual_sink, libvirtual_source] if get_option('adrian-aec') module_echo_cancel_sources += [ diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c index 480d3614f..39e825c96 100644 --- a/src/modules/module-filter-apply.c +++ b/src/modules/module-filter-apply.c @@ -272,10 +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. */ - if (so->source->vsource) - filter->source_master = so->source->vsource->output_from_master->source; - else - 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; } @@ -477,18 +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) { - if (source->vsource) - fltr = filter_new(name, parameters, NULL, source->vsource->output_from_master->source); - else - 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; - if (source->vsource) - fltr->source_master = source->vsource->output_from_master->source; - else - fltr->source_master = source->output_from_master->source; + fltr->source_master = source->vsource->output_from_master->source; } break; diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index cfe322e82..248d953f6 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -529,7 +529,6 @@ void pa_core_update_default_sink(pa_core *core) { * a > b -> return 1 */ static int compare_sources(pa_source *a, pa_source *b, bool ignore_configured_virtual_default) { pa_core *core; - bool a_is_vsource, b_is_vsource; core = a->core; @@ -573,25 +572,10 @@ static int compare_sources(pa_source *a, pa_source *b, bool ignore_configured_vi if (a->priority > b->priority) return 1; - /* Let sources like pipe source or null source win against filter sources - During consolidation, we have to detect the presence of the vsource or - output_to_master variable. When the virtual sources have been migrated, - this will simplify. */ - a_is_vsource = false; - if (a->vsource) - a_is_vsource = true; - else if (a->output_from_master) - a_is_vsource = true; - - b_is_vsource = false; - if (b->vsource) - b_is_vsource = true; - else if (b->output_from_master) - b_is_vsource = true; - - if (a_is_vsource && !b_is_vsource) + /* Let sources like pipe source or null source win against filter sources */ + if (a->vsource && !b->vsource) return -1; - if (!a_is_vsource && b_is_vsource) + if (!a->vsource && b->vsource) return 1; /* If the sources are monitors, we can compare the monitored sinks. */ diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 98f50fd80..f4537d355 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -1307,19 +1307,10 @@ bool pa_source_output_may_move(pa_source_output *o) { bool pa_source_output_is_filter_loop(pa_source_output *target, pa_source *s) { unsigned PA_UNUSED i = 0; - /* During consolidation, we have to support s->output_from_master and - * s->vsource->output_from_master. The first will disappear after all - * virtual sources use the new code. */ - while (s && (s->output_from_master || (s->vsource && s->vsource->output_from_master))) { - if (s->vsource) { - if (s->vsource->output_from_master == target) - return true; - s = s->vsource->output_from_master->source; - } else { - if (s->output_from_master == target) - return true; - s = s->output_from_master->source; - } + while (s && (s->vsource && s->vsource->output_from_master)) { + if (s->vsource->output_from_master == target) + return true; + s = s->vsource->output_from_master->source; pa_assert(i++ < 100); } return false; @@ -1331,11 +1322,8 @@ static bool is_filter_source_moving(pa_source_output *o) { if (!source) return false; - while (source->output_from_master || (source->vsource && source->vsource->output_from_master)) { - if (source->vsource) - source = source->vsource->output_from_master->source; - else - 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; diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 969142984..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); @@ -1234,19 +1234,11 @@ bool pa_source_flat_volume_enabled(pa_source *s) { pa_source *pa_source_get_master(pa_source *s) { pa_source_assert_ref(s); - /* During consolidation, we have to support s->output_from_master and - * s->vsource->output_from_master. The first will disappear after all - * virtual sources use the new code. */ while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { - if (PA_UNLIKELY(s->vsource && !s->vsource->output_from_master)) + if (PA_UNLIKELY(!s->vsource || (s->vsource && !s->vsource->output_from_master))) return NULL; - if (PA_UNLIKELY(!s->vsource && !s->output_from_master)) - return NULL; - if (s->output_from_master) - s = s->output_from_master->source; - else - s = s->vsource->output_from_master->source; + s = s->vsource->output_from_master->source; } return s; @@ -1256,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 || s->vsource->output_from_master != NULL)); + return (s->vsource->output_from_master != NULL); } /* Called from main context */ diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 52464ee16..4ec0b327e 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -162,7 +162,6 @@ 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 */