diff --git a/src/modules/meson.build b/src/modules/meson.build index dbf330204..21466c82c 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -13,6 +13,17 @@ libvirtual_sink = shared_library('virtual_sink', install_dir : modlibexecdir ) +libvirtual_source = shared_library('virtual_source', + 'virtual-source-common.c', + 'virtual-source-common.h', + c_args : [pa_c_args, server_c_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep], + install_rpath : privlibdir, + install : true, + install_dir : modlibexecdir +) + # module name, sources, [headers, extra flags, extra deps, extra libs] all_modules = [ [ 'module-allow-passthrough', 'module-allow-passthrough.c' ], @@ -67,7 +78,7 @@ all_modules = [ [ 'module-tunnel-source', ['module-tunnel.c', 'restart-module.c'], [], [], [x11_dep] ], [ 'module-tunnel-source-new', ['module-tunnel-source-new.c', 'restart-module.c'] ], [ 'module-virtual-sink', 'module-virtual-sink.c', [], [], [], libvirtual_sink ], - [ 'module-virtual-source', 'module-virtual-source.c' ], + [ 'module-virtual-source', 'module-virtual-source.c', [], [], [], libvirtual_source ], [ 'module-volume-restore', 'module-volume-restore.c' ], ] diff --git a/src/modules/module-virtual-source.c b/src/modules/module-virtual-source.c index 8dd7fc90f..4e39a53ea 100644 --- a/src/modules/module-virtual-source.c +++ b/src/modules/module-virtual-source.c @@ -26,6 +26,8 @@ #include +#include + #include #include #include @@ -63,15 +65,7 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; - /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ - /* bool autoloaded; */ - - pa_source *source; - pa_source_output *source_output; - - pa_memblockq *memblockq; - - bool auto_desc; + pa_vsource *vsource; unsigned channels; /* optional fields for uplink sink */ @@ -79,6 +73,7 @@ struct userdata { pa_usec_t block_usec; pa_memblockq *sink_memblockq; pa_rtpoll *rtpoll; + bool auto_desc; }; @@ -96,6 +91,97 @@ static const char* const valid_modargs[] = { NULL }; +static void filter_process_chunk(uint8_t *src, uint8_t *dst, unsigned in_count, unsigned out_count, void *userdata) { + struct userdata *u; + size_t nbytes; + + pa_assert_se(u = userdata); + pa_assert(in_count == out_count); + + nbytes = in_count * pa_frame_size(&u->vsource->source->sample_spec); + + /* if uplink sink exists, pull data from there; simplify by using + same length as chunk provided by source */ + if (u->sink && (u->sink->thread_info.state == PA_SINK_RUNNING)) { + pa_memchunk tchunk; + pa_mix_info streams[2]; + pa_memchunk chunk; + void *src_copy; + int ch; + pa_source_output *o; + + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); + + /* get data from the sink */ + while (pa_memblockq_peek(u->sink_memblockq, &tchunk) < 0) { + pa_memchunk nchunk; + + /* make sure we get nbytes from the sink with render_full, + otherwise we cannot mix with the uplink */ + pa_sink_render_full(u->sink, nbytes, &nchunk); + pa_memblockq_push(u->sink_memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + pa_assert(tchunk.length == nbytes); + + /* move the read pointer for sink memblockq */ + pa_memblockq_drop(u->sink_memblockq, tchunk.length); + + o = u->vsource->output_from_master; + + /* allocate source chunk */ + chunk.index = 0; + chunk.length = nbytes; + chunk.memblock = pa_memblock_new(o->source->core->mempool, nbytes); + pa_assert(chunk.memblock); + + /* Copy source data to chunk */ + src_copy = pa_memblock_acquire_chunk(&chunk); + memcpy(src_copy, src, nbytes); + + /* set-up mixing structure + volume was taken care of in sink and source already */ + streams[0].chunk = chunk; + for(ch=0;chsample_spec.channels;ch++) + streams[0].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ + streams[0].volume.channels = o->sample_spec.channels; + + streams[1].chunk = tchunk; + for(ch=0;chsample_spec.channels;ch++) + streams[1].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ + streams[1].volume.channels = o->sample_spec.channels; + + /* do mixing */ + pa_mix(streams, /* 2 streams to be mixed */ + 2, + dst, /* put result in dst */ + nbytes, /* same length as input */ + (const pa_sample_spec *)&o->sample_spec, /* same sample spec for input and output */ + NULL, /* no volume information */ + false); /* no mute */ + + pa_memblock_release(chunk.memblock); + pa_memblock_unref(tchunk.memblock); + pa_memblock_unref(chunk.memblock); + } else + /* Copy input to output */ + memcpy(dst, src, nbytes); +} + +/* When the source output moves, the asyncmsgq of the uplink sink has + * to change as well */ +static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_assert(u = o->userdata); + + pa_virtual_source_output_moving(o, dest); + if (dest && u->sink) { + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); + } +} + /* Called from I/O thread context */ static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { @@ -125,8 +211,8 @@ static int sink_set_state_in_main_thread_cb(pa_sink *s, pa_sink_state_t state, p if (state == PA_SINK_RUNNING) { /* need to wake-up source if it was suspended */ - pa_log_debug("Resuming source %s, because its uplink sink became active.", u->source->name); - pa_source_suspend(u->source, false, PA_SUSPEND_ALL); + pa_log_debug("Resuming source %s, because its uplink sink became active.", u->vsource->source->name); + pa_source_suspend(u->vsource->source, false, PA_SUSPEND_ALL); /* FIXME: if there's no client connected, the source will suspend and playback will be stuck. You'd want to prevent the source from @@ -151,347 +237,13 @@ static void sink_update_requested_latency_cb(pa_sink *s) { } -/* Called from I/O thread context */ -static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SOURCE(o)->userdata; - - switch (code) { - - case PA_SOURCE_MESSAGE_GET_LATENCY: - - /* The source is _put() before the source output is, so let's - * make sure we don't access it in that time. Also, the - * source output is first shut down, the source second. */ - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) { - *((pa_usec_t*) data) = 0; - return 0; - } - - *((pa_usec_t*) data) = - - /* Get the latency of the master source */ - pa_source_get_latency_within_thread(u->source_output->source, true) + - - /* Add the latency internal to our source output on top */ - /* FIXME, no idea what I am doing here */ - pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec); - - /* Add resampler delay */ - *((int64_t*) data) += pa_resampler_get_delay_usec(u->source_output->thread_info.resampler); - - return 0; - } - - return pa_source_process_msg(o, code, data, offset, chunk); -} - -/* Called from main context */ -static int source_set_state_in_main_thread_cb(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state)) - return 0; - - pa_source_output_cork(u->source_output, state == PA_SOURCE_SUSPENDED); - return 0; -} - -/* Called from I/O thread context */ -static void source_update_requested_latency_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) - return; - - /* Just hand this one over to the master source */ - pa_source_output_set_requested_latency_within_thread( - u->source_output, - pa_source_get_requested_latency_within_thread(s)); -} - -/* Called from main context */ -static void source_set_volume_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(s->state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state)) - return; - - pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, true); -} - -/* Called from main context */ -static void source_set_mute_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(s->state) || - !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->state)) - return; - - pa_source_output_set_mute(u->source_output, s->muted, s->save_muted); -} - -/* Called from input thread context */ -static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - return; - - if (!PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) { - pa_log("push when no link?"); - return; - } - - /* PUT YOUR CODE HERE TO DO SOMETHING WITH THE SOURCE DATA */ - - /* if uplink sink exists, pull data from there; simplify by using - same length as chunk provided by source */ - if (u->sink && (u->sink->thread_info.state == PA_SINK_RUNNING)) { - pa_memchunk tchunk; - size_t nbytes = chunk->length; - pa_mix_info streams[2]; - pa_memchunk target_chunk; - void *target; - int ch; - - /* Hmm, process any rewind request that might be queued up */ - pa_sink_process_rewind(u->sink, 0); - - /* get data from the sink */ - while (pa_memblockq_peek(u->sink_memblockq, &tchunk) < 0) { - pa_memchunk nchunk; - - /* make sure we get nbytes from the sink with render_full, - otherwise we cannot mix with the uplink */ - pa_sink_render_full(u->sink, nbytes, &nchunk); - pa_memblockq_push(u->sink_memblockq, &nchunk); - pa_memblock_unref(nchunk.memblock); - } - pa_assert(tchunk.length == chunk->length); - - /* move the read pointer for sink memblockq */ - pa_memblockq_drop(u->sink_memblockq, tchunk.length); - - /* allocate target chunk */ - /* this could probably be done in-place, but having chunk as both - the input and output creates issues with reference counts */ - target_chunk.index = 0; - target_chunk.length = chunk->length; - pa_assert(target_chunk.length == chunk->length); - - target_chunk.memblock = pa_memblock_new(o->source->core->mempool, - target_chunk.length); - pa_assert( target_chunk.memblock ); - - /* get target pointer */ - target = pa_memblock_acquire_chunk(&target_chunk); - - /* set-up mixing structure - volume was taken care of in sink and source already */ - streams[0].chunk = *chunk; - for(ch=0;chsample_spec.channels;ch++) - streams[0].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ - streams[0].volume.channels = o->sample_spec.channels; - - streams[1].chunk = tchunk; - for(ch=0;chsample_spec.channels;ch++) - streams[1].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ - streams[1].volume.channels = o->sample_spec.channels; - - /* do mixing */ - pa_mix(streams, /* 2 streams to be mixed */ - 2, - target, /* put result in target chunk */ - chunk->length, /* same length as input */ - (const pa_sample_spec *)&o->sample_spec, /* same sample spec for input and output */ - NULL, /* no volume information */ - false); /* no mute */ - - pa_memblock_release(target_chunk.memblock); - pa_memblock_unref(tchunk.memblock); /* clean-up */ - - /* forward the data to the virtual source */ - pa_source_post(u->source, &target_chunk); - - pa_memblock_unref(target_chunk.memblock); /* clean-up */ - - } else { - /* forward the data to the virtual source */ - pa_source_post(u->source, chunk); - } - -} - -/* Called from input thread context */ -static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - /* If the source is not yet linked, there is nothing to rewind */ - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_process_rewind(u->source, nbytes); - - /* FIXME, no idea what I am doing here */ -#if 0 - pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL); - u->send_counter -= (int64_t) nbytes; -#endif -} - -/* Called from output thread context */ -static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - pa_source_set_max_rewind_within_thread(u->source, nbytes); -} - -/* Called from output thread context */ -static void source_output_attach_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll); - pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency); - pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency); - pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o)); - - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_attach_within_thread(u->source); -} - -/* Called from output thread context */ -static void source_output_detach_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_source_output_assert_io_context(o); - pa_assert_se(u = o->userdata); - - if (PA_SOURCE_IS_LINKED(u->source->thread_info.state)) - pa_source_detach_within_thread(u->source); - pa_source_set_rtpoll(u->source, NULL); -} - -/* Called from output thread context except when cork() is called without valid source.*/ -static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_assert_se(u = o->userdata); - - /* FIXME */ -#if 0 - if (PA_SOURCE_OUTPUT_IS_LINKED(state) && o->thread_info.state == PA_SOURCE_OUTPUT_INIT && o->source) { - - u->skip = pa_usec_to_bytes(PA_CLIP_SUB(pa_source_get_latency_within_thread(o->source, false), - u->latency), - &o->sample_spec); - - pa_log_info("Skipping %lu bytes", (unsigned long) u->skip); - } -#endif -} - -/* Called from main thread */ -static void source_output_kill_cb(pa_source_output *o) { - struct userdata *u; - - pa_source_output_assert_ref(o); - pa_assert_ctl_context(); - pa_assert_se(u = o->userdata); - - /* The order here matters! We first kill the source so that streams - * can properly be moved away while the source output is still connected - * to the master. */ - pa_source_output_cork(u->source_output, true); - pa_source_unlink(u->source); - pa_source_output_unlink(u->source_output); - - pa_source_output_unref(u->source_output); - u->source_output = NULL; - - pa_source_unref(u->source); - u->source = NULL; - - pa_module_unload_request(u->module, true); -} - -/* Called from main thread */ -static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { - struct userdata *u; - uint32_t idx; - pa_source_output *output; - - pa_source_output_assert_ref(o); - pa_assert_ctl_context(); - pa_assert_se(u = o->userdata); - - if (dest) { - pa_source_set_asyncmsgq(u->source, dest->asyncmsgq); - pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags); - } else - pa_source_set_asyncmsgq(u->source, NULL); - - /* Propagate asyncmsq change to attached virtual sources */ - PA_IDXSET_FOREACH(output, u->source->outputs, idx) { - if (output->destination_source && output->moving) - output->moving(output, u->source); - } - - if (u->auto_desc && dest) { - const char *z; - pa_proplist *pl; - - pl = pa_proplist_new(); - z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Source %s on %s", - pa_proplist_gets(u->source->proplist, "device.vsource.name"), z ? z : dest->name); - - pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl); - pa_proplist_free(pl); - } -} - int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma; pa_source *master=NULL; - pa_source_output_new_data source_output_data; - pa_source_new_data source_data; bool use_volume_sharing = true; - bool force_flat_volume = false; /* optional for uplink_sink */ pa_sink_new_data sink_data; @@ -512,7 +264,6 @@ int pa__init(pa_module*m) { pa_assert(master); ss = master->sample_spec; - ss.format = PA_SAMPLE_FLOAT32; map = master->channel_map; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { pa_log("Invalid sample format specification or channel map"); @@ -524,24 +275,9 @@ int pa__init(pa_module*m) { goto fail; } - if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { - pa_log("force_flat_volume= expects a boolean argument"); - goto fail; - } - - if (use_volume_sharing && force_flat_volume) { - pa_log("Flat volume can't be forced when using volume sharing."); - goto fail; - } - u = pa_xnew0(struct userdata, 1); u->module = m; m->userdata = u; - u->memblockq = pa_memblockq_new("module-virtual-source memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, NULL); - if (!u->memblockq) { - pa_log("Failed to create source memblockq."); - goto fail; - } u->channels = ss.channels; /* The rtpoll created here is never run. It is only necessary to avoid crashes @@ -551,93 +287,14 @@ int pa__init(pa_module*m) { * call pa_asyncmsq_process_one() themselves. */ u->rtpoll = pa_rtpoll_new(); - /* Create source */ - pa_source_new_data_init(&source_data); - source_data.driver = __FILE__; - source_data.module = m; - if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) - source_data.name = pa_sprintf_malloc("%s.vsource", master->name); - pa_source_new_data_set_sample_spec(&source_data, &ss); - pa_source_new_data_set_channel_map(&source_data, &map); - pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); - pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - pa_proplist_sets(source_data.proplist, "device.vsource.name", source_data.name); - - if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log("Invalid properties"); - pa_source_new_data_done(&source_data); - goto fail; - } - - if ((u->auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { - const char *z; - - z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Source %s on %s", source_data.name, z ? z : master->name); - } - - u->source = pa_source_new(m->core, &source_data, (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)) - | (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0)); - - pa_source_new_data_done(&source_data); - - if (!u->source) { - pa_log("Failed to create source."); - goto fail; - } - - u->source->parent.process_msg = source_process_msg_cb; - u->source->set_state_in_main_thread = source_set_state_in_main_thread_cb; - u->source->update_requested_latency = source_update_requested_latency_cb; - pa_source_set_set_mute_callback(u->source, source_set_mute_cb); - if (!use_volume_sharing) { - pa_source_set_set_volume_callback(u->source, source_set_volume_cb); - pa_source_enable_decibel_volume(u->source, true); - } - /* Normally this flag would be enabled automatically be we can force it. */ - if (force_flat_volume) - u->source->flags |= PA_SOURCE_FLAT_VOLUME; - u->source->userdata = u; - - pa_source_set_asyncmsgq(u->source, master->asyncmsgq); - - /* Create source output */ - pa_source_output_new_data_init(&source_output_data); - source_output_data.driver = __FILE__; - source_output_data.module = m; - pa_source_output_new_data_set_source(&source_output_data, master, false, true); - source_output_data.destination_source = u->source; - - pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Source Stream of %s", pa_proplist_gets(u->source->proplist, PA_PROP_DEVICE_DESCRIPTION)); - pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); - pa_source_output_new_data_set_sample_spec(&source_output_data, &ss); - pa_source_output_new_data_set_channel_map(&source_output_data, &map); - source_output_data.flags |= PA_SOURCE_OUTPUT_START_CORKED; - - pa_source_output_new(&u->source_output, m->core, &source_output_data); - pa_source_output_new_data_done(&source_output_data); - - if (!u->source_output) + /* Create virtual source */ + if (!(u->vsource = pa_virtual_source_create(master, "vsource", "Virtual Source", &ss, &map, + &ss, &map, m, u, ma, use_volume_sharing, true))) goto fail; - u->source_output->push = source_output_push_cb; - u->source_output->process_rewind = source_output_process_rewind_cb; - u->source_output->update_max_rewind = source_output_update_max_rewind_cb; - u->source_output->kill = source_output_kill_cb; - u->source_output->attach = source_output_attach_cb; - u->source_output->detach = source_output_detach_cb; - u->source_output->state_change = source_output_state_change_cb; - u->source_output->moving = source_output_moving_cb; - u->source_output->userdata = u; - - u->source->output_from_master = u->source_output; - - /* The order here is important. The output must be put first, - * otherwise streams might attach to the source before the - * source output is attached to the master. */ - pa_source_output_put(u->source_output); - pa_source_put(u->source); - pa_source_output_cork(u->source_output, false); + /* Set callback for virtual source */ + u->vsource->process_chunk = filter_process_chunk; + u->vsource->output_from_master->moving = source_output_moving_cb; /* Create optional uplink sink */ pa_sink_new_data_init(&sink_data); @@ -693,6 +350,9 @@ int pa__init(pa_module*m) { u->sink = NULL; } + if (pa_virtual_source_activate(u->vsource) < 0) + goto fail; + pa_modargs_free(ma); return 0; @@ -712,7 +372,7 @@ int pa__get_n_used(pa_module *m) { pa_assert(m); pa_assert_se(u = m->userdata); - return pa_source_linked_by(u->source); + return pa_source_linked_by(u->vsource->source); } void pa__done(pa_module*m) { @@ -723,31 +383,14 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - /* See comments in source_output_kill_cb() above regarding - * destruction order! */ - - if (u->source_output) - pa_source_output_cork(u->source_output, true); - - if (u->source) - pa_source_unlink(u->source); - - if (u->source_output) { - pa_source_output_unlink(u->source_output); - pa_source_output_unref(u->source_output); - } - - if (u->source) - pa_source_unref(u->source); + if (u->vsource) + pa_virtual_source_destroy(u->vsource); if (u->sink) { pa_sink_unlink(u->sink); pa_sink_unref(u->sink); } - if (u->memblockq) - pa_memblockq_free(u->memblockq); - if (u->sink_memblockq) pa_memblockq_free(u->sink_memblockq); diff --git a/src/modules/virtual-source-common.c b/src/modules/virtual-source-common.c new file mode 100644 index 000000000..cbbbadd56 --- /dev/null +++ b/src/modules/virtual-source-common.c @@ -0,0 +1,1110 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include + +PA_DEFINE_PRIVATE_CLASS(pa_vsource, pa_msgobject); +#define PA_VSOURCE(o) (pa_vsource_cast(o)) + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + +#define MIN_BLOCK_SIZE 16 +#define LATENCY_MARGIN (5 * PA_USEC_PER_MSEC) + +enum { + SOURCE_MESSAGE_UPDATE_PARAMETERS = PA_SOURCE_MESSAGE_MAX +}; + +enum { + VSOURCE_MESSAGE_FREE_PARAMETERS, + VSOURCE_MESSAGE_OUTPUT_ATTACHED +}; + +/* Helper functions */ + +static inline pa_source_output* get_output_from_source(pa_source *s) { + + if (!s->vsource || !s->vsource->output_from_master) + return NULL; + return s->vsource->output_from_master; +} + +static int check_block_sizes(size_t fixed_block_frames, size_t fixed_input_block_frames, size_t overlap_frames, pa_vsource *vs) { + size_t max_block_frames; + size_t max_frame_size; + + max_frame_size = PA_MAX(pa_frame_size(&vs->source->sample_spec), pa_frame_size(&vs->output_from_master->sample_spec)); + + max_block_frames = pa_mempool_block_size_max(vs->core->mempool); + max_block_frames = max_block_frames / max_frame_size; + + if (fixed_block_frames > max_block_frames || fixed_input_block_frames > max_block_frames || overlap_frames + MIN_BLOCK_SIZE > max_block_frames) { + pa_log_warn("At least one of fixed_block_size, fixed_input_block_size or overlap_frames exceeds maximum."); + return -1; + } + + if (fixed_block_frames > 0 && fixed_block_frames < MIN_BLOCK_SIZE) { + pa_log_warn("fixed_block_size too small."); + return -1; + } + + if (fixed_input_block_frames > 0 && fixed_input_block_frames < MIN_BLOCK_SIZE) { + pa_log_warn("fixed_input_block_size too small."); + return -1; + } + + if (fixed_block_frames + overlap_frames > max_block_frames) { + pa_log_warn("Sum of fixed_block_size and overlap_frames exceeds maximum."); + return -1; + } + + if (fixed_input_block_frames > max_block_frames) { + pa_log_warn("fixed_input_block_size exceeds maximum."); + return -1; + } + + if (fixed_input_block_frames != 0 && fixed_block_frames > fixed_input_block_frames) { + pa_log_warn("fixed_block_size larger than fixed_input_block_size."); + return -1; + } + + return 0; +} + +static void set_latency_range_within_thread(pa_vsource *vsource) { + pa_usec_t min_latency, max_latency; + pa_source_output *o; + pa_source *s; + + s = vsource->source; + pa_assert(s); + o = vsource->output_from_master; + pa_assert(o); + + min_latency = o->source->thread_info.min_latency; + max_latency = o->source->thread_info.max_latency; + + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) { + if (vsource->max_latency) + max_latency = PA_MIN(vsource->max_latency, max_latency); + + if (vsource->fixed_block_size) { + pa_usec_t latency; + + latency = pa_bytes_to_usec(vsource->fixed_block_size * pa_frame_size(&s->sample_spec), &s->sample_spec); + min_latency = PA_MAX(min_latency, latency); + } + + max_latency = PA_MAX(max_latency, min_latency); + } + + pa_source_set_latency_range_within_thread(s, min_latency, max_latency); +} + +/* Called from I/O thread context */ +static void set_memblockq_rewind(pa_vsource *vsource) { + + if (vsource->memblockq) { + size_t rewind_size; + size_t in_fs; + + in_fs = pa_frame_size(&vsource->output_from_master->sample_spec); + rewind_size = PA_MAX(vsource->fixed_input_block_size, vsource->overlap_frames) * in_fs; + pa_memblockq_set_maxrewind(vsource->memblockq, rewind_size); + } +} + +/* Source callbacks */ + +/* Called from I/O thread context */ +int pa_virtual_source_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + pa_source_output *o; + pa_vsource *vsource; + + pa_source *s = PA_SOURCE(obj); + vsource = s->vsource; + pa_assert(vsource); + o = vsource->output_from_master; + pa_assert(o); + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: + + /* The source is _put() before the source output is, so let's + * make sure we don't access it in that time. Also, the + * source output is first shut down, the source second. */ + if (!PA_SOURCE_IS_LINKED(s->thread_info.state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = + + /* Get the latency of the master source */ + pa_source_get_latency_within_thread(o->source, true) + + + /* Add the latency internal to our source output on top */ + pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec); + + /* Add latenccy caused by the local memblockq */ + if (vsource->memblockq) + *((int64_t*) data) += pa_bytes_to_usec(pa_memblockq_get_length(vsource->memblockq), &o->sample_spec); + + /* Add resampler delay */ + *((int64_t*) data) += pa_resampler_get_delay_usec(o->thread_info.resampler); + + + /* Add additional filter latency if required. */ + if (vsource->get_extra_latency) + *((int64_t*) data) += vsource->get_extra_latency(s); + + return 0; + + case SOURCE_MESSAGE_UPDATE_PARAMETERS: + + /* Let the module update the filter parameters. Because the main thread + * is waiting, variables can be accessed freely in the callback. */ + if (vsource->update_filter_parameters) { + void *parameters; + size_t old_block_size, old_input_block_size, old_overlap_frames; + + /* Save old block sizes */ + old_block_size = vsource->fixed_block_size; + old_input_block_size = vsource->fixed_input_block_size; + old_overlap_frames = vsource->overlap_frames; + + parameters = vsource->update_filter_parameters(data, s->userdata); + if (parameters) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(vsource), VSOURCE_MESSAGE_FREE_PARAMETERS, parameters, 0, NULL, NULL); + + /* Updating the parameters may have changed the block sizes, so check them again. */ + if (check_block_sizes(vsource->fixed_block_size, vsource->fixed_input_block_size, vsource->overlap_frames, vsource) < 0) { + pa_log_warn("Invalid new block sizes, keeping old values."); + vsource->fixed_block_size = old_block_size; + vsource->fixed_input_block_size = old_input_block_size; + vsource->overlap_frames = old_overlap_frames; + } + + /* Set rewind of memblockq */ + set_memblockq_rewind(vsource); + + /* Inform the filter of the block sizes in use */ + if (vsource->update_block_sizes) + vsource->update_block_sizes(vsource->fixed_block_size, vsource->fixed_input_block_size, vsource->overlap_frames, s->userdata); + + /* If the block sizes changed the latency range may have changed as well. */ + set_latency_range_within_thread(vsource); + } + + return 0; + + } + + return pa_source_process_msg(obj, code, data, offset, chunk); +} + +/* Called from main context */ +int pa_virtual_source_set_state_in_main_thread(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) { + pa_source_output *o; + + pa_source_assert_ref(s); + o = get_output_from_source(s); + pa_assert(o); + + if (!PA_SOURCE_IS_LINKED(state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + return 0; + + pa_source_output_cork(o, state == PA_SOURCE_SUSPENDED); + return 0; +} + +/* Called from the IO thread. */ +int pa_virtual_source_set_state_in_io_thread(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause) { + pa_vsource *vsource; + + pa_source_assert_ref(s); + vsource = s->vsource; + pa_assert(vsource); + + if (PA_SOURCE_IS_OPENED(new_state) && !PA_SOURCE_IS_OPENED(s->thread_info.state)) + set_latency_range_within_thread(vsource); + + return 0; +} + +/* Called from I/O thread context */ +void pa_virtual_source_update_requested_latency(pa_source *s) { + pa_vsource *vsource; + pa_source_output *o; + pa_usec_t latency; + + pa_source_assert_ref(s); + vsource = s->vsource; + pa_assert(vsource); + o = vsource->output_from_master; + pa_assert(o); + + if (!PA_SOURCE_IS_LINKED(s->thread_info.state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)) + return; + + latency = pa_source_get_requested_latency_within_thread(s); + if (vsource->max_latency) + latency = PA_MIN(vsource->max_latency, latency); + + /* If we are using fixed blocksize, part of the latency is implemented + * in the virtual source. Reduce master latency by this amount. Do not set + * the latency too small to avoid high CPU load and underruns. */ + if (vsource->fixed_block_size) { + size_t in_fs; + pa_usec_t fixed_block_latency, min_latency; + + in_fs = pa_frame_size(&o->sample_spec); + fixed_block_latency = pa_bytes_to_usec(vsource->fixed_block_size * in_fs, &o->sample_spec); + min_latency = o->source->thread_info.min_latency; + if (min_latency < LATENCY_MARGIN) + min_latency += LATENCY_MARGIN; + + if (latency < fixed_block_latency + min_latency) + latency = min_latency; + else + latency = latency - fixed_block_latency; + } + + /* Now hand this one over to the master source */ + pa_source_output_set_requested_latency_within_thread(o, latency); +} + +/* Called from main context */ +void pa_virtual_source_set_volume(pa_source *s) { + pa_source_output *o; + pa_cvolume vol; + + pa_source_assert_ref(s); + o = get_output_from_source(s); + pa_assert(o); + + if (!PA_SOURCE_IS_LINKED(s->state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + return; + + /* Remap the volume, source and source output may have different + * channel counts. */ + vol = s->real_volume; + pa_cvolume_remap(&vol, &s->channel_map, &o->channel_map); + pa_source_output_set_volume(o, &vol, s->save_volume, true); +} + +/* Called from main context */ +void pa_virtual_source_set_mute(pa_source *s) { + pa_source_output *o; + + pa_source_assert_ref(s); + o = get_output_from_source(s); + pa_assert(o); + + if (!PA_SOURCE_IS_LINKED(s->state) || + !PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + return; + + pa_source_output_set_mute(o, s->muted, s->save_muted); +} + +/* Source output callbacks */ + +/* Called from output thread context */ +void pa_virtual_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { + pa_source *s; + size_t length, in_fs, out_fs; + pa_vsource *vsource; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + pa_assert(chunk); + + if (!PA_SOURCE_IS_LINKED(s->thread_info.state) || !PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)) + return; + + if (!vsource->process_chunk || !vsource->memblockq) { + pa_source_post(s, chunk); + return; + } + + out_fs = pa_frame_size(&s->sample_spec); + in_fs = pa_frame_size(&o->sample_spec); + + pa_memblockq_push_align(vsource->memblockq, chunk); + length = pa_memblockq_get_length(vsource->memblockq); + + while (length > vsource->fixed_block_size * in_fs || (vsource->fixed_block_size > 0 && length == vsource->fixed_block_size * in_fs)) { + uint8_t *src, *dst; + size_t in_count; + size_t overlap_frames, max_block_frames; + unsigned n; + pa_memchunk tchunk, schunk; + + /* Determine number of output samples */ + n = length / in_fs; + if (vsource->fixed_input_block_size && n > vsource->fixed_input_block_size) + n = vsource->fixed_input_block_size; + if (vsource->fixed_block_size && n > vsource->fixed_block_size) + n = vsource->fixed_block_size; + + n = PA_MIN(n, vsource->max_chunk_size / in_fs); + + pa_assert(n > 0); + + /* Determine number of overlap frames */ + overlap_frames = vsource->overlap_frames; + if (vsource->get_current_overlap) + overlap_frames = PA_MIN(overlap_frames, vsource->get_current_overlap(o)); + + /* For fixed input block size ignore overlap frames */ + if (vsource->fixed_input_block_size) { + overlap_frames = 0; + if (n > vsource->fixed_input_block_size) + n = vsource->fixed_input_block_size; + else + overlap_frames = vsource->fixed_input_block_size - n; + } + + /* In case of variable block size, it may be possible, that the sum of + * new samples and history data exceeds pa_mempool_block_size_max(). + * Then the number of new samples must be limited. */ + max_block_frames = pa_mempool_block_size_max(o->source->core->mempool) / PA_MAX(in_fs, out_fs); + if (n + overlap_frames > max_block_frames) + n = max_block_frames - overlap_frames; + + /* Get input data */ + in_count = n + overlap_frames; + if (overlap_frames) + pa_memblockq_rewind(vsource->memblockq, overlap_frames * in_fs); + pa_memblockq_peek_fixed_size(vsource->memblockq, in_count * in_fs, &schunk); + pa_memblockq_drop(vsource->memblockq, in_count * in_fs); + + /* Prepare output chunk */ + tchunk.index = 0; + tchunk.length = n * out_fs; + tchunk.memblock = pa_memblock_new(o->source->core->mempool, tchunk.length); + + src = pa_memblock_acquire_chunk(&schunk); + dst = pa_memblock_acquire(tchunk.memblock); + + /* Let the filter process the chunk */ + vsource->process_chunk(src, dst, in_count, n, o->userdata); + + pa_memblock_release(tchunk.memblock); + pa_memblock_release(schunk.memblock); + pa_memblock_unref(schunk.memblock); + + /* Post data */ + pa_source_post(s, &tchunk); + + pa_memblock_unref(tchunk.memblock); + length = pa_memblockq_get_length(vsource->memblockq); + } +} + + /* Called from I/O thread context */ +void pa_virtual_source_output_process_rewind(pa_source_output *o, size_t nbytes) { + pa_source *s; + pa_vsource *vsource; + size_t in_fs, out_fs; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + out_fs = pa_frame_size(&s->sample_spec); + in_fs = pa_frame_size(&o->sample_spec); + + /* If the source is not yet linked, there is nothing to rewind */ + if (!PA_SOURCE_IS_LINKED(s->thread_info.state)) + return; + + /* If the source output is corked, ignore the rewind request. */ + if (o->thread_info.state == PA_SOURCE_OUTPUT_CORKED) + return; + + /* If we have a memblockq, the source is not rewindable, else + * pass the rewind on to the source */ + if (vsource->memblockq) + pa_memblockq_seek(vsource->memblockq, - nbytes, PA_SEEK_RELATIVE, true); + else + pa_source_process_rewind(s, nbytes * out_fs / in_fs); +} + +/* Called from source I/O thread context. */ +void pa_virtual_source_output_update_max_rewind(pa_source_output *o, size_t nbytes) { + pa_source *s; + pa_vsource *vsource; + size_t in_fs, out_fs; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + out_fs = pa_frame_size(&s->sample_spec); + in_fs = pa_frame_size(&o->sample_spec); + + /* Set rewind of memblockq */ + set_memblockq_rewind(vsource); + + if (!vsource->memblockq) + pa_source_set_max_rewind_within_thread(s, nbytes * out_fs / in_fs); +} + +/* Called from I/O thread context */ +void pa_virtual_source_output_update_source_latency_range(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + set_latency_range_within_thread(vsource); +} + +/* Called from I/O thread context */ +void pa_virtual_source_output_update_source_fixed_latency(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + pa_usec_t latency; + size_t out_fs; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + out_fs = pa_frame_size(&s->sample_spec); + + /* For filters with fixed block size we have to add the block size minus 1 sample + * to the fixed latency. */ + latency = o->source->thread_info.fixed_latency; + if (vsource->fixed_block_size && !(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + latency += pa_bytes_to_usec((vsource->fixed_block_size - 1) * out_fs, &s->sample_spec); + + pa_source_set_fixed_latency_within_thread(s, latency); +} + +/* Called from I/O thread context */ +void pa_virtual_source_output_attach(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + size_t out_fs, master_fs; + pa_usec_t latency; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + out_fs = pa_frame_size(&s->sample_spec); + master_fs = pa_frame_size(&o->source->sample_spec); + + pa_source_set_rtpoll(s, o->source->thread_info.rtpoll); + + set_latency_range_within_thread(vsource); + + /* For filters with fixed block size we have to add the block size minus 1 sample + * to the fixed latency. */ + latency = o->source->thread_info.fixed_latency; + if (vsource->fixed_block_size && !(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + latency += pa_bytes_to_usec((vsource->fixed_block_size - 1) * out_fs, &s->sample_spec); + + pa_source_set_fixed_latency_within_thread(s, latency); + + /* Set max_rewind, virtual sources can only rewind when there is no memblockq */ + if (vsource->memblockq) + pa_source_set_max_rewind_within_thread(s, 0); + else + pa_source_set_max_rewind_within_thread(s, o->source->thread_info.max_rewind * out_fs / master_fs); + + /* Set rewind of memblockq */ + set_memblockq_rewind(vsource); + + /* This call is needed to remove the UNAVAILABLE suspend cause after + * a move when the previous master source disappeared. */ + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(vsource), VSOURCE_MESSAGE_OUTPUT_ATTACHED, NULL, 0, NULL, NULL); + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) + pa_source_attach_within_thread(s); +} + +/* Called from output thread context */ +void pa_virtual_source_output_detach(pa_source_output *o) { + pa_source *s; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + s = o->destination_source; + pa_assert(s); + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) + pa_source_detach_within_thread(s); + + pa_source_set_rtpoll(s, NULL); +} + +/* Called from main thread */ +void pa_virtual_source_output_kill(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + pa_module *m; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + /* The order here matters! We first kill the source so that streams + * can properly be moved away while the source output is still connected + * to the master. It may be possible that the source output is connected + * to a virtual source which has lost its master, so do not try to cork + * if the source has no I/O context. */ + if (o->source && o->source->asyncmsgq) + pa_source_output_cork(o, true); + pa_source_unlink(s); + pa_source_output_unlink(o); + + pa_source_output_unref(o); + + if (vsource->memblockq) + pa_memblockq_free(vsource->memblockq); + + /* Virtual sources must set the module */ + m = s->module; + pa_assert(m); + pa_source_unref(s); + + vsource->source = NULL; + vsource->output_from_master = NULL; + vsource->memblockq = NULL; + + pa_module_unload_request(m, true); +} + +/* Called from main context */ +bool pa_virtual_source_output_may_move_to(pa_source_output *o, pa_source *dest) { + pa_source *s; + pa_vsource *vsource; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + if (vsource->autoloaded) + return false; + + return s != dest; +} + +/* Called from main thread */ +void pa_virtual_source_output_moving(pa_source_output *o, pa_source *dest) { + pa_source *s; + pa_vsource *vsource; + uint32_t idx; + pa_source_output *output; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + if (dest) { + pa_source_set_asyncmsgq(s, dest->asyncmsgq); + pa_source_update_flags(s, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags); + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_MASTER_DEVICE, dest->name); + vsource->source_moving = true; + } else + pa_source_set_asyncmsgq(s, NULL); + + if (dest && vsource->set_description) + vsource->set_description(o, dest); + + else { + if (vsource->auto_desc && dest) { + const char *z; + pa_proplist *pl; + char *proplist_name; + + pl = pa_proplist_new(); + proplist_name = pa_sprintf_malloc("device.%s.name", vsource->source_type); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "%s %s on %s", vsource->desc_head, + pa_proplist_gets(s->proplist, proplist_name), z ? z : dest->name); + + pa_source_update_proplist(s, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + pa_xfree(proplist_name); + } + + if (dest) + pa_proplist_setf(o->proplist, PA_PROP_MEDIA_NAME, "%s Stream from %s", vsource->desc_head, pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)); + } + + /* Propagate asyncmsq change to attached virtual sources */ + PA_IDXSET_FOREACH(output, s->outputs, idx) { + if (output->destination_source && output->moving) + output->moving(output, s); + } + +} + +/* Called from main context */ +void pa_virtual_source_output_volume_changed(pa_source_output *o) { + pa_source *s; + pa_vsource *vsource; + pa_cvolume vol; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + vsource = s->vsource; + pa_assert(vsource); + + /* Preserve source volume if the master source is changing */ + if (vsource->source_moving) { + vsource->source_moving = false; + return; + } + + /* Remap the volume, source and source output may have different + * channel counts. */ + vol = o->volume; + pa_cvolume_remap(&vol, &o->channel_map, &s->channel_map); + pa_source_volume_changed(s, &vol); +} + +/* Called from main context */ +void pa_virtual_source_output_mute_changed(pa_source_output *o) { + pa_source *s; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + + pa_source_mute_changed(s, o->muted); +} + +/* Called from main context */ +void pa_virtual_source_output_suspend(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause) { + pa_source *s; + + pa_source_output_assert_ref(o); + s = o->destination_source; + pa_assert(s); + + if (!PA_SOURCE_IS_LINKED(s->state)) + return; + + if (o->source->state != PA_SOURCE_SUSPENDED || o->source->suspend_cause == PA_SUSPEND_IDLE) + pa_source_suspend(s, false, PA_SUSPEND_UNAVAILABLE); + else + pa_source_suspend(s, true, PA_SUSPEND_UNAVAILABLE); +} + +/* Other functions */ + +void pa_virtual_source_set_callbacks(pa_source *s, bool use_volume_sharing) { + + s->parent.process_msg = pa_virtual_source_process_msg; + s->set_state_in_main_thread = pa_virtual_source_set_state_in_main_thread; + s->set_state_in_io_thread = pa_virtual_source_set_state_in_io_thread; + s->update_requested_latency = pa_virtual_source_update_requested_latency; + pa_source_set_set_mute_callback(s, pa_virtual_source_set_mute); + if (!use_volume_sharing) { + pa_source_set_set_volume_callback(s, pa_virtual_source_set_volume); + pa_source_enable_decibel_volume(s, true); + } +} + +void pa_virtual_source_output_set_callbacks(pa_source_output *o, bool use_volume_sharing) { + + o->push = pa_virtual_source_output_push; + o->update_source_latency_range = pa_virtual_source_output_update_source_latency_range; + o->update_source_fixed_latency = pa_virtual_source_output_update_source_fixed_latency; + o->kill = pa_virtual_source_output_kill; + o->attach = pa_virtual_source_output_attach; + o->detach = pa_virtual_source_output_detach; + o->may_move_to = pa_virtual_source_output_may_move_to; + o->moving = pa_virtual_source_output_moving; + o->volume_changed = use_volume_sharing ? NULL : pa_virtual_source_output_volume_changed; + o->mute_changed = pa_virtual_source_output_mute_changed; + o->suspend = pa_virtual_source_output_suspend; + o->update_max_rewind = pa_virtual_source_output_update_max_rewind; + o->process_rewind = pa_virtual_source_output_process_rewind; +} + +static int vsource_process_msg(pa_msgobject *obj, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_vsource *vsource; + pa_source *s; + pa_source_output *o; + + pa_assert(obj); + pa_assert_ctl_context(); + + vsource = PA_VSOURCE(obj); + + switch (code) { + + case VSOURCE_MESSAGE_FREE_PARAMETERS: + + pa_assert(userdata); + pa_assert(vsource->free_filter_parameters); + vsource->free_filter_parameters(userdata); + return 0; + + case VSOURCE_MESSAGE_OUTPUT_ATTACHED: + + s = vsource->source; + o = vsource->output_from_master; + + /* This may happen if a message is still pending after the vsink was + * destroyed. */ + if (!s || !o) + return 0; + + if (PA_SOURCE_IS_LINKED(s->state)) { + if (o->source->state != PA_SOURCE_SUSPENDED || o->source->suspend_cause == PA_SUSPEND_IDLE) + pa_source_suspend(s, false, PA_SUSPEND_UNAVAILABLE); + else + pa_source_suspend(s, true, PA_SUSPEND_UNAVAILABLE); + } + return 0; + } + return 0; +} + +int pa_virtual_source_activate(pa_vsource *vs) { + + pa_assert(vs); + pa_assert(vs->source); + pa_assert(vs->output_from_master); + + /* Check that block sizes are plausible */ + if (check_block_sizes(vs->fixed_block_size, vs->fixed_input_block_size, vs->overlap_frames, vs) < 0) { + pa_log_warn("Invalid block sizes."); + return -1; + } + + /* Set source output latency at startup to max_latency if specified. */ + if (vs->max_latency) + pa_source_output_set_requested_latency(vs->output_from_master, vs->max_latency); + + /* The order here is important. The output must be put first, + * otherwise streams might attach to the source before the source + * output is attached to the master. */ + pa_source_output_put(vs->output_from_master); + pa_source_put(vs->source); + + /* If volume sharing and flat volumes are disabled, we have to apply the source volume to the source output. */ + if (!(vs->source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) && !pa_source_flat_volume_enabled(vs->output_from_master->source)) { + pa_cvolume vol; + + vol = vs->source->real_volume; + pa_cvolume_remap(&vol, &vs->source->channel_map, &vs->output_from_master->channel_map); + pa_source_output_set_volume(vs->output_from_master, &vol, vs->source->save_volume, true); + } + + pa_source_output_cork(vs->output_from_master, false); + + return 0; +} + +void pa_virtual_source_destroy(pa_vsource *vs) { + + pa_assert(vs); + + /* See comments in source_output_kill() above regarding + * destruction order! */ + if (vs->output_from_master && PA_SOURCE_OUTPUT_IS_LINKED(vs->output_from_master->state)) + pa_source_output_cork(vs->output_from_master, true); + + if (vs->source) + pa_source_unlink(vs->source); + + if (vs->output_from_master) { + pa_source_output_unlink(vs->output_from_master); + pa_source_output_unref(vs->output_from_master); + vs->output_from_master = NULL; + } + + if (vs->memblockq) + pa_memblockq_free(vs->memblockq); + + if (vs->source) { + pa_source_unref(vs->source); + vs->source = NULL; + } + + /* We have to use pa_msgobject_unref() here because there may still be pending + * VSOURCE_MESSAGE_OUTPUT_ATTACHED messages. */ + pa_msgobject_unref(PA_MSGOBJECT(vs)); +} + +/* Manually create a vsource structure. */ +pa_vsource* pa_virtual_source_vsource_new(pa_source *s) { + pa_vsource *vsource; + + pa_assert(s); + + /* Create new vource */ + vsource = pa_msgobject_new(pa_vsource); + vsource->parent.process_msg = vsource_process_msg; + + vsource->source = s; + vsource->core = s->core; + s->vsource = vsource; + + /* Reset virtual source parameters */ + vsource->output_from_master = NULL; + vsource->memblockq = NULL; + vsource->auto_desc = false; + vsource->source_moving = false; + vsource->desc_head = "Unknown Sink"; + vsource->source_type = "unknown"; + vsource->autoloaded = false; + vsource->max_chunk_size = pa_frame_align(pa_mempool_block_size_max(s->core->mempool), &s->sample_spec); + vsource->fixed_block_size = 0; + vsource->fixed_input_block_size = 0; + vsource->overlap_frames = 0; + vsource->max_latency = 0; + vsource->process_chunk = NULL; + vsource->get_extra_latency = NULL; + vsource->set_description = NULL; + vsource->update_filter_parameters = NULL; + vsource->update_block_sizes = NULL; + vsource->free_filter_parameters = NULL; + + return vsource; +} + +pa_vsource *pa_virtual_source_create(pa_source *master, const char *source_type, const char *desc_prefix, + pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *source_output_ss, pa_channel_map *source_output_map, + pa_module *m, void *userdata, pa_modargs *ma, + bool use_volume_sharing, bool create_memblockq) { + + pa_source_output_new_data source_output_data; + pa_source_new_data source_data; + char *source_type_property; + bool auto_desc; + bool force_flat_volume = false; + bool remix = true; + pa_resample_method_t resample_method = PA_RESAMPLER_INVALID; + pa_vsource *vsource; + pa_source *s; + pa_source_output *o; + + /* Make sure all necessary values are set. Only userdata and source description + * are allowed to be NULL. */ + pa_assert(master); + pa_assert(source_ss); + pa_assert(source_map); + pa_assert(source_output_ss); + pa_assert(source_output_map); + pa_assert(m); + pa_assert(ma); + + /* We do not support resampling in filters */ + pa_assert(source_output_ss->rate == source_ss->rate); + + if (!source_type) + source_type = "unknown"; + if (!desc_prefix) + desc_prefix = "Unknown Source"; + + /* Get some command line arguments. Because there is no common default + * for use_volume_sharing, this value must be passed as argument to + * pa_virtual_source_create(). */ + + if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { + pa_log("force_flat_volume= expects a boolean argument"); + return NULL; + } + + if (use_volume_sharing && force_flat_volume) { + pa_log("Flat volume can't be forced when using volume sharing."); + return NULL; + } + + if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) { + pa_log("Invalid boolean remix parameter"); + return NULL; + } + + if (pa_modargs_get_resample_method(ma, &resample_method) < 0) { + pa_log("Invalid resampling method"); + return NULL; + } + + /* Create source */ + pa_source_new_data_init(&source_data); + source_data.driver = m->name; + source_data.module = m; + if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) + source_data.name = pa_sprintf_malloc("%s.%s", master->name, source_type); + pa_source_new_data_set_sample_spec(&source_data, source_ss); + pa_source_new_data_set_channel_map(&source_data, source_map); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&source_data); + return NULL; + } + + s = pa_source_new(m->core, &source_data, (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)) + | (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0)); + + pa_source_new_data_done(&source_data); + + if (!s) { + pa_log("Failed to create source."); + return NULL; + } + + /* Set name and description properties after the source has been created, + * otherwise they may be duplicate. */ + if ((auto_desc = !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(s->proplist, PA_PROP_DEVICE_DESCRIPTION, "%s %s on %s", desc_prefix, s->name, z ? z : master->name); + } + + source_type_property = pa_sprintf_malloc("device.%s.name", source_type); + pa_proplist_sets(s->proplist, source_type_property, s->name); + pa_xfree(source_type_property); + + /* Create vsource structure. */ + vsource = pa_virtual_source_vsource_new(s); + + pa_virtual_source_set_callbacks(s, use_volume_sharing); + vsource->auto_desc = auto_desc; + vsource->desc_head = desc_prefix; + vsource->source_type = source_type; + + /* Normally this flag would be enabled automatically be we can force it. */ + if (force_flat_volume) + s->flags |= PA_SOURCE_FLAT_VOLUME; + s->userdata = userdata; + + pa_source_set_asyncmsgq(s, master->asyncmsgq); + + /* Create source output */ + pa_source_output_new_data_init(&source_output_data); + source_output_data.driver = __FILE__; + source_output_data.module = m; + pa_source_output_new_data_set_source(&source_output_data, master, false, true); + source_output_data.destination_source = s; + + pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "%s Stream of %s", desc_prefix, pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)); + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_source_output_new_data_set_sample_spec(&source_output_data, source_output_ss); + pa_source_output_new_data_set_channel_map(&source_output_data, source_output_map); + source_output_data.resample_method = resample_method; + source_output_data.flags = (remix ? 0 : PA_SOURCE_OUTPUT_NO_REMIX) | PA_SOURCE_OUTPUT_START_CORKED; + if (!pa_safe_streq(master->name, m->core->default_source->name)) + source_output_data.preferred_source = pa_xstrdup(master->name); + + if (pa_modargs_get_proplist(ma, "source_output_properties", source_output_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid source output properties"); + pa_source_output_new_data_done(&source_output_data); + pa_virtual_source_destroy(vsource); + return NULL; + } + + pa_source_output_new(&o, m->core, &source_output_data); + pa_source_output_new_data_done(&source_output_data); + + if (!o) { + pa_log("Could not create source-output"); + pa_virtual_source_destroy(vsource); + return NULL; + } + + pa_virtual_source_output_set_callbacks(o, use_volume_sharing); + o->userdata = userdata; + + vsource->output_from_master = o; + + vsource->autoloaded = false; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &vsource->autoloaded) < 0) { + pa_log("Failed to parse autoloaded value"); + pa_virtual_source_destroy(vsource); + return NULL; + } + + if (create_memblockq) { + char *tmp; + pa_memchunk silence; + + tmp = pa_sprintf_malloc("%s memblockq", desc_prefix); + pa_silence_memchunk_get(&s->core->silence_cache, s->core->mempool, &silence, &o->sample_spec, 0); + vsource->memblockq = pa_memblockq_new(tmp, 0, MEMBLOCKQ_MAXLENGTH, 0, source_output_ss, 1, 1, 0, &silence); + pa_memblock_unref(silence.memblock); + pa_xfree(tmp); + } + + return vsource; +} + +/* Send request to update filter parameters to the I/O-thread. */ +void pa_virtual_source_request_parameter_update(pa_vsource *vs, void *parameters) { + + pa_assert(vs); + pa_assert(vs->source); + + /* parameters may be NULL if it is enough to have access to userdata from the + * callback. */ + pa_asyncmsgq_send(vs->source->asyncmsgq, PA_MSGOBJECT(vs->source), SOURCE_MESSAGE_UPDATE_PARAMETERS, parameters, 0, NULL); +} diff --git a/src/modules/virtual-source-common.h b/src/modules/virtual-source-common.h new file mode 100644 index 000000000..ff07a2ee4 --- /dev/null +++ b/src/modules/virtual-source-common.h @@ -0,0 +1,71 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#include +#include + +/* Callbacks for virtual sources. */ +int pa_virtual_source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk); + +int pa_virtual_source_set_state_in_main_thread(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause); +int pa_virtual_source_set_state_in_io_thread(pa_source *s, pa_source_state_t new_state, pa_suspend_cause_t new_suspend_cause); + +void pa_virtual_source_update_requested_latency(pa_source *s); +void pa_virtual_source_set_volume(pa_source *s); +void pa_virtual_source_set_mute(pa_source *s); + +void pa_virtual_source_output_push(pa_source_output *o, const pa_memchunk *chunk); + +void pa_virtual_source_output_update_source_latency_range(pa_source_output *o); +void pa_virtual_source_output_update_source_fixed_latency(pa_source_output *o); + +void pa_virtual_source_output_process_rewind(pa_source_output *o, size_t nbytes); +void pa_virtual_source_output_update_max_rewind(pa_source_output *o, size_t nbytes); + +void pa_virtual_source_output_detach(pa_source_output *o); +void pa_virtual_source_output_attach(pa_source_output *o); +void pa_virtual_source_output_kill(pa_source_output *o); +void pa_virtual_source_output_moving(pa_source_output *o, pa_source *dest); +bool pa_virtual_source_output_may_move_to(pa_source_output *o, pa_source *dest); + +void pa_virtual_source_output_volume_changed(pa_source_output *o); +void pa_virtual_source_output_mute_changed(pa_source_output *o); + +void pa_virtual_source_output_suspend(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause); + +/* Set callbacks for virtual source and source output. */ +void pa_virtual_source_set_callbacks(pa_source *s, bool use_volume_sharing); +void pa_virtual_source_output_set_callbacks(pa_source_output *o, bool use_volume_sharing); + +/* Create a new virtual source. Returns a filled vsource structure or NULL on failure. */ +pa_vsource *pa_virtual_source_create(pa_source *master, const char *source_type, const char *desc_prefix, + pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *source_output_ss, pa_channel_map *source_output_map, + pa_module *m, void *userdata, pa_modargs *ma, + bool use_volume_sharing, bool create_memblockq); + +/* Activate the new virtual source. */ +int pa_virtual_source_activate(pa_vsource *vs); + +/* Destroys the objects associated with the virtual source. */ +void pa_virtual_source_destroy(pa_vsource *vs); + +/* Create vsource structure */ +pa_vsource* pa_virtual_source_vsource_new(pa_source *s); + +/* Update filter parameters */ +void pa_virtual_source_request_parameter_update(pa_vsource *vs, void *parameters);