mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-10-29 05:40:23 -04:00
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:
commit
8490a9793a
29 changed files with 3862 additions and 3727 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 += [
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
1404
src/modules/virtual-sink-common.c
Normal file
1404
src/modules/virtual-sink-common.c
Normal file
File diff suppressed because it is too large
Load diff
77
src/modules/virtual-sink-common.h
Normal file
77
src/modules/virtual-sink-common.h
Normal 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);
|
||||
1515
src/modules/virtual-source-common.c
Normal file
1515
src/modules/virtual-source-common.c
Normal file
File diff suppressed because it is too large
Load diff
74
src/modules/virtual-source-common.h
Normal file
74
src/modules/virtual-source-common.h
Normal 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);
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue