From 045c1d602dcba57868845ba3270510593c39480f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 May 2008 23:34:41 +0000 Subject: merge glitch-free branch back into trunk git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@2445 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/pulsecore/sink.c | 736 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 486 insertions(+), 250 deletions(-) (limited to 'src/pulsecore/sink.c') diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 9adb6097..31c3cfc8 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -47,43 +48,128 @@ #define MAX_MIX_CHANNELS 32 #define MIX_BUFFER_LENGTH (PA_PAGE_SIZE) -#define SILENCE_BUFFER_LENGTH (PA_PAGE_SIZE*12) +#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC) static PA_DEFINE_CHECK_TYPE(pa_sink, pa_msgobject); static void sink_free(pa_object *s); +pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data) { + pa_assert(data); + + memset(data, 0, sizeof(*data)); + data->proplist = pa_proplist_new(); + + return data; +} + +void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name) { + pa_assert(data); + + pa_xfree(data->name); + data->name = pa_xstrdup(name); +} + +void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map) { + pa_assert(data); + + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume) { + pa_assert(data); + + if ((data->volume_is_set = !!volume)) + data->volume = *volume; +} + +void pa_sink_new_data_set_muted(pa_sink_new_data *data, pa_bool_t mute) { + pa_assert(data); + + data->muted_is_set = TRUE; + data->muted = !!mute; +} + +void pa_sink_new_data_done(pa_sink_new_data *data) { + pa_assert(data); + + pa_xfree(data->name); + pa_proplist_free(data->proplist); +} + +static void reset_callbacks(pa_sink *s) { + pa_assert(s); + + s->set_state = NULL; + s->get_volume = NULL; + s->set_volume = NULL; + s->get_mute = NULL; + s->set_mute = NULL; + s->request_rewind = NULL; + s->update_requested_latency = NULL; +} + pa_sink* pa_sink_new( pa_core *core, - const char *driver, - const char *name, - int fail, - const pa_sample_spec *spec, - const pa_channel_map *map) { + pa_sink_new_data *data, + pa_sink_flags_t flags) { pa_sink *s; - char *n = NULL; - char st[256]; - pa_channel_map tmap; + const char *name; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_source_new_data source_data; + const char *dn; pa_assert(core); - pa_assert(name); - pa_assert(spec); + pa_assert(data); + pa_assert(data->name); + + s = pa_msgobject_new(pa_sink); - pa_return_null_if_fail(pa_sample_spec_valid(spec)); + if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SINK, s, data->namereg_fail))) { + pa_xfree(s); + return NULL; + } - if (!map) - pa_return_null_if_fail((map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT))); + pa_sink_new_data_set_name(data, name); - pa_return_null_if_fail(map && pa_channel_map_valid(map)); - pa_return_null_if_fail(map->channels == spec->channels); - pa_return_null_if_fail(!driver || pa_utf8_valid(driver)); - pa_return_null_if_fail(name && pa_utf8_valid(name) && *name); + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_NEW], data) < 0) { + pa_xfree(s); + pa_namereg_unregister(core, name); + return NULL; + } - s = pa_msgobject_new(pa_sink); + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); + pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]); - if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SINK, s, fail))) { + pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec)); + + if (!data->channel_map_is_set) + pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT)); + + pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map)); + pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels); + + if (!data->volume_is_set) + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + + pa_return_null_if_fail(pa_cvolume_valid(&data->volume)); + pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels); + + if (!data->muted_is_set) + data->muted = FALSE; + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_FIXATE], data) < 0) { pa_xfree(s); + pa_namereg_unregister(core, name); return NULL; } @@ -92,57 +178,78 @@ pa_sink* pa_sink_new( s->core = core; s->state = PA_SINK_INIT; - s->flags = 0; + s->flags = flags; s->name = pa_xstrdup(name); - s->description = NULL; - s->driver = pa_xstrdup(driver); - s->module = NULL; + s->proplist = pa_proplist_copy(data->proplist); + s->driver = pa_xstrdup(data->driver); + s->module = data->module; - s->sample_spec = *spec; - s->channel_map = *map; + s->sample_spec = data->sample_spec; + s->channel_map = data->channel_map; s->inputs = pa_idxset_new(NULL, NULL); s->n_corked = 0; - pa_cvolume_reset(&s->volume, spec->channels); - s->muted = FALSE; + s->volume = data->volume; + s->muted = data->muted; s->refresh_volume = s->refresh_mute = FALSE; - s->get_latency = NULL; - s->set_volume = NULL; - s->get_volume = NULL; - s->set_mute = NULL; - s->get_mute = NULL; - s->set_state = NULL; + reset_callbacks(s); s->userdata = NULL; s->asyncmsgq = NULL; s->rtpoll = NULL; - s->silence = NULL; - - pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0); - - pa_sample_spec_snprint(st, sizeof(st), spec); - pa_log_info("Created sink %u \"%s\" with sample spec \"%s\"", s->index, s->name, st); - n = pa_sprintf_malloc("%s.monitor", name); - - if (!(s->monitor_source = pa_source_new(core, driver, n, 0, spec, map))) - pa_log_warn("Failed to create monitor source."); - else { - char *d; - s->monitor_source->monitor_of = s; - d = pa_sprintf_malloc("Monitor Source of %s", s->name); - pa_source_set_description(s->monitor_source, d); - pa_xfree(d); - } + pa_silence_memchunk_get( + &core->silence_cache, + core->mempool, + &s->silence, + &s->sample_spec, + 0); - pa_xfree(n); + s->min_latency = DEFAULT_MIN_LATENCY; + s->max_latency = s->min_latency; s->thread_info.inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); s->thread_info.soft_volume = s->volume; s->thread_info.soft_muted = s->muted; s->thread_info.state = s->state; + s->thread_info.rewind_nbytes = 0; + s->thread_info.max_rewind = 0; + s->thread_info.requested_latency_valid = FALSE; + s->thread_info.requested_latency = 0; + + pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0); + + pa_log_info("Created sink %u \"%s\" with sample spec %s and channel map %s", + s->index, + s->name, + pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map)); + + pa_source_new_data_init(&source_data); + pa_source_new_data_set_sample_spec(&source_data, &s->sample_spec); + pa_source_new_data_set_channel_map(&source_data, &s->channel_map); + source_data.name = pa_sprintf_malloc("%s.monitor", name); + source_data.driver = data->driver; + source_data.module = data->module; + + dn = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Monitor of %s", dn ? dn : s->name); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "monitor"); + + s->monitor_source = pa_source_new(core, &source_data, 0); + + pa_source_new_data_done(&source_data); + + if (!s->monitor_source) { + pa_sink_unlink(s); + pa_sink_unref(s); + return NULL; + } + + s->monitor_source->monitor_of = s; + pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind); return s; } @@ -157,15 +264,16 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { return 0; suspend_change = - (s->state == PA_SINK_SUSPENDED && PA_SINK_OPENED(state)) || - (PA_SINK_OPENED(s->state) && state == PA_SINK_SUSPENDED); + (s->state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(state)) || + (PA_SINK_IS_OPENED(s->state) && state == PA_SINK_SUSPENDED); if (s->set_state) if ((ret = s->set_state(s, state)) < 0) return -1; - if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0) - return -1; + if (s->asyncmsgq) + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0) + return -1; s->state = state; @@ -193,12 +301,17 @@ void pa_sink_put(pa_sink* s) { pa_assert(s->asyncmsgq); pa_assert(s->rtpoll); + pa_assert(!s->min_latency || !s->max_latency || s->min_latency <= s->max_latency); + + if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) + s->flags |= PA_SINK_DECIBEL_VOLUME; + pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0); pa_source_put(s->monitor_source); pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index); - pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], s); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PUT], s); } void pa_sink_unlink(pa_sink* s) { @@ -215,7 +328,7 @@ void pa_sink_unlink(pa_sink* s) { * may be called multiple times on the same sink without bad * effects. */ - linked = PA_SINK_LINKED(s->state); + linked = PA_SINK_IS_LINKED(s->state); if (linked) pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK], s); @@ -235,12 +348,7 @@ void pa_sink_unlink(pa_sink* s) { else s->state = PA_SINK_UNLINKED; - s->get_latency = NULL; - s->get_volume = NULL; - s->set_volume = NULL; - s->set_mute = NULL; - s->get_mute = NULL; - s->set_state = NULL; + reset_callbacks(s); if (s->monitor_source) pa_source_unlink(s->monitor_source); @@ -258,7 +366,7 @@ static void sink_free(pa_object *o) { pa_assert(s); pa_assert(pa_sink_refcnt(s) == 0); - if (PA_SINK_LINKED(s->state)) + if (PA_SINK_IS_LINKED(s->state)) pa_sink_unlink(s); pa_log_info("Freeing sink %u \"%s\"", s->index, s->name); @@ -275,18 +383,20 @@ static void sink_free(pa_object *o) { pa_hashmap_free(s->thread_info.inputs, NULL, NULL); - if (s->silence) - pa_memblock_unref(s->silence); + if (s->silence.memblock) + pa_memblock_unref(s->silence.memblock); pa_xfree(s->name); - pa_xfree(s->description); pa_xfree(s->driver); + + if (s->proplist) + pa_proplist_free(s->proplist); + pa_xfree(s); } void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) { pa_sink_assert_ref(s); - pa_assert(q); s->asyncmsgq = q; @@ -296,7 +406,6 @@ void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) { void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) { pa_sink_assert_ref(s); - pa_assert(p); s->rtpoll = p; if (s->monitor_source) @@ -305,7 +414,7 @@ void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) { int pa_sink_update_status(pa_sink*s) { pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); if (s->state == PA_SINK_SUSPENDED) return 0; @@ -315,7 +424,7 @@ int pa_sink_update_status(pa_sink*s) { int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) { pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); if (suspend) return sink_set_state(s, PA_SINK_SUSPENDED); @@ -323,17 +432,35 @@ int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) { return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE); } -void pa_sink_ping(pa_sink *s) { +void pa_sink_process_rewind(pa_sink *s, size_t nbytes) { + pa_sink_input *i; + void *state = NULL; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + /* Make sure the sink code already reset the counter! */ + pa_assert(s->thread_info.rewind_nbytes <= 0); + + if (nbytes <= 0) + return; + + pa_log_debug("Processing rewind..."); + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) { + pa_sink_input_assert_ref(i); + pa_sink_input_process_rewind(i, nbytes); + } + + if (s->monitor_source && PA_SOURCE_IS_OPENED(pa_source_get_state(s->monitor_source))) + pa_source_process_rewind(s->monitor_source, nbytes); - pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_PING, NULL, 0, NULL, NULL); } -static unsigned fill_mix_info(pa_sink *s, size_t length, pa_mix_info *info, unsigned maxinfo) { +static unsigned fill_mix_info(pa_sink *s, size_t *length, pa_mix_info *info, unsigned maxinfo) { pa_sink_input *i; unsigned n = 0; void *state = NULL; + size_t mixlength = *length; pa_sink_assert_ref(s); pa_assert(info); @@ -341,8 +468,16 @@ static unsigned fill_mix_info(pa_sink *s, size_t length, pa_mix_info *info, unsi while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)) && maxinfo > 0) { pa_sink_input_assert_ref(i); - if (pa_sink_input_peek(i, length, &info->chunk, &info->volume) < 0) + if (pa_sink_input_peek(i, *length, &info->chunk, &info->volume) < 0) + continue; + + if (mixlength == 0 || info->chunk.length < mixlength) + mixlength = info->chunk.length; + + if (pa_memblock_is_silence(info->chunk.memblock)) { + pa_memblock_unref(info->chunk.memblock); continue; + } info->userdata = pa_sink_input_ref(i); @@ -354,6 +489,9 @@ static unsigned fill_mix_info(pa_sink *s, size_t length, pa_mix_info *info, unsi maxinfo--; } + if (mixlength > 0) + *length = mixlength; + return n; } @@ -421,12 +559,14 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { size_t block_size_max; pa_sink_assert_ref(s); - pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); pa_assert(pa_frame_aligned(length, &s->sample_spec)); pa_assert(result); pa_sink_ref(s); + s->thread_info.rewind_nbytes = 0; + if (length <= 0) length = pa_frame_align(MIX_BUFFER_LENGTH, &s->sample_spec); @@ -436,24 +576,15 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { pa_assert(length > 0); - n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, length, info, MAX_MIX_CHANNELS) : 0; + n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, &length, info, MAX_MIX_CHANNELS) : 0; if (n == 0) { - if (length > SILENCE_BUFFER_LENGTH) - length = pa_frame_align(SILENCE_BUFFER_LENGTH, &s->sample_spec); - - pa_assert(length > 0); - - if (!s->silence || pa_memblock_get_length(s->silence) < length) { - if (s->silence) - pa_memblock_unref(s->silence); - s->silence = pa_silence_memblock_new(s->core->mempool, &s->sample_spec, length); - } + *result = s->silence; + pa_memblock_ref(result->memblock); - result->memblock = pa_memblock_ref(s->silence); - result->length = length; - result->index = 0; + if (result->length > length) + result->length = length; } else if (n == 1) { pa_cvolume volume; @@ -467,6 +598,7 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&volume)) { + pa_log("adjusting volume "); pa_memchunk_make_writable(result, 0); if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) pa_silence_memchunk(result, &s->sample_spec); @@ -478,7 +610,11 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { result->memblock = pa_memblock_new(s->core->mempool, length); ptr = pa_memblock_acquire(result->memblock); - result->length = pa_mix(info, n, ptr, length, &s->sample_spec, &s->thread_info.soft_volume, s->thread_info.soft_muted); + result->length = pa_mix(info, n, + ptr, length, + &s->sample_spec, + &s->thread_info.soft_volume, + s->thread_info.soft_muted); pa_memblock_release(result->memblock); result->index = 0; @@ -487,7 +623,7 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { if (s->thread_info.state == PA_SINK_RUNNING) inputs_drop(s, info, n, result->length); - if (s->monitor_source && PA_SOURCE_OPENED(pa_source_get_state(s->monitor_source))) + if (s->monitor_source && PA_SOURCE_IS_OPENED(pa_source_get_state(s->monitor_source))) pa_source_post(s->monitor_source, result); pa_sink_unref(s); @@ -496,9 +632,10 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { pa_mix_info info[MAX_MIX_CHANNELS]; unsigned n; + size_t length, block_size_max; pa_sink_assert_ref(s); - pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); pa_assert(target); pa_assert(target->memblock); pa_assert(target->length > 0); @@ -506,34 +643,46 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { pa_sink_ref(s); - n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, target->length, info, MAX_MIX_CHANNELS) : 0; + s->thread_info.rewind_nbytes = 0; + + length = target->length; + block_size_max = pa_mempool_block_size_max(s->core->mempool); + if (length > block_size_max) + length = pa_frame_align(block_size_max, &s->sample_spec); + + n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, &length, info, MAX_MIX_CHANNELS) : 0; if (n == 0) { + if (target->length > length) + target->length = length; + pa_silence_memchunk(target, &s->sample_spec); } else if (n == 1) { - if (target->length > info[0].chunk.length) - target->length = info[0].chunk.length; + pa_cvolume volume; - if (s->thread_info.soft_muted) + if (target->length > length) + target->length = length; + + pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); + + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) pa_silence_memchunk(target, &s->sample_spec); else { - void *src, *ptr; - pa_cvolume volume; - - ptr = pa_memblock_acquire(target->memblock); - src = pa_memblock_acquire(info[0].chunk.memblock); + pa_memchunk vchunk; - memcpy((uint8_t*) ptr + target->index, - (uint8_t*) src + info[0].chunk.index, - target->length); + vchunk = info[0].chunk; + pa_memblock_ref(vchunk.memblock); - pa_memblock_release(target->memblock); - pa_memblock_release(info[0].chunk.memblock); + if (vchunk.length > target->length) + vchunk.length = target->length; - pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); + if (!pa_cvolume_is_norm(&volume)) { + pa_memchunk_make_writable(&vchunk, 0); + pa_volume_memchunk(&vchunk, &s->sample_spec, &volume); + } - if (!pa_cvolume_is_norm(&volume)) - pa_volume_memchunk(target, &s->sample_spec, &volume); + pa_memchunk_memcpy(target, &vchunk); + pa_memblock_unref(vchunk.memblock); } } else { @@ -542,8 +691,7 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { ptr = pa_memblock_acquire(target->memblock); target->length = pa_mix(info, n, - (uint8_t*) ptr + target->index, - target->length, + (uint8_t*) ptr + target->index, length, &s->sample_spec, &s->thread_info.soft_volume, s->thread_info.soft_muted); @@ -554,7 +702,7 @@ void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { if (s->thread_info.state == PA_SINK_RUNNING) inputs_drop(s, info, n, target->length); - if (s->monitor_source && PA_SOURCE_OPENED(pa_source_get_state(s->monitor_source))) + if (s->monitor_source && PA_SOURCE_IS_OPENED(pa_source_get_state(s->monitor_source))) pa_source_post(s->monitor_source, target); pa_sink_unref(s); @@ -565,7 +713,7 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { size_t l, d; pa_sink_assert_ref(s); - pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); pa_assert(target); pa_assert(target->memblock); pa_assert(target->length > 0); @@ -573,6 +721,8 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { pa_sink_ref(s); + s->thread_info.rewind_nbytes = 0; + l = target->length; d = 0; while (l > 0) { @@ -591,11 +741,13 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { pa_sink_assert_ref(s); - pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); pa_assert(length > 0); pa_assert(pa_frame_aligned(length, &s->sample_spec)); pa_assert(result); + s->thread_info.rewind_nbytes = 0; + /*** This needs optimization ***/ result->index = 0; @@ -605,50 +757,16 @@ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { pa_sink_render_into_full(s, result); } -void pa_sink_skip(pa_sink *s, size_t length) { - pa_sink_input *i; - void *state = NULL; - - pa_sink_assert_ref(s); - pa_assert(PA_SINK_OPENED(s->thread_info.state)); - pa_assert(length > 0); - pa_assert(pa_frame_aligned(length, &s->sample_spec)); - - if (pa_source_used_by(s->monitor_source)) { - pa_memchunk chunk; - - /* If something is connected to our monitor source, we have to - * pass valid data to it */ - - while (length > 0) { - pa_sink_render(s, length, &chunk); - pa_memblock_unref(chunk.memblock); - - pa_assert(chunk.length <= length); - length -= chunk.length; - } - - } else { - /* Ok, noone cares about the rendered data, so let's not even render it */ - - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) { - pa_sink_input_assert_ref(i); - pa_sink_input_drop(i, length); - } - } -} - pa_usec_t pa_sink_get_latency(pa_sink *s) { pa_usec_t usec = 0; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); - if (!PA_SINK_OPENED(s->state)) - return 0; + /* The returned value is supposed to be in the time domain of the sound card! */ - if (s->get_latency) - return s->get_latency(s); + if (!PA_SINK_IS_OPENED(s->state)) + return 0; if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) return 0; @@ -660,7 +778,7 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume) { int changed; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert(volume); changed = !pa_cvolume_equal(volume, &s->volume); @@ -680,7 +798,7 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s) { struct pa_cvolume old_volume; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); old_volume = s->volume; @@ -700,7 +818,7 @@ void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) { int changed; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); changed = s->muted != mute; s->muted = mute; @@ -719,7 +837,7 @@ pa_bool_t pa_sink_get_mute(pa_sink *s) { pa_bool_t old_muted; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); old_muted = s->muted; @@ -735,43 +853,34 @@ pa_bool_t pa_sink_get_mute(pa_sink *s) { return s->muted; } -void pa_sink_set_module(pa_sink *s, pa_module *m) { - pa_sink_assert_ref(s); - - if (s->module == m) - return; - - s->module = m; - - if (s->monitor_source) - pa_source_set_module(s->monitor_source, m); - - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); -} - void pa_sink_set_description(pa_sink *s, const char *description) { + const char *old; pa_sink_assert_ref(s); - if (!description && !s->description) + if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION)) return; - if (description && s->description && !strcmp(description, s->description)) + old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (old && description && !strcmp(old, description)) return; - pa_xfree(s->description); - s->description = pa_xstrdup(description); + if (description) + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description); + else + pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION); if (s->monitor_source) { char *n; - n = pa_sprintf_malloc("Monitor Source of %s", s->description? s->description : s->name); + n = pa_sprintf_malloc("Monitor Source of %s", description ? description : s->name); pa_source_set_description(s->monitor_source, n); pa_xfree(n); } - if (PA_SINK_LINKED(s->state)) { + if (PA_SINK_IS_LINKED(s->state)) { pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); - pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], s); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s); } } @@ -779,7 +888,7 @@ unsigned pa_sink_linked_by(pa_sink *s) { unsigned ret; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); ret = pa_idxset_size(s->inputs); @@ -796,16 +905,15 @@ unsigned pa_sink_used_by(pa_sink *s) { unsigned ret; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); ret = pa_idxset_size(s->inputs); pa_assert(ret >= s->n_corked); - ret -= s->n_corked; /* Streams connected to our monitor source do not matter for * pa_sink_used_by()!.*/ - return ret; + return ret - s->n_corked; } int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { @@ -817,6 +925,11 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse case PA_SINK_MESSAGE_ADD_INPUT: { pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* If you change anything here, make sure to change the + * sink input handling a few lines down at + * PA_SINK_MESSAGE_FINISH_MOVE, too. */ + pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i)); /* Since the caller sleeps in pa_sink_input_put(), we can @@ -841,9 +954,16 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse if (i->attach) i->attach(i); - /* If you change anything here, make sure to change the - * ghost sink input handling a few lines down at - * PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, too. */ + pa_sink_input_set_state_within_thread(i, i->state); + + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); + + pa_sink_invalidate_requested_latency(s); + + /* We don't rewind here automatically. This is left to the + * sink input implementor because some sink inputs need a + * slow start, i.e. need some time to buffer client + * samples before beginning streaming. */ return 0; } @@ -853,7 +973,9 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse /* If you change anything here, make sure to change the * sink input handling a few lines down at - * PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, too. */ + * PA_SINK_MESSAGE_PREPAPRE_MOVE, too. */ + + pa_sink_input_set_state_within_thread(i, i->state); if (i->detach) i->detach(i); @@ -881,82 +1003,93 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index))) pa_sink_input_unref(i); + pa_sink_invalidate_requested_latency(s); + pa_sink_request_rewind(s, 0); + return 0; } - case PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER: { - pa_sink_input_move_info *info = userdata; - int volume_is_norm; + case PA_SINK_MESSAGE_START_MOVE: { + pa_sink_input *i = PA_SINK_INPUT(userdata); /* We don't support moving synchronized streams. */ - pa_assert(!info->sink_input->sync_prev); - pa_assert(!info->sink_input->sync_next); - pa_assert(!info->sink_input->thread_info.sync_next); - pa_assert(!info->sink_input->thread_info.sync_prev); + pa_assert(!i->sync_prev); + pa_assert(!i->sync_next); + pa_assert(!i->thread_info.sync_next); + pa_assert(!i->thread_info.sync_prev); - if (info->sink_input->detach) - info->sink_input->detach(info->sink_input); + if (i->thread_info.state != PA_SINK_INPUT_CORKED) { + pa_usec_t usec = 0; + size_t sink_nbytes, total_nbytes; - pa_assert(info->sink_input->thread_info.attached); - info->sink_input->thread_info.attached = FALSE; + /* Get the latency of the sink */ + if (PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; - if (info->ghost_sink_input) { - pa_assert(info->buffer_bytes > 0); - pa_assert(info->buffer); + sink_nbytes = pa_usec_to_bytes(usec, &s->sample_spec); + total_nbytes = sink_nbytes + pa_memblockq_get_length(i->thread_info.render_memblockq); - volume_is_norm = pa_cvolume_is_norm(&info->sink_input->thread_info.volume); + if (total_nbytes > 0) { + i->thread_info.rewrite_nbytes = i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, total_nbytes) : total_nbytes; + i->thread_info.rewrite_flush = TRUE; + pa_sink_input_process_rewind(i, sink_nbytes); + } + } - pa_log_debug("Buffering %lu bytes ...", (unsigned long) info->buffer_bytes); + if (i->detach) + i->detach(i); - while (info->buffer_bytes > 0) { - pa_memchunk memchunk; - pa_cvolume volume; - size_t n; + pa_assert(i->thread_info.attached); + i->thread_info.attached = FALSE; - if (pa_sink_input_peek(info->sink_input, info->buffer_bytes, &memchunk, &volume) < 0) - break; + /* Let's remove the sink input ...*/ + if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index))) + pa_sink_input_unref(i); - n = memchunk.length > info->buffer_bytes ? info->buffer_bytes : memchunk.length; - pa_sink_input_drop(info->sink_input, n); - memchunk.length = n; + pa_sink_invalidate_requested_latency(s); - if (!volume_is_norm) { - pa_memchunk_make_writable(&memchunk, 0); - pa_volume_memchunk(&memchunk, &s->sample_spec, &volume); - } + pa_log_debug("Requesting rewind due to started move"); + pa_sink_request_rewind(s, 0); - if (pa_memblockq_push(info->buffer, &memchunk) < 0) { - pa_memblock_unref(memchunk.memblock); - break; - } + return 0; + } - pa_memblock_unref(memchunk.memblock); - info->buffer_bytes -= n; - } + case PA_SINK_MESSAGE_FINISH_MOVE: { + pa_sink_input *i = PA_SINK_INPUT(userdata); - /* Add the remaining already resampled chunk to the buffer */ - if (info->sink_input->thread_info.resampled_chunk.memblock) - pa_memblockq_push(info->buffer, &info->sink_input->thread_info.resampled_chunk); + /* We don't support moving synchronized streams. */ + pa_assert(!i->sync_prev); + pa_assert(!i->sync_next); + pa_assert(!i->thread_info.sync_next); + pa_assert(!i->thread_info.sync_prev); - pa_memblockq_sink_input_set_queue(info->ghost_sink_input, info->buffer); + pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i)); - pa_log_debug("Buffered %lu bytes ...", (unsigned long) pa_memblockq_get_length(info->buffer)); - } + pa_assert(!i->thread_info.attached); + i->thread_info.attached = TRUE; - /* Let's remove the sink input ...*/ - if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(info->sink_input->index))) - pa_sink_input_unref(info->sink_input); + if (i->attach) + i->attach(i); + + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); - /* .. and add the ghost sink input instead */ - if (info->ghost_sink_input) { - pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(info->ghost_sink_input->index), pa_sink_input_ref(info->ghost_sink_input)); - info->ghost_sink_input->thread_info.sync_prev = info->ghost_sink_input->thread_info.sync_next = NULL; + pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency); - pa_assert(!info->ghost_sink_input->thread_info.attached); - info->ghost_sink_input->thread_info.attached = TRUE; + if (i->thread_info.state != PA_SINK_INPUT_CORKED) { + pa_usec_t usec = 0; + size_t nbytes; - if (info->ghost_sink_input->attach) - info->ghost_sink_input->attach(info->ghost_sink_input); + /* Get the latency of the sink */ + if (PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + nbytes = pa_usec_to_bytes(usec, &s->sample_spec); + + if (nbytes > 0) + pa_sink_input_drop(i, nbytes); + + pa_log_debug("Requesting rewind due to finished move"); + pa_sink_request_rewind(s, nbytes); } return 0; @@ -964,10 +1097,14 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse case PA_SINK_MESSAGE_SET_VOLUME: s->thread_info.soft_volume = *((pa_cvolume*) userdata); + + pa_sink_request_rewind(s, 0); return 0; case PA_SINK_MESSAGE_SET_MUTE: s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata); + + pa_sink_request_rewind(s, 0); return 0; case PA_SINK_MESSAGE_GET_VOLUME: @@ -978,9 +1115,6 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse *((pa_bool_t*) userdata) = s->thread_info.soft_muted; return 0; - case PA_SINK_MESSAGE_PING: - return 0; - case PA_SINK_MESSAGE_SET_STATE: s->thread_info.state = PA_PTR_TO_UINT(userdata); @@ -992,13 +1126,20 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse * asyncmsgq and rtpoll fields can be changed without * problems */ pa_sink_detach_within_thread(s); - break; + return 0; case PA_SINK_MESSAGE_ATTACH: /* Reattach all streams */ pa_sink_attach_within_thread(s); - break; + return 0; + + case PA_SINK_MESSAGE_GET_REQUESTED_LATENCY: { + + pa_usec_t *usec = userdata; + *usec = pa_sink_get_requested_latency_within_thread(s); + return 0; + } case PA_SINK_MESSAGE_GET_LATENCY: case PA_SINK_MESSAGE_MAX: @@ -1023,14 +1164,14 @@ int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend) { void pa_sink_detach(pa_sink *s) { pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_DETACH, NULL, 0, NULL); } void pa_sink_attach(pa_sink *s) { pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(PA_SINK_IS_LINKED(s->state)); pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_ATTACH, NULL, 0, NULL); } @@ -1040,7 +1181,7 @@ void pa_sink_detach_within_thread(pa_sink *s) { void *state = NULL; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->thread_info.state)); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) if (i->detach) @@ -1055,7 +1196,7 @@ void pa_sink_attach_within_thread(pa_sink *s) { void *state = NULL; pa_sink_assert_ref(s); - pa_assert(PA_SINK_LINKED(s->thread_info.state)); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) if (i->attach) @@ -1064,3 +1205,98 @@ void pa_sink_attach_within_thread(pa_sink *s) { if (s->monitor_source) pa_source_attach_within_thread(s->monitor_source); } + +void pa_sink_request_rewind(pa_sink*s, size_t nbytes) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + if (nbytes <= 0) + nbytes = s->thread_info.max_rewind; + + nbytes = PA_MIN(nbytes, s->thread_info.max_rewind); + + if (nbytes <= s->thread_info.rewind_nbytes) + return; + + s->thread_info.rewind_nbytes = nbytes; + + if (s->request_rewind) + s->request_rewind(s); +} + +pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { + pa_usec_t result = (pa_usec_t) -1; + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + + if (s->thread_info.requested_latency_valid) + return s->thread_info.requested_latency; + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + + if (i->thread_info.requested_sink_latency != (pa_usec_t) -1 && + (result == (pa_usec_t) -1 || result > i->thread_info.requested_sink_latency)) + result = i->thread_info.requested_sink_latency; + + if (result != (pa_usec_t) -1) { + if (s->max_latency > 0 && result > s->max_latency) + result = s->max_latency; + + if (s->min_latency > 0 && result < s->min_latency) + result = s->min_latency; + } + + s->thread_info.requested_latency = result; + s->thread_info.requested_latency_valid = TRUE; + + return result; +} + +pa_usec_t pa_sink_get_requested_latency(pa_sink *s) { + pa_usec_t usec = 0; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (!PA_SINK_IS_OPENED(s->state)) + return 0; + + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) < 0) + return 0; + + if (usec == (pa_usec_t) -1) + usec = s->max_latency; + + return usec; +} + +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + + if (max_rewind == s->thread_info.max_rewind) + return; + + s->thread_info.max_rewind = max_rewind; + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); + + if (s->monitor_source) + pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind); +} + +void pa_sink_invalidate_requested_latency(pa_sink *s) { + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + s->thread_info.requested_latency_valid = FALSE; + + if (s->update_requested_latency) + s->update_requested_latency(s); +} -- cgit