Merge branch 'virt_source_consolidation' into 'master'

Virtual sink and source consolidation

Closes #1154

See merge request pulseaudio/pulseaudio!661
This commit is contained in:
Georg Chini 2025-10-09 22:51:11 +00:00
commit 8490a9793a
29 changed files with 3862 additions and 3727 deletions

View file

@ -33,6 +33,9 @@
#include "echo-cancel.h" #include "echo-cancel.h"
#include <modules/virtual-sink-common.h>
#include <modules/virtual-source-common.h>
#include <pulse/xmalloc.h> #include <pulse/xmalloc.h>
#include <pulse/timeval.h> #include <pulse/timeval.h>
#include <pulse/rtclock.h> #include <pulse/rtclock.h>
@ -41,11 +44,9 @@
#include <pulsecore/atomic.h> #include <pulsecore/atomic.h>
#include <pulsecore/macro.h> #include <pulsecore/macro.h>
#include <pulsecore/namereg.h> #include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h> #include <pulsecore/module.h>
#include <pulsecore/core-rtclock.h> #include <pulsecore/core-rtclock.h>
#include <pulsecore/core-util.h> #include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h> #include <pulsecore/log.h>
#include <pulsecore/rtpoll.h> #include <pulsecore/rtpoll.h>
#include <pulsecore/sample-util.h> #include <pulsecore/sample-util.h>
@ -222,12 +223,14 @@ struct userdata {
pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write; pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write;
pa_source *source; pa_source *source;
pa_vsource *vsource;
bool source_auto_desc; bool source_auto_desc;
pa_source_output *source_output; pa_source_output *source_output;
pa_memblockq *source_memblockq; /* echo canceller needs fixed sized chunks */ pa_memblockq *source_memblockq; /* echo canceller needs fixed sized chunks */
size_t source_skip; size_t source_skip;
pa_sink *sink; pa_sink *sink;
pa_vsink *vsink;
bool sink_auto_desc; bool sink_auto_desc;
pa_sink_input *sink_input; pa_sink_input *sink_input;
pa_memblockq *sink_memblockq; pa_memblockq *sink_memblockq;
@ -435,40 +438,6 @@ static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t
return pa_source_process_msg(o, code, data, offset, chunk); 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 */ /* 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) { 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; struct userdata *u;
@ -519,23 +488,6 @@ static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, p
return 0; 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 */ /* Called from source I/O thread context */
static void source_update_requested_latency_cb(pa_source *s) { static void source_update_requested_latency_cb(pa_source *s) {
struct userdata *u; struct userdata *u;
@ -557,27 +509,6 @@ static void source_update_requested_latency_cb(pa_source *s) {
pa_source_output_set_requested_latency_within_thread(u->source_output, latency); 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 */ /* Called from sink I/O thread context */
static void sink_request_rewind_cb(pa_sink *s) { static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u; struct userdata *u;
@ -610,20 +541,6 @@ static void source_set_volume_cb(pa_source *s) {
pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, true); 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. */ /* Called from main context. */
static void source_get_volume_cb(pa_source *s) { static void source_get_volume_cb(pa_source *s) {
struct userdata *u; struct userdata *u;
@ -660,20 +577,6 @@ static void source_set_mute_cb(pa_source *s) {
pa_source_output_set_mute(u->source_output, s->muted, s->save_muted); 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. */ /* Called from source I/O thread context. */
static void apply_diff_time(struct userdata *u, int64_t diff_time) { static void apply_diff_time(struct userdata *u, int64_t diff_time) {
int64_t diff; int64_t diff;
@ -1156,21 +1059,6 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in
return pa_sink_input_process_msg(obj, code, data, offset, chunk); 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. */ /* Called from source I/O thread context. */
static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) { static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) {
struct userdata *u; struct userdata *u;
@ -1183,18 +1071,6 @@ static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbyte
pa_source_set_max_rewind_within_thread(u->source, nbytes); 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. */ /* Called from sink I/O thread context. */
static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) { static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) {
struct userdata *u; struct userdata *u;
@ -1221,20 +1097,6 @@ static void source_output_update_source_requested_latency_cb(pa_source_output *o
pa_log_debug("Source output update requested latency %lld", (long long) latency); 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. */ /* Called from source I/O thread context. */
static void source_output_update_source_latency_range_cb(pa_source_output *o) { static void source_output_update_source_latency_range_cb(pa_source_output *o) {
struct userdata *u; struct userdata *u;
@ -1249,19 +1111,6 @@ static void source_output_update_source_latency_range_cb(pa_source_output *o) {
pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency); 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. */ /* Called from source I/O thread context. */
static void source_output_update_source_fixed_latency_cb(pa_source_output *o) { static void source_output_update_source_fixed_latency_cb(pa_source_output *o) {
struct userdata *u; struct userdata *u;
@ -1329,6 +1178,10 @@ static void sink_input_attach_cb(pa_sink_input *i) {
PA_RTPOLL_LATE, PA_RTPOLL_LATE,
u->asyncmsgq); 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)) if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_attach_within_thread(u->sink); pa_sink_attach_within_thread(u->sink);
} }
@ -1357,14 +1210,10 @@ static void source_output_detach_cb(pa_source_output *o) {
static void sink_input_detach_cb(pa_sink_input *i) { static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u; struct userdata *u;
pa_sink_input_assert_ref(i); pa_virtual_sink_input_detach(i);
pa_assert_se(u = i->userdata); 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); pa_log_debug("Sink input %d detach", i->index);
if (u->rtpoll_item_write) { if (u->rtpoll_item_write) {
@ -1517,19 +1366,12 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
} }
/* Called from main context */ /* 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; struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata); pa_assert_se(u = i->userdata);
if (dest) { if (u->vsink->auto_desc) {
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) {
const char *y, *z; const char *y, *z;
pa_proplist *pl; pa_proplist *pl;
@ -1548,26 +1390,6 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
} }
} }
/* Called from main context */
static void sink_input_volume_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_volume_changed(u->sink, &i->volume);
}
/* Called from main context */
static void sink_input_mute_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_mute_changed(u->sink, i->muted);
}
/* Called from main context */ /* Called from main context */
static int canceller_process_msg_cb(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { 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; struct pa_echo_canceller_msg *msg;
@ -1911,6 +1733,10 @@ int pa__init(pa_module*m) {
pa_source_set_asyncmsgq(u->source, source_master->asyncmsgq); 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 */ /* Create sink */
pa_sink_new_data_init(&sink_data); pa_sink_new_data_init(&sink_data);
sink_data.driver = __FILE__; sink_data.driver = __FILE__;
@ -1948,18 +1774,16 @@ int pa__init(pa_module*m) {
goto fail; 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_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; 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->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); pa_sink_set_asyncmsgq(u->sink, sink_master->asyncmsgq);
/* Create source output */ /* Create source output */
@ -1999,7 +1823,7 @@ int pa__init(pa_module*m) {
u->source_output->moving = source_output_moving_cb; u->source_output->moving = source_output_moving_cb;
u->source_output->userdata = u; u->source_output->userdata = u;
u->source->output_from_master = u->source_output; u->vsource->output_from_master = u->source_output;
/* Create sink input */ /* Create sink input */
pa_sink_input_new_data_init(&sink_input_data); pa_sink_input_new_data_init(&sink_input_data);
@ -2022,26 +1846,19 @@ int pa__init(pa_module*m) {
if (!u->sink_input) if (!u->sink_input)
goto fail; 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->parent.process_msg = sink_input_process_msg_cb;
u->sink_input->pop = sink_input_pop_cb; u->sink_input->pop = sink_input_pop_cb;
u->sink_input->process_rewind = sink_input_process_rewind_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_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->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb; u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb; u->sink_input->detach = sink_input_detach_cb;
u->sink_input->state_change = sink_input_state_change_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->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->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); pa_sink_input_get_silence(u->sink_input, &silence);
@ -2173,6 +1990,12 @@ void pa__done(pa_module*m) {
pa_sink_input_unref(u->sink_input); pa_sink_input_unref(u->sink_input);
} }
if (u->vsink)
pa_xfree(u->vsink);
if (u->vsource)
pa_xfree(u->vsource);
if (u->source) if (u->source)
pa_source_unref(u->source); pa_source_unref(u->source);
if (u->sink) if (u->sink)

View file

@ -2,6 +2,28 @@ if host_machine.system() != 'windows'
subdir('rtp') subdir('rtp')
endif endif
libvirtual_sink = shared_library('virtual_sink',
'virtual-sink-common.c',
'virtual-sink-common.h',
c_args : [pa_c_args, server_c_args],
include_directories : [configinc, topinc],
dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep],
install_rpath : privlibdir,
install : true,
install_dir : modlibexecdir
)
libvirtual_source = shared_library('virtual_source',
'virtual-source-common.c',
'virtual-source-common.h',
c_args : [pa_c_args, server_c_args],
include_directories : [configinc, topinc],
dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep],
install_rpath : privlibdir,
install : true,
install_dir : modlibexecdir
)
# module name, sources, [headers, extra flags, extra deps, extra libs] # module name, sources, [headers, extra flags, extra deps, extra libs]
all_modules = [ all_modules = [
[ 'module-allow-passthrough', 'module-allow-passthrough.c' ], [ 'module-allow-passthrough', 'module-allow-passthrough.c' ],
@ -28,7 +50,7 @@ all_modules = [
[ 'module-http-protocol-tcp', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_HTTP', '-DUSE_TCP_SOCKETS'], [], libprotocol_http ], [ 'module-http-protocol-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-http-protocol-unix', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_HTTP', '-DUSE_UNIX_SOCKETS'], [], libprotocol_http ],
[ 'module-intended-roles', 'module-intended-roles.c' ], [ '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-loopback', 'module-loopback.c' ],
[ 'module-match', 'module-match.c' ], [ 'module-match', 'module-match.c' ],
[ 'module-native-protocol-fd', 'module-native-protocol-fd.c', [], [], [], libprotocol_native ], [ 'module-native-protocol-fd', 'module-native-protocol-fd.c', [], [], [], libprotocol_native ],
@ -37,8 +59,8 @@ all_modules = [
[ 'module-null-sink', 'module-null-sink.c' ], [ 'module-null-sink', 'module-null-sink.c' ],
[ 'module-null-source', 'module-null-source.c' ], [ 'module-null-source', 'module-null-source.c' ],
[ 'module-position-event-sounds', 'module-position-event-sounds.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-remap-source', 'module-remap-source.c', [], [], [], libvirtual_source ],
[ 'module-rescue-streams', 'module-rescue-streams.c' ], [ 'module-rescue-streams', 'module-rescue-streams.c' ],
[ 'module-role-cork', ['module-role-cork.c', 'stream-interaction.c'], 'stream-interaction.h' ], [ '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' ], [ 'module-role-ducking', ['module-role-ducking.c', 'stream-interaction.c'], 'stream-interaction.h' ],
@ -55,8 +77,8 @@ all_modules = [
[ 'module-tunnel-sink-new', ['module-tunnel-sink-new.c', 'restart-module.c'] ], [ 'module-tunnel-sink-new', ['module-tunnel-sink-new.c', 'restart-module.c'] ],
[ 'module-tunnel-source', ['module-tunnel.c', 'restart-module.c'], [], [], [x11_dep] ], [ 'module-tunnel-source', ['module-tunnel.c', 'restart-module.c'], [], [], [x11_dep] ],
[ 'module-tunnel-source-new', ['module-tunnel-source-new.c', 'restart-module.c'] ], [ '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-virtual-source', 'module-virtual-source.c', [], [], [], libvirtual_source ],
[ 'module-volume-restore', 'module-volume-restore.c' ], [ 'module-volume-restore', 'module-volume-restore.c' ],
] ]
@ -163,13 +185,13 @@ endif
if fftw_dep.found() if fftw_dep.found()
all_modules += [ 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 endif
if dbus_dep.found() and fftw_dep.found() if dbus_dep.found() and fftw_dep.found()
all_modules += [ 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 endif
@ -251,7 +273,7 @@ module_echo_cancel_sources = [
module_echo_cancel_orc_sources = [] module_echo_cancel_orc_sources = []
module_echo_cancel_flags = [] module_echo_cancel_flags = []
module_echo_cancel_deps = [libatomic_ops_dep] module_echo_cancel_deps = [libatomic_ops_dep]
module_echo_cancel_libs = [] module_echo_cancel_libs = [libvirtual_sink, libvirtual_source]
if get_option('adrian-aec') if get_option('adrian-aec')
module_echo_cancel_sources += [ module_echo_cancel_sources += [

View file

@ -124,10 +124,10 @@ struct output {
pa_memblockq *memblockq; pa_memblockq *memblockq;
/* For communication of the stream latencies to the main thread */ /* For communication of the stream latencies to the main thread */
pa_usec_t total_latency; int64_t total_latency;
struct { struct {
pa_usec_t timestamp; pa_usec_t timestamp;
pa_usec_t sink_latency; int64_t sink_latency;
size_t output_memblockq_size; size_t output_memblockq_size;
uint64_t receive_counter; uint64_t receive_counter;
} latency_snapshot; } latency_snapshot;
@ -249,8 +249,8 @@ static uint32_t rate_controller(
static void adjust_rates(struct userdata *u) { static void adjust_rates(struct userdata *u) {
struct output *o; struct output *o;
struct sink_snapshot rdata; struct sink_snapshot rdata;
pa_usec_t avg_total_latency = 0; int64_t avg_total_latency = 0;
pa_usec_t target_latency = 0; int64_t target_latency = 0;
pa_usec_t max_sink_latency = 0; pa_usec_t max_sink_latency = 0;
pa_usec_t min_total_latency = (pa_usec_t)-1; pa_usec_t min_total_latency = (pa_usec_t)-1;
uint32_t base_rate; uint32_t base_rate;
@ -280,7 +280,7 @@ static void adjust_rates(struct userdata *u) {
return; return;
PA_IDXSET_FOREACH(o, u->outputs, idx) { PA_IDXSET_FOREACH(o, u->outputs, idx) {
pa_usec_t snapshot_latency; int64_t snapshot_latency;
int64_t time_difference; int64_t time_difference;
if (!o->sink_input || !PA_SINK_IS_OPENED(o->sink->state)) if (!o->sink_input || !PA_SINK_IS_OPENED(o->sink->state))
@ -319,7 +319,7 @@ static void adjust_rates(struct userdata *u) {
/* Debug output */ /* 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); 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); 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++; n++;

View file

@ -28,6 +28,8 @@
#include <config.h> #include <config.h>
#endif #endif
#include <modules/virtual-sink-common.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <float.h> #include <float.h>
@ -50,10 +52,8 @@
#include <pulsecore/i18n.h> #include <pulsecore/i18n.h>
#include <pulsecore/aupdate.h> #include <pulsecore/aupdate.h>
#include <pulsecore/namereg.h> #include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h> #include <pulsecore/module.h>
#include <pulsecore/core-util.h> #include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h> #include <pulsecore/log.h>
#include <pulsecore/rtpoll.h> #include <pulsecore/rtpoll.h>
#include <pulsecore/sample-util.h> #include <pulsecore/sample-util.h>
@ -85,9 +85,7 @@ PA_MODULE_USAGE(
struct userdata { struct userdata {
pa_module *module; pa_module *module;
pa_sink *sink; pa_vsink *vsink;
pa_sink_input *sink_input;
bool autoloaded;
size_t channels; size_t channels;
size_t fft_size;//length (res) of fft size_t fft_size;//length (res) of fft
@ -125,8 +123,6 @@ struct userdata {
pa_database *database; pa_database *database;
char **base_profiles; char **base_profiles;
bool automatic_description;
}; };
static const char* const valid_modargs[] = { 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; u->input_buffer_max = min_buffer_length;
} }
/* Called from I/O thread context */ static pa_usec_t sink_get_extra_latency_cb(pa_sink *s) {
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) {
struct userdata *u; struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata); pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(state) || return pa_bytes_to_usec(pa_memblockq_get_length(u->output_q), &u->vsink->input_to_master->sample_spec);
!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);
} }
#if 1 #if 1
@ -521,14 +395,14 @@ static void dsp_logic(
#endif #endif
static void flatten_to_memblockq(struct userdata *u) { 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; pa_memchunk tchunk;
char *dst; char *dst;
size_t i = 0; size_t i = 0;
while(i < u->output_buffer_length) { while(i < u->output_buffer_length) {
tchunk.index = 0; tchunk.index = 0;
tchunk.length = PA_MIN((u->output_buffer_length - i), mbs); 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); //pa_log_debug("pushing %ld into the q", tchunk.length);
dst = pa_memblock_acquire(tchunk.memblock); dst = pa_memblock_acquire(tchunk.memblock);
memcpy(dst, u->output_buffer + i, tchunk.length); 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) { 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; unsigned a_i;
float *H, X; float *H, X;
size_t iterations, offset; 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) { 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; size_t samples = in->length/fs;
float *src = pa_memblock_acquire_chunk(in); float *src = pa_memblock_acquire_chunk(in);
pa_assert(u->samples_gathered + samples <= u->input_buffer_max); 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_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata); pa_assert_se(u = i->userdata);
pa_assert(chunk); 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; return -1;
/* FIXME: Please clean this up. I see more commented code lines /* FIXME: Please clean this up. I see more commented code lines
* than uncommented code lines. I am sorry, but I am too dumb to * than uncommented code lines. I am sorry, but I am too dumb to
* understand this. */ * understand this. */
fs = pa_frame_size(&(u->sink->sample_spec)); fs = pa_frame_size(&(u->vsink->sink->sample_spec));
mbs = pa_mempool_block_size_max(u->sink->core->mempool); mbs = pa_mempool_block_size_max(u->vsink->sink->core->mempool);
if (pa_memblockq_get_length(u->output_q) > 0) { if (pa_memblockq_get_length(u->output_q) > 0) {
//pa_log_debug("qsize is %ld", pa_memblockq_get_length(u->output_q)); //pa_log_debug("qsize is %ld", pa_memblockq_get_length(u->output_q));
goto END; 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); target_samples = PA_ROUND_UP(nbytes / fs, u->R);
////pa_log_debug("vanilla mbs = %ld",mbs); ////pa_log_debug("vanilla mbs = %ld",mbs);
//mbs = PA_ROUND_DOWN(mbs / fs, u->R); //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; chunk->memblock = NULL;
/* Hmm, process any rewind request that might be queued up */ /* 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_log_debug("start output-buffered %ld, input-buffered %ld, requested %ld",buffered_samples,u->samples_gathered,samples_requested);
//pa_rtclock_get(&start); //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); pa_assert(input_remaining > 0);
while (pa_memblockq_peek(u->input_q, &tchunk) < 0) { while (pa_memblockq_peek(u->input_q, &tchunk) < 0) {
//pa_sink_render(u->sink, input_remaining * fs, &tchunk); //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_memblockq_push(u->input_q, &tchunk);
pa_memblock_unref(tchunk.memblock); pa_memblock_unref(tchunk.memblock);
} }
@ -700,26 +574,6 @@ END:
return 0; 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 #if 0
static void reset_filter(struct userdata *u) { static void reset_filter(struct userdata *u) {
size_t fs = pa_frame_size(&u->sink->sample_spec); size_t fs = pa_frame_size(&u->sink->sample_spec);
@ -738,148 +592,6 @@ static void reset_filter(struct userdata *u) {
} }
#endif #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) { static void pack(char **strs, size_t len, char **packed, size_t *length) {
size_t t_len = 0; size_t t_len = 0;
size_t headers = (1+len) * sizeof(uint16_t); 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]); pa_aupdate_read_end(u->a_H[c]);
} }
key.data = u->sink->name; key.data = u->vsink->sink->name;
key.size = strlen(key.data); key.size = strlen(key.data);
data.data = state; data.data = state;
data.size = filter_state_size + packed_length; data.size = filter_state_size + packed_length;
@ -1032,7 +744,7 @@ static void load_state(struct userdata *u) {
return; return;
} }
key.data = u->sink->name; key.data = u->vsink->sink->name;
key.size = strlen(key.data); key.size = strlen(key.data);
if (pa_database_get(database, &key, &value) != NULL) { if (pa_database_get(database, &key, &value) != NULL) {
@ -1062,55 +774,12 @@ static void load_state(struct userdata *u) {
pa_database_close(database); 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) { int pa__init(pa_module*m) {
struct userdata *u; struct userdata *u;
pa_sample_spec ss; pa_sample_spec ss;
pa_channel_map map; pa_channel_map map;
pa_modargs *ma; pa_modargs *ma;
pa_sink *master; pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
size_t i; size_t i;
unsigned c; unsigned c;
float *H; float *H;
@ -1194,104 +863,23 @@ int pa__init(pa_module*m) {
for (c = 0; c < u->channels; ++c) for (c = 0; c < u->channels; ++c)
u->base_profiles[c] = pa_xstrdup("default"); u->base_profiles[c] = pa_xstrdup("default");
/* Create sink */ /* Create virtual sink */
pa_sink_new_data_init(&sink_data); if (!(u->vsink = pa_virtual_sink_create(master, "equalizer", "FFT based equalizer Sink", &ss, &map,
sink_data.driver = __FILE__; &ss, &map, m, u, ma, use_volume_sharing, false, 0)))
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);
goto fail; goto fail;
}
if (!pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION)) { u->vsink->get_extra_latency = sink_get_extra_latency_cb;
const char *master_description; 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); u->input_q = pa_memblockq_new("module-equalizer-sink input_q", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &u->vsink->sink->silence);
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->output_q = pa_memblockq_new("module-equalizer-sink output_q", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, NULL); 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 = NULL;
u->output_buffer_length = 0; u->output_buffer_length = 0;
u->output_buffer_max_length = 0; u->output_buffer_max_length = 0;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); u->vsink->memblockq = u->input_q;
//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;
dbus_init(u); dbus_init(u);
@ -1311,12 +899,8 @@ int pa__init(pa_module*m) {
/* load old parameters */ /* load old parameters */
load_state(u); load_state(u);
/* The order here is important. The input must be put first, if (pa_virtual_sink_activate(u->vsink) < 0)
* otherwise streams might attach to the sink before the sink goto fail;
* 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); pa_modargs_free(ma);
@ -1337,7 +921,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m); pa_assert(m);
pa_assert_se(u = m->userdata); 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) { void pa__done(pa_module*m) {
@ -1349,7 +933,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata)) if (!(u = m->userdata))
return; return;
save_state(u); if (u->vsink && u->vsink->sink)
save_state(u);
dbus_done(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[c]);
pa_xfree(u->base_profiles); 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_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->inverse_plan);
fftwf_destroy_plan(u->forward_plan); fftwf_destroy_plan(u->forward_plan);
@ -1622,21 +1195,21 @@ void dbus_init(struct userdata *u) {
uint32_t dummy; uint32_t dummy;
DBusMessage *message = NULL; DBusMessage *message = NULL;
pa_idxset *sink_list = NULL; pa_idxset *sink_list = NULL;
u->dbus_protocol=pa_dbus_protocol_get(u->sink->core); u->dbus_protocol=pa_dbus_protocol_get(u->vsink->sink->core);
u->dbus_path=pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index); 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); 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); sink_list = pa_shared_get(u->vsink->sink->core, SINKLIST);
u->database = pa_shared_get(u->sink->core, EQDB); u->database = pa_shared_get(u->vsink->sink->core, EQDB);
if (sink_list == NULL) { if (sink_list == NULL) {
char *state_path; char *state_path;
sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func); 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(state_path = pa_state_path(NULL, false));
pa_assert_se(u->database = pa_database_open(state_path, "equalizer-presets", false, true)); pa_assert_se(u->database = pa_database_open(state_path, "equalizer-presets", false, true));
pa_xfree(state_path); pa_xfree(state_path);
pa_shared_set(u->sink->core, EQDB, u->database); pa_shared_set(u->vsink->sink->core, EQDB, u->database);
pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core); 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_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME);
} }
pa_idxset_put(sink_list, u, &dummy); 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); pa_dbus_protocol_send_signal(u->dbus_protocol, message);
dbus_message_unref(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); pa_idxset_remove_by_data(sink_list,u,&dummy);
if (pa_idxset_size(sink_list) == 0) { if (pa_idxset_size(sink_list) == 0) {
pa_dbus_protocol_unregister_extension(u->dbus_protocol, EXTNAME); pa_dbus_protocol_unregister_extension(u->dbus_protocol, EXTNAME);
pa_dbus_protocol_remove_interface(u->dbus_protocol, MANAGER_PATH, manager_info.name); 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_database_close(u->database);
pa_shared_remove(u->sink->core, SINKLIST); pa_shared_remove(u->module->core, SINKLIST);
pa_xfree(sink_list); pa_xfree(sink_list);
} }
pa_dbus_protocol_remove_interface(u->dbus_protocol, u->dbus_path, equalizer_info.name); 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(conn);
pa_assert(msg); 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); 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; rev = 1;
n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE(u); 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; fft_size = (uint32_t) u->fft_size;
channels = (uint32_t) u->channels; channels = (uint32_t) u->channels;

View file

@ -272,7 +272,7 @@ static bool find_paired_master(struct userdata *u, struct filter *filter, pa_obj
} }
/* Make sure we're not routing to another instance of /* Make sure we're not routing to another instance of
* the same filter. */ * the same filter. */
filter->source_master = so->source->output_from_master->source; filter->source_master = so->source->vsource->output_from_master->source;
} else { } else {
filter->source_master = so->source; filter->source_master = so->source;
} }
@ -293,7 +293,7 @@ static bool find_paired_master(struct userdata *u, struct filter *filter, pa_obj
if (pa_streq(module_name, si->sink->module->name)) { if (pa_streq(module_name, si->sink->module->name)) {
/* Make sure we're not routing to another instance of /* Make sure we're not routing to another instance of
* the same filter. */ * the same filter. */
filter->sink_master = si->sink->input_to_master->sink; filter->sink_master = si->sink->vsink->input_to_master->sink;
} else { } else {
filter->sink_master = si->sink; filter->sink_master = si->sink;
} }
@ -461,7 +461,7 @@ static void find_filters_for_module(struct userdata *u, pa_module *m, const char
if (sink->module == m) { if (sink->module == m) {
pa_assert(pa_sink_is_filter(sink)); pa_assert(pa_sink_is_filter(sink));
fltr = filter_new(name, parameters, sink->input_to_master->sink, NULL); fltr = filter_new(name, parameters, sink->vsink->input_to_master->sink, NULL);
fltr->module_index = m->index; fltr->module_index = m->index;
fltr->sink = sink; fltr->sink = sink;
@ -474,12 +474,12 @@ static void find_filters_for_module(struct userdata *u, pa_module *m, const char
pa_assert(pa_source_is_filter(source)); pa_assert(pa_source_is_filter(source));
if (!fltr) { if (!fltr) {
fltr = filter_new(name, parameters, NULL, source->output_from_master->source); fltr = filter_new(name, parameters, NULL, source->vsource->output_from_master->source);
fltr->module_index = m->index; fltr->module_index = m->index;
fltr->source = source; fltr->source = source;
} else { } else {
fltr->source = source; fltr->source = source;
fltr->source_master = source->output_from_master->source; fltr->source_master = source->vsource->output_from_master->source;
} }
break; break;

View file

@ -24,16 +24,16 @@
#include <config.h> #include <config.h>
#endif #endif
#include <modules/virtual-sink-common.h>
#include <math.h> #include <math.h>
#include <pulse/xmalloc.h> #include <pulse/xmalloc.h>
#include <pulsecore/i18n.h> #include <pulsecore/i18n.h>
#include <pulsecore/namereg.h> #include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h> #include <pulsecore/module.h>
#include <pulsecore/core-util.h> #include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h> #include <pulsecore/log.h>
#include <pulsecore/rtpoll.h> #include <pulsecore/rtpoll.h>
#include <pulsecore/sample-util.h> #include <pulsecore/sample-util.h>
@ -76,8 +76,7 @@ They are not related and where possible the names of the LADSPA port variables c
struct userdata { struct userdata {
pa_module *module; pa_module *module;
pa_sink *sink; pa_vsink *vsink;
pa_sink_input *sink_input;
const LADSPA_Descriptor *descriptor; const LADSPA_Descriptor *descriptor;
LADSPA_Handle handle[PA_CHANNELS_MAX]; LADSPA_Handle handle[PA_CHANNELS_MAX];
@ -91,8 +90,6 @@ struct userdata {
about control out ports. We connect them all to this single buffer. */ about control out ports. We connect them all to this single buffer. */
LADSPA_Data control_out; LADSPA_Data control_out;
pa_memblockq *memblockq;
bool *use_default; bool *use_default;
pa_sample_spec ss; pa_sample_spec ss;
@ -100,9 +97,6 @@ struct userdata {
pa_dbus_protocol *dbus_protocol; pa_dbus_protocol *dbus_protocol;
char *dbus_path; char *dbus_path;
#endif #endif
bool auto_desc;
bool autoloaded;
}; };
static const char* const valid_modargs[] = { static const char* const valid_modargs[] = {
@ -124,13 +118,7 @@ static const char* const valid_modargs[] = {
NULL 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 int write_control_parameters(struct userdata *u, double *control_values, bool *use_default);
static void connect_control_ports(struct userdata *u);
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
@ -230,7 +218,7 @@ static void set_algorithm_parameters(DBusConnection *conn, DBusMessage *msg, DBu
goto error; 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); 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) { static void dbus_init(struct userdata *u) {
pa_assert_se(u); pa_assert_se(u);
u->dbus_protocol = pa_dbus_protocol_get(u->sink->core); u->dbus_protocol = pa_dbus_protocol_get(u->vsink->sink->core);
u->dbus_path = pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index); 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); 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 */ #endif /* HAVE_DBUS */
/* Called from I/O thread context */ /* 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) { static void filter_process_chunk(uint8_t *src_p, uint8_t *dst_p, unsigned in_count, unsigned out_count, void *userdata) {
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) {
struct userdata *u; struct userdata *u;
unsigned h, c;
float *src, *dst; float *src, *dst;
size_t fs;
unsigned n, h, c;
pa_memchunk tchunk;
pa_sink_input_assert_ref(i); pa_assert_se(u = userdata);
pa_assert(chunk); pa_assert(in_count == out_count);
pa_assert_se(u = i->userdata); src = (float *)src_p;
dst = (float *)dst_p;
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);
for (h = 0; h < (u->channels / u->max_ladspaport_count); h++) { for (h = 0; h < (u->channels / u->max_ladspaport_count); h++) {
for (c = 0; c < u->input_count; c++) 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); 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], n); u->descriptor->run(u->handle[h], in_count);
for (c = 0; c < u->output_count; c++) 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_sample_clamp(PA_SAMPLE_FLOAT32NE, dst + h * u->max_ladspaport_count + c, u->channels * sizeof(float), u->output[c], sizeof(float), in_count);
}
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);
} }
} }
/* Called from main context */ /* Called from I/O thread context */
static void sink_input_mute_changed_cb(pa_sink_input *i) { static void reset_filter(pa_sink *s, size_t amount) {
struct userdata *u; struct userdata *u;
unsigned c;
pa_sink_input_assert_ref(i); pa_assert_se(u = s->userdata);
pa_assert_se(u = i->userdata);
pa_sink_mute_changed(u->sink, i->muted); pa_log_debug("Resetting plugin");
}
/* Called from main context */ /* Reset the plugin */
static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause) { if (u->descriptor->deactivate)
struct userdata *u; for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
u->descriptor->deactivate(u->handle[c]);
pa_sink_input_assert_ref(i); if (u->descriptor->activate)
pa_assert_se(u = i->userdata); for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
u->descriptor->activate(u->handle[c]);
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);
} }
static int parse_control_parameters(struct userdata *u, const char *cdata, double *read_values, bool *use_default) { static int parse_control_parameters(struct userdata *u, const char *cdata, double *read_values, bool *use_default) {
@ -791,11 +427,12 @@ fail:
return -1; 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; unsigned long p = 0, h = 0, c;
const LADSPA_Descriptor *d; const LADSPA_Descriptor *d;
pa_assert(u); pa_assert_se(u = userdata);
pa_assert_se(d = u->descriptor); pa_assert_se(d = u->descriptor);
for (p = 0; p < d->PortCount; p++) { 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) { static int validate_control_parameters(struct userdata *u, double *control_values, bool *use_default) {
unsigned long p = 0, h = 0; unsigned long p = 0, h = 0;
const LADSPA_Descriptor *d; const LADSPA_Descriptor *d;
@ -1000,15 +643,12 @@ int pa__init(pa_module*m) {
char *t; char *t;
const char *master_name; const char *master_name;
pa_sink *master; 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; const char *plugin, *label, *input_ladspaport_map, *output_ladspaport_map;
LADSPA_Descriptor_Function descriptor_func; LADSPA_Descriptor_Function descriptor_func;
unsigned long input_ladspaport[PA_CHANNELS_MAX], output_ladspaport[PA_CHANNELS_MAX]; unsigned long input_ladspaport[PA_CHANNELS_MAX], output_ladspaport[PA_CHANNELS_MAX];
const char *e, *cdata; const char *e, *cdata;
const LADSPA_Descriptor *d; const LADSPA_Descriptor *d;
unsigned long p, h, j, n_control, c; unsigned long p, h, j, n_control, c;
pa_memchunk silence;
pa_assert(m); pa_assert(m);
@ -1273,113 +913,25 @@ int pa__init(pa_module*m) {
for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
d->activate(u->handle[c]); d->activate(u->handle[c]);
/* Create sink */ /* Create virtual sink */
pa_sink_new_data_init(&sink_data); if (!(u->vsink = pa_virtual_sink_create(master, "ladspa", "LADSPA Sink", &ss, &map,
sink_data.driver = __FILE__; &ss, &map, m, u, ma, true, true, 0)))
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)
goto fail; goto fail;
u->sink_input->pop = sink_input_pop_cb; u->vsink->process_chunk = filter_process_chunk;
u->sink_input->process_rewind = sink_input_process_rewind_cb; u->vsink->rewind_filter = reset_filter;
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; u->vsink->update_filter_parameters = update_filter_parameters;
u->sink_input->update_max_request = sink_input_update_max_request_cb; u->vsink->max_chunk_size = u->block_size;
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->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); if (pa_virtual_sink_activate(u->vsink) < 0)
u->memblockq = pa_memblockq_new("module-ladspa-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence); goto fail;
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);
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
dbus_init(u); dbus_init(u);
@ -1404,7 +956,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m); pa_assert(m);
pa_assert_se(u = m->userdata); 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) { void pa__done(pa_module*m) {
@ -1416,26 +968,12 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata)) if (!(u = m->userdata))
return; return;
/* See comments in sink_input_kill_cb() above regarding
* destruction order! */
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
dbus_done(u); dbus_done(u);
#endif #endif
if (u->sink_input) if (u->vsink)
pa_sink_input_cork(u->sink_input, true); pa_virtual_sink_destroy(u->vsink);
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);
for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) { for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) {
if (u->handle[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->control);
pa_xfree(u->use_default); pa_xfree(u->use_default);
pa_xfree(u); pa_xfree(u);

View file

@ -21,13 +21,13 @@
#include <config.h> #include <config.h>
#endif #endif
#include <modules/virtual-sink-common.h>
#include <pulse/xmalloc.h> #include <pulse/xmalloc.h>
#include <pulsecore/namereg.h> #include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h> #include <pulsecore/module.h>
#include <pulsecore/core-util.h> #include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h> #include <pulsecore/log.h>
#include <pulsecore/rtpoll.h> #include <pulsecore/rtpoll.h>
@ -50,10 +50,7 @@ PA_MODULE_USAGE(
struct userdata { struct userdata {
pa_module *module; pa_module *module;
pa_sink *sink; pa_vsink *vsink;
pa_sink_input *sink_input;
bool auto_desc;
}; };
static const char* const valid_modargs[] = { static const char* const valid_modargs[] = {
@ -70,273 +67,12 @@ static const char* const valid_modargs[] = {
NULL 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) { int pa__init(pa_module*m) {
struct userdata *u; struct userdata *u;
pa_sample_spec ss; pa_sample_spec ss;
pa_resample_method_t resample_method = PA_RESAMPLER_INVALID;
pa_channel_map sink_map, stream_map; pa_channel_map sink_map, stream_map;
pa_modargs *ma; pa_modargs *ma;
pa_sink *master; pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
bool remix = true;
pa_assert(m); pa_assert(m);
@ -371,100 +107,17 @@ int pa__init(pa_module*m) {
if (pa_channel_map_equal(&stream_map, &master->channel_map)) if (pa_channel_map_equal(&stream_map, &master->channel_map))
pa_log_warn("No remapping configured, proceeding nonetheless!"); 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 = pa_xnew0(struct userdata, 1);
u->module = m; u->module = m;
m->userdata = u; m->userdata = u;
/* Create sink */ /* Create virtual sink */
pa_sink_new_data_init(&sink_data); if (!(u->vsink = pa_virtual_sink_create(master, "remapped", "Remapped Sink", &ss, &sink_map,
sink_data.driver = __FILE__; &ss, &stream_map, m, u, ma, false, false, 0)))
sink_data.module = m; goto fail;
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");
if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { if (pa_virtual_sink_activate(u->vsink) < 0)
pa_log("Invalid properties");
pa_sink_new_data_done(&sink_data);
goto fail; 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); pa_modargs_free(ma);
@ -485,7 +138,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m); pa_assert(m);
pa_assert_se(u = m->userdata); 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) { void pa__done(pa_module*m) {
@ -496,22 +149,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata)) if (!(u = m->userdata))
return; return;
/* See comments in sink_input_kill_cb() above regarding if (u->vsink)
* destruction order! */ pa_virtual_sink_destroy(u->vsink);
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); pa_xfree(u);
} }

View file

@ -22,6 +22,8 @@
#include <config.h> #include <config.h>
#endif #endif
#include <modules/virtual-source-common.h>
#include <stdio.h> #include <stdio.h>
#include <pulse/xmalloc.h> #include <pulse/xmalloc.h>
@ -47,6 +49,7 @@ PA_MODULE_USAGE(
"source_properties=<properties for the source> " "source_properties=<properties for the source> "
"master=<name of source to filter> " "master=<name of source to filter> "
"master_channel_map=<channel map> " "master_channel_map=<channel map> "
"uplink_sink=<name> (optional)"
"format=<sample format> " "format=<sample format> "
"rate=<sample rate> " "rate=<sample rate> "
"channels=<number of channels> " "channels=<number of channels> "
@ -57,10 +60,7 @@ PA_MODULE_USAGE(
struct userdata { struct userdata {
pa_module *module; pa_module *module;
pa_source *source; pa_vsource *vsource;
pa_source_output *source_output;
bool auto_desc;
}; };
static const char* const valid_modargs[] = { static const char* const valid_modargs[] = {
@ -68,6 +68,7 @@ static const char* const valid_modargs[] = {
"source_properties", "source_properties",
"master", "master",
"master_channel_map", "master_channel_map",
"uplink_sink",
"format", "format",
"rate", "rate",
"channels", "channels",
@ -77,226 +78,12 @@ static const char* const valid_modargs[] = {
NULL 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) { int pa__init(pa_module*m) {
struct userdata *u; struct userdata *u;
pa_sample_spec ss; pa_sample_spec ss;
pa_resample_method_t resample_method = PA_RESAMPLER_INVALID;
pa_channel_map source_map, stream_map; pa_channel_map source_map, stream_map;
pa_modargs *ma; pa_modargs *ma;
pa_source *master; pa_source *master;
pa_source_output_new_data source_output_data;
pa_source_new_data source_data;
bool remix = true;
pa_assert(m); pa_assert(m);
@ -331,98 +118,17 @@ int pa__init(pa_module*m) {
if (pa_channel_map_equal(&stream_map, &master->channel_map)) if (pa_channel_map_equal(&stream_map, &master->channel_map))
pa_log_warn("No remapping configured, proceeding nonetheless!"); 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 = pa_xnew0(struct userdata, 1);
u->module = m; u->module = m;
m->userdata = u; m->userdata = u;
/* Create source */ /* Create virtual sink */
pa_source_new_data_init(&source_data); if (!(u->vsource = pa_virtual_source_create(master, "remapped", "Remapped Source", &ss, &source_map,
source_data.driver = __FILE__; &ss, &stream_map, m, u, ma, false, false)))
source_data.module = m; goto fail;
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");
if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { if (pa_virtual_source_activate(u->vsource) < 0)
pa_log("Invalid properties.");
pa_source_new_data_done(&source_data);
goto fail; 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); pa_modargs_free(ma);
@ -443,7 +149,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m); pa_assert(m);
pa_assert_se(u = m->userdata); 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) { void pa__done(pa_module*m) {
@ -454,22 +160,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata)) if (!(u = m->userdata))
return; return;
/* See comments in source_output_kill_cb() above regarding if (u->vsource)
* destruction order! */ pa_virtual_source_destroy(u->vsource);
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);
pa_xfree(u); pa_xfree(u);
} }

View file

@ -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); 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_log_info("Source %s idle for too long, suspending ...", d->source->name);
pa_source_suspend(d->source, true, PA_SUSPEND_IDLE); pa_source_suspend(d->source, true, PA_SUSPEND_IDLE);
pa_core_maybe_vacuum(d->userdata->core); 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) { 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; 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) { if (d) {
resume(d); resume(d);
if (d->source) { if (d->source) {
if (pa_source_check_suspend(d->source, NULL) <= 0) if (pa_source_check_suspend(d->source, NULL, NULL) <= 0)
restart(d); restart(d);
} else { } else {
/* The source output is connected to a monitor source. */ /* 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; struct device_info *d;
if ((d = pa_hashmap_get(u->device_infos, s->sink))) if ((d = pa_hashmap_get(u->device_infos, s->sink)))
restart(d); 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; 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) if (pa_sink_check_suspend(s->source->monitor_of, NULL, s) <= 0)
d = pa_hashmap_get(u->device_infos, s->source->monitor_of); d = pa_hashmap_get(u->device_infos, s->source->monitor_of);
} else { } 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); d = pa_hashmap_get(u->device_infos, s->source);
} }
if (d) if (d)
restart(d); restart_check_uplink(d, s, u);
return PA_HOOK_OK; 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_sink_input_assert_ref(s);
pa_assert(u); 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))) if ((d = pa_hashmap_get(u->device_infos, s->sink)))
restart(d); 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; 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) if (pa_sink_check_suspend(s->source->monitor_of, NULL, s) <= 0)
d = pa_hashmap_get(u->device_infos, s->source->monitor_of); d = pa_hashmap_get(u->device_infos, s->source->monitor_of);
} else { } 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); d = pa_hashmap_get(u->device_infos, s->source);
} }
if (d) if (d)
restart(d); restart_check_uplink(d, s, u);
return PA_HOOK_OK; 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_sink_input_assert_ref(s);
pa_assert(u); pa_assert(u);
if (s->state == PA_SINK_INPUT_RUNNING && s->sink) if (s->sink) {
if ((d = pa_hashmap_get(u->device_infos, s->sink))) if ((d = pa_hashmap_get(u->device_infos, s->sink))) {
resume(d); 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; 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_assert(c);
pa_source_output_assert_ref(s); pa_source_output_assert_ref(s);
pa_assert(u); pa_assert(u);
struct device_info *d = NULL;
if (s->state == PA_SOURCE_OUTPUT_RUNNING && s->source) { if (s->state == PA_SOURCE_OUTPUT_RUNNING && s->source) {
struct device_info *d;
if (s->source->monitor_of) if (s->source->monitor_of)
d = pa_hashmap_get(u->device_infos, 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) if (d)
resume(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; 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); pa_hashmap_put(u->device_infos, o, d);
if ((d->sink && pa_sink_check_suspend(d->sink, NULL, NULL) <= 0) || 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); restart(d);
return PA_HOOK_OK; 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)) { } else if (pa_source_isinstance(o)) {
pa_source *s = PA_SOURCE(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)) if (PA_SOURCE_IS_OPENED(s->state))
restart(d); restart(d);
} }

View file

@ -22,15 +22,15 @@
#include <config.h> #include <config.h>
#endif #endif
#include <modules/virtual-sink-common.h>
#include <pulse/gccmacro.h> #include <pulse/gccmacro.h>
#include <pulse/xmalloc.h> #include <pulse/xmalloc.h>
#include <pulsecore/i18n.h> #include <pulsecore/i18n.h>
#include <pulsecore/namereg.h> #include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h> #include <pulsecore/module.h>
#include <pulsecore/core-util.h> #include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h> #include <pulsecore/log.h>
#include <pulsecore/rtpoll.h> #include <pulsecore/rtpoll.h>
#include <pulsecore/sample-util.h> #include <pulsecore/sample-util.h>
@ -46,25 +46,17 @@ PA_MODULE_USAGE(
"master=<name of sink to filter> " "master=<name of sink to filter> "
"rate=<sample rate> " "rate=<sample rate> "
"channels=<number of channels> " "channels=<number of channels> "
"format=<sample format> "
"channel_map=<channel map> " "channel_map=<channel map> "
"use_volume_sharing=<yes or no> " "use_volume_sharing=<yes or no> "
"force_flat_volume=<yes or no> " "force_flat_volume=<yes or no> "
)); ));
#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
struct userdata { struct userdata {
pa_module *module; pa_module *module;
/* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ pa_vsink *vsink;
/* bool autoloaded; */
pa_sink *sink;
pa_sink_input *sink_input;
pa_memblockq *memblockq;
bool auto_desc;
unsigned channels; unsigned channels;
}; };
@ -74,6 +66,7 @@ static const char* const valid_modargs[] = {
"master", "master",
"rate", "rate",
"channels", "channels",
"format",
"channel_map", "channel_map",
"use_volume_sharing", "use_volume_sharing",
"force_flat_volume", "force_flat_volume",
@ -81,397 +74,17 @@ static const char* const valid_modargs[] = {
}; };
/* Called from I/O thread context */ /* 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) { static void filter_process_chunk(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata) {
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) {
struct userdata *u; struct userdata *u;
unsigned buffer_size;
pa_sink_assert_ref(s); pa_assert_se(u = userdata);
pa_assert_se(u = s->userdata); pa_assert(in_count == out_count);
if (!PA_SINK_IS_LINKED(state) || buffer_size = pa_frame_size(&u->vsink->sink->sample_spec) * in_count;
!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 */
/* As an example, copy input to output */ /* As an example, copy input to output */
for (c = 0; c < u->channels; c++) { memcpy(dst, src, buffer_size);
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);
} }
int pa__init(pa_module*m) { int pa__init(pa_module*m) {
@ -480,11 +93,7 @@ int pa__init(pa_module*m) {
pa_channel_map map; pa_channel_map map;
pa_modargs *ma; pa_modargs *ma;
pa_sink *master=NULL; pa_sink *master=NULL;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
bool use_volume_sharing = true; bool use_volume_sharing = true;
bool force_flat_volume = false;
pa_memchunk silence;
pa_assert(m); pa_assert(m);
@ -501,7 +110,6 @@ int pa__init(pa_module*m) {
pa_assert(master); pa_assert(master);
ss = master->sample_spec; ss = master->sample_spec;
ss.format = PA_SAMPLE_FLOAT32;
map = master->channel_map; map = master->channel_map;
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { 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"); pa_log("Invalid sample format specification or channel map");
@ -513,118 +121,23 @@ int pa__init(pa_module*m) {
goto fail; 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 = pa_xnew0(struct userdata, 1);
u->module = m; u->module = m;
m->userdata = u; m->userdata = u;
u->channels = ss.channels; u->channels = ss.channels;
/* Create sink */ /* Create virtual sink */
pa_sink_new_data_init(&sink_data); if (!(u->vsink = pa_virtual_sink_create(master, "vsink", "Virtual Sink", &ss, &map,
sink_data.driver = __FILE__; &ss, &map, m, u, ma, use_volume_sharing, true, 0)))
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)
goto fail; goto fail;
u->sink_input->pop = sink_input_pop_cb; /* Set callback for virtual sink */
u->sink_input->process_rewind = sink_input_process_rewind_cb; u->vsink->process_chunk = filter_process_chunk;
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;
u->sink->input_to_master = u->sink_input; /* INITIALIZE YOUR FILTER HERE */
pa_sink_input_get_silence(u->sink_input, &silence); if (pa_virtual_sink_activate(u->vsink) < 0)
u->memblockq = pa_memblockq_new("module-virtual-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence); goto fail;
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);
pa_modargs_free(ma); pa_modargs_free(ma);
@ -645,7 +158,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m); pa_assert(m);
pa_assert_se(u = m->userdata); 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) { void pa__done(pa_module*m) {
@ -656,25 +169,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata)) if (!(u = m->userdata))
return; return;
/* See comments in sink_input_kill_cb() above regarding if (u->vsink)
* destruction order! */ pa_virtual_sink_destroy(u->vsink);
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);
pa_xfree(u); pa_xfree(u);
} }

View file

@ -26,6 +26,8 @@
#include <pulse/xmalloc.h> #include <pulse/xmalloc.h>
#include <modules/virtual-source-common.h>
#include <pulsecore/i18n.h> #include <pulsecore/i18n.h>
#include <pulsecore/macro.h> #include <pulsecore/macro.h>
#include <pulsecore/namereg.h> #include <pulsecore/namereg.h>
@ -63,23 +65,8 @@ PA_MODULE_USAGE(
struct userdata { struct userdata {
pa_module *module; pa_module *module;
/* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ pa_vsource *vsource;
/* bool autoloaded; */
pa_source *source;
pa_source_output *source_output;
pa_memblockq *memblockq;
bool auto_desc;
unsigned channels; unsigned channels;
/* optional fields for uplink sink */
pa_sink *sink;
pa_usec_t block_usec;
pa_memblockq *sink_memblockq;
pa_rtpoll *rtpoll;
}; };
static const char* const valid_modargs[] = { static const char* const valid_modargs[] = {
@ -93,393 +80,21 @@ static const char* const valid_modargs[] = {
"channel_map", "channel_map",
"use_volume_sharing", "use_volume_sharing",
"force_flat_volume", "force_flat_volume",
"autoloaded",
NULL NULL
}; };
/* Called from I/O thread context */ static void filter_process_chunk(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata) {
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; struct userdata *u;
size_t nbytes;
pa_sink_assert_ref(s); pa_assert_se(u = userdata);
pa_assert_se(u = s->userdata); pa_assert(in_count == out_count);
if (!PA_SINK_IS_LINKED(state)) { nbytes = in_count * pa_frame_size(&u->vsource->source->sample_spec);
return 0;
}
if (state == PA_SINK_RUNNING) { /* Copy input to output */
/* need to wake-up source if it was suspended */ memcpy(dst, src, nbytes);
pa_log_debug("Resuming source %s, because its uplink sink became active.", u->source->name);
pa_source_suspend(u->source, false, PA_SUSPEND_ALL);
/* FIXME: if there's no client connected, the source will suspend
and playback will be stuck. You'd want to prevent the source from
sleeping when the uplink sink is active; even if the audio is
discarded at least the app isn't stuck */
} else {
/* nothing to do, if the sink becomes idle or suspended let
module-suspend-idle handle the sources later */
}
return 0;
}
static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
/* FIXME: there's no latency support */
}
/* Called from I/O thread context */
static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SOURCE(o)->userdata;
switch (code) {
case PA_SOURCE_MESSAGE_GET_LATENCY:
/* The source is _put() before the source output is, so let's
* make sure we don't access it in that time. Also, the
* source output is first shut down, the source second. */
if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) {
*((pa_usec_t*) data) = 0;
return 0;
}
*((pa_usec_t*) data) =
/* Get the latency of the master source */
pa_source_get_latency_within_thread(u->source_output->source, true) +
/* Add the latency internal to our source output on top */
/* FIXME, no idea what I am doing here */
pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec);
/* Add resampler delay */
*((int64_t*) data) += pa_resampler_get_delay_usec(u->source_output->thread_info.resampler);
return 0;
}
return pa_source_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int source_set_state_in_main_thread_cb(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) {
struct userdata *u;
pa_source_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SOURCE_IS_LINKED(state) ||
!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state))
return 0;
pa_source_output_cork(u->source_output, state == PA_SOURCE_SUSPENDED);
return 0;
}
/* Called from I/O thread context */
static void source_update_requested_latency_cb(pa_source *s) {
struct userdata *u;
pa_source_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state))
return;
/* Just hand this one over to the master source */
pa_source_output_set_requested_latency_within_thread(
u->source_output,
pa_source_get_requested_latency_within_thread(s));
}
/* Called from main context */
static void source_set_volume_cb(pa_source *s) {
struct userdata *u;
pa_source_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SOURCE_IS_LINKED(s->state) ||
!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state))
return;
pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, true);
}
/* Called from main context */
static void source_set_mute_cb(pa_source *s) {
struct userdata *u;
pa_source_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SOURCE_IS_LINKED(s->state) ||
!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state))
return;
pa_source_output_set_mute(u->source_output, s->muted, s->save_muted);
}
/* Called from input thread context */
static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state))
return;
if (!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) {
pa_log("push when no link?");
return;
}
/* PUT YOUR CODE HERE TO DO SOMETHING WITH THE SOURCE DATA */
/* if uplink sink exists, pull data from there; simplify by using
same length as chunk provided by source */
if (u->sink && (u->sink->thread_info.state == PA_SINK_RUNNING)) {
pa_memchunk tchunk;
size_t nbytes = chunk->length;
pa_mix_info streams[2];
pa_memchunk target_chunk;
void *target;
int ch;
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind(u->sink, 0);
/* get data from the sink */
while (pa_memblockq_peek(u->sink_memblockq, &tchunk) < 0) {
pa_memchunk nchunk;
/* make sure we get nbytes from the sink with render_full,
otherwise we cannot mix with the uplink */
pa_sink_render_full(u->sink, nbytes, &nchunk);
pa_memblockq_push(u->sink_memblockq, &nchunk);
pa_memblock_unref(nchunk.memblock);
}
pa_assert(tchunk.length == chunk->length);
/* move the read pointer for sink memblockq */
pa_memblockq_drop(u->sink_memblockq, tchunk.length);
/* allocate target chunk */
/* this could probably be done in-place, but having chunk as both
the input and output creates issues with reference counts */
target_chunk.index = 0;
target_chunk.length = chunk->length;
pa_assert(target_chunk.length == chunk->length);
target_chunk.memblock = pa_memblock_new(o->source->core->mempool,
target_chunk.length);
pa_assert( target_chunk.memblock );
/* get target pointer */
target = pa_memblock_acquire_chunk(&target_chunk);
/* set-up mixing structure
volume was taken care of in sink and source already */
streams[0].chunk = *chunk;
for(ch=0;ch<o->sample_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;ch<o->sample_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) { int pa__init(pa_module*m) {
@ -488,14 +103,7 @@ int pa__init(pa_module*m) {
pa_channel_map map; pa_channel_map map;
pa_modargs *ma; pa_modargs *ma;
pa_source *master=NULL; pa_source *master=NULL;
pa_source_output_new_data source_output_data;
pa_source_new_data source_data;
bool use_volume_sharing = true; bool use_volume_sharing = true;
bool force_flat_volume = false;
/* optional for uplink_sink */
pa_sink_new_data sink_data;
size_t nbytes;
pa_assert(m); pa_assert(m);
@ -512,7 +120,6 @@ int pa__init(pa_module*m) {
pa_assert(master); pa_assert(master);
ss = master->sample_spec; ss = master->sample_spec;
ss.format = PA_SAMPLE_FLOAT32;
map = master->channel_map; map = master->channel_map;
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { 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"); pa_log("Invalid sample format specification or channel map");
@ -524,174 +131,21 @@ int pa__init(pa_module*m) {
goto fail; 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 = pa_xnew0(struct userdata, 1);
u->module = m; u->module = m;
m->userdata = u; 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; u->channels = ss.channels;
/* The rtpoll created here is never run. It is only necessary to avoid crashes /* Create virtual source */
* when module-virtual-source is used together with module-loopback or if (!(u->vsource = pa_virtual_source_create(master, "vsource", "Virtual Source", &ss, &map,
* module-combine-sink. Both modules base their asyncmsq on the rtpoll provided &ss, &map, m, u, ma, use_volume_sharing, true)))
* by the sink. module-loopback and combine-sink only work because they
* call pa_asyncmsq_process_one() themselves. */
u->rtpoll = pa_rtpoll_new();
/* Create source */
pa_source_new_data_init(&source_data);
source_data.driver = __FILE__;
source_data.module = m;
if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL))))
source_data.name = pa_sprintf_malloc("%s.vsource", master->name);
pa_source_new_data_set_sample_spec(&source_data, &ss);
pa_source_new_data_set_channel_map(&source_data, &map);
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
pa_proplist_sets(source_data.proplist, "device.vsource.name", source_data.name);
if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_source_new_data_done(&source_data);
goto fail;
}
if ((u->auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
const char *z;
z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Source %s on %s", source_data.name, z ? z : master->name);
}
u->source = pa_source_new(m->core, &source_data, (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY))
| (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0));
pa_source_new_data_done(&source_data);
if (!u->source) {
pa_log("Failed to create source.");
goto fail;
}
u->source->parent.process_msg = source_process_msg_cb;
u->source->set_state_in_main_thread = source_set_state_in_main_thread_cb;
u->source->update_requested_latency = source_update_requested_latency_cb;
pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
if (!use_volume_sharing) {
pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
pa_source_enable_decibel_volume(u->source, true);
}
/* Normally this flag would be enabled automatically be we can force it. */
if (force_flat_volume)
u->source->flags |= PA_SOURCE_FLAT_VOLUME;
u->source->userdata = u;
pa_source_set_asyncmsgq(u->source, master->asyncmsgq);
/* Create source output */
pa_source_output_new_data_init(&source_output_data);
source_output_data.driver = __FILE__;
source_output_data.module = m;
pa_source_output_new_data_set_source(&source_output_data, master, false, true);
source_output_data.destination_source = u->source;
pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Source Stream of %s", pa_proplist_gets(u->source->proplist, PA_PROP_DEVICE_DESCRIPTION));
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_source_output_new_data_set_sample_spec(&source_output_data, &ss);
pa_source_output_new_data_set_channel_map(&source_output_data, &map);
source_output_data.flags |= PA_SOURCE_OUTPUT_START_CORKED;
pa_source_output_new(&u->source_output, m->core, &source_output_data);
pa_source_output_new_data_done(&source_output_data);
if (!u->source_output)
goto fail; goto fail;
u->source_output->push = source_output_push_cb; /* Set callback for virtual source */
u->source_output->process_rewind = source_output_process_rewind_cb; u->vsource->process_chunk = filter_process_chunk;
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; if (pa_virtual_source_activate(u->vsource) < 0)
goto fail;
/* The order here is important. The output must be put first,
* otherwise streams might attach to the source before the
* source output is attached to the master. */
pa_source_output_put(u->source_output);
pa_source_put(u->source);
pa_source_output_cork(u->source_output, false);
/* Create optional uplink sink */
pa_sink_new_data_init(&sink_data);
sink_data.driver = __FILE__;
sink_data.module = m;
if ((sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "uplink_sink", NULL)))) {
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &map);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "uplink sink");
pa_proplist_sets(sink_data.proplist, "device.uplink_sink.name", sink_data.name);
if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
const char *z;
z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Uplink Sink %s on %s", sink_data.name, z ? z : master->name);
}
u->sink_memblockq = pa_memblockq_new("module-virtual-source sink_memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, NULL);
if (!u->sink_memblockq) {
pa_sink_new_data_done(&sink_data);
pa_log("Failed to create sink memblockq.");
goto fail;
}
u->sink = pa_sink_new(m->core, &sink_data, 0); /* FIXME, sink has no capabilities */
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
pa_log("Failed to create sink.");
goto fail;
}
u->sink->parent.process_msg = sink_process_msg_cb;
u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
pa_sink_set_rtpoll(u->sink, u->rtpoll);
/* FIXME: no idea what I am doing here */
u->block_usec = BLOCK_USEC;
nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec);
pa_sink_set_max_rewind(u->sink, 0);
pa_sink_set_max_request(u->sink, nbytes);
pa_sink_put(u->sink);
} else {
pa_sink_new_data_done(&sink_data);
/* optional uplink sink not enabled */
u->sink = NULL;
}
pa_modargs_free(ma); pa_modargs_free(ma);
@ -712,7 +166,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m); pa_assert(m);
pa_assert_se(u = m->userdata); 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) { void pa__done(pa_module*m) {
@ -723,36 +177,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata)) if (!(u = m->userdata))
return; return;
/* See comments in source_output_kill_cb() above regarding if (u->vsource)
* destruction order! */ pa_virtual_source_destroy(u->vsource);
if (u->source_output)
pa_source_output_cork(u->source_output, true);
if (u->source)
pa_source_unlink(u->source);
if (u->source_output) {
pa_source_output_unlink(u->source_output);
pa_source_output_unref(u->source_output);
}
if (u->source)
pa_source_unref(u->source);
if (u->sink) {
pa_sink_unlink(u->sink);
pa_sink_unref(u->sink);
}
if (u->memblockq)
pa_memblockq_free(u->memblockq);
if (u->sink_memblockq)
pa_memblockq_free(u->sink_memblockq);
if (u->rtpoll)
pa_rtpoll_free(u->rtpoll);
pa_xfree(u); pa_xfree(u);
} }

View file

@ -29,6 +29,8 @@
#include <fftw3.h> #include <fftw3.h>
#include <modules/virtual-sink-common.h>
#include <pulse/gccmacro.h> #include <pulse/gccmacro.h>
#include <pulse/xmalloc.h> #include <pulse/xmalloc.h>
@ -73,14 +75,7 @@ PA_MODULE_USAGE(
struct userdata { struct userdata {
pa_module *module; pa_module *module;
bool autoloaded; pa_vsink *vsink;
pa_sink *sink;
pa_sink_input *sink_input;
pa_memblockq *memblockq_sink;
bool auto_desc;
size_t fftlen; size_t fftlen;
size_t hrir_samples; size_t hrir_samples;
@ -111,6 +106,76 @@ static const char* const valid_modargs[] = {
NULL 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 */ /* Vector size of 4 floats */
#define v_size 4 #define v_size 4
static void * alloc(size_t x, size_t s) { static void * alloc(size_t x, size_t s) {
@ -124,26 +189,6 @@ static void * alloc(size_t x, size_t s) {
return t; 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 */ /* Mirror channels for symmetrical impulse */
static pa_channel_position_t mirror_channel(pa_channel_position_t channel) { static pa_channel_position_t mirror_channel(pa_channel_position_t channel) {
switch (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) { int pa__init(pa_module*m) {
struct userdata *u; struct userdata *u;
pa_sample_spec ss_input, ss_output; 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_left_file;
const char *hrir_right_file; const char *hrir_right_file;
pa_sink *master=NULL; pa_sink *master=NULL;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
bool use_volume_sharing = true; 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; unsigned i, j, ear, found_channel_left, found_channel_right;
pa_sample_spec ss; pa_sample_spec ss;
@ -722,7 +319,7 @@ int pa__init(pa_module*m) {
float *hrir_temp_data; float *hrir_temp_data;
size_t hrir_samples; size_t hrir_samples;
size_t hrir_copied_length, hrir_total_length; size_t hrir_copied_length, hrir_total_length;
int hrir_channels; unsigned hrir_channels;
int fftlen; int fftlen;
float *impulse_temp=NULL; float *impulse_temp=NULL;
@ -823,114 +420,20 @@ int pa__init(pa_module*m) {
goto fail; 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); pa_channel_map_init_stereo(&map_output);
u = pa_xnew0(struct userdata, 1); u = pa_xnew0(struct userdata, 1);
u->module = m; u->module = m;
m->userdata = u; m->userdata = u;
/* Create sink */ /* Create virtual sink */
pa_sink_new_data_init(&sink_data); if (!(u->vsink = pa_virtual_sink_create(master, "vsurroundsink", "Virtual Surround Sink", &ss_input, &map,
sink_data.driver = __FILE__; &ss_output, &map_output, m, u, ma, use_volume_sharing, true, 0)))
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)
goto fail; goto fail;
u->sink_input->pop = sink_input_pop_cb; u->vsink->process_chunk = filter_process_chunk;
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->sink->input_to_master = u->sink_input; 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_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,
PA_RESAMPLER_SRC_SINC_BEST_QUALITY, PA_RESAMPLER_NO_REMAP); 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; 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_left);
pa_xfree(mapping_right); 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); u->vsink->fixed_block_size = BLOCK_SIZE;
pa_memblock_unref(silence.memblock); 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); if (pa_virtual_sink_activate(u->vsink) < 0)
pa_memblockq_flush_read(u->memblockq_sink); goto fail;
pa_sink_put(u->sink);
pa_sink_input_put(u->sink_input);
pa_modargs_free(ma); pa_modargs_free(ma);
@ -1183,7 +683,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m); pa_assert(m);
pa_assert_se(u = m->userdata); 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) { void pa__done(pa_module*m) {
@ -1195,23 +695,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata)) if (!(u = m->userdata))
return; return;
/* See comments in sink_input_kill_cb() above regarding if (u->vsink)
* destruction order! */ pa_virtual_sink_destroy(u->vsink);
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->p_fw) { if (u->p_fw) {
for (i = 0, j = u->inputs; i < j; i++) { for (i = 0, j = u->inputs; i < j; i++) {

File diff suppressed because it is too large Load diff

View file

@ -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 <http://www.gnu.org/licenses/>.
***/
#include <pulsecore/sink.h>
#include <pulsecore/modargs.h>
/* 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);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,74 @@
/***
This file is part of PulseAudio.
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#include <pulsecore/source.h>
#include <pulsecore/modargs.h>
/* Callbacks for virtual sources. */
int pa_virtual_source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk);
int pa_virtual_source_set_state_in_main_thread(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause);
int pa_virtual_source_set_state_in_io_thread(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause);
void pa_virtual_source_update_requested_latency(pa_source *s);
void pa_virtual_source_set_volume(pa_source *s);
void pa_virtual_source_set_mute(pa_source *s);
void pa_virtual_source_output_push(pa_source_output *o, const pa_memchunk *chunk);
void pa_virtual_source_output_update_source_latency_range(pa_source_output *o);
void pa_virtual_source_output_update_source_fixed_latency(pa_source_output *o);
void pa_virtual_source_output_process_rewind(pa_source_output *o, size_t nbytes);
void pa_virtual_source_output_update_max_rewind(pa_source_output *o, size_t nbytes);
void pa_virtual_source_output_detach(pa_source_output *o);
void pa_virtual_source_output_attach(pa_source_output *o);
void pa_virtual_source_output_kill(pa_source_output *o);
void pa_virtual_source_output_moving(pa_source_output *o, pa_source *dest);
bool pa_virtual_source_output_may_move_to(pa_source_output *o, pa_source *dest);
void pa_virtual_source_output_volume_changed(pa_source_output *o);
void pa_virtual_source_output_mute_changed(pa_source_output *o);
void pa_virtual_source_output_suspend(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause);
/* Set callbacks for virtual source and source output. */
void pa_virtual_source_set_callbacks(pa_source *s, bool use_volume_sharing);
void pa_virtual_source_output_set_callbacks(pa_source_output *o, bool use_volume_sharing);
/* Create a new virtual source. Returns a filled vsource structure or NULL on failure. */
pa_vsource *pa_virtual_source_create(pa_source *master, const char *source_type, const char *desc_prefix,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *source_output_ss, pa_channel_map *source_output_map,
pa_module *m, void *userdata, pa_modargs *ma,
bool use_volume_sharing, bool create_memblockq);
/* Activate the new virtual source. */
int pa_virtual_source_activate(pa_vsource *vs);
/* Destroys the objects associated with the virtual source. */
void pa_virtual_source_destroy(pa_vsource *vs);
/* Create vsource structure */
pa_vsource* pa_virtual_source_vsource_new(pa_source *s);
/* Update filter parameters */
void pa_virtual_source_request_parameter_update(pa_vsource *vs, void *parameters);
/* Post data, mix in uplink sink */
void pa_virtual_source_post(pa_source *s, const pa_memchunk *chunk);

View file

@ -415,7 +415,7 @@ finish:
/* a < b -> return -1 /* a < b -> return -1
* a == b -> return 0 * a == b -> return 0
* a > b -> return 1 */ * 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; pa_core *core;
core = a->core; core = a->core;
@ -429,23 +429,37 @@ static int compare_sinks(pa_sink *a, pa_sink *b) {
return 1; return 1;
/* The policy default sink is preferred over any other sink. */ /* The policy default sink is preferred over any other sink. */
if (pa_safe_streq(b->name, core->policy_default_sink)) if (pa_safe_streq(b->name, core->policy_default_sink)) {
return -1; if (!ignore_configured_virtual_default || !pa_sink_is_filter(b))
if (pa_safe_streq(a->name, core->policy_default_sink)) return -1;
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 /* The configured default sink is preferred over any other sink
* except the policy default sink. */ * except the policy default sink. */
if (pa_safe_streq(b->name, core->configured_default_sink)) if (pa_safe_streq(b->name, core->configured_default_sink)) {
return -1; if (!ignore_configured_virtual_default || !pa_sink_is_filter(b))
if (pa_safe_streq(a->name, core->configured_default_sink)) return -1;
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) if (a->priority < b->priority)
return -1; return -1;
if (a->priority > b->priority) if (a->priority > b->priority)
return 1; 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 /* 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 * 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. */ * 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; 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 *best = NULL;
pa_sink *sink; pa_sink *sink;
uint32_t idx; uint32_t idx;
pa_sink *old_default_sink;
pa_assert(core); pa_assert(core);
@ -474,10 +487,21 @@ void pa_core_update_default_sink(pa_core *core) {
continue; continue;
} }
if (compare_sinks(sink, best) > 0) if (compare_sinks(sink, best, ignore_configured_virtual_default) > 0)
best = sink; 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; old_default_sink = core->default_sink;
if (best == old_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 -1
* a == b -> return 0 * a == b -> return 0
* a > b -> return 1 */ * 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; pa_core *core;
core = a->core; core = a->core;
@ -517,17 +541,25 @@ static int compare_sources(pa_source *a, pa_source *b) {
return 1; return 1;
/* The policy default source is preferred over any other source. */ /* The policy default source is preferred over any other source. */
if (pa_safe_streq(b->name, core->policy_default_source)) if (pa_safe_streq(b->name, core->policy_default_source)) {
return -1; if (!ignore_configured_virtual_default || !pa_source_is_filter(b))
if (pa_safe_streq(a->name, core->policy_default_source)) return -1;
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 /* The configured default source is preferred over any other source
* except the policy default source. */ * except the policy default source. */
if (pa_safe_streq(b->name, core->configured_default_source)) if (pa_safe_streq(b->name, core->configured_default_source)) {
return -1; if (!ignore_configured_virtual_default || !pa_source_is_filter(b))
if (pa_safe_streq(a->name, core->configured_default_source)) return -1;
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. */ /* Monitor sources lose to non-monitor sources. */
if (a->monitor_of && !b->monitor_of) 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) if (a->priority > b->priority)
return 1; return 1;
/* Let sources like pipe source or null source win against filter sources */
if (a->vsource && !b->vsource)
return -1;
if (!a->vsource && b->vsource)
return 1;
/* If the sources are monitors, we can compare the monitored sinks. */ /* If the sources are monitors, we can compare the monitored sinks. */
if (a->monitor_of) 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 /* 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 * 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; 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 *best = NULL;
pa_source *source; pa_source *source;
uint32_t idx; uint32_t idx;
pa_source *old_default_source;
pa_assert(core); pa_assert(core);
@ -572,10 +609,21 @@ void pa_core_update_default_source(pa_core *core) {
continue; continue;
} }
if (compare_sources(source, best) > 0) if (compare_sources(source, best, ignore_configured_virtual_default) > 0)
best = source; 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; old_default_source = core->default_source;
if (best == old_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) if (!si->sink)
continue; 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 /* 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 same time, in which case we want to make sure we don't
interfere with that */ 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) if (!so->source)
continue; 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 /* 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 same time, in which case we want to make sure we don't
interfere with that */ interfere with that */

View file

@ -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_sink(pa_core *core);
void pa_core_update_default_source(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); void pa_core_set_exit_idle_time(pa_core *core, int time);
/* Check whether no one is connected to this core */ /* Check whether no one is connected to this core */

View file

@ -816,6 +816,18 @@ size_t pa_memblockq_get_prebuf(pa_memblockq *bq) {
return bq->prebuf; 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 pa_memblockq_pop_missing(pa_memblockq *bq) {
size_t l; size_t l;

View file

@ -112,6 +112,11 @@ bool pa_memblockq_is_readable(pa_memblockq *bq);
/* Return the length of the queue in bytes */ /* Return the length of the queue in bytes */
size_t pa_memblockq_get_length(pa_memblockq *bq); 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 /* Return the number of bytes that are missing since the last call to
* this function, reset the internal counter to 0. */ * this function, reset the internal counter to 0. */
size_t pa_memblockq_pop_missing(pa_memblockq *bq); size_t pa_memblockq_pop_missing(pa_memblockq *bq);

View file

@ -1782,12 +1782,13 @@ bool pa_sink_input_may_move(pa_sink_input *i) {
return true; 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; unsigned PA_UNUSED i = 0;
while (s && s->input_to_master) {
if (s->input_to_master == target) while (s && (s->vsink && s->vsink->input_to_master)) {
if (s->vsink->input_to_master == target)
return true; return true;
s = s->input_to_master->sink; s = s->vsink->input_to_master->sink;
pa_assert(i++ < 100); pa_assert(i++ < 100);
} }
return false; return false;
@ -1799,8 +1800,8 @@ static bool is_filter_sink_moving(pa_sink_input *i) {
if (!sink) if (!sink)
return false; return false;
while (sink->input_to_master) { while (sink->vsink && sink->vsink->input_to_master) {
sink = sink->input_to_master->sink; sink = sink->vsink->input_to_master->sink;
if (!sink) if (!sink)
return true; return true;
@ -1826,7 +1827,7 @@ bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) {
return false; return false;
/* Make sure we're not creating a filter sink cycle */ /* 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); pa_log_debug("Can't connect input to %s, as that would create a cycle.", dest->name);
return false; return false;
} }
@ -2239,10 +2240,28 @@ void pa_sink_input_fail_move(pa_sink_input *i) {
if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP) if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP)
return; return;
/* Can we move the sink input to the default sink? */ /* Try to rescue stream if configured */
if (i->core->rescue_streams && pa_sink_input_may_move_to(i, i->core->default_sink)) { if (i->core->rescue_streams) {
if (pa_sink_input_finish_move(i, i->core->default_sink, false) >= 0)
return; /* 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) if (i->moving)

View file

@ -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); 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); 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); pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i);
/* To be used exclusively by the sink driver IO thread */ /* To be used exclusively by the sink driver IO thread */

View file

@ -286,7 +286,8 @@ pa_sink* pa_sink_new(
s->inputs = pa_idxset_new(NULL, NULL); s->inputs = pa_idxset_new(NULL, NULL);
s->n_corked = 0; s->n_corked = 0;
s->input_to_master = NULL; s->vsink = NULL;
s->uplink_of = NULL;
s->reference_volume = s->real_volume = data->volume; s->reference_volume = s->real_volume = data->volume;
pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
@ -1693,10 +1694,10 @@ pa_sink *pa_sink_get_master(pa_sink *s) {
pa_sink_assert_ref(s); pa_sink_assert_ref(s);
while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
if (PA_UNLIKELY(!s->input_to_master)) if (PA_UNLIKELY(!s->vsink || (s->vsink && !s->vsink->input_to_master)))
return NULL; return NULL;
s = s->input_to_master->sink; s = s->vsink->input_to_master->sink;
} }
return s; return s;
@ -1706,7 +1707,7 @@ pa_sink *pa_sink_get_master(pa_sink *s) {
bool pa_sink_is_filter(pa_sink *s) { bool pa_sink_is_filter(pa_sink *s) {
pa_sink_assert_ref(s); pa_sink_assert_ref(s);
return (s->input_to_master != NULL); return ((s->vsink != NULL) && (s->vsink->input_to_master != NULL));
} }
/* Called from main context */ /* Called from main context */
@ -2552,7 +2553,7 @@ unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_sourc
} }
if (s->monitor_source) 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; 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 (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) if (s->update_requested_latency)
s->update_requested_latency(s); s->update_requested_latency(s);
@ -4064,9 +4068,32 @@ void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool
if (!i->sink) if (!i->sink)
continue; continue;
/* Don't move sink-inputs which connect filter sinks to their target sinks */ /* If this is a filter stream and the default sink is set to a filter sink within
if (i->origin_sink) * 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; continue;
}
/* If default_sink_changed is false, the old sink became unavailable, so all streams must be moved. */ /* 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) if (pa_safe_streq(old_sink->name, i->preferred_sink) && default_sink_changed)

View file

@ -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); 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 { struct pa_sink {
pa_msgobject parent; pa_msgobject parent;
@ -88,7 +172,8 @@ struct pa_sink {
pa_idxset *inputs; pa_idxset *inputs;
unsigned n_corked; unsigned n_corked;
pa_source *monitor_source; pa_source *monitor_source;
pa_sink_input *input_to_master; /* non-NULL only for filter sinks */ pa_vsink *vsink; /* non-NULL only for filter sinks */
pa_vsource *uplink_of; /* non-NULL only for uplink sinks */
pa_volume_t base_volume; /* shall be constant */ pa_volume_t base_volume; /* shall be constant */
unsigned n_volume_steps; /* shall be constant */ unsigned n_volume_steps; /* shall be constant */

View file

@ -1304,12 +1304,13 @@ bool pa_source_output_may_move(pa_source_output *o) {
return true; 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; unsigned PA_UNUSED i = 0;
while (s && s->output_from_master) {
if (s->output_from_master == target) while (s && (s->vsource && s->vsource->output_from_master)) {
if (s->vsource->output_from_master == target)
return true; return true;
s = s->output_from_master->source; s = s->vsource->output_from_master->source;
pa_assert(i++ < 100); pa_assert(i++ < 100);
} }
return false; return false;
@ -1321,8 +1322,8 @@ static bool is_filter_source_moving(pa_source_output *o) {
if (!source) if (!source)
return false; return false;
while (source->output_from_master) { while (source->vsource && source->vsource->output_from_master) {
source = source->output_from_master->source; source = source->vsource->output_from_master->source;
if (!source) if (!source)
return true; return true;
@ -1347,7 +1348,7 @@ bool pa_source_output_may_move_to(pa_source_output *o, pa_source *dest) {
return false; return false;
/* Make sure we're not creating a filter source cycle */ /* 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); pa_log_debug("Can't connect output to %s, as that would create a cycle.", dest->name);
return false; return false;
} }
@ -1660,10 +1661,28 @@ void pa_source_output_fail_move(pa_source_output *o) {
if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_STOP) if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_STOP)
return; return;
/* Can we move the source output to the default source? */ /* Try to rescue stream if configured */
if (o->core->rescue_streams && pa_source_output_may_move_to(o, o->core->default_source)) { if (o->core->rescue_streams) {
if (pa_source_output_finish_move(o, o->core->default_source, false) >= 0)
return; /* 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) if (o->moving)

View file

@ -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); 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); 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); pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o);
/* To be used exclusively by the source driver thread */ /* To be used exclusively by the source driver thread */

View file

@ -273,7 +273,7 @@ pa_source* pa_source_new(
s->outputs = pa_idxset_new(NULL, NULL); s->outputs = pa_idxset_new(NULL, NULL);
s->n_corked = 0; s->n_corked = 0;
s->monitor_of = NULL; s->monitor_of = NULL;
s->output_from_master = NULL; s->vsource = NULL;
s->reference_volume = s->real_volume = data->volume; s->reference_volume = s->real_volume = data->volume;
pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
@ -1235,10 +1235,10 @@ pa_source *pa_source_get_master(pa_source *s) {
pa_source_assert_ref(s); pa_source_assert_ref(s);
while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
if (PA_UNLIKELY(!s->output_from_master)) if (PA_UNLIKELY(!s->vsource || (s->vsource && !s->vsource->output_from_master)))
return NULL; return NULL;
s = s->output_from_master->source; s = s->vsource->output_from_master->source;
} }
return s; return s;
@ -1248,7 +1248,7 @@ pa_source *pa_source_get_master(pa_source *s) {
bool pa_source_is_filter(pa_source *s) { bool pa_source_is_filter(pa_source *s) {
pa_source_assert_ref(s); pa_source_assert_ref(s);
return (s->output_from_master != NULL); return (s->vsource->output_from_master != NULL);
} }
/* Called from main context */ /* Called from main context */
@ -2019,7 +2019,7 @@ unsigned pa_source_used_by(pa_source *s) {
} }
/* Called from main thread */ /* 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; unsigned ret;
pa_source_output *o; pa_source_output *o;
uint32_t idx; uint32_t idx;
@ -2033,7 +2033,7 @@ unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) {
ret = 0; ret = 0;
PA_IDXSET_FOREACH(o, s->outputs, idx) { PA_IDXSET_FOREACH(o, s->outputs, idx) {
if (o == ignore) if (o == ignore_output)
continue; continue;
/* We do not assert here. It is perfectly valid for a source output to /* We do not assert here. It is perfectly valid for a source output to
@ -2053,6 +2053,9 @@ unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) {
ret ++; ret ++;
} }
if (s->vsource && s->vsource->uplink_sink)
ret += pa_sink_check_suspend(s->vsource->uplink_sink, ignore_input, ignore_output);
return ret; return ret;
} }
@ -2400,6 +2403,16 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) {
(result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency)) (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency))
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) if (result != (pa_usec_t) -1)
result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency);
@ -3033,9 +3046,32 @@ void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_sour
if (!o->source) if (!o->source)
continue; continue;
/* Don't move source-outputs which connect sources to filter sources */ /* If this is a filter stream and the default source is set to a filter source within
if (o->destination_source) * 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; continue;
}
/* If default_source_changed is false, the old source became unavailable, so all streams must be moved. */ /* 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) if (pa_safe_streq(old_source->name, o->preferred_source) && default_source_changed)

View file

@ -57,6 +57,79 @@ typedef void(*pa_source_cb_t)(pa_source *s);
typedef int (*pa_source_get_mute_cb_t)(pa_source *s, bool *mute); typedef int (*pa_source_get_mute_cb_t)(pa_source *s, bool *mute);
/* Virtual source structure */
typedef struct pa_vsource {
pa_msgobject parent; /* Message object */
pa_core *core; /* Pointer to core */
pa_source *source; /* A pointer to the virtual source */
pa_source_output *output_from_master; /* source output from the master source */
pa_memblockq *memblockq; /* Memblockq of the virtual source, may be NULL */
bool auto_desc; /* Automatically adapt description on move */
bool source_moving; /* Set when master source changes to preserve volume */
const char *desc_head; /* Leading part of description string used for the
* source and source input when auto_desc is true */
const char *source_type; /* Name for the type of source, used as suffix for
* the source name if the name is derived from the
* master source. */
bool autoloaded; /* True if the source was not loaded manually */
size_t max_chunk_size; /* Maximum chunk size in bytes that the filter will
* accept, set to pa_mempool_block_size_max() by default */
size_t fixed_block_size; /* Block size in frames for fixed block size filters,
* 0 if block size is controlled by pulseaudio. */
size_t fixed_input_block_size; /* Input block size in frames. If not 0, input data for
* process_chunk() will always have the same size.
* If not enough new data is available, the remaining
* samples will be filled with history. */
size_t overlap_frames; /* Some filters require old input samples in addtion to
* the current data. The variable contains the number of
* previous frames that will be passed to process_chunk().
* The actual number of history frames may be variable if
* the filter defines the get_current_overlap() function.
* In this case, overlap_frames contains the maximum
* number of history frames. */
pa_usec_t max_latency; /* Maximum latency allowed for the source, 0 if unused */
pa_sink *uplink_sink; /* Uplink sink if present, otherwise NULL */
/* Callback to process a chunk of data by the filter. Called from I/O thread
* context. May be NULL */
void (*process_chunk)(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata);
/* Callback to retrieve additional latency caused by the filter. Called from
* I/O thread context. May be NULL */
pa_usec_t (*get_extra_latency)(pa_source *s);
/* If defined, this function is called from the source-output push() callback
* to retrieve the current number of history frames to include in the next
* chunk. Called from I/O thread. */
size_t (*get_current_overlap)(pa_source_output *o);
/* If set and dest is valid, this function is called in the moving() callback
* to change the description of source and source-output. Called from main context.
* May be NULL */
void (*set_description)(pa_source_output *o, pa_source *dest);
/* If set, this function will be called after update_filter_parameters() to
* inform the filter of the block sizes that will be used. These may differ
* from the sizes set in update_filter_parameters() if the function tries to
* set an invalid combination of block sizes. Called from I/O thread. */
void (*update_block_sizes)(size_t fixed_block_size, size_t fixed_input_block_size, size_t overlap_frames, void *userdata);
/* If set, this function is called in I/O thread context when an update of the
* filter parameters is requested. May be NULL. The function must replace
* the currently used parameter structure by the new structure in parameters
* and return a pointer to the old structure so that it can be freed in the
* main thread using free_filter_parameters(). If the old structure can be
* re-used, the function may return NULL. update_filter_parameters() may
* also modify the block sizes. */
void *(*update_filter_parameters)(void *parameters, void *userdata);
/* Frees a parameter structure. May only be NULL, if update_filter_parameters()
* is also NULL or if update_filter_parameters() always returns NULL. Called
* from main thread. */
void (*free_filter_parameters)(void *parameters);
} pa_vsource;
struct pa_source { struct pa_source {
pa_msgobject parent; pa_msgobject parent;
@ -89,7 +162,7 @@ struct pa_source {
pa_idxset *outputs; pa_idxset *outputs;
unsigned n_corked; unsigned n_corked;
pa_sink *monitor_of; /* may be NULL */ 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 */ pa_volume_t base_volume; /* shall be constant */
unsigned n_volume_steps; /* shall be constant */ unsigned n_volume_steps; /* shall be constant */
@ -426,7 +499,7 @@ unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that ar
/* Returns how many streams are active that don't allow suspensions. If /* Returns how many streams are active that don't allow suspensions. If
* "ignore" is non-NULL, that stream is not included in the count. */ * "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); const char *pa_source_state_to_string(pa_source_state_t state);

View file

@ -32,6 +32,7 @@ typedef struct pa_sink_input pa_sink_input;
typedef struct pa_source pa_source; typedef struct pa_source pa_source;
typedef struct pa_source_volume_change pa_source_volume_change; typedef struct pa_source_volume_change pa_source_volume_change;
typedef struct pa_source_output pa_source_output; typedef struct pa_source_output pa_source_output;
typedef struct pa_vsource pa_vsource;
#endif #endif