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 <modules/virtual-sink-common.h>
#include <modules/virtual-source-common.h>
#include <pulse/xmalloc.h>
#include <pulse/timeval.h>
#include <pulse/rtclock.h>
@ -41,11 +44,9 @@
#include <pulsecore/atomic.h>
#include <pulsecore/macro.h>
#include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h>
#include <pulsecore/core-rtclock.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/sample-util.h>
@ -222,12 +223,14 @@ struct userdata {
pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write;
pa_source *source;
pa_vsource *vsource;
bool source_auto_desc;
pa_source_output *source_output;
pa_memblockq *source_memblockq; /* echo canceller needs fixed sized chunks */
size_t source_skip;
pa_sink *sink;
pa_vsink *vsink;
bool sink_auto_desc;
pa_sink_input *sink_input;
pa_memblockq *sink_memblockq;
@ -435,40 +438,6 @@ static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t
return pa_source_process_msg(o, code, data, offset, chunk);
}
/* Called from sink I/O thread context */
static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
case PA_SINK_MESSAGE_GET_LATENCY:
/* The sink is _put() before the sink input is, so let's
* make sure we don't access it in that time. Also, the
* sink input is first shut down, the sink second. */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
*((int64_t*) data) = 0;
return 0;
}
*((int64_t*) data) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
/* Add resampler delay */
*((int64_t*) data) += pa_resampler_get_delay_usec(u->sink_input->thread_info.resampler);
return 0;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int source_set_state_in_main_thread_cb(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) {
struct userdata *u;
@ -519,23 +488,6 @@ static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, p
return 0;
}
/* Called from the IO thread. */
static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) {
struct userdata *u;
pa_assert(s);
pa_assert_se(u = s->userdata);
/* When set to running or idle for the first time, request a rewind
* of the master sink to make sure we are heard immediately */
if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) {
pa_log_debug("Requesting rewind due to state change.");
pa_sink_input_request_rewind(u->sink_input, 0, false, true, true);
}
return 0;
}
/* Called from source I/O thread context */
static void source_update_requested_latency_cb(pa_source *s) {
struct userdata *u;
@ -557,27 +509,6 @@ static void source_update_requested_latency_cb(pa_source *s) {
pa_source_output_set_requested_latency_within_thread(u->source_output, latency);
}
/* Called from sink I/O thread context */
static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_usec_t latency;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
pa_log_debug("Sink update requested latency");
/* Cap the maximum latency so we don't have to process too large chunks */
latency = PA_MIN(pa_sink_get_requested_latency_within_thread(s),
pa_bytes_to_usec(u->sink_blocksize, &s->sample_spec) * MAX_LATENCY_BLOCKS);
pa_sink_input_set_requested_latency_within_thread(u->sink_input, latency);
}
/* Called from sink I/O thread context */
static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u;
@ -610,20 +541,6 @@ static void source_set_volume_cb(pa_source *s) {
pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, true);
}
/* Called from main context */
static void sink_set_volume_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, true);
}
/* Called from main context. */
static void source_get_volume_cb(pa_source *s) {
struct userdata *u;
@ -660,20 +577,6 @@ static void source_set_mute_cb(pa_source *s) {
pa_source_output_set_mute(u->source_output, s->muted, s->save_muted);
}
/* Called from main context */
static void sink_set_mute_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
}
/* Called from source I/O thread context. */
static void apply_diff_time(struct userdata *u, int64_t diff_time) {
int64_t diff;
@ -1156,21 +1059,6 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in
return pa_sink_input_process_msg(obj, code, data, offset, chunk);
}
/* Called from sink I/O thread context. */
static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_memblockq_set_maxrewind(u->sink_memblockq, nbytes);
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
/* Called from source I/O thread context. */
static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) {
struct userdata *u;
@ -1183,18 +1071,6 @@ static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbyte
pa_source_set_max_rewind_within_thread(u->source, nbytes);
}
/* Called from sink I/O thread context. */
static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_log_debug("Sink input update max request %lld", (long long) nbytes);
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
/* Called from sink I/O thread context. */
static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) {
struct userdata *u;
@ -1221,20 +1097,6 @@ static void source_output_update_source_requested_latency_cb(pa_source_output *o
pa_log_debug("Source output update requested latency %lld", (long long) latency);
}
/* Called from sink I/O thread context. */
static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_log_debug("Sink input update latency range %lld %lld",
(long long) i->sink->thread_info.min_latency,
(long long) i->sink->thread_info.max_latency);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from source I/O thread context. */
static void source_output_update_source_latency_range_cb(pa_source_output *o) {
struct userdata *u;
@ -1249,19 +1111,6 @@ static void source_output_update_source_latency_range_cb(pa_source_output *o) {
pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
}
/* Called from sink I/O thread context. */
static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_log_debug("Sink input update fixed latency %lld",
(long long) i->sink->thread_info.fixed_latency);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
}
/* Called from source I/O thread context. */
static void source_output_update_source_fixed_latency_cb(pa_source_output *o) {
struct userdata *u;
@ -1329,6 +1178,10 @@ static void sink_input_attach_cb(pa_sink_input *i) {
PA_RTPOLL_LATE,
u->asyncmsgq);
/* This call is needed to remove the UNAVAILABLE suspend cause after
* a move when the previous master sink disappeared. */
pa_virtual_sink_send_input_attached_message(u->vsink);
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_attach_within_thread(u->sink);
}
@ -1357,14 +1210,10 @@ static void source_output_detach_cb(pa_source_output *o) {
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_virtual_sink_input_detach(i);
pa_assert_se(u = i->userdata);
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_detach_within_thread(u->sink);
pa_sink_set_rtpoll(u->sink, NULL);
pa_log_debug("Sink input %d detach", i->index);
if (u->rtpoll_item_write) {
@ -1517,19 +1366,12 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
}
/* Called from main context */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
static void sink_set_description_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (dest) {
pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
} else
pa_sink_set_asyncmsgq(u->sink, NULL);
if (u->sink_auto_desc && dest) {
if (u->vsink->auto_desc) {
const char *y, *z;
pa_proplist *pl;
@ -1548,26 +1390,6 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
}
}
/* Called from main context */
static void sink_input_volume_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_volume_changed(u->sink, &i->volume);
}
/* Called from main context */
static void sink_input_mute_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_mute_changed(u->sink, i->muted);
}
/* Called from main context */
static int canceller_process_msg_cb(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
struct pa_echo_canceller_msg *msg;
@ -1911,6 +1733,10 @@ int pa__init(pa_module*m) {
pa_source_set_asyncmsgq(u->source, source_master->asyncmsgq);
/* Create vsource structure. Only needed for output_from_master field, otherwise
* unused because the virtual source here is too different from other filters */
u->vsource = pa_virtual_source_vsource_new(u->source);
/* Create sink */
pa_sink_new_data_init(&sink_data);
sink_data.driver = __FILE__;
@ -1948,18 +1774,16 @@ int pa__init(pa_module*m) {
goto fail;
}
u->sink->parent.process_msg = sink_process_msg_cb;
pa_virtual_sink_set_callbacks(u->sink, u->use_volume_sharing);
u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb;
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->request_rewind = sink_request_rewind_cb;
pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
if (!u->use_volume_sharing) {
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
pa_sink_enable_decibel_volume(u->sink, true);
}
u->sink->userdata = u;
u->vsink = pa_virtual_sink_vsink_new(u->sink, 0);
u->vsink->set_description = sink_set_description_cb;
u->vsink->auto_desc = u->sink_auto_desc;
u->vsink->max_latency = pa_bytes_to_usec(u->sink_blocksize, &u->sink->sample_spec) * MAX_LATENCY_BLOCKS;
pa_sink_set_asyncmsgq(u->sink, sink_master->asyncmsgq);
/* Create source output */
@ -1999,7 +1823,7 @@ int pa__init(pa_module*m) {
u->source_output->moving = source_output_moving_cb;
u->source_output->userdata = u;
u->source->output_from_master = u->source_output;
u->vsource->output_from_master = u->source_output;
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
@ -2022,26 +1846,19 @@ int pa__init(pa_module*m) {
if (!u->sink_input)
goto fail;
pa_virtual_sink_input_set_callbacks(u->sink_input, u->use_volume_sharing);
u->sink_input->parent.process_msg = sink_input_process_msg_cb;
u->sink_input->pop = sink_input_pop_cb;
u->sink_input->process_rewind = sink_input_process_rewind_cb;
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->state_change = sink_input_state_change_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
u->sink_input->moving = sink_input_moving_cb;
if (!u->use_volume_sharing)
u->sink_input->volume_changed = sink_input_volume_changed_cb;
u->sink_input->mute_changed = sink_input_mute_changed_cb;
u->sink_input->userdata = u;
u->sink->input_to_master = u->sink_input;
u->vsink->input_to_master = u->sink_input;
pa_sink_input_get_silence(u->sink_input, &silence);
@ -2173,6 +1990,12 @@ void pa__done(pa_module*m) {
pa_sink_input_unref(u->sink_input);
}
if (u->vsink)
pa_xfree(u->vsink);
if (u->vsource)
pa_xfree(u->vsource);
if (u->source)
pa_source_unref(u->source);
if (u->sink)

View file

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

View file

@ -124,10 +124,10 @@ struct output {
pa_memblockq *memblockq;
/* For communication of the stream latencies to the main thread */
pa_usec_t total_latency;
int64_t total_latency;
struct {
pa_usec_t timestamp;
pa_usec_t sink_latency;
int64_t sink_latency;
size_t output_memblockq_size;
uint64_t receive_counter;
} latency_snapshot;
@ -249,8 +249,8 @@ static uint32_t rate_controller(
static void adjust_rates(struct userdata *u) {
struct output *o;
struct sink_snapshot rdata;
pa_usec_t avg_total_latency = 0;
pa_usec_t target_latency = 0;
int64_t avg_total_latency = 0;
int64_t target_latency = 0;
pa_usec_t max_sink_latency = 0;
pa_usec_t min_total_latency = (pa_usec_t)-1;
uint32_t base_rate;
@ -280,7 +280,7 @@ static void adjust_rates(struct userdata *u) {
return;
PA_IDXSET_FOREACH(o, u->outputs, idx) {
pa_usec_t snapshot_latency;
int64_t snapshot_latency;
int64_t time_difference;
if (!o->sink_input || !PA_SINK_IS_OPENED(o->sink->state))
@ -319,7 +319,7 @@ static void adjust_rates(struct userdata *u) {
/* Debug output */
pa_log_debug("[%s] Snapshot sink latency = %0.2fms, total snapshot latency = %0.2fms", o->sink->name, (double) o->latency_snapshot.sink_latency / PA_USEC_PER_MSEC, (double) snapshot_latency / PA_USEC_PER_MSEC);
if (o->total_latency > 10*PA_USEC_PER_SEC)
if (o->total_latency > (int64_t)(10*PA_USEC_PER_SEC))
pa_log_warn("[%s] Total latency of output is very high (%0.2fms), most likely the audio timing in one of your drivers is broken.", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC);
n++;

View file

@ -28,6 +28,8 @@
#include <config.h>
#endif
#include <modules/virtual-sink-common.h>
#include <stdlib.h>
#include <stdio.h>
#include <float.h>
@ -50,10 +52,8 @@
#include <pulsecore/i18n.h>
#include <pulsecore/aupdate.h>
#include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/sample-util.h>
@ -85,9 +85,7 @@ PA_MODULE_USAGE(
struct userdata {
pa_module *module;
pa_sink *sink;
pa_sink_input *sink_input;
bool autoloaded;
pa_vsink *vsink;
size_t channels;
size_t fft_size;//length (res) of fft
@ -125,8 +123,6 @@ struct userdata {
pa_database *database;
char **base_profiles;
bool automatic_description;
};
static const char* const valid_modargs[] = {
@ -237,134 +233,12 @@ static void alloc_input_buffers(struct userdata *u, size_t min_buffer_length) {
u->input_buffer_max = min_buffer_length;
}
/* Called from I/O thread context */
static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
case PA_SINK_MESSAGE_GET_LATENCY: {
//size_t fs=pa_frame_size(&u->sink->sample_spec);
/* The sink is _put() before the sink input is, so let's
* make sure we don't access it in that time. Also, the
* sink input is first shut down, the sink second. */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
*((int64_t*) data) = 0;
return 0;
}
*((int64_t*) data) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->output_q) +
pa_memblockq_get_length(u->input_q), &u->sink_input->sink->sample_spec) +
pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
// pa_bytes_to_usec(u->samples_gathered * fs, &u->sink->sample_spec);
//+ pa_bytes_to_usec(u->latency * fs, ss)
/* Add resampler latency */
*((int64_t*) data) += pa_resampler_get_delay_usec(u->sink_input->thread_info.resampler);
return 0;
}
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) {
static pa_usec_t sink_get_extra_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return 0;
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
/* Called from the IO thread. */
static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) {
struct userdata *u;
pa_assert(s);
pa_assert_se(u = s->userdata);
/* When set to running or idle for the first time, request a rewind
* of the master sink to make sure we are heard immediately */
if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) {
pa_log_debug("Requesting rewind due to state change.");
pa_sink_input_request_rewind(u->sink_input, 0, false, true, true);
}
return 0;
}
/* Called from I/O thread context */
static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes+pa_memblockq_get_length(u->input_q), true, false, false);
}
/* Called from I/O thread context */
static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
pa_sink_get_requested_latency_within_thread(s));
}
/* Called from main context */
static void sink_set_volume_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, true);
}
/* Called from main context */
static void sink_set_mute_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
return pa_bytes_to_usec(pa_memblockq_get_length(u->output_q), &u->vsink->input_to_master->sample_spec);
}
#if 1
@ -521,14 +395,14 @@ static void dsp_logic(
#endif
static void flatten_to_memblockq(struct userdata *u) {
size_t mbs = pa_mempool_block_size_max(u->sink->core->mempool);
size_t mbs = pa_mempool_block_size_max(u->vsink->sink->core->mempool);
pa_memchunk tchunk;
char *dst;
size_t i = 0;
while(i < u->output_buffer_length) {
tchunk.index = 0;
tchunk.length = PA_MIN((u->output_buffer_length - i), mbs);
tchunk.memblock = pa_memblock_new(u->sink->core->mempool, tchunk.length);
tchunk.memblock = pa_memblock_new(u->vsink->sink->core->mempool, tchunk.length);
//pa_log_debug("pushing %ld into the q", tchunk.length);
dst = pa_memblock_acquire(tchunk.memblock);
memcpy(dst, u->output_buffer + i, tchunk.length);
@ -540,7 +414,7 @@ static void flatten_to_memblockq(struct userdata *u) {
}
static void process_samples(struct userdata *u) {
size_t fs = pa_frame_size(&(u->sink->sample_spec));
size_t fs = pa_frame_size(&(u->vsink->sink->sample_spec));
unsigned a_i;
float *H, X;
size_t iterations, offset;
@ -590,7 +464,7 @@ static void process_samples(struct userdata *u) {
}
static void input_buffer(struct userdata *u, pa_memchunk *in) {
size_t fs = pa_frame_size(&(u->sink->sample_spec));
size_t fs = pa_frame_size(&(u->vsink->sink->sample_spec));
size_t samples = in->length/fs;
float *src = pa_memblock_acquire_chunk(in);
pa_assert(u->samples_gathered + samples <= u->input_buffer_max);
@ -617,22 +491,22 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_assert(chunk);
pa_assert(u->sink);
pa_assert(u->vsink->sink);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
if (!PA_SINK_IS_LINKED(u->vsink->sink->thread_info.state))
return -1;
/* FIXME: Please clean this up. I see more commented code lines
* than uncommented code lines. I am sorry, but I am too dumb to
* understand this. */
fs = pa_frame_size(&(u->sink->sample_spec));
mbs = pa_mempool_block_size_max(u->sink->core->mempool);
fs = pa_frame_size(&(u->vsink->sink->sample_spec));
mbs = pa_mempool_block_size_max(u->vsink->sink->core->mempool);
if (pa_memblockq_get_length(u->output_q) > 0) {
//pa_log_debug("qsize is %ld", pa_memblockq_get_length(u->output_q));
goto END;
}
//nbytes = PA_MIN(nbytes, pa_mempool_block_size_max(u->sink->core->mempool));
//nbytes = PA_MIN(nbytes, pa_mempool_block_size_max(u->vsink->sink->core->mempool));
target_samples = PA_ROUND_UP(nbytes / fs, u->R);
////pa_log_debug("vanilla mbs = %ld",mbs);
//mbs = PA_ROUND_DOWN(mbs / fs, u->R);
@ -651,7 +525,7 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
chunk->memblock = NULL;
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind(u->sink, 0);
pa_sink_process_rewind(u->vsink->sink, 0);
//pa_log_debug("start output-buffered %ld, input-buffered %ld, requested %ld",buffered_samples,u->samples_gathered,samples_requested);
//pa_rtclock_get(&start);
@ -661,7 +535,7 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
pa_assert(input_remaining > 0);
while (pa_memblockq_peek(u->input_q, &tchunk) < 0) {
//pa_sink_render(u->sink, input_remaining * fs, &tchunk);
pa_sink_render_full(u->sink, PA_MIN(input_remaining * fs, mbs), &tchunk);
pa_sink_render_full(u->vsink->sink, PA_MIN(input_remaining * fs, mbs), &tchunk);
pa_memblockq_push(u->input_q, &tchunk);
pa_memblock_unref(tchunk.memblock);
}
@ -700,26 +574,6 @@ END:
return 0;
}
/* Called from main context */
static void sink_input_volume_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_volume_changed(u->sink, &i->volume);
}
/* Called from main context */
static void sink_input_mute_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_mute_changed(u->sink, i->muted);
}
#if 0
static void reset_filter(struct userdata *u) {
size_t fs = pa_frame_size(&u->sink->sample_spec);
@ -738,148 +592,6 @@ static void reset_filter(struct userdata *u) {
}
#endif
/* Called from I/O thread context */
static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
size_t amount = 0;
pa_log_debug("Rewind callback!");
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* If the sink is not yet linked, there is nothing to rewind */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
if (u->sink->thread_info.rewind_nbytes > 0) {
size_t max_rewrite;
//max_rewrite = nbytes;
max_rewrite = nbytes + pa_memblockq_get_length(u->input_q);
//PA_MIN(pa_memblockq_get_length(u->input_q), nbytes);
amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
u->sink->thread_info.rewind_nbytes = 0;
if (amount > 0) {
//invalidate the output q
pa_memblockq_seek(u->input_q, - (int64_t) amount, PA_SEEK_RELATIVE, true);
pa_log("Resetting filter");
//reset_filter(u); //this is the "proper" thing to do...
}
}
pa_sink_process_rewind(u->sink, amount);
pa_memblockq_rewind(u->input_q, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_memblockq_set_maxrewind(u->input_q, nbytes);
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
size_t fs;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
fs = pa_frame_size(&u->sink_input->sample_spec);
pa_sink_set_max_request_within_thread(u->sink, PA_ROUND_UP(nbytes / fs, u->R) * fs);
}
/* Called from I/O thread context */
static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
}
/* Called from I/O thread context */
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_detach_within_thread(u->sink);
pa_sink_set_rtpoll(u->sink, NULL);
}
/* Called from I/O thread context */
static void sink_input_attach_cb(pa_sink_input *i) {
struct userdata *u;
size_t fs, max_request;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
fs = pa_frame_size(&u->sink_input->sample_spec);
/* set buffer size to max request, no overlap copy */
max_request = PA_ROUND_UP(pa_sink_input_get_max_request(u->sink_input) / fs, u->R);
max_request = PA_MAX(max_request, u->window_size);
pa_sink_set_max_request_within_thread(u->sink, max_request * fs);
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_attach_within_thread(u->sink);
}
/* Called from main context */
static void sink_input_kill_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* The order here matters! We first kill the sink so that streams
* can properly be moved away while the sink input is still connected
* to the master. */
pa_sink_input_cork(u->sink_input, true);
pa_sink_unlink(u->sink);
pa_sink_input_unlink(u->sink_input);
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
/* Leave u->sink alone for now, it will be cleaned up on module
* unload (and it is needed during unload as well). */
pa_module_unload_request(u->module, true);
}
static void pack(char **strs, size_t len, char **packed, size_t *length) {
size_t t_len = 0;
size_t headers = (1+len) * sizeof(uint16_t);
@ -967,7 +679,7 @@ static void save_state(struct userdata *u) {
pa_aupdate_read_end(u->a_H[c]);
}
key.data = u->sink->name;
key.data = u->vsink->sink->name;
key.size = strlen(key.data);
data.data = state;
data.size = filter_state_size + packed_length;
@ -1032,7 +744,7 @@ static void load_state(struct userdata *u) {
return;
}
key.data = u->sink->name;
key.data = u->vsink->sink->name;
key.size = strlen(key.data);
if (pa_database_get(database, &key, &value) != NULL) {
@ -1062,55 +774,12 @@ static void load_state(struct userdata *u) {
pa_database_close(database);
}
/* Called from main context */
static bool sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
return u->sink != dest;
}
/* Called from main context */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (u->autoloaded) {
/* We were autoloaded, and don't support moving. Let's unload ourselves. */
pa_log_debug("Can't move autoloaded stream, unloading");
pa_module_unload_request(u->module, true);
}
if (dest) {
pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
if (u->automatic_description) {
const char *master_description;
char *new_description;
master_description = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
new_description = pa_sprintf_malloc(_("FFT based equalizer on %s"),
master_description ? master_description : dest->name);
pa_sink_set_description(u->sink, new_description);
pa_xfree(new_description);
}
} else
pa_sink_set_asyncmsgq(u->sink, NULL);
}
int pa__init(pa_module*m) {
struct userdata *u;
pa_sample_spec ss;
pa_channel_map map;
pa_modargs *ma;
pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
size_t i;
unsigned c;
float *H;
@ -1194,104 +863,23 @@ int pa__init(pa_module*m) {
for (c = 0; c < u->channels; ++c)
u->base_profiles[c] = pa_xstrdup("default");
/* Create sink */
pa_sink_new_data_init(&sink_data);
sink_data.driver = __FILE__;
sink_data.module = m;
if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
sink_data.name = pa_sprintf_malloc("%s.equalizer", master->name);
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &map);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_sink_new_data_done(&sink_data);
/* Create virtual sink */
if (!(u->vsink = pa_virtual_sink_create(master, "equalizer", "FFT based equalizer Sink", &ss, &map,
&ss, &map, m, u, ma, use_volume_sharing, false, 0)))
goto fail;
}
if (!pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION)) {
const char *master_description;
u->vsink->get_extra_latency = sink_get_extra_latency_cb;
u->vsink->fixed_block_size = u->R;
u->vsink->max_request_frames_min = u->window_size;
u->vsink->input_to_master->pop = sink_input_pop_cb;
master_description = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION,
_("FFT based equalizer on %s"), master_description ? master_description : master->name);
u->automatic_description = true;
}
u->autoloaded = DEFAULT_AUTOLOADED;
if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) {
pa_log("Failed to parse autoloaded value");
goto fail;
}
u->sink = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY))
| (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
pa_log("Failed to create sink.");
goto fail;
}
u->sink->parent.process_msg = sink_process_msg_cb;
u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb;
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->request_rewind = sink_request_rewind_cb;
pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
if (!use_volume_sharing) {
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
pa_sink_enable_decibel_volume(u->sink, true);
}
u->sink->userdata = u;
u->input_q = pa_memblockq_new("module-equalizer-sink input_q", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &u->sink->silence);
u->input_q = pa_memblockq_new("module-equalizer-sink input_q", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &u->vsink->sink->silence);
u->output_q = pa_memblockq_new("module-equalizer-sink output_q", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, NULL);
u->output_buffer = NULL;
u->output_buffer_length = 0;
u->output_buffer_max_length = 0;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
//pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->R*fs, &ss));
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true);
sink_input_data.origin_sink = u->sink;
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Equalized Stream");
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
sink_input_data.flags |= PA_SINK_INPUT_START_CORKED;
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
goto fail;
u->sink_input->pop = sink_input_pop_cb;
u->sink_input->process_rewind = sink_input_process_rewind_cb;
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
u->sink_input->moving = sink_input_moving_cb;
if (!use_volume_sharing)
u->sink_input->volume_changed = sink_input_volume_changed_cb;
u->sink_input->mute_changed = sink_input_mute_changed_cb;
u->sink_input->userdata = u;
u->sink->input_to_master = u->sink_input;
u->vsink->memblockq = u->input_q;
dbus_init(u);
@ -1311,12 +899,8 @@ int pa__init(pa_module*m) {
/* load old parameters */
load_state(u);
/* The order here is important. The input must be put first,
* otherwise streams might attach to the sink before the sink
* input is attached to the master. */
pa_sink_input_put(u->sink_input);
pa_sink_put(u->sink);
pa_sink_input_cork(u->sink_input, false);
if (pa_virtual_sink_activate(u->vsink) < 0)
goto fail;
pa_modargs_free(ma);
@ -1337,7 +921,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m);
pa_assert_se(u = m->userdata);
return pa_sink_linked_by(u->sink);
return pa_sink_linked_by(u->vsink->sink);
}
void pa__done(pa_module*m) {
@ -1349,7 +933,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
save_state(u);
if (u->vsink && u->vsink->sink)
save_state(u);
dbus_done(u);
@ -1357,26 +942,14 @@ void pa__done(pa_module*m) {
pa_xfree(u->base_profiles[c]);
pa_xfree(u->base_profiles);
/* See comments in sink_input_kill_cb() above regarding
* destruction order! */
if (u->sink_input)
pa_sink_input_cork(u->sink_input, true);
if (u->sink)
pa_sink_unlink(u->sink);
if (u->sink_input) {
pa_sink_input_unlink(u->sink_input);
pa_sink_input_unref(u->sink_input);
}
if (u->sink)
pa_sink_unref(u->sink);
pa_xfree(u->output_buffer);
pa_memblockq_free(u->output_q);
pa_memblockq_free(u->input_q);
if (u->vsink)
pa_virtual_sink_destroy(u->vsink);
if (u->output_q)
pa_memblockq_free(u->output_q);
fftwf_destroy_plan(u->inverse_plan);
fftwf_destroy_plan(u->forward_plan);
@ -1622,21 +1195,21 @@ void dbus_init(struct userdata *u) {
uint32_t dummy;
DBusMessage *message = NULL;
pa_idxset *sink_list = NULL;
u->dbus_protocol=pa_dbus_protocol_get(u->sink->core);
u->dbus_path=pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index);
u->dbus_protocol=pa_dbus_protocol_get(u->vsink->sink->core);
u->dbus_path=pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->vsink->sink->index);
pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, u->dbus_path, &equalizer_info, u) >= 0);
sink_list = pa_shared_get(u->sink->core, SINKLIST);
u->database = pa_shared_get(u->sink->core, EQDB);
sink_list = pa_shared_get(u->vsink->sink->core, SINKLIST);
u->database = pa_shared_get(u->vsink->sink->core, EQDB);
if (sink_list == NULL) {
char *state_path;
sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func);
pa_shared_set(u->sink->core, SINKLIST, sink_list);
pa_shared_set(u->vsink->sink->core, SINKLIST, sink_list);
pa_assert_se(state_path = pa_state_path(NULL, false));
pa_assert_se(u->database = pa_database_open(state_path, "equalizer-presets", false, true));
pa_xfree(state_path);
pa_shared_set(u->sink->core, EQDB, u->database);
pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core);
pa_shared_set(u->vsink->sink->core, EQDB, u->database);
pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->vsink->sink->core);
pa_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME);
}
pa_idxset_put(sink_list, u, &dummy);
@ -1657,14 +1230,14 @@ void dbus_done(struct userdata *u) {
pa_dbus_protocol_send_signal(u->dbus_protocol, message);
dbus_message_unref(message);
pa_assert_se(sink_list=pa_shared_get(u->sink->core,SINKLIST));
pa_assert_se(sink_list=pa_shared_get(u->module->core,SINKLIST));
pa_idxset_remove_by_data(sink_list,u,&dummy);
if (pa_idxset_size(sink_list) == 0) {
pa_dbus_protocol_unregister_extension(u->dbus_protocol, EXTNAME);
pa_dbus_protocol_remove_interface(u->dbus_protocol, MANAGER_PATH, manager_info.name);
pa_shared_remove(u->sink->core, EQDB);
pa_shared_remove(u->module->core, EQDB);
pa_database_close(u->database);
pa_shared_remove(u->sink->core, SINKLIST);
pa_shared_remove(u->module->core, SINKLIST);
pa_xfree(sink_list);
}
pa_dbus_protocol_remove_interface(u->dbus_protocol, u->dbus_path, equalizer_info.name);
@ -2234,7 +1807,7 @@ void equalizer_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *_u)
pa_assert(conn);
pa_assert(msg);
rate = (uint32_t) u->sink->sample_spec.rate;
rate = (uint32_t) u->vsink->sink->sample_spec.rate;
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &rate);
}
@ -2260,7 +1833,7 @@ void equalizer_get_all(DBusConnection *conn, DBusMessage *msg, void *_u) {
rev = 1;
n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE(u);
rate = (uint32_t) u->sink->sample_spec.rate;
rate = (uint32_t) u->vsink->sink->sample_spec.rate;
fft_size = (uint32_t) u->fft_size;
channels = (uint32_t) u->channels;

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

View file

@ -24,16 +24,16 @@
#include <config.h>
#endif
#include <modules/virtual-sink-common.h>
#include <math.h>
#include <pulse/xmalloc.h>
#include <pulsecore/i18n.h>
#include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/rtpoll.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 {
pa_module *module;
pa_sink *sink;
pa_sink_input *sink_input;
pa_vsink *vsink;
const LADSPA_Descriptor *descriptor;
LADSPA_Handle handle[PA_CHANNELS_MAX];
@ -91,8 +90,6 @@ struct userdata {
about control out ports. We connect them all to this single buffer. */
LADSPA_Data control_out;
pa_memblockq *memblockq;
bool *use_default;
pa_sample_spec ss;
@ -100,9 +97,6 @@ struct userdata {
pa_dbus_protocol *dbus_protocol;
char *dbus_path;
#endif
bool auto_desc;
bool autoloaded;
};
static const char* const valid_modargs[] = {
@ -124,13 +118,7 @@ static const char* const valid_modargs[] = {
NULL
};
/* The PA_SINK_MESSAGE types that extend the predefined messages. */
enum {
LADSPA_SINK_MESSAGE_UPDATE_PARAMETERS = PA_SINK_MESSAGE_MAX
};
static int write_control_parameters(struct userdata *u, double *control_values, bool *use_default);
static void connect_control_ports(struct userdata *u);
#ifdef HAVE_DBUS
@ -230,7 +218,7 @@ static void set_algorithm_parameters(DBusConnection *conn, DBusMessage *msg, DBu
goto error;
}
pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), LADSPA_SINK_MESSAGE_UPDATE_PARAMETERS, NULL, 0, NULL);
pa_virtual_sink_request_parameter_update(u->vsink, NULL);
pa_dbus_send_empty_reply(conn, msg);
@ -312,8 +300,8 @@ static pa_dbus_interface_info ladspa_info = {
static void dbus_init(struct userdata *u) {
pa_assert_se(u);
u->dbus_protocol = pa_dbus_protocol_get(u->sink->core);
u->dbus_path = pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index);
u->dbus_protocol = pa_dbus_protocol_get(u->vsink->sink->core);
u->dbus_path = pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->vsink->sink->index);
pa_dbus_protocol_add_interface(u->dbus_protocol, u->dbus_path, &ladspa_info, u);
}
@ -337,393 +325,41 @@ static void dbus_done(struct userdata *u) {
#endif /* HAVE_DBUS */
/* Called from I/O thread context */
static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
case PA_SINK_MESSAGE_GET_LATENCY:
/* The sink is _put() before the sink input is, so let's
* make sure we don't access it in that time. Also, the
* sink input is first shut down, the sink second. */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
*((int64_t*) data) = 0;
return 0;
}
*((int64_t*) data) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
/* Add resampler latency */
*((int64_t*) data) += pa_resampler_get_delay_usec(u->sink_input->thread_info.resampler);
return 0;
case LADSPA_SINK_MESSAGE_UPDATE_PARAMETERS:
/* rewind the stream to throw away the previously rendered data */
pa_log_debug("Requesting rewind due to parameter update.");
pa_sink_request_rewind(u->sink, -1);
/* change the sink parameters */
connect_control_ports(u);
return 0;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return 0;
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
/* Called from the IO thread. */
static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) {
struct userdata *u;
pa_assert(s);
pa_assert_se(u = s->userdata);
/* When set to running or idle for the first time, request a rewind
* of the master sink to make sure we are heard immediately */
if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) {
pa_log_debug("Requesting rewind due to state change.");
pa_sink_input_request_rewind(u->sink_input, 0, false, true, true);
}
return 0;
}
/* Called from I/O thread context */
static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_request_rewind(u->sink_input,
s->thread_info.rewind_nbytes +
pa_memblockq_get_length(u->memblockq), true, false, false);
}
/* Called from I/O thread context */
static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
pa_sink_get_requested_latency_within_thread(s));
}
/* Called from main context */
static void sink_set_mute_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
}
/* Called from I/O thread context */
static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
static void filter_process_chunk(uint8_t *src_p, uint8_t *dst_p, unsigned in_count, unsigned out_count, void *userdata) {
struct userdata *u;
unsigned h, c;
float *src, *dst;
size_t fs;
unsigned n, h, c;
pa_memchunk tchunk;
pa_sink_input_assert_ref(i);
pa_assert(chunk);
pa_assert_se(u = i->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
return -1;
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind(u->sink, 0);
while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
pa_memchunk nchunk;
pa_sink_render(u->sink, nbytes, &nchunk);
pa_memblockq_push(u->memblockq, &nchunk);
pa_memblock_unref(nchunk.memblock);
}
tchunk.length = PA_MIN(nbytes, tchunk.length);
pa_assert(tchunk.length > 0);
fs = pa_frame_size(&i->sample_spec);
n = (unsigned) (PA_MIN(tchunk.length, u->block_size) / fs);
pa_assert(n > 0);
chunk->index = 0;
chunk->length = n*fs;
chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length);
pa_memblockq_drop(u->memblockq, chunk->length);
src = pa_memblock_acquire_chunk(&tchunk);
dst = pa_memblock_acquire(chunk->memblock);
pa_assert_se(u = userdata);
pa_assert(in_count == out_count);
src = (float *)src_p;
dst = (float *)dst_p;
for (h = 0; h < (u->channels / u->max_ladspaport_count); h++) {
for (c = 0; c < u->input_count; c++)
pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c], sizeof(float), src+ h*u->max_ladspaport_count + c, u->channels*sizeof(float), n);
u->descriptor->run(u->handle[h], n);
pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c], sizeof(float), src + h * u->max_ladspaport_count + c, u->channels * sizeof(float), in_count);
u->descriptor->run(u->handle[h], in_count);
for (c = 0; c < u->output_count; c++)
pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst + h*u->max_ladspaport_count + c, u->channels*sizeof(float), u->output[c], sizeof(float), n);
}
pa_memblock_release(tchunk.memblock);
pa_memblock_release(chunk->memblock);
pa_memblock_unref(tchunk.memblock);
return 0;
}
/* Called from I/O thread context */
static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
size_t amount = 0;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* If the sink is not yet linked, there is nothing to rewind */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
if (u->sink->thread_info.rewind_nbytes > 0) {
size_t max_rewrite;
max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq);
amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
u->sink->thread_info.rewind_nbytes = 0;
if (amount > 0) {
unsigned c;
pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true);
pa_log_debug("Resetting plugin");
/* Reset the plugin */
if (u->descriptor->deactivate)
for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
u->descriptor->deactivate(u->handle[c]);
if (u->descriptor->activate)
for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
u->descriptor->activate(u->handle[c]);
}
}
pa_sink_process_rewind(u->sink, amount);
pa_memblockq_rewind(u->memblockq, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_memblockq_set_maxrewind(u->memblockq, nbytes);
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
}
/* Called from I/O thread context */
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_detach_within_thread(u->sink);
pa_sink_set_rtpoll(u->sink, NULL);
}
/* Called from I/O thread context */
static void sink_input_attach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_attach_within_thread(u->sink);
}
/* Called from main context */
static void sink_input_kill_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* The order here matters! We first kill the sink so that streams
* can properly be moved away while the sink input is still connected
* to the master. */
pa_sink_input_cork(u->sink_input, true);
pa_sink_unlink(u->sink);
pa_sink_input_unlink(u->sink_input);
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_sink_unref(u->sink);
u->sink = NULL;
pa_module_unload_request(u->module, true);
}
/* Called from main context */
static bool sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (u->autoloaded)
return false;
return u->sink != dest;
}
/* Called from main context */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (dest) {
pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
} else
pa_sink_set_asyncmsgq(u->sink, NULL);
if (u->auto_desc && dest) {
const char *z;
pa_proplist *pl;
pl = pa_proplist_new();
z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s",
pa_proplist_gets(u->sink->proplist, "device.ladspa.name"), z ? z : dest->name);
pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
pa_proplist_free(pl);
pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst + h * u->max_ladspaport_count + c, u->channels * sizeof(float), u->output[c], sizeof(float), in_count);
}
}
/* Called from main context */
static void sink_input_mute_changed_cb(pa_sink_input *i) {
/* Called from I/O thread context */
static void reset_filter(pa_sink *s, size_t amount) {
struct userdata *u;
unsigned c;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_assert_se(u = s->userdata);
pa_sink_mute_changed(u->sink, i->muted);
}
pa_log_debug("Resetting plugin");
/* Called from main context */
static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!PA_SINK_IS_LINKED(u->sink->state))
return;
if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE)
pa_sink_suspend(u->sink, false, PA_SUSPEND_UNAVAILABLE);
else
pa_sink_suspend(u->sink, true, PA_SUSPEND_UNAVAILABLE);
/* Reset the plugin */
if (u->descriptor->deactivate)
for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
u->descriptor->deactivate(u->handle[c]);
if (u->descriptor->activate)
for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
u->descriptor->activate(u->handle[c]);
}
static int parse_control_parameters(struct userdata *u, const char *cdata, double *read_values, bool *use_default) {
@ -791,11 +427,12 @@ fail:
return -1;
}
static void connect_control_ports(struct userdata *u) {
static void connect_control_ports(void *userdata) {
struct userdata *u;
unsigned long p = 0, h = 0, c;
const LADSPA_Descriptor *d;
pa_assert(u);
pa_assert_se(u = userdata);
pa_assert_se(d = u->descriptor);
for (p = 0; p < d->PortCount; p++) {
@ -819,6 +456,12 @@ static void connect_control_ports(struct userdata *u) {
}
}
static void *update_filter_parameters(void *parameters, void *userdata) {
connect_control_ports(userdata);
return NULL;
}
static int validate_control_parameters(struct userdata *u, double *control_values, bool *use_default) {
unsigned long p = 0, h = 0;
const LADSPA_Descriptor *d;
@ -1000,15 +643,12 @@ int pa__init(pa_module*m) {
char *t;
const char *master_name;
pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
const char *plugin, *label, *input_ladspaport_map, *output_ladspaport_map;
LADSPA_Descriptor_Function descriptor_func;
unsigned long input_ladspaport[PA_CHANNELS_MAX], output_ladspaport[PA_CHANNELS_MAX];
const char *e, *cdata;
const LADSPA_Descriptor *d;
unsigned long p, h, j, n_control, c;
pa_memchunk silence;
pa_assert(m);
@ -1273,113 +913,25 @@ int pa__init(pa_module*m) {
for (c = 0; c < (u->channels / u->max_ladspaport_count); c++)
d->activate(u->handle[c]);
/* Create sink */
pa_sink_new_data_init(&sink_data);
sink_data.driver = __FILE__;
sink_data.module = m;
if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
sink_data.name = pa_sprintf_malloc("%s.ladspa", master->name);
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &map);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
pa_proplist_sets(sink_data.proplist, "device.ladspa.module", plugin);
pa_proplist_sets(sink_data.proplist, "device.ladspa.label", d->Label);
pa_proplist_sets(sink_data.proplist, "device.ladspa.name", d->Name);
pa_proplist_sets(sink_data.proplist, "device.ladspa.maker", d->Maker);
pa_proplist_sets(sink_data.proplist, "device.ladspa.copyright", d->Copyright);
pa_proplist_setf(sink_data.proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID);
if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_sink_new_data_done(&sink_data);
goto fail;
}
u->autoloaded = DEFAULT_AUTOLOADED;
if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) {
pa_log("Failed to parse autoloaded value");
goto fail;
}
if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
const char *z;
z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", d->Name, z ? z : master->name);
}
u->sink = pa_sink_new(m->core, &sink_data,
(master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)) | PA_SINK_SHARE_VOLUME_WITH_MASTER);
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
pa_log("Failed to create sink.");
goto fail;
}
u->sink->parent.process_msg = sink_process_msg_cb;
u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb;
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->request_rewind = sink_request_rewind_cb;
pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true);
sink_input_data.origin_sink = u->sink;
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream");
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
if (pa_modargs_get_proplist(ma, "sink_input_properties", sink_input_data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_sink_input_new_data_done(&sink_input_data);
goto fail;
}
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
sink_input_data.flags |= PA_SINK_INPUT_START_CORKED;
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
/* Create virtual sink */
if (!(u->vsink = pa_virtual_sink_create(master, "ladspa", "LADSPA Sink", &ss, &map,
&ss, &map, m, u, ma, true, true, 0)))
goto fail;
u->sink_input->pop = sink_input_pop_cb;
u->sink_input->process_rewind = sink_input_process_rewind_cb;
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
u->sink_input->moving = sink_input_moving_cb;
u->sink_input->mute_changed = sink_input_mute_changed_cb;
u->sink_input->suspend = sink_input_suspend_cb;
u->sink_input->userdata = u;
u->vsink->process_chunk = filter_process_chunk;
u->vsink->rewind_filter = reset_filter;
u->vsink->update_filter_parameters = update_filter_parameters;
u->vsink->max_chunk_size = u->block_size;
u->sink->input_to_master = u->sink_input;
pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.module", plugin);
pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.label", d->Label);
pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.name", d->Name);
pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.maker", d->Maker);
pa_proplist_sets(u->vsink->sink->proplist, "device.ladspa.copyright", d->Copyright);
pa_proplist_setf(u->vsink->sink->proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID);
pa_sink_input_get_silence(u->sink_input, &silence);
u->memblockq = pa_memblockq_new("module-ladspa-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence);
pa_memblock_unref(silence.memblock);
/* The order here is important. The input must be put first,
* otherwise streams might attach to the sink before the sink
* input is attached to the master. */
pa_sink_input_put(u->sink_input);
pa_sink_put(u->sink);
pa_sink_input_cork(u->sink_input, false);
if (pa_virtual_sink_activate(u->vsink) < 0)
goto fail;
#ifdef HAVE_DBUS
dbus_init(u);
@ -1404,7 +956,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m);
pa_assert_se(u = m->userdata);
return pa_sink_linked_by(u->sink);
return pa_sink_linked_by(u->vsink->sink);
}
void pa__done(pa_module*m) {
@ -1416,26 +968,12 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
/* See comments in sink_input_kill_cb() above regarding
* destruction order! */
#ifdef HAVE_DBUS
dbus_done(u);
#endif
if (u->sink_input)
pa_sink_input_cork(u->sink_input, true);
if (u->sink)
pa_sink_unlink(u->sink);
if (u->sink_input) {
pa_sink_input_unlink(u->sink_input);
pa_sink_input_unref(u->sink_input);
}
if (u->sink)
pa_sink_unref(u->sink);
if (u->vsink)
pa_virtual_sink_destroy(u->vsink);
for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) {
if (u->handle[c]) {
@ -1464,9 +1002,6 @@ void pa__done(pa_module*m) {
}
}
if (u->memblockq)
pa_memblockq_free(u->memblockq);
pa_xfree(u->control);
pa_xfree(u->use_default);
pa_xfree(u);

View file

@ -21,13 +21,13 @@
#include <config.h>
#endif
#include <modules/virtual-sink-common.h>
#include <pulse/xmalloc.h>
#include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/rtpoll.h>
@ -50,10 +50,7 @@ PA_MODULE_USAGE(
struct userdata {
pa_module *module;
pa_sink *sink;
pa_sink_input *sink_input;
bool auto_desc;
pa_vsink *vsink;
};
static const char* const valid_modargs[] = {
@ -70,273 +67,12 @@ static const char* const valid_modargs[] = {
NULL
};
/* Called from I/O thread context */
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
case PA_SINK_MESSAGE_GET_LATENCY:
/* The sink is _put() before the sink input is, so let's
* make sure we don't access it yet */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
*((int64_t*) data) = 0;
return 0;
}
*((int64_t*) data) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
/* Add resampler latency */
*((int64_t*) data) += pa_resampler_get_delay_usec(u->sink_input->thread_info.resampler);
return 0;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int sink_set_state_in_main_thread(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return 0;
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
/* Called from the IO thread. */
static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) {
struct userdata *u;
pa_assert(s);
pa_assert_se(u = s->userdata);
/* When set to running or idle for the first time, request a rewind
* of the master sink to make sure we are heard immediately */
if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) {
pa_log_debug("Requesting rewind due to state change.");
pa_sink_input_request_rewind(u->sink_input, 0, false, true, true);
}
return 0;
}
/* Called from I/O thread context */
static void sink_request_rewind(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, true, false, false);
}
/* Called from I/O thread context */
static void sink_update_requested_latency(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
pa_sink_get_requested_latency_within_thread(s));
}
/* Called from I/O thread context */
static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert(chunk);
pa_assert_se(u = i->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
return -1;
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind(u->sink, 0);
pa_sink_render(u->sink, nbytes, chunk);
return 0;
}
/* Called from I/O thread context */
static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
size_t amount = 0;
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* If the sink is not yet linked, there is nothing to rewind */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
if (u->sink->thread_info.rewind_nbytes > 0) {
amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes);
u->sink->thread_info.rewind_nbytes = 0;
}
pa_sink_process_rewind(u->sink, amount);
}
/* Called from I/O thread context */
static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
}
/* Called from I/O thread context */
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_detach_within_thread(u->sink);
pa_sink_set_rtpoll(u->sink, NULL);
}
/* Called from I/O thread context */
static void sink_input_attach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_attach_within_thread(u->sink);
}
/* Called from main context */
static void sink_input_kill_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* The order here matters! We first kill the sink so that streams
* can properly be moved away while the sink input is still connected
* to the master. */
pa_sink_input_cork(u->sink_input, true);
pa_sink_unlink(u->sink);
pa_sink_input_unlink(u->sink_input);
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_sink_unref(u->sink);
u->sink = NULL;
pa_module_unload_request(u->module, true);
}
/* Called from main context */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (dest) {
pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
} else
pa_sink_set_asyncmsgq(u->sink, NULL);
if (u->auto_desc && dest) {
const char *k;
pa_proplist *pl;
pl = pa_proplist_new();
k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name);
pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
pa_proplist_free(pl);
}
}
int pa__init(pa_module*m) {
struct userdata *u;
pa_sample_spec ss;
pa_resample_method_t resample_method = PA_RESAMPLER_INVALID;
pa_channel_map sink_map, stream_map;
pa_modargs *ma;
pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
bool remix = true;
pa_assert(m);
@ -371,100 +107,17 @@ int pa__init(pa_module*m) {
if (pa_channel_map_equal(&stream_map, &master->channel_map))
pa_log_warn("No remapping configured, proceeding nonetheless!");
if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) {
pa_log("Invalid boolean remix parameter");
goto fail;
}
if (pa_modargs_get_resample_method(ma, &resample_method) < 0) {
pa_log("Invalid resampling method");
goto fail;
}
u = pa_xnew0(struct userdata, 1);
u->module = m;
m->userdata = u;
/* Create sink */
pa_sink_new_data_init(&sink_data);
sink_data.driver = __FILE__;
sink_data.module = m;
if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
sink_data.name = pa_sprintf_malloc("%s.remapped", master->name);
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &sink_map);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
/* Create virtual sink */
if (!(u->vsink = pa_virtual_sink_create(master, "remapped", "Remapped Sink", &ss, &sink_map,
&ss, &stream_map, m, u, ma, false, false, 0)))
goto fail;
if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_sink_new_data_done(&sink_data);
if (pa_virtual_sink_activate(u->vsink) < 0)
goto fail;
}
if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
const char *k;
k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name);
}
u->sink = pa_sink_new(m->core, &sink_data, master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
pa_log("Failed to create sink.");
goto fail;
}
u->sink->parent.process_msg = sink_process_msg;
u->sink->set_state_in_main_thread = sink_set_state_in_main_thread;
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
u->sink->update_requested_latency = sink_update_requested_latency;
u->sink->request_rewind = sink_request_rewind;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true);
sink_input_data.origin_sink = u->sink;
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream");
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &stream_map);
sink_input_data.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX) | PA_SINK_INPUT_START_CORKED;
sink_input_data.resample_method = resample_method;
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
goto fail;
u->sink_input->pop = sink_input_pop_cb;
u->sink_input->process_rewind = sink_input_process_rewind_cb;
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->moving = sink_input_moving_cb;
u->sink_input->userdata = u;
u->sink->input_to_master = u->sink_input;
/* The order here is important. The input must be put first,
* otherwise streams might attach to the sink before the sink
* input is attached to the master. */
pa_sink_input_put(u->sink_input);
pa_sink_put(u->sink);
pa_sink_input_cork(u->sink_input, false);
pa_modargs_free(ma);
@ -485,7 +138,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m);
pa_assert_se(u = m->userdata);
return pa_sink_linked_by(u->sink);
return pa_sink_linked_by(u->vsink->sink);
}
void pa__done(pa_module*m) {
@ -496,22 +149,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
/* See comments in sink_input_kill_cb() above regarding
* destruction order! */
if (u->sink_input)
pa_sink_input_cork(u->sink_input, true);
if (u->sink)
pa_sink_unlink(u->sink);
if (u->sink_input) {
pa_sink_input_unlink(u->sink_input);
pa_sink_input_unref(u->sink_input);
}
if (u->sink)
pa_sink_unref(u->sink);
if (u->vsink)
pa_virtual_sink_destroy(u->vsink);
pa_xfree(u);
}

View file

@ -22,6 +22,8 @@
#include <config.h>
#endif
#include <modules/virtual-source-common.h>
#include <stdio.h>
#include <pulse/xmalloc.h>
@ -47,6 +49,7 @@ PA_MODULE_USAGE(
"source_properties=<properties for the source> "
"master=<name of source to filter> "
"master_channel_map=<channel map> "
"uplink_sink=<name> (optional)"
"format=<sample format> "
"rate=<sample rate> "
"channels=<number of channels> "
@ -57,10 +60,7 @@ PA_MODULE_USAGE(
struct userdata {
pa_module *module;
pa_source *source;
pa_source_output *source_output;
bool auto_desc;
pa_vsource *vsource;
};
static const char* const valid_modargs[] = {
@ -68,6 +68,7 @@ static const char* const valid_modargs[] = {
"source_properties",
"master",
"master_channel_map",
"uplink_sink",
"format",
"rate",
"channels",
@ -77,226 +78,12 @@ static const char* const valid_modargs[] = {
NULL
};
/* Called from I/O thread context */
static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SOURCE(o)->userdata;
switch (code) {
case PA_SOURCE_MESSAGE_GET_LATENCY:
/* The source is _put() before the source output is, so let's
* make sure we don't access it in that time. Also, the
* source output is first shut down, the source second. */
if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) {
*((int64_t*) data) = 0;
return 0;
}
*((int64_t*) data) =
/* Get the latency of the master source */
pa_source_get_latency_within_thread(u->source_output->source, true) +
/* Add the latency internal to our source output on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec);
/* Add resampler delay */
*((int64_t*) data) += pa_resampler_get_delay_usec(u->source_output->thread_info.resampler);
return 0;
}
return pa_source_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int source_set_state_in_main_thread_cb(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) {
struct userdata *u;
pa_source_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SOURCE_IS_LINKED(state) ||
!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state))
return 0;
pa_source_output_cork(u->source_output, state == PA_SOURCE_SUSPENDED);
return 0;
}
/* Called from I/O thread context */
static void source_update_requested_latency_cb(pa_source *s) {
struct userdata *u;
pa_source_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state))
return;
pa_log_debug("Source update requested latency.");
/* Just hand this one over to the master source */
pa_source_output_set_requested_latency_within_thread(
u->source_output,
pa_source_get_requested_latency_within_thread(s));
}
/* Called from output thread context */
static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state))
return;
if (!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) {
pa_log("push when no link?");
return;
}
pa_source_post(u->source, chunk);
}
/* Called from output thread context */
static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
/* If the source is not yet linked, there is nothing to rewind */
if (PA_SOURCE_IS_LINKED(u->source->thread_info.state))
pa_source_process_rewind(u->source, nbytes);
}
/* Called from output thread context */
static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
pa_source_set_max_rewind_within_thread(u->source, nbytes);
}
/* Called from output thread context */
static void source_output_detach_cb(pa_source_output *o) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
if (PA_SOURCE_IS_LINKED(u->source->thread_info.state))
pa_source_detach_within_thread(u->source);
pa_source_set_rtpoll(u->source, NULL);
}
/* Called from output thread context */
static void source_output_attach_cb(pa_source_output *o) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll);
pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency);
pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o));
if (PA_SOURCE_IS_LINKED(u->source->thread_info.state))
pa_source_attach_within_thread(u->source);
}
/* Called from main thread */
static void source_output_kill_cb(pa_source_output *o) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_assert_ctl_context();
pa_assert_se(u = o->userdata);
/* The order here matters! We first kill the source so that streams
* can properly be moved away while the source output is still connected
* to the master. */
pa_source_output_cork(u->source_output, true);
pa_source_unlink(u->source);
pa_source_output_unlink(u->source_output);
pa_source_output_unref(u->source_output);
u->source_output = NULL;
pa_source_unref(u->source);
u->source = NULL;
pa_module_unload_request(u->module, true);
}
/* Called from output thread context except when cork() is called without valid source. */
static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_assert_se(u = o->userdata);
pa_log_debug("Source output %d state %d.", o->index, state);
}
/* Called from main thread */
static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
struct userdata *u;
uint32_t idx;
pa_source_output *output;
pa_source_output_assert_ref(o);
pa_assert_ctl_context();
pa_assert_se(u = o->userdata);
if (dest) {
pa_source_set_asyncmsgq(u->source, dest->asyncmsgq);
pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags);
} else
pa_source_set_asyncmsgq(u->source, NULL);
/* Propagate asyncmsq change to attached virtual sources */
PA_IDXSET_FOREACH(output, u->source->outputs, idx) {
if (output->destination_source && output->moving)
output->moving(output, u->source);
}
if (u->auto_desc && dest) {
const char *k;
pa_proplist *pl;
pl = pa_proplist_new();
k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name);
pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl);
pa_proplist_free(pl);
}
}
int pa__init(pa_module*m) {
struct userdata *u;
pa_sample_spec ss;
pa_resample_method_t resample_method = PA_RESAMPLER_INVALID;
pa_channel_map source_map, stream_map;
pa_modargs *ma;
pa_source *master;
pa_source_output_new_data source_output_data;
pa_source_new_data source_data;
bool remix = true;
pa_assert(m);
@ -331,98 +118,17 @@ int pa__init(pa_module*m) {
if (pa_channel_map_equal(&stream_map, &master->channel_map))
pa_log_warn("No remapping configured, proceeding nonetheless!");
if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) {
pa_log("Invalid boolean remix parameter.");
goto fail;
}
if (pa_modargs_get_resample_method(ma, &resample_method) < 0) {
pa_log("Invalid resampling method");
goto fail;
}
u = pa_xnew0(struct userdata, 1);
u->module = m;
m->userdata = u;
/* Create source */
pa_source_new_data_init(&source_data);
source_data.driver = __FILE__;
source_data.module = m;
if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL))))
source_data.name = pa_sprintf_malloc("%s.remapped", master->name);
pa_source_new_data_set_sample_spec(&source_data, &ss);
pa_source_new_data_set_channel_map(&source_data, &source_map);
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
/* Create virtual sink */
if (!(u->vsource = pa_virtual_source_create(master, "remapped", "Remapped Source", &ss, &source_map,
&ss, &stream_map, m, u, ma, false, false)))
goto fail;
if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties.");
pa_source_new_data_done(&source_data);
if (pa_virtual_source_activate(u->vsource) < 0)
goto fail;
}
if ((u->auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
const char *k;
k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name);
}
u->source = pa_source_new(m->core, &source_data, master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY));
pa_source_new_data_done(&source_data);
if (!u->source) {
pa_log("Failed to create source.");
goto fail;
}
u->source->parent.process_msg = source_process_msg_cb;
u->source->set_state_in_main_thread = source_set_state_in_main_thread_cb;
u->source->update_requested_latency = source_update_requested_latency_cb;
u->source->userdata = u;
pa_source_set_asyncmsgq(u->source, master->asyncmsgq);
/* Create source output */
pa_source_output_new_data_init(&source_output_data);
source_output_data.driver = __FILE__;
source_output_data.module = m;
pa_source_output_new_data_set_source(&source_output_data, master, false, true);
source_output_data.destination_source = u->source;
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream");
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_source_output_new_data_set_sample_spec(&source_output_data, &ss);
pa_source_output_new_data_set_channel_map(&source_output_data, &stream_map);
source_output_data.flags = (remix ? 0 : PA_SOURCE_OUTPUT_NO_REMIX) | PA_SOURCE_OUTPUT_START_CORKED;
source_output_data.resample_method = resample_method;
pa_source_output_new(&u->source_output, m->core, &source_output_data);
pa_source_output_new_data_done(&source_output_data);
if (!u->source_output)
goto fail;
u->source_output->push = source_output_push_cb;
u->source_output->process_rewind = source_output_process_rewind_cb;
u->source_output->update_max_rewind = source_output_update_max_rewind_cb;
u->source_output->kill = source_output_kill_cb;
u->source_output->attach = source_output_attach_cb;
u->source_output->detach = source_output_detach_cb;
u->source_output->state_change = source_output_state_change_cb;
u->source_output->moving = source_output_moving_cb;
u->source_output->userdata = u;
u->source->output_from_master = u->source_output;
/* The order here is important. The output must be put first,
* otherwise streams might attach to the source before the
* source output is attached to the master. */
pa_source_output_put(u->source_output);
pa_source_put(u->source);
pa_source_output_cork(u->source_output, false);
pa_modargs_free(ma);
@ -443,7 +149,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m);
pa_assert_se(u = m->userdata);
return pa_source_linked_by(u->source);
return pa_source_linked_by(u->vsource->source);
}
void pa__done(pa_module*m) {
@ -454,22 +160,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
/* See comments in source_output_kill_cb() above regarding
* destruction order! */
if (u->source_output)
pa_source_output_cork(u->source_output, true);
if (u->source)
pa_source_unlink(u->source);
if (u->source_output) {
pa_source_output_unlink(u->source_output);
pa_source_output_unref(u->source_output);
}
if (u->source)
pa_source_unref(u->source);
if (u->vsource)
pa_virtual_source_destroy(u->vsource);
pa_xfree(u);
}

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);
}
if (d->source && pa_source_check_suspend(d->source, NULL) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) {
if (d->source && pa_source_check_suspend(d->source, NULL, NULL) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) {
pa_log_info("Source %s idle for too long, suspending ...", d->source->name);
pa_source_suspend(d->source, true, PA_SUSPEND_IDLE);
pa_core_maybe_vacuum(d->userdata->core);
@ -109,6 +109,29 @@ static void resume(struct device_info *d) {
}
}
/* If the monitor source of a sink becomes idle and the sink is
* idle as well, we have to check if it is an uplink sink because
* the underlying virtual source might also have become idle. */
static void restart_check_uplink(struct device_info *d, pa_source_output *ignore, struct userdata *u) {
struct device_info *d_master;
pa_assert(d);
pa_assert(u);
restart(d);
if (!d->sink || (d->sink && !d->sink->uplink_of))
return;
if (!d->sink->uplink_of->source)
return;
if ((d_master = pa_hashmap_get(u->device_infos, d->sink->uplink_of->source))) {
if (pa_source_check_suspend(d_master->source, NULL, ignore) <= 0)
restart(d_master);
}
}
static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {
struct device_info *d;
@ -145,7 +168,7 @@ static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_outpu
if (d) {
resume(d);
if (d->source) {
if (pa_source_check_suspend(d->source, NULL) <= 0)
if (pa_source_check_suspend(d->source, NULL, NULL) <= 0)
restart(d);
} else {
/* The source output is connected to a monitor source. */
@ -170,6 +193,13 @@ static pa_hook_result_t sink_input_unlink_hook_cb(pa_core *c, pa_sink_input *s,
struct device_info *d;
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
restart(d);
if (s->sink->uplink_of && s->sink->uplink_of->source) {
if (pa_source_check_suspend(s->sink->uplink_of->source, s, NULL) <= 0) {
if ((d = pa_hashmap_get(u->device_infos, s->sink->uplink_of->source)))
restart(d);
}
}
}
return PA_HOOK_OK;
@ -189,12 +219,12 @@ static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_outpu
if (pa_sink_check_suspend(s->source->monitor_of, NULL, s) <= 0)
d = pa_hashmap_get(u->device_infos, s->source->monitor_of);
} else {
if (pa_source_check_suspend(s->source, s) <= 0)
if (pa_source_check_suspend(s->source, NULL, s) <= 0)
d = pa_hashmap_get(u->device_infos, s->source);
}
if (d)
restart(d);
restart_check_uplink(d, s, u);
return PA_HOOK_OK;
}
@ -206,10 +236,18 @@ static pa_hook_result_t sink_input_move_start_hook_cb(pa_core *c, pa_sink_input
pa_sink_input_assert_ref(s);
pa_assert(u);
if (pa_sink_check_suspend(s->sink, s, NULL) <= 0)
if (pa_sink_check_suspend(s->sink, s, NULL) <= 0) {
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
restart(d);
if (s->sink->uplink_of && s->sink->uplink_of->source) {
if (pa_source_check_suspend(s->sink->uplink_of->source, s, NULL) <= 0) {
if ((d = pa_hashmap_get(u->device_infos, s->sink->uplink_of->source)))
restart(d);
}
}
}
return PA_HOOK_OK;
}
@ -240,12 +278,12 @@ static pa_hook_result_t source_output_move_start_hook_cb(pa_core *c, pa_source_o
if (pa_sink_check_suspend(s->source->monitor_of, NULL, s) <= 0)
d = pa_hashmap_get(u->device_infos, s->source->monitor_of);
} else {
if (pa_source_check_suspend(s->source, s) <= 0)
if (pa_source_check_suspend(s->source, NULL, s) <= 0)
d = pa_hashmap_get(u->device_infos, s->source);
}
if (d)
restart(d);
restart_check_uplink(d, s, u);
return PA_HOOK_OK;
}
@ -278,9 +316,14 @@ static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_inp
pa_sink_input_assert_ref(s);
pa_assert(u);
if (s->state == PA_SINK_INPUT_RUNNING && s->sink)
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
resume(d);
if (s->sink) {
if ((d = pa_hashmap_get(u->device_infos, s->sink))) {
if (s->state == PA_SINK_INPUT_RUNNING)
resume(d);
else if (s->state == PA_SINK_INPUT_CORKED && pa_sink_check_suspend(s->sink, NULL, NULL) <= 0)
restart(d);
}
}
return PA_HOOK_OK;
}
@ -289,9 +332,9 @@ static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_sourc
pa_assert(c);
pa_source_output_assert_ref(s);
pa_assert(u);
struct device_info *d = NULL;
if (s->state == PA_SOURCE_OUTPUT_RUNNING && s->source) {
struct device_info *d;
if (s->source->monitor_of)
d = pa_hashmap_get(u->device_infos, s->source->monitor_of);
@ -300,6 +343,15 @@ static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_sourc
if (d)
resume(d);
} else if (s->state == PA_SOURCE_OUTPUT_CORKED && s->source) {
if (s->source->monitor_of && pa_sink_check_suspend(s->source->monitor_of, NULL, NULL) <= 0)
d = pa_hashmap_get(u->device_infos, s->source->monitor_of);
else if (pa_source_check_suspend(s->source, NULL, NULL) <= 0)
d = pa_hashmap_get(u->device_infos, s->source);
if (d)
restart_check_uplink(d, NULL, u);
}
return PA_HOOK_OK;
@ -349,7 +401,7 @@ static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct user
pa_hashmap_put(u->device_infos, o, d);
if ((d->sink && pa_sink_check_suspend(d->sink, NULL, NULL) <= 0) ||
(d->source && pa_source_check_suspend(d->source, NULL) <= 0))
(d->source && pa_source_check_suspend(d->source, NULL, NULL) <= 0))
restart(d);
return PA_HOOK_OK;
@ -407,7 +459,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s
} else if (pa_source_isinstance(o)) {
pa_source *s = PA_SOURCE(o);
if (pa_source_check_suspend(s, NULL) <= 0)
if (pa_source_check_suspend(s, NULL, NULL) <= 0)
if (PA_SOURCE_IS_OPENED(s->state))
restart(d);
}

View file

@ -22,15 +22,15 @@
#include <config.h>
#endif
#include <modules/virtual-sink-common.h>
#include <pulse/gccmacro.h>
#include <pulse/xmalloc.h>
#include <pulsecore/i18n.h>
#include <pulsecore/namereg.h>
#include <pulsecore/sink.h>
#include <pulsecore/module.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/sample-util.h>
@ -46,25 +46,17 @@ PA_MODULE_USAGE(
"master=<name of sink to filter> "
"rate=<sample rate> "
"channels=<number of channels> "
"format=<sample format> "
"channel_map=<channel map> "
"use_volume_sharing=<yes or no> "
"force_flat_volume=<yes or no> "
));
#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
struct userdata {
pa_module *module;
/* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */
/* bool autoloaded; */
pa_vsink *vsink;
pa_sink *sink;
pa_sink_input *sink_input;
pa_memblockq *memblockq;
bool auto_desc;
unsigned channels;
};
@ -74,6 +66,7 @@ static const char* const valid_modargs[] = {
"master",
"rate",
"channels",
"format",
"channel_map",
"use_volume_sharing",
"force_flat_volume",
@ -81,397 +74,17 @@ static const char* const valid_modargs[] = {
};
/* Called from I/O thread context */
static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
case PA_SINK_MESSAGE_GET_LATENCY:
/* The sink is _put() before the sink input is, so let's
* make sure we don't access it in that time. Also, the
* sink input is first shut down, the sink second. */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
*((int64_t*) data) = 0;
return 0;
}
*((int64_t*) data) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
/* Add resampler latency */
*((int64_t*) data) += pa_resampler_get_delay_usec(u->sink_input->thread_info.resampler);
return 0;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) {
static void filter_process_chunk(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata) {
struct userdata *u;
unsigned buffer_size;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
pa_assert_se(u = userdata);
pa_assert(in_count == out_count);
if (!PA_SINK_IS_LINKED(state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return 0;
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
/* Called from the IO thread. */
static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) {
struct userdata *u;
pa_assert(s);
pa_assert_se(u = s->userdata);
/* When set to running or idle for the first time, request a rewind
* of the master sink to make sure we are heard immediately */
if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) {
pa_log_debug("Requesting rewind due to state change.");
pa_sink_input_request_rewind(u->sink_input, 0, false, true, true);
}
return 0;
}
/* Called from I/O thread context */
static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_request_rewind(u->sink_input,
s->thread_info.rewind_nbytes +
pa_memblockq_get_length(u->memblockq), true, false, false);
}
/* Called from I/O thread context */
static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
pa_sink_get_requested_latency_within_thread(s));
}
/* Called from main context */
static void sink_set_volume_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, true);
}
/* Called from main context */
static void sink_set_mute_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
}
/* Called from I/O thread context */
static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
struct userdata *u;
float *src, *dst;
size_t fs;
unsigned n, c;
pa_memchunk tchunk;
pa_usec_t current_latency PA_GCC_UNUSED;
pa_sink_input_assert_ref(i);
pa_assert(chunk);
pa_assert_se(u = i->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
return -1;
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind(u->sink, 0);
/* (1) IF YOU NEED A FIXED BLOCK SIZE USE
* pa_memblockq_peek_fixed_size() HERE INSTEAD. NOTE THAT FILTERS
* WHICH CAN DEAL WITH DYNAMIC BLOCK SIZES ARE HIGHLY
* PREFERRED. */
while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
pa_memchunk nchunk;
pa_sink_render(u->sink, nbytes, &nchunk);
pa_memblockq_push(u->memblockq, &nchunk);
pa_memblock_unref(nchunk.memblock);
}
/* (2) IF YOU NEED A FIXED BLOCK SIZE, THIS NEXT LINE IS NOT
* NECESSARY */
tchunk.length = PA_MIN(nbytes, tchunk.length);
pa_assert(tchunk.length > 0);
fs = pa_frame_size(&i->sample_spec);
n = (unsigned) (tchunk.length / fs);
pa_assert(n > 0);
chunk->index = 0;
chunk->length = n*fs;
chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length);
pa_memblockq_drop(u->memblockq, chunk->length);
src = pa_memblock_acquire_chunk(&tchunk);
dst = pa_memblock_acquire(chunk->memblock);
/* (3) PUT YOUR CODE HERE TO DO SOMETHING WITH THE DATA */
buffer_size = pa_frame_size(&u->vsink->sink->sample_spec) * in_count;
/* As an example, copy input to output */
for (c = 0; c < u->channels; c++) {
pa_sample_clamp(PA_SAMPLE_FLOAT32NE,
dst+c, u->channels * sizeof(float),
src+c, u->channels * sizeof(float),
n);
}
pa_memblock_release(tchunk.memblock);
pa_memblock_release(chunk->memblock);
pa_memblock_unref(tchunk.memblock);
/* (4) IF YOU NEED THE LATENCY FOR SOMETHING ACQUIRE IT LIKE THIS: */
current_latency =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(i->sink, false) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec);
return 0;
}
/* Called from I/O thread context */
static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
size_t amount = 0;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* If the sink is not yet linked, there is nothing to rewind */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
if (u->sink->thread_info.rewind_nbytes > 0) {
size_t max_rewrite;
max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq);
amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
u->sink->thread_info.rewind_nbytes = 0;
if (amount > 0) {
pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true);
/* (5) PUT YOUR CODE HERE TO RESET YOUR FILTER */
}
}
pa_sink_process_rewind(u->sink, amount);
pa_memblockq_rewind(u->memblockq, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_memblockq_set_maxrewind(u->memblockq, nbytes);
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* (6) IF YOU NEED A FIXED BLOCK SIZE ROUND nbytes UP TO MULTIPLES
* OF IT HERE. THE PA_ROUND_UP MACRO IS USEFUL FOR THAT. */
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
/* Called from I/O thread context */
static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* (7) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
* BLOCK MINUS ONE SAMPLE HERE. pa_usec_to_bytes_round_up() IS
* USEFUL FOR THAT. */
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
}
/* Called from I/O thread context */
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_detach_within_thread(u->sink);
pa_sink_set_rtpoll(u->sink, NULL);
}
/* Called from I/O thread context */
static void sink_input_attach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
/* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
* BLOCK MINUS ONE SAMPLE HERE. SEE (7) */
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
/* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND
* pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT
* HERE. SEE (6) */
pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_attach_within_thread(u->sink);
}
/* Called from main context */
static void sink_input_kill_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* The order here matters! We first kill the sink so that streams
* can properly be moved away while the sink input is still connected
* to the master. */
pa_sink_input_cork(u->sink_input, true);
pa_sink_unlink(u->sink);
pa_sink_input_unlink(u->sink_input);
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_sink_unref(u->sink);
u->sink = NULL;
pa_module_unload_request(u->module, true);
}
/* Called from main context */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (dest) {
pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
} else
pa_sink_set_asyncmsgq(u->sink, NULL);
if (u->auto_desc && dest) {
const char *z;
pa_proplist *pl;
pl = pa_proplist_new();
z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s",
pa_proplist_gets(u->sink->proplist, "device.vsink.name"), z ? z : dest->name);
pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
pa_proplist_free(pl);
}
}
/* Called from main context */
static void sink_input_volume_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_volume_changed(u->sink, &i->volume);
}
/* Called from main context */
static void sink_input_mute_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_mute_changed(u->sink, i->muted);
memcpy(dst, src, buffer_size);
}
int pa__init(pa_module*m) {
@ -480,11 +93,7 @@ int pa__init(pa_module*m) {
pa_channel_map map;
pa_modargs *ma;
pa_sink *master=NULL;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
bool use_volume_sharing = true;
bool force_flat_volume = false;
pa_memchunk silence;
pa_assert(m);
@ -501,7 +110,6 @@ int pa__init(pa_module*m) {
pa_assert(master);
ss = master->sample_spec;
ss.format = PA_SAMPLE_FLOAT32;
map = master->channel_map;
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
pa_log("Invalid sample format specification or channel map");
@ -513,118 +121,23 @@ int pa__init(pa_module*m) {
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) {
pa_log("force_flat_volume= expects a boolean argument");
goto fail;
}
if (use_volume_sharing && force_flat_volume) {
pa_log("Flat volume can't be forced when using volume sharing.");
goto fail;
}
u = pa_xnew0(struct userdata, 1);
u->module = m;
m->userdata = u;
u->channels = ss.channels;
/* Create sink */
pa_sink_new_data_init(&sink_data);
sink_data.driver = __FILE__;
sink_data.module = m;
if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
sink_data.name = pa_sprintf_malloc("%s.vsink", master->name);
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &map);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
pa_proplist_sets(sink_data.proplist, "device.vsink.name", sink_data.name);
if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_sink_new_data_done(&sink_data);
goto fail;
}
if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
const char *z;
z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s", sink_data.name, z ? z : master->name);
}
u->sink = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))
| (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
pa_log("Failed to create sink.");
goto fail;
}
u->sink->parent.process_msg = sink_process_msg_cb;
u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb;
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->request_rewind = sink_request_rewind_cb;
pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
if (!use_volume_sharing) {
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
pa_sink_enable_decibel_volume(u->sink, true);
}
/* Normally this flag would be enabled automatically be we can force it. */
if (force_flat_volume)
u->sink->flags |= PA_SINK_FLAT_VOLUME;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true);
sink_input_data.origin_sink = u->sink;
pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION));
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
sink_input_data.flags |= PA_SINK_INPUT_START_CORKED;
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
/* Create virtual sink */
if (!(u->vsink = pa_virtual_sink_create(master, "vsink", "Virtual Sink", &ss, &map,
&ss, &map, m, u, ma, use_volume_sharing, true, 0)))
goto fail;
u->sink_input->pop = sink_input_pop_cb;
u->sink_input->process_rewind = sink_input_process_rewind_cb;
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->moving = sink_input_moving_cb;
u->sink_input->volume_changed = use_volume_sharing ? NULL : sink_input_volume_changed_cb;
u->sink_input->mute_changed = sink_input_mute_changed_cb;
u->sink_input->userdata = u;
/* Set callback for virtual sink */
u->vsink->process_chunk = filter_process_chunk;
u->sink->input_to_master = u->sink_input;
/* INITIALIZE YOUR FILTER HERE */
pa_sink_input_get_silence(u->sink_input, &silence);
u->memblockq = pa_memblockq_new("module-virtual-sink memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence);
pa_memblock_unref(silence.memblock);
/* (9) INITIALIZE ANYTHING ELSE YOU NEED HERE */
/* The order here is important. The input must be put first,
* otherwise streams might attach to the sink before the sink
* input is attached to the master. */
pa_sink_input_put(u->sink_input);
pa_sink_put(u->sink);
pa_sink_input_cork(u->sink_input, false);
if (pa_virtual_sink_activate(u->vsink) < 0)
goto fail;
pa_modargs_free(ma);
@ -645,7 +158,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m);
pa_assert_se(u = m->userdata);
return pa_sink_linked_by(u->sink);
return pa_sink_linked_by(u->vsink->sink);
}
void pa__done(pa_module*m) {
@ -656,25 +169,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
/* See comments in sink_input_kill_cb() above regarding
* destruction order! */
if (u->sink_input)
pa_sink_input_cork(u->sink_input, true);
if (u->sink)
pa_sink_unlink(u->sink);
if (u->sink_input) {
pa_sink_input_unlink(u->sink_input);
pa_sink_input_unref(u->sink_input);
}
if (u->sink)
pa_sink_unref(u->sink);
if (u->memblockq)
pa_memblockq_free(u->memblockq);
if (u->vsink)
pa_virtual_sink_destroy(u->vsink);
pa_xfree(u);
}

View file

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

View file

@ -29,6 +29,8 @@
#include <fftw3.h>
#include <modules/virtual-sink-common.h>
#include <pulse/gccmacro.h>
#include <pulse/xmalloc.h>
@ -73,14 +75,7 @@ PA_MODULE_USAGE(
struct userdata {
pa_module *module;
bool autoloaded;
pa_sink *sink;
pa_sink_input *sink_input;
pa_memblockq *memblockq_sink;
bool auto_desc;
pa_vsink *vsink;
size_t fftlen;
size_t hrir_samples;
@ -111,6 +106,76 @@ static const char* const valid_modargs[] = {
NULL
};
static void filter_process_chunk(uint8_t *src_p, uint8_t *dst_p, unsigned in_count, unsigned out_count, void *userdata) {
struct userdata *u;
int ear;
unsigned c;
size_t s, fftlen;
float fftlen_if, *revspace;
float *src, *dst;
pa_assert_se(u = userdata);
pa_assert(in_count == u->fftlen);
pa_assert(out_count == BLOCK_SIZE);
src = (float *)src_p;
dst = (float *)dst_p;
for (c = 0; c < u->inputs; c++) {
for (s = 0, fftlen = u->fftlen; s < fftlen; s++) {
u->inspace[c][s] = src[s * u->inputs + c];
}
}
fftlen_if = 1.0f / (float)u->fftlen;
revspace = u->revspace + u->fftlen - BLOCK_SIZE;
pa_memzero(u->outspace[0], BLOCK_SIZE * 4);
pa_memzero(u->outspace[1], BLOCK_SIZE * 4);
for (c = 0; c < u->inputs; c++) {
fftwf_complex *f_in = u->f_in;
fftwf_complex *f_out = u->f_out;
fftwf_execute(u->p_fw[c]);
for (ear = 0; ear < 2; ear++) {
fftwf_complex *f_ir = u->f_ir[c * 2 + ear];
float *outspace = u->outspace[ear];
for (s = 0, fftlen = u->fftlen / 2 + 1; s < fftlen; s++) {
float re = f_ir[s][0] * f_in[s][0] - f_ir[s][1] * f_in[s][1];
float im = f_ir[s][1] * f_in[s][0] + f_ir[s][0] * f_in[s][1];
f_out[s][0] = re;
f_out[s][1] = im;
}
fftwf_execute(u->p_bw);
for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; ++s)
outspace[s] += revspace[s] * fftlen_if;
}
}
for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; s++) {
float output;
float *outspace = u->outspace[0];
output = outspace[s];
if (output < -1.0) output = -1.0;
if (output > 1.0) output = 1.0;
dst[s * 2 + 0] = output;
outspace = u->outspace[1];
output = outspace[s];
if (output < -1.0) output = -1.0;
if (output > 1.0) output = 1.0;
dst[s * 2 + 1] = output;
}
}
/* Vector size of 4 floats */
#define v_size 4
static void * alloc(size_t x, size_t s) {
@ -124,26 +189,6 @@ static void * alloc(size_t x, size_t s) {
return t;
}
static size_t sink_input_samples(size_t nbytes)
{
return nbytes / 8;
}
static size_t sink_input_bytes(size_t nsamples)
{
return nsamples * 8;
}
static size_t sink_samples(const struct userdata *u, size_t nbytes)
{
return nbytes / (u->inputs * 4);
}
static size_t sink_bytes(const struct userdata *u, size_t nsamples)
{
return nsamples * (u->inputs * 4);
}
/* Mirror channels for symmetrical impulse */
static pa_channel_position_t mirror_channel(pa_channel_position_t channel) {
switch (channel) {
@ -255,449 +300,6 @@ static void normalize_hrir_stereo(float * hrir_data, float * hrir_right_data, un
}
}
/* Called from I/O thread context */
static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
case PA_SINK_MESSAGE_GET_LATENCY:
/* The sink is _put() before the sink input is, so let's
* make sure we don't access it in that time. Also, the
* sink input is first shut down, the sink second. */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
*((pa_usec_t*) data) = 0;
return 0;
}
*((pa_usec_t*) data) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
/* Add resampler latency */
*((int64_t*) data) += pa_resampler_get_delay_usec(u->sink_input->thread_info.resampler);
return 0;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return 0;
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
/* Called from the IO thread. */
static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, pa_suspend_cause_t new_suspend_cause) {
struct userdata *u;
pa_assert(s);
pa_assert_se(u = s->userdata);
/* When set to running or idle for the first time, request a rewind
* of the master sink to make sure we are heard immediately */
if (PA_SINK_IS_OPENED(new_state) && s->thread_info.state == PA_SINK_INIT) {
pa_log_debug("Requesting rewind due to state change.");
pa_sink_input_request_rewind(u->sink_input, 0, false, true, true);
}
return 0;
}
/* Called from I/O thread context */
static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u;
size_t nbytes_sink, nbytes_input;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
nbytes_sink = s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq_sink);
nbytes_input = sink_input_bytes(sink_samples(u, nbytes_sink));
/* Just hand this one over to the master sink */
pa_sink_input_request_rewind(u->sink_input, nbytes_input, true, false, false);
}
/* Called from I/O thread context */
static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
pa_sink_get_requested_latency_within_thread(s));
}
/* Called from main context */
static void sink_set_volume_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, true);
}
/* Called from main context */
static void sink_set_mute_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(s->state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->state))
return;
pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
}
static size_t memblockq_missing(pa_memblockq *bq) {
size_t l, tlength;
pa_assert(bq);
tlength = pa_memblockq_get_tlength(bq);
if ((l = pa_memblockq_get_length(bq)) >= tlength)
return 0;
l = tlength - l;
return l >= pa_memblockq_get_minreq(bq) ? l : 0;
}
/* Called from I/O thread context */
static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes_input, pa_memchunk *chunk) {
struct userdata *u;
float *src, *dst;
int c, ear;
size_t s, bytes_missing, fftlen;
pa_memchunk tchunk;
float fftlen_if, *revspace;
pa_sink_input_assert_ref(i);
pa_assert(chunk);
pa_assert_se(u = i->userdata);
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind(u->sink, 0);
while ((bytes_missing = memblockq_missing(u->memblockq_sink)) != 0) {
pa_memchunk nchunk;
pa_sink_render(u->sink, bytes_missing, &nchunk);
pa_memblockq_push(u->memblockq_sink, &nchunk);
pa_memblock_unref(nchunk.memblock);
}
pa_memblockq_rewind(u->memblockq_sink, sink_bytes(u, u->fftlen - BLOCK_SIZE));
pa_memblockq_peek_fixed_size(u->memblockq_sink, sink_bytes(u, u->fftlen), &tchunk);
pa_memblockq_drop(u->memblockq_sink, tchunk.length);
/* Now tchunk contains enough data to perform the FFT
* This should be equal to u->fftlen */
chunk->index = 0;
chunk->length = sink_input_bytes(BLOCK_SIZE);
chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length);
src = pa_memblock_acquire_chunk(&tchunk);
for (c = 0; c < u->inputs; c++) {
for (s = 0, fftlen = u->fftlen; s < fftlen; s++) {
u->inspace[c][s] = src[s * u->inputs + c];
}
}
pa_memblock_release(tchunk.memblock);
pa_memblock_unref(tchunk.memblock);
fftlen_if = 1.0f / (float)u->fftlen;
revspace = u->revspace + u->fftlen - BLOCK_SIZE;
pa_memzero(u->outspace[0], BLOCK_SIZE * 4);
pa_memzero(u->outspace[1], BLOCK_SIZE * 4);
for (c = 0; c < u->inputs; c++) {
fftwf_complex *f_in = u->f_in;
fftwf_complex *f_out = u->f_out;
fftwf_execute(u->p_fw[c]);
for (ear = 0; ear < 2; ear++) {
fftwf_complex *f_ir = u->f_ir[c * 2 + ear];
float *outspace = u->outspace[ear];
for (s = 0, fftlen = u->fftlen / 2 + 1; s < fftlen; s++) {
float re = f_ir[s][0] * f_in[s][0] - f_ir[s][1] * f_in[s][1];
float im = f_ir[s][1] * f_in[s][0] + f_ir[s][0] * f_in[s][1];
f_out[s][0] = re;
f_out[s][1] = im;
}
fftwf_execute(u->p_bw);
for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; ++s)
outspace[s] += revspace[s] * fftlen_if;
}
}
dst = pa_memblock_acquire_chunk(chunk);
for (s = 0, fftlen = BLOCK_SIZE; s < fftlen; s++) {
float output;
float *outspace = u->outspace[0];
output = outspace[s];
if (output < -1.0) output = -1.0;
if (output > 1.0) output = 1.0;
dst[s * 2 + 0] = output;
outspace = u->outspace[1];
output = outspace[s];
if (output < -1.0) output = -1.0;
if (output > 1.0) output = 1.0;
dst[s * 2 + 1] = output;
}
pa_memblock_release(chunk->memblock);
return 0;
}
/* Called from I/O thread context */
static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes_input) {
struct userdata *u;
size_t amount = 0;
size_t nbytes_sink;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input));
if (u->sink->thread_info.rewind_nbytes > 0) {
size_t max_rewrite;
max_rewrite = nbytes_sink + pa_memblockq_get_length(u->memblockq_sink);
amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite);
u->sink->thread_info.rewind_nbytes = 0;
if (amount > 0) {
pa_memblockq_seek(u->memblockq_sink, - (int64_t) amount, PA_SEEK_RELATIVE, true);
}
}
pa_sink_process_rewind(u->sink, amount);
pa_memblockq_rewind(u->memblockq_sink, nbytes_sink);
}
/* Called from I/O thread context */
static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes_input) {
struct userdata *u;
size_t nbytes_sink, nbytes_memblockq;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input));
nbytes_memblockq = sink_bytes(u, sink_input_samples(nbytes_input) + u->fftlen);
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_memblockq_set_maxrewind(u->memblockq_sink, nbytes_memblockq);
pa_sink_set_max_rewind_within_thread(u->sink, nbytes_sink);
}
/* Called from I/O thread context */
static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes_input) {
struct userdata *u;
size_t nbytes_sink;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
nbytes_sink = sink_bytes(u, sink_input_samples(nbytes_input));
nbytes_sink = PA_ROUND_UP(nbytes_sink, sink_bytes(u, BLOCK_SIZE));
pa_sink_set_max_request_within_thread(u->sink, nbytes_sink);
}
/* Called from I/O thread context */
static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
}
/* Called from I/O thread context */
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (PA_SINK_IS_LINKED(u->sink->thread_info.state))
pa_sink_detach_within_thread(u->sink);
pa_sink_set_rtpoll(u->sink, NULL);
}
/* Called from I/O thread context */
static void sink_input_attach_cb(pa_sink_input *i) {
struct userdata *u;
size_t max_request;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
max_request = sink_bytes(u, sink_input_samples(pa_sink_input_get_max_request(i)));
max_request = PA_ROUND_UP(max_request, sink_bytes(u, BLOCK_SIZE));
pa_sink_set_max_request_within_thread(u->sink, max_request);
/* FIXME: Too small max_rewind:
* https://bugs.freedesktop.org/show_bug.cgi?id=53709 */
pa_sink_set_max_rewind_within_thread(u->sink, sink_bytes(u, sink_input_samples(pa_sink_input_get_max_rewind(i))));
pa_sink_attach_within_thread(u->sink);
}
/* Called from main context */
static void sink_input_kill_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* The order here matters! We first kill the sink input, followed
* by the sink. That means the sink callbacks must be protected
* against an unconnected sink input! */
pa_sink_input_cork(u->sink_input, true);
pa_sink_input_unlink(u->sink_input);
pa_sink_unlink(u->sink);
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_sink_unref(u->sink);
u->sink = NULL;
pa_module_unload_request(u->module, true);
}
/* Called from main context */
static bool sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (u->autoloaded)
return false;
return u->sink != dest;
}
/* Called from main context */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (dest) {
pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
} else
pa_sink_set_asyncmsgq(u->sink, NULL);
if (u->auto_desc && dest) {
const char *z;
pa_proplist *pl;
pl = pa_proplist_new();
z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Surround Sink %s on %s",
pa_proplist_gets(u->sink->proplist, "device.vsurroundsink.name"), z ? z : dest->name);
pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
pa_proplist_free(pl);
}
}
/* Called from main context */
static void sink_input_volume_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_volume_changed(u->sink, &i->volume);
}
/* Called from main context */
static void sink_input_mute_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_mute_changed(u->sink, i->muted);
}
int pa__init(pa_module*m) {
struct userdata *u;
pa_sample_spec ss_input, ss_output;
@ -707,12 +309,7 @@ int pa__init(pa_module*m) {
const char *hrir_left_file;
const char *hrir_right_file;
pa_sink *master=NULL;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
bool use_volume_sharing = true;
bool force_flat_volume = false;
pa_memchunk silence;
const char* z;
unsigned i, j, ear, found_channel_left, found_channel_right;
pa_sample_spec ss;
@ -722,7 +319,7 @@ int pa__init(pa_module*m) {
float *hrir_temp_data;
size_t hrir_samples;
size_t hrir_copied_length, hrir_total_length;
int hrir_channels;
unsigned hrir_channels;
int fftlen;
float *impulse_temp=NULL;
@ -823,114 +420,20 @@ int pa__init(pa_module*m) {
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) {
pa_log("force_flat_volume= expects a boolean argument");
goto fail;
}
if (use_volume_sharing && force_flat_volume) {
pa_log("Flat volume can't be forced when using volume sharing.");
goto fail;
}
pa_channel_map_init_stereo(&map_output);
u = pa_xnew0(struct userdata, 1);
u->module = m;
m->userdata = u;
/* Create sink */
pa_sink_new_data_init(&sink_data);
sink_data.driver = __FILE__;
sink_data.module = m;
if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
sink_data.name = pa_sprintf_malloc("%s.vsurroundsink", master->name);
pa_sink_new_data_set_sample_spec(&sink_data, &ss_input);
pa_sink_new_data_set_channel_map(&sink_data, &map);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
pa_proplist_sets(sink_data.proplist, "device.vsurroundsink.name", sink_data.name);
if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_sink_new_data_done(&sink_data);
goto fail;
}
u->autoloaded = DEFAULT_AUTOLOADED;
if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) {
pa_log("Failed to parse autoloaded value");
goto fail;
}
if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Surround Sink %s on %s", sink_data.name, z ? z : master->name);
}
u->sink = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))
| (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
pa_log("Failed to create sink.");
goto fail;
}
u->sink->parent.process_msg = sink_process_msg_cb;
u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb;
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->request_rewind = sink_request_rewind_cb;
pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
if (!use_volume_sharing) {
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
pa_sink_enable_decibel_volume(u->sink, true);
}
/* Normally this flag would be enabled automatically but we can force it. */
if (force_flat_volume)
u->sink->flags |= PA_SINK_FLAT_VOLUME;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
pa_sink_input_new_data_set_sink(&sink_input_data, master, false, true);
sink_input_data.origin_sink = u->sink;
pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Surround Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION));
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss_output);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &map_output);
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
/* Create virtual sink */
if (!(u->vsink = pa_virtual_sink_create(master, "vsurroundsink", "Virtual Surround Sink", &ss_input, &map,
&ss_output, &map_output, m, u, ma, use_volume_sharing, true, 0)))
goto fail;
u->sink_input->pop = sink_input_pop_cb;
u->sink_input->process_rewind = sink_input_process_rewind_cb;
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
u->sink_input->moving = sink_input_moving_cb;
u->sink_input->volume_changed = use_volume_sharing ? NULL : sink_input_volume_changed_cb;
u->sink_input->mute_changed = sink_input_mute_changed_cb;
u->sink_input->userdata = u;
u->vsink->process_chunk = filter_process_chunk;
u->sink->input_to_master = u->sink_input;
pa_sink_input_get_silence(u->sink_input, &silence);
resampler = pa_resampler_new(u->sink->core->mempool, &hrir_left_temp_ss, &hrir_map, &ss_input, &hrir_map, u->sink->core->lfe_crossover_freq,
resampler = pa_resampler_new(u->vsink->sink->core->mempool, &hrir_left_temp_ss, &hrir_map, &ss_input, &hrir_map, u->vsink->sink->core->lfe_crossover_freq,
PA_RESAMPLER_SRC_SINC_BEST_QUALITY, PA_RESAMPLER_NO_REMAP);
hrir_samples = hrir_left_temp_chunk.length / pa_frame_size(&hrir_left_temp_ss) * ss_input.rate / hrir_left_temp_ss.rate;
@ -1128,14 +631,11 @@ int pa__init(pa_module*m) {
pa_xfree(mapping_left);
pa_xfree(mapping_right);
u->memblockq_sink = pa_memblockq_new("module-virtual-surround-sink memblockq (input)", 0, MEMBLOCKQ_MAXLENGTH, sink_bytes(u, BLOCK_SIZE), &ss_input, 0, 0, sink_bytes(u, u->fftlen), &silence);
pa_memblock_unref(silence.memblock);
u->vsink->fixed_block_size = BLOCK_SIZE;
u->vsink->overlap_frames = u->fftlen - BLOCK_SIZE;
pa_memblockq_seek(u->memblockq_sink, sink_bytes(u, u->fftlen - BLOCK_SIZE), PA_SEEK_RELATIVE, false);
pa_memblockq_flush_read(u->memblockq_sink);
pa_sink_put(u->sink);
pa_sink_input_put(u->sink_input);
if (pa_virtual_sink_activate(u->vsink) < 0)
goto fail;
pa_modargs_free(ma);
@ -1183,7 +683,7 @@ int pa__get_n_used(pa_module *m) {
pa_assert(m);
pa_assert_se(u = m->userdata);
return pa_sink_linked_by(u->sink);
return pa_sink_linked_by(u->vsink->sink);
}
void pa__done(pa_module*m) {
@ -1195,23 +695,8 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
/* See comments in sink_input_kill_cb() above regarding
* destruction order! */
if (u->sink_input)
pa_sink_input_unlink(u->sink_input);
if (u->sink)
pa_sink_unlink(u->sink);
if (u->sink_input)
pa_sink_input_unref(u->sink_input);
if (u->sink)
pa_sink_unref(u->sink);
if (u->memblockq_sink)
pa_memblockq_free(u->memblockq_sink);
if (u->vsink)
pa_virtual_sink_destroy(u->vsink);
if (u->p_fw) {
for (i = 0, j = u->inputs; i < j; i++) {

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 0
* a > b -> return 1 */
static int compare_sinks(pa_sink *a, pa_sink *b) {
static int compare_sinks(pa_sink *a, pa_sink *b, bool ignore_configured_virtual_default) {
pa_core *core;
core = a->core;
@ -429,23 +429,37 @@ static int compare_sinks(pa_sink *a, pa_sink *b) {
return 1;
/* The policy default sink is preferred over any other sink. */
if (pa_safe_streq(b->name, core->policy_default_sink))
return -1;
if (pa_safe_streq(a->name, core->policy_default_sink))
return 1;
if (pa_safe_streq(b->name, core->policy_default_sink)) {
if (!ignore_configured_virtual_default || !pa_sink_is_filter(b))
return -1;
}
if (pa_safe_streq(a->name, core->policy_default_sink)) {
if (!ignore_configured_virtual_default || !pa_sink_is_filter(a))
return 1;
}
/* The configured default sink is preferred over any other sink
* except the policy default sink. */
if (pa_safe_streq(b->name, core->configured_default_sink))
return -1;
if (pa_safe_streq(a->name, core->configured_default_sink))
return 1;
if (pa_safe_streq(b->name, core->configured_default_sink)) {
if (!ignore_configured_virtual_default || !pa_sink_is_filter(b))
return -1;
}
if (pa_safe_streq(a->name, core->configured_default_sink)) {
if (!ignore_configured_virtual_default || !pa_sink_is_filter(a))
return 1;
}
if (a->priority < b->priority)
return -1;
if (a->priority > b->priority)
return 1;
/* Let sinks like pipe sink or null sink win against filter sinks */
if (a->vsink && !b->vsink)
return -1;
if (!a->vsink && b->vsink)
return 1;
/* It's hard to find any difference between these sinks, but maybe one of
* them is already the default sink? If so, it's best to keep it as the
* default to avoid changing the routing for no good reason. */
@ -457,11 +471,10 @@ static int compare_sinks(pa_sink *a, pa_sink *b) {
return 0;
}
void pa_core_update_default_sink(pa_core *core) {
pa_sink *pa_core_find_best_sink(pa_core *core, bool ignore_configured_virtual_default) {
pa_sink *best = NULL;
pa_sink *sink;
uint32_t idx;
pa_sink *old_default_sink;
pa_assert(core);
@ -474,10 +487,21 @@ void pa_core_update_default_sink(pa_core *core) {
continue;
}
if (compare_sinks(sink, best) > 0)
if (compare_sinks(sink, best, ignore_configured_virtual_default) > 0)
best = sink;
}
return best;
}
void pa_core_update_default_sink(pa_core *core) {
pa_sink *best;
pa_sink *old_default_sink;
pa_assert(core);
best = pa_core_find_best_sink(core, false);
old_default_sink = core->default_sink;
if (best == old_default_sink)
@ -503,7 +527,7 @@ void pa_core_update_default_sink(pa_core *core) {
/* a < b -> return -1
* a == b -> return 0
* a > b -> return 1 */
static int compare_sources(pa_source *a, pa_source *b) {
static int compare_sources(pa_source *a, pa_source *b, bool ignore_configured_virtual_default) {
pa_core *core;
core = a->core;
@ -517,17 +541,25 @@ static int compare_sources(pa_source *a, pa_source *b) {
return 1;
/* The policy default source is preferred over any other source. */
if (pa_safe_streq(b->name, core->policy_default_source))
return -1;
if (pa_safe_streq(a->name, core->policy_default_source))
return 1;
if (pa_safe_streq(b->name, core->policy_default_source)) {
if (!ignore_configured_virtual_default || !pa_source_is_filter(b))
return -1;
}
if (pa_safe_streq(a->name, core->policy_default_source)) {
if (!ignore_configured_virtual_default || !pa_source_is_filter(a))
return 1;
}
/* The configured default source is preferred over any other source
* except the policy default source. */
if (pa_safe_streq(b->name, core->configured_default_source))
return -1;
if (pa_safe_streq(a->name, core->configured_default_source))
return 1;
if (pa_safe_streq(b->name, core->configured_default_source)) {
if (!ignore_configured_virtual_default || !pa_source_is_filter(b))
return -1;
}
if (pa_safe_streq(a->name, core->configured_default_source)) {
if (!ignore_configured_virtual_default || !pa_source_is_filter(a))
return 1;
}
/* Monitor sources lose to non-monitor sources. */
if (a->monitor_of && !b->monitor_of)
@ -540,9 +572,15 @@ static int compare_sources(pa_source *a, pa_source *b) {
if (a->priority > b->priority)
return 1;
/* Let sources like pipe source or null source win against filter sources */
if (a->vsource && !b->vsource)
return -1;
if (!a->vsource && b->vsource)
return 1;
/* If the sources are monitors, we can compare the monitored sinks. */
if (a->monitor_of)
return compare_sinks(a->monitor_of, b->monitor_of);
return compare_sinks(a->monitor_of, b->monitor_of, false);
/* It's hard to find any difference between these sources, but maybe one of
* them is already the default source? If so, it's best to keep it as the
@ -555,11 +593,10 @@ static int compare_sources(pa_source *a, pa_source *b) {
return 0;
}
void pa_core_update_default_source(pa_core *core) {
pa_source *pa_core_find_best_source(pa_core *core, bool ignore_configured_virtual_default) {
pa_source *best = NULL;
pa_source *source;
uint32_t idx;
pa_source *old_default_source;
pa_assert(core);
@ -572,10 +609,21 @@ void pa_core_update_default_source(pa_core *core) {
continue;
}
if (compare_sources(source, best) > 0)
if (compare_sources(source, best, ignore_configured_virtual_default) > 0)
best = source;
}
return best;
}
void pa_core_update_default_source(pa_core *core) {
pa_source *best;
pa_source *old_default_source;
pa_assert(core);
best = pa_core_find_best_source(core, false);
old_default_source = core->default_source;
if (best == old_default_source)
@ -693,11 +741,6 @@ void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink
if (!si->sink)
continue;
/* Skip this sink input if it is connecting a filter sink to
* the master */
if (si->origin_sink)
continue;
/* It might happen that a stream and a sink are set up at the
same time, in which case we want to make sure we don't
interfere with that */
@ -727,11 +770,6 @@ void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_sou
if (!so->source)
continue;
/* Skip this source output if it is connecting a filter source to
* the master */
if (so->destination_source)
continue;
/* It might happen that a stream and a source are set up at the
same time, in which case we want to make sure we don't
interfere with that */

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_source(pa_core *core);
pa_sink *pa_core_find_best_sink(pa_core *core, bool ignore_configured_virtual_default);
pa_source *pa_core_find_best_source(pa_core *core, bool ignore_configured_virtual_default);
void pa_core_set_exit_idle_time(pa_core *core, int time);
/* Check whether no one is connected to this core */

View file

@ -816,6 +816,18 @@ size_t pa_memblockq_get_prebuf(pa_memblockq *bq) {
return bq->prebuf;
}
size_t pa_memblockq_get_missing(pa_memblockq *bq) {
size_t l;
pa_assert(bq);
if ((l = pa_memblockq_get_length(bq)) >= bq->tlength)
return 0;
l = bq->tlength - l;
return PA_MAX(bq->minreq, l);
}
size_t pa_memblockq_pop_missing(pa_memblockq *bq) {
size_t l;

View file

@ -112,6 +112,11 @@ bool pa_memblockq_is_readable(pa_memblockq *bq);
/* Return the length of the queue in bytes */
size_t pa_memblockq_get_length(pa_memblockq *bq);
/* Return the number of bytes missing to achieve the target length.
* If the number of missing bytes is smaller than minreq but larger
* than 0, minreq will be returned. */
size_t pa_memblockq_get_missing(pa_memblockq *bq);
/* Return the number of bytes that are missing since the last call to
* this function, reset the internal counter to 0. */
size_t pa_memblockq_pop_missing(pa_memblockq *bq);

View file

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

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);
void pa_sink_input_fail_move(pa_sink_input *i);
bool pa_sink_input_is_filter_loop(pa_sink_input *target, pa_sink *s);
pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i);
/* To be used exclusively by the sink driver IO thread */

View file

@ -286,7 +286,8 @@ pa_sink* pa_sink_new(
s->inputs = pa_idxset_new(NULL, NULL);
s->n_corked = 0;
s->input_to_master = NULL;
s->vsink = NULL;
s->uplink_of = NULL;
s->reference_volume = s->real_volume = data->volume;
pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
@ -1693,10 +1694,10 @@ pa_sink *pa_sink_get_master(pa_sink *s) {
pa_sink_assert_ref(s);
while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
if (PA_UNLIKELY(!s->input_to_master))
if (PA_UNLIKELY(!s->vsink || (s->vsink && !s->vsink->input_to_master)))
return NULL;
s = s->input_to_master->sink;
s = s->vsink->input_to_master->sink;
}
return s;
@ -1706,7 +1707,7 @@ pa_sink *pa_sink_get_master(pa_sink *s) {
bool pa_sink_is_filter(pa_sink *s) {
pa_sink_assert_ref(s);
return (s->input_to_master != NULL);
return ((s->vsink != NULL) && (s->vsink->input_to_master != NULL));
}
/* Called from main context */
@ -2552,7 +2553,7 @@ unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_sourc
}
if (s->monitor_source)
ret += pa_source_check_suspend(s->monitor_source, ignore_output);
ret += pa_source_check_suspend(s->monitor_source, ignore_input, ignore_output);
return ret;
}
@ -3258,6 +3259,9 @@ void pa_sink_invalidate_requested_latency(pa_sink *s, bool dynamic) {
if (PA_SINK_IS_LINKED(s->thread_info.state)) {
if (s->uplink_of && s->uplink_of->source)
pa_source_invalidate_requested_latency(s->uplink_of->source, dynamic);
if (s->update_requested_latency)
s->update_requested_latency(s);
@ -4064,9 +4068,32 @@ void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool
if (!i->sink)
continue;
/* Don't move sink-inputs which connect filter sinks to their target sinks */
if (i->origin_sink)
/* If this is a filter stream and the default sink is set to a filter sink within
* the same filter chain, we would create a loop and therefore have to find another
* sink to move to. */
if (i->origin_sink && pa_sink_input_is_filter_loop(i, core->default_sink)) {
pa_sink *best;
/* If the default sink changed to our filter chain, lets make the current
* master the preferred sink. */
if (default_sink_changed) {
pa_xfree(i->preferred_sink);
i->preferred_sink = pa_xstrdup(i->sink->name);
continue;
}
best = pa_core_find_best_sink(core, true);
if (!best || !pa_sink_input_may_move_to(i, best))
continue;
pa_log_info("Moving sink input %u \"%s\" to the default sink would create a filter loop, moving to %s instead.",
i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), best->name);
pa_sink_input_move_to(i, best, false);
continue;
}
/* If default_sink_changed is false, the old sink became unavailable, so all streams must be moved. */
if (pa_safe_streq(old_sink->name, i->preferred_sink) && default_sink_changed)

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);
/* Virtual sink structure */
typedef struct pa_vsink {
pa_msgobject parent; /* Message object */
pa_sink *sink; /* A pointer to the virtual sink */
pa_sink_input *input_to_master; /* Sink input to the master sink */
pa_memblockq *memblockq; /* Memblockq of the virtual sink, may be NULL */
size_t drop_bytes; /* Number of bytes to drop during sink_input_pop()
* in sink input sample speci. Used during rewind
* of fixed block size filters */
bool auto_desc; /* Automatically adapt description on move */
const char *desc_head; /* Leading part of description string used for the
* sink and sink input when auto_desc is true */
const char *sink_type; /* Name for the type of sink, used as suffix for
* the sink name if the name is derived from the
* master sink. */
bool autoloaded; /* True if the sink was not loaded manually */
size_t max_chunk_size; /* Maximum chunk size in bytes that the filter will
* accept, set to pa_mempool_block_size_max() by default */
size_t fixed_block_size; /* Block size in frames for fixed block size filters,
* 0 if block size is controlled by pulseaudio. */
size_t fixed_input_block_size; /* Input block size in frames. If not 0, input data for
* process_chunk() will always have the same size.
* If not enough new data is available, the remaining
* samples will be filled with history. */
size_t overlap_frames; /* Some filters require old input samples in addtion to
* the current data. The variable contains the number of
* previous frames that will be passed to process_chunk().
* The actual number of history frames may be variable if
* the filter defines the get_current_overlap() function.
* In this case, overlap_frames contains the maximum
* number of history frames. */
size_t max_request_frames_min; /* Minimum value for max_request in frames, 0 if unused */
pa_usec_t max_latency; /* Maximum latency allowed for the sink, 0 if unused */
int max_rewind; /* Maximum number of frames that the sink can rewind.
* 0 means unlimited, -1 disables rewinding */
/* Callback to rewind the filter when pulseaudio requests it. Called from
* I/O thread context. May be NULL */
void (*rewind_filter)(pa_sink *s, size_t amount);
/* Callback to process a chunk of data by the filter. Called from I/O thread
* context. May be NULL */
void (*process_chunk)(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata);
/* Callback to communicate the max_rewind value to the filter. Called from
* I/O thread context whenever the max_rewind value changes. May be NULL */
void (*set_filter_max_rewind)(pa_sink_input *i, size_t amount);
/* Callback to retrieve additional latency caused by the filter. Called from
* I/O thread context. May be NULL */
pa_usec_t (*get_extra_latency)(pa_sink *s);
/* If defined, this function is called from the sink-input pop() callback
* to retrieve the current number of history frames to include in the next
* chunk. Called from I/O thread. */
size_t (*get_current_overlap)(pa_sink_input *i);
/* If set and dest is valid, this function is called in the moving() callback
* to change the description of sink and sink_input. Called from main context.
* May be NULL */
void (*set_description)(pa_sink_input *i, pa_sink *dest);
/* If set, this function will be called after update_filter_parameters() to
* inform the filter of the block sizes that will be used. These may differ
* from the sizes set in update_filter_parameters() if the function tries to
* set an invalid combination of block sizes. Called from I/O thread. */
void (*update_block_sizes)(size_t fixed_block_size, size_t fixed_input_block_size, size_t overlap_frames, void *userdata);
/* If set, this function is called in I/O thread context when an update of the
* filter parameters is requested. May be NULL. The function must replace
* the currently used parameter structure by the new structure in parameters
* and return a pointer to the old structure so that it can be freed in the
* main thread using free_filter_parameters(). If the old structure can be
* re-used, the function may return NULL. update_filter_parameters() may
* also modify the block sizes. */
void *(*update_filter_parameters)(void *parameters, void *userdata);
/* Frees a parameter structure. May only be NULL, if update_filter_parameters()
* is also NULL or if update_filter_parameters() always returns NULL. Called
* from main thread. */
void (*free_filter_parameters)(void *parameters);
} pa_vsink;
struct pa_sink {
pa_msgobject parent;
@ -88,7 +172,8 @@ struct pa_sink {
pa_idxset *inputs;
unsigned n_corked;
pa_source *monitor_source;
pa_sink_input *input_to_master; /* non-NULL only for filter sinks */
pa_vsink *vsink; /* non-NULL only for filter sinks */
pa_vsource *uplink_of; /* non-NULL only for uplink sinks */
pa_volume_t base_volume; /* shall be constant */
unsigned n_volume_steps; /* shall be constant */

View file

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

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);
void pa_source_output_fail_move(pa_source_output *o);
bool pa_source_output_is_filter_loop(pa_source_output *target, pa_source *s);
pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o);
/* To be used exclusively by the source driver thread */

View file

@ -273,7 +273,7 @@ pa_source* pa_source_new(
s->outputs = pa_idxset_new(NULL, NULL);
s->n_corked = 0;
s->monitor_of = NULL;
s->output_from_master = NULL;
s->vsource = NULL;
s->reference_volume = s->real_volume = data->volume;
pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
@ -1235,10 +1235,10 @@ pa_source *pa_source_get_master(pa_source *s) {
pa_source_assert_ref(s);
while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
if (PA_UNLIKELY(!s->output_from_master))
if (PA_UNLIKELY(!s->vsource || (s->vsource && !s->vsource->output_from_master)))
return NULL;
s = s->output_from_master->source;
s = s->vsource->output_from_master->source;
}
return s;
@ -1248,7 +1248,7 @@ pa_source *pa_source_get_master(pa_source *s) {
bool pa_source_is_filter(pa_source *s) {
pa_source_assert_ref(s);
return (s->output_from_master != NULL);
return (s->vsource->output_from_master != NULL);
}
/* Called from main context */
@ -2019,7 +2019,7 @@ unsigned pa_source_used_by(pa_source *s) {
}
/* Called from main thread */
unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) {
unsigned pa_source_check_suspend(pa_source *s, pa_sink_input *ignore_input, pa_source_output *ignore_output) {
unsigned ret;
pa_source_output *o;
uint32_t idx;
@ -2033,7 +2033,7 @@ unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) {
ret = 0;
PA_IDXSET_FOREACH(o, s->outputs, idx) {
if (o == ignore)
if (o == ignore_output)
continue;
/* We do not assert here. It is perfectly valid for a source output to
@ -2053,6 +2053,9 @@ unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) {
ret ++;
}
if (s->vsource && s->vsource->uplink_sink)
ret += pa_sink_check_suspend(s->vsource->uplink_sink, ignore_input, ignore_output);
return ret;
}
@ -2400,6 +2403,16 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) {
(result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency))
result = o->thread_info.requested_source_latency;
if (s->vsource && s->vsource->uplink_sink) {
pa_usec_t uplink_sink_latency;
uplink_sink_latency = pa_sink_get_requested_latency_within_thread(s->vsource->uplink_sink);
if (uplink_sink_latency != (pa_usec_t) -1 &&
(result == (pa_usec_t) -1 || result > uplink_sink_latency))
result = uplink_sink_latency;
}
if (result != (pa_usec_t) -1)
result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency);
@ -3033,9 +3046,32 @@ void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_sour
if (!o->source)
continue;
/* Don't move source-outputs which connect sources to filter sources */
if (o->destination_source)
/* If this is a filter stream and the default source is set to a filter source within
* the same filter chain, we would create a loop and therefore have to find another
* source to move to. */
if (o->destination_source && pa_source_output_is_filter_loop(o, core->default_source)) {
pa_source *best;
/* If the default source changed to our filter chain, lets make the current
* master the preferred source. */
if (default_source_changed) {
pa_xfree(o->preferred_source);
o->preferred_source = pa_xstrdup(o->source->name);
continue;
}
best = pa_core_find_best_source(core, true);
if (!best || !pa_source_output_may_move_to(o, best))
continue;
pa_log_info("Moving source output %u \"%s\" to the default source would create a filter loop, moving to %s instead.",
o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), best->name);
pa_source_output_move_to(o, best, false);
continue;
}
/* If default_source_changed is false, the old source became unavailable, so all streams must be moved. */
if (pa_safe_streq(old_source->name, o->preferred_source) && default_source_changed)

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

View file

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