X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/61b07768c2f7fcc38a32ba31db837a57335ed664..8534149fbe87c63a5af85f5610c0f62b45500d90:/src/pulsecore/sink-input.c diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index ad6b9ca7..065fd2d3 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -38,20 +38,60 @@ #include #include #include +#include #include "sink-input.h" #define MEMBLOCKQ_MAXLENGTH (32*1024*1024) #define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE) -static PA_DEFINE_CHECK_TYPE(pa_sink_input, pa_msgobject); +PA_DEFINE_PUBLIC_CLASS(pa_sink_input, pa_msgobject); static void sink_input_free(pa_object *o); +static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v); +static void sink_input_set_ramping_info(pa_sink_input* i, pa_volume_t pre_virtual_volume, pa_volume_t target_virtual_volume, pa_usec_t t); +static void sink_input_set_ramping_info_for_mute(pa_sink_input* i, pa_bool_t mute, pa_usec_t t); +static void sink_input_volume_ramping(pa_sink_input* i, pa_memchunk* chunk); +static void sink_input_rewind_ramp_info(pa_sink_input *i, size_t nbytes); +static void sink_input_release_envelope(pa_sink_input *i); + +static int check_passthrough_connection(pa_sink_input_flags_t flags, pa_sink *dest) { + + if (dest->flags & PA_SINK_PASSTHROUGH) { + + if (pa_idxset_size(dest->inputs) > 0) { + + pa_sink_input *alt_i; + uint32_t idx; + + alt_i = pa_idxset_first(dest->inputs, &idx); + + /* only need to check the first input is not PASSTHROUGH */ + if (alt_i->flags & PA_SINK_INPUT_PASSTHROUGH) { + pa_log_warn("Sink is already connected to PASSTHROUGH input"); + return -PA_ERR_BUSY; + } + + /* Current inputs are PCM, check new input is not PASSTHROUGH */ + if (flags & PA_SINK_INPUT_PASSTHROUGH) { + pa_log_warn("Sink is already connected, cannot accept new PASSTHROUGH INPUT"); + return -PA_ERR_BUSY; + } + } + + } else { + if (flags & PA_SINK_INPUT_PASSTHROUGH) { + pa_log_warn("Cannot connect PASSTHROUGH sink input to sink without PASSTHROUGH capabilities"); + return -PA_ERR_INVALID; + } + } + return PA_OK; +} pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) { pa_assert(data); - memset(data, 0, sizeof(*data)); + pa_zero(*data); data->resample_method = PA_RESAMPLER_INVALID; data->proplist = pa_proplist_new(); @@ -91,6 +131,18 @@ void pa_sink_input_new_data_apply_volume_factor(pa_sink_input_new_data *data, co } } +void pa_sink_input_new_data_apply_volume_factor_sink(pa_sink_input_new_data *data, const pa_cvolume *volume_factor) { + pa_assert(data); + pa_assert(volume_factor); + + if (data->volume_factor_sink_is_set) + pa_sw_cvolume_multiply(&data->volume_factor_sink, &data->volume_factor_sink, volume_factor); + else { + data->volume_factor_sink_is_set = TRUE; + data->volume_factor_sink = *volume_factor; + } +} + void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) { pa_assert(data); @@ -114,6 +166,7 @@ static void reset_callbacks(pa_sink_input *i) { i->update_max_request = NULL; i->update_sink_requested_latency = NULL; i->update_sink_latency_range = NULL; + i->update_sink_fixed_latency = NULL; i->attach = NULL; i->detach = NULL; i->suspend = NULL; @@ -124,24 +177,27 @@ static void reset_callbacks(pa_sink_input *i) { i->state_change = NULL; i->may_move_to = NULL; i->send_event = NULL; + i->volume_changed = NULL; + i->mute_changed = NULL; } /* Called from main context */ int pa_sink_input_new( pa_sink_input **_i, pa_core *core, - pa_sink_input_new_data *data, - pa_sink_input_flags_t flags) { + pa_sink_input_new_data *data) { pa_sink_input *i; pa_resampler *resampler = NULL; char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_channel_map original_cm; int r; + char *pt; pa_assert(_i); pa_assert(core); pa_assert(data); + pa_assert_ctl_context(); if (data->client) pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist); @@ -160,6 +216,9 @@ int pa_sink_input_new( pa_return_val_if_fail(PA_SINK_IS_LINKED(pa_sink_get_state(data->sink)), -PA_ERR_BADSTATE); pa_return_val_if_fail(!data->sync_base || (data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED), -PA_ERR_INVALID); + r = check_passthrough_connection(data->flags, data->sink); + pa_return_val_if_fail(r == PA_OK, r); + if (!data->sample_spec_is_set) data->sample_spec = data->sink->sample_spec; @@ -172,44 +231,38 @@ int pa_sink_input_new( pa_channel_map_init_extend(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); } - pa_return_val_if_fail(pa_channel_map_valid(&data->channel_map), -PA_ERR_INVALID); pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID); if (!data->volume_is_set) { - - if (data->sink->flags & PA_SINK_FLAT_VOLUME) { - data->volume = *pa_sink_get_volume(data->sink, FALSE); - pa_cvolume_remap(&data->volume, &data->sink->channel_map, &data->channel_map); - data->volume_is_absolute = TRUE; - } else { - pa_cvolume_reset(&data->volume, data->sample_spec.channels); - data->volume_is_absolute = FALSE; - } - + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->volume_is_absolute = FALSE; data->save_volume = FALSE; } - pa_return_val_if_fail(pa_cvolume_valid(&data->volume), -PA_ERR_INVALID); pa_return_val_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec), -PA_ERR_INVALID); if (!data->volume_factor_is_set) pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels); - pa_return_val_if_fail(pa_cvolume_valid(&data->volume_factor), -PA_ERR_INVALID); pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor, &data->sample_spec), -PA_ERR_INVALID); + if (!data->volume_factor_sink_is_set) + pa_cvolume_reset(&data->volume_factor_sink, data->sink->sample_spec.channels); + + pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor_sink, &data->sink->sample_spec), -PA_ERR_INVALID); + if (!data->muted_is_set) data->muted = FALSE; - if (flags & PA_SINK_INPUT_FIX_FORMAT) + if (data->flags & PA_SINK_INPUT_FIX_FORMAT) data->sample_spec.format = data->sink->sample_spec.format; - if (flags & PA_SINK_INPUT_FIX_RATE) + if (data->flags & PA_SINK_INPUT_FIX_RATE) data->sample_spec.rate = data->sink->sample_spec.rate; original_cm = data->channel_map; - if (flags & PA_SINK_INPUT_FIX_CHANNELS) { + if (data->flags & PA_SINK_INPUT_FIX_CHANNELS) { data->sample_spec.channels = data->sink->sample_spec.channels; data->channel_map = data->sink->channel_map; } @@ -228,7 +281,7 @@ int pa_sink_input_new( if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0) return r; - if ((flags & PA_SINK_INPUT_FAIL_ON_SUSPEND) && + if ((data->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND) && pa_sink_get_state(data->sink) == PA_SINK_SUSPENDED) { pa_log_warn("Failed to create sink input: sink is suspended."); return -PA_ERR_BADSTATE; @@ -239,7 +292,7 @@ int pa_sink_input_new( return -PA_ERR_TOOLARGE; } - if ((flags & PA_SINK_INPUT_VARIABLE_RATE) || + if ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) || !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) || !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) { @@ -248,9 +301,9 @@ int pa_sink_input_new( &data->sample_spec, &data->channel_map, &data->sink->sample_spec, &data->sink->channel_map, data->resample_method, - ((flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | - ((flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | - (core->disable_remixing || (flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { pa_log_warn("Unsupported resampling operation."); return -PA_ERR_NOTSUPPORTED; @@ -263,7 +316,7 @@ int pa_sink_input_new( i->core = core; i->state = PA_SINK_INPUT_INIT; - i->flags = flags; + i->flags = data->flags; i->proplist = pa_proplist_copy(data->proplist); i->driver = pa_xstrdup(pa_path_get_filename(data->driver)); i->module = data->module; @@ -276,18 +329,21 @@ int pa_sink_input_new( i->channel_map = data->channel_map; if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !data->volume_is_absolute) { + pa_cvolume remapped; + /* When the 'absolute' bool is not set then we'll treat the volume * as relative to the sink volume even in flat volume mode */ - - pa_cvolume t = *pa_sink_get_volume(data->sink, FALSE); - pa_cvolume_remap(&t, &data->sink->channel_map, &data->channel_map); - - pa_sw_cvolume_multiply(&i->virtual_volume, &data->volume, &t); + remapped = data->sink->reference_volume; + pa_cvolume_remap(&remapped, &data->sink->channel_map, &data->channel_map); + pa_sw_cvolume_multiply(&i->volume, &data->volume, &remapped); } else - i->virtual_volume = data->volume; + i->volume = data->volume; i->volume_factor = data->volume_factor; - pa_cvolume_init(&i->soft_volume); + i->volume_factor_sink = data->volume_factor_sink; + i->real_ratio = i->reference_ratio = data->volume; + pa_cvolume_reset(&i->soft_volume, i->sample_spec.channels); + pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels); i->save_volume = data->save_volume; i->save_sink = data->save_sink; i->save_muted = data->save_muted; @@ -309,6 +365,16 @@ int pa_sink_input_new( reset_callbacks(i); i->userdata = NULL; + /* Set Ramping info */ + i->thread_info.ramp_info.is_ramping = FALSE; + i->thread_info.ramp_info.envelope_dead = TRUE; + i->thread_info.ramp_info.envelope = NULL; + i->thread_info.ramp_info.item = NULL; + i->thread_info.ramp_info.envelope_dying = 0; + + pa_atomic_store(&i->before_ramping_v, 0); + pa_atomic_store(&i->before_ramping_m, 0); + i->thread_info.state = i->state; i->thread_info.attached = FALSE; pa_atomic_store(&i->thread_info.drained, 1); @@ -334,18 +400,21 @@ int pa_sink_input_new( 0, &i->sink->silence); - pa_assert_se(pa_idxset_put(core->sink_inputs, pa_sink_input_ref(i), &i->index) == 0); - pa_assert_se(pa_idxset_put(i->sink->inputs, i, NULL) == 0); + pa_assert_se(pa_idxset_put(core->sink_inputs, i, &i->index) == 0); + pa_assert_se(pa_idxset_put(i->sink->inputs, pa_sink_input_ref(i), NULL) == 0); if (i->client) pa_assert_se(pa_idxset_put(i->client->sink_inputs, i, NULL) >= 0); - pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s", + pt = pa_proplist_to_string_sep(i->proplist, "\n "); + pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s\n %s", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)), i->sink->name, pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map)); + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + pt); + pa_xfree(pt); /* Don't forget to call pa_sink_input_put! */ @@ -356,6 +425,7 @@ int pa_sink_input_new( /* Called from main context */ static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) { pa_assert(i); + pa_assert_ctl_context(); if (!i->sink) return; @@ -370,6 +440,7 @@ static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) { static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) { pa_sink_input *ssync; pa_assert(i); + pa_assert_ctl_context(); if (state == PA_SINK_INPUT_DRAINED) state = PA_SINK_INPUT_RUNNING; @@ -399,6 +470,9 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) for (ssync = i->sync_next; ssync; ssync = ssync->sync_next) pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync); + + if (PA_SINK_INPUT_IS_LINKED(state)) + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); } pa_sink_update_status(i->sink); @@ -408,7 +482,9 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) void pa_sink_input_unlink(pa_sink_input *i) { pa_bool_t linked; pa_source_output *o, *p = NULL; + pa_assert(i); + pa_assert_ctl_context(); /* See pa_sink_unlink() for a couple of comments how this function * works */ @@ -447,11 +523,8 @@ void pa_sink_input_unlink(pa_sink_input *i) { if (linked && i->sink) { /* We might need to update the sink's volume if we are in flat volume mode. */ - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); - } + if (i->sink->flags & PA_SINK_FLAT_VOLUME) + pa_sink_set_volume(i->sink, NULL, FALSE, FALSE); if (i->sink->asyncmsgq) pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0); @@ -479,6 +552,7 @@ static void sink_input_free(pa_object *o) { pa_sink_input* i = PA_SINK_INPUT(o); pa_assert(i); + pa_assert_ctl_context(); pa_assert(pa_sink_input_refcnt(i) == 0); if (PA_SINK_INPUT_IS_LINKED(i->state)) @@ -486,7 +560,16 @@ static void sink_input_free(pa_object *o) { pa_log_info("Freeing input %u \"%s\"", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME))); - pa_assert(!i->thread_info.attached); + /* Side note: this function must be able to destruct properly any + * kind of sink input in any state, even those which are + * "half-moved" or are connected to sinks that have no asyncmsgq + * and are hence half-destructed themselves! */ + + if (i->thread_info.ramp_info.envelope) { + pa_log_debug ("Freeing envelope\n"); + pa_envelope_free(i->thread_info.ramp_info.envelope); + i->thread_info.ramp_info.envelope = NULL; + } if (i->thread_info.render_memblockq) pa_memblockq_free(i->thread_info.render_memblockq); @@ -510,7 +593,9 @@ static void sink_input_free(pa_object *o) { /* Called from main context */ void pa_sink_input_put(pa_sink_input *i) { pa_sink_input_state_t state; + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(i->state == PA_SINK_INPUT_INIT); @@ -525,12 +610,10 @@ void pa_sink_input_put(pa_sink_input *i) { i->state = state; /* We might need to update the sink's volume if we are in flat volume mode. */ - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); - } else - pa_sw_cvolume_multiply(&i->soft_volume, &i->virtual_volume, &i->volume_factor); + if (i->sink->flags & PA_SINK_FLAT_VOLUME) + pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume); + else + set_real_ratio(i, &i->volume); i->thread_info.soft_volume = i->soft_volume; i->thread_info.muted = i->muted; @@ -546,6 +629,7 @@ void pa_sink_input_put(pa_sink_input *i) { /* Called from main context */ void pa_sink_input_kill(pa_sink_input*i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); i->kill(i); @@ -556,6 +640,7 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) { pa_usec_t r[2] = { 0, 0 }; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0); @@ -571,12 +656,14 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) { /* Called from thread context */ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa_memchunk *chunk, pa_cvolume *volume) { - pa_bool_t do_volume_adj_here; + pa_bool_t do_volume_adj_here, need_volume_factor_sink; pa_bool_t volume_is_norm; + pa_bool_t ramping; size_t block_size_max_sink, block_size_max_sink_input; size_t ilength; pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(slength, &i->sink->sample_spec)); pa_assert(chunk); @@ -616,8 +703,9 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p * to adjust the volume *before* we resample. Otherwise we can do * it after and leave it for the sink code */ - do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map); + do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map) || i->thread_info.ramp_info.is_ramping; volume_is_norm = pa_cvolume_is_norm(&i->thread_info.soft_volume) && !i->thread_info.muted; + need_volume_factor_sink = !pa_cvolume_is_norm(&i->volume_factor_sink); while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) { pa_memchunk tchunk; @@ -649,6 +737,7 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p while (tchunk.length > 0) { pa_memchunk wchunk; + pa_bool_t nvfs = need_volume_factor_sink; wchunk = tchunk; pa_memblock_ref(wchunk.memblock); @@ -657,24 +746,48 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p wchunk.length = block_size_max_sink_input; /* It might be necessary to adjust the volume here */ - if (do_volume_adj_here && !volume_is_norm) { + if (do_volume_adj_here && !volume_is_norm && !i->thread_info.ramp_info.is_ramping) { pa_memchunk_make_writable(&wchunk, 0); - if (i->thread_info.muted) + if (i->thread_info.muted) { pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec); - else + nvfs = FALSE; + + } else if (!i->thread_info.resampler && nvfs) { + pa_cvolume v; + + /* If we don't need a resampler we can merge the + * post and the pre volume adjustment into one */ + + pa_sw_cvolume_multiply(&v, &i->thread_info.soft_volume, &i->volume_factor_sink); + pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &v); + nvfs = FALSE; + + } else pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.soft_volume); } - if (!i->thread_info.resampler) + if (!i->thread_info.resampler) { + + if (nvfs) { + pa_memchunk_make_writable(&wchunk, 0); + pa_volume_memchunk(&wchunk, &i->sink->sample_spec, &i->volume_factor_sink); + } + pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk); - else { + } else { pa_memchunk rchunk; pa_resampler_run(i->thread_info.resampler, &wchunk, &rchunk); /* pa_log_debug("pushing %lu", (unsigned long) rchunk.length); */ if (rchunk.memblock) { + + if (nvfs) { + pa_memchunk_make_writable(&rchunk, 0); + pa_volume_memchunk(&rchunk, &i->sink->sample_spec, &i->volume_factor_sink); + } + pa_memblockq_push_align(i->thread_info.render_memblockq, &rchunk); pa_memblock_unref(rchunk.memblock); } @@ -699,6 +812,23 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p if (chunk->length > block_size_max_sink) chunk->length = block_size_max_sink; + ramping = i->thread_info.ramp_info.is_ramping; + if (ramping) + sink_input_volume_ramping(i, chunk); + + if (!i->thread_info.ramp_info.envelope_dead) { + i->thread_info.ramp_info.envelope_dying += chunk->length; + pa_log_debug("Envelope dying is %d, chunk length is %zu, dead thresholder is %lu\n", i->thread_info.ramp_info.envelope_dying, + chunk->length, + i->sink->thread_info.max_rewind + pa_envelope_length(i->thread_info.ramp_info.envelope)); + + if (i->thread_info.ramp_info.envelope_dying >= (int32_t) (i->sink->thread_info.max_rewind + pa_envelope_length(i->thread_info.ramp_info.envelope))) { + pa_log_debug("RELEASE Envelop"); + i->thread_info.ramp_info.envelope_dead = TRUE; + sink_input_release_envelope(i); + } + } + /* Let's see if we had to apply the volume adjustment ourselves, * or if this can be done by the sink for us */ @@ -714,8 +844,9 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p /* Called from thread context */ void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec */) { - pa_sink_input_assert_ref(i); + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); pa_assert(nbytes > 0); @@ -729,8 +860,9 @@ void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec * void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sample spec */) { size_t lbq; pa_bool_t called = FALSE; - pa_sink_input_assert_ref(i); + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); @@ -741,6 +873,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam if (nbytes > 0 && !i->thread_info.dont_rewind_render) { pa_log_debug("Have to rewind %lu bytes on render memblockq.", (unsigned long) nbytes); pa_memblockq_rewind(i->thread_info.render_memblockq, nbytes); + sink_input_rewind_ramp_info(i, nbytes); } if (i->thread_info.rewrite_nbytes == (size_t) -1) { @@ -748,7 +881,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam /* We were asked to drop all buffered data, and rerequest new * data from implementor the next time push() is called */ - pa_memblockq_flush_write(i->thread_info.render_memblockq); + pa_memblockq_flush_write(i->thread_info.render_memblockq, TRUE); } else if (i->thread_info.rewrite_nbytes > 0) { size_t max_rewrite, amount; @@ -797,9 +930,29 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam i->thread_info.dont_rewind_render = FALSE; } +/* Called from thread context */ +size_t pa_sink_input_get_max_rewind(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_rewind) : i->sink->thread_info.max_rewind; +} + +/* Called from thread context */ +size_t pa_sink_input_get_max_request(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + /* We're not verifying the status here, to allow this to be called + * in the state change handler between _INIT and _RUNNING */ + + return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_request) : i->sink->thread_info.max_request; +} + /* Called from thread context */ void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) { pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); @@ -812,6 +965,7 @@ void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the /* Called from thread context */ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) { pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); @@ -822,21 +976,24 @@ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the /* Called from thread context */ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) { pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) + usec = i->sink->thread_info.fixed_latency; if (usec != (pa_usec_t) -1) - usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); i->thread_info.requested_sink_latency = usec; - pa_sink_invalidate_requested_latency(i->sink); + pa_sink_invalidate_requested_latency(i->sink, TRUE); return usec; } /* Called from main context */ pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) { - pa_usec_t min_latency, max_latency; - pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); @@ -847,10 +1004,14 @@ pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) * we have to touch the thread info data directly */ if (i->sink) { - pa_sink_get_latency_range(i->sink, &min_latency, &max_latency); + if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) + usec = pa_sink_get_fixed_latency(i->sink); - if (usec != (pa_usec_t) -1) + if (usec != (pa_usec_t) -1) { + pa_usec_t min_latency, max_latency; + pa_sink_get_latency_range(i->sink, &min_latency, &max_latency); usec = PA_CLAMP(usec, min_latency, max_latency); + } } i->thread_info.requested_sink_latency = usec; @@ -861,6 +1022,7 @@ pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) /* Called from main context */ pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { pa_usec_t usec = 0; @@ -875,93 +1037,61 @@ pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) { } /* Called from main context */ -void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save) { +static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - pa_assert(volume); - pa_assert(pa_cvolume_valid(volume)); - pa_assert(pa_cvolume_compatible(volume, &i->sample_spec)); - - if (pa_cvolume_equal(volume, &i->virtual_volume)) - return; - - i->virtual_volume = *volume; - i->save_volume = save; + pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec)); - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; - - /* We are in flat volume mode, so let's update all sink input - * volumes and update the flat volume of the sink */ - - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE); - - } else { - - /* OK, we are in normal volume mode. The volume only affects - * ourselves */ - pa_sw_cvolume_multiply(&i->soft_volume, volume, &i->volume_factor); - - /* Hooks have the ability to play games with i->soft_volume */ - pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); + /* This basically calculates: + * + * i->real_ratio := v + * i->soft_volume := i->real_ratio * i->volume_factor */ - pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); - } + if (v) + i->real_ratio = *v; + else + pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels); - /* The virtual volume changed, let's tell people so */ - pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor); + /* We don't copy the data to the thread_info data. That's left for someone else to do */ } /* Called from main context */ -const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) { - pa_sink_input_assert_ref(i); - pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) { + + /* Do not allow for volume changes for non-audio types */ + if (i->flags & PA_SINK_INPUT_PASSTHROUGH) + return; - return &i->virtual_volume; + /* test ramping -> return pa_sink_input_set_volume_with_ramping(i, volume, save, absolute, 2000 * PA_USEC_PER_MSEC); */ + return pa_sink_input_set_volume_with_ramping(i, volume, save, absolute, 0); } /* Called from main context */ -pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v) { +pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute) { pa_sink_input_assert_ref(i); - pa_assert(v); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - *v = i->virtual_volume; - - /* This always returns a relative volume, even in flat volume mode */ - - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume sv; - - sv = *pa_sink_get_volume(i->sink, FALSE); - - pa_sw_cvolume_divide(v, v, - pa_cvolume_remap(&sv, &i->sink->channel_map, &i->channel_map)); - } + if (absolute || !(i->sink->flags & PA_SINK_FLAT_VOLUME)) + *volume = i->volume; + else + *volume = i->reference_ratio; - return v; + return volume; } /* Called from main context */ void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save) { - pa_assert(i); - pa_sink_input_assert_ref(i); - pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - - if (!i->muted == !mute) - return; - - i->muted = mute; - i->save_muted = save; - - pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0); - pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + /* test ramping -> return pa_sink_input_set_mute_with_ramping(i, mute, save, 2000 * PA_USEC_PER_MSEC); */ + return pa_sink_input_set_mute_with_ramping(i, mute, save, 0); } /* Called from main context */ pa_bool_t pa_sink_input_get_mute(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); return i->muted; @@ -970,11 +1100,12 @@ pa_bool_t pa_sink_input_get_mute(pa_sink_input *i) { /* Called from main thread */ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (p) pa_proplist_update(i->proplist, mode, p); - if (PA_SINK_IS_LINKED(i->state)) { + if (PA_SINK_INPUT_IS_LINKED(i->state)) { pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i); pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); } @@ -983,6 +1114,7 @@ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_p /* Called from main context */ void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING); @@ -991,6 +1123,7 @@ void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) { /* Called from main context */ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_return_val_if_fail(i->thread_info.resampler, -PA_ERR_BADSTATE); @@ -1009,13 +1142,14 @@ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { void pa_sink_input_set_name(pa_sink_input *i, const char *name) { const char *old; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (!name && !pa_proplist_contains(i->proplist, PA_PROP_MEDIA_NAME)) return; old = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME); - if (old && name && !strcmp(old, name)) + if (old && name && pa_streq(old, name)) return; if (name) @@ -1032,6 +1166,7 @@ void pa_sink_input_set_name(pa_sink_input *i, const char *name) { /* Called from main context */ pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); return i->actual_resample_method; } @@ -1039,13 +1174,14 @@ pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) { /* Called from main context */ pa_bool_t pa_sink_input_may_move(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); if (i->flags & PA_SINK_INPUT_DONT_MOVE) return FALSE; if (i->sync_next || i->sync_prev) { - pa_log_warn("Moving synchronised streams not supported."); + pa_log_warn("Moving synchronized streams not supported."); return FALSE; } @@ -1055,6 +1191,7 @@ pa_bool_t pa_sink_input_may_move(pa_sink_input *i) { /* Called from main context */ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_sink_assert_ref(dest); @@ -1069,6 +1206,9 @@ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { return FALSE; } + if (check_passthrough_connection(i->flags, dest) < 0) + return FALSE; + if (i->may_move_to) if (!i->may_move_to(i, dest)) return FALSE; @@ -1079,10 +1219,10 @@ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { /* Called from main context */ int pa_sink_input_start_move(pa_sink_input *i) { pa_source_output *o, *p = NULL; - pa_sink *origin; int r; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(i->sink); @@ -1092,8 +1232,6 @@ int pa_sink_input_start_move(pa_sink_input *i) { if ((r = pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], i)) < 0) return r; - origin = i->sink; - /* Kill directly connected outputs */ while ((o = pa_idxset_first(i->direct_outputs, NULL))) { pa_assert(o != p); @@ -1107,24 +1245,19 @@ int pa_sink_input_start_move(pa_sink_input *i) { if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) pa_assert_se(i->sink->n_corked-- >= 1); - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; - - /* Make the absolute volume relative */ - i->virtual_volume = i->soft_volume; - i->soft_volume = i->volume_factor; - + if (i->sink->flags & PA_SINK_FLAT_VOLUME) /* We might need to update the sink's volume if we are in flat * volume mode. */ - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); - } + pa_sink_set_volume(i->sink, NULL, FALSE, FALSE); pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0); pa_sink_update_status(i->sink); + pa_cvolume_remap(&i->volume_factor_sink, &i->sink->channel_map, &i->channel_map); i->sink = NULL; + pa_sink_input_unref(i); + return 0; } @@ -1133,6 +1266,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { pa_resampler *new_resampler; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(!i->sink); pa_sink_assert_ref(dest); @@ -1172,7 +1306,9 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { i->sink = dest; i->save_sink = save; - pa_idxset_put(dest->inputs, i, NULL); + pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL); + + pa_cvolume_remap(&i->volume_factor_sink, &i->channel_map, &i->sink->channel_map); if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) i->sink->n_corked++; @@ -1199,16 +1335,15 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { pa_sink_update_status(dest); if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume new_volume; + pa_cvolume remapped; - /* Make relative volume absolute again */ - pa_cvolume t = dest->virtual_volume; - pa_cvolume_remap(&t, &dest->channel_map, &i->channel_map); - pa_sw_cvolume_multiply(&i->virtual_volume, &i->virtual_volume, &t); + /* Make relative volumes absolute */ + remapped = dest->reference_volume; + pa_cvolume_remap(&remapped, &dest->channel_map, &i->channel_map); + pa_sw_cvolume_multiply(&i->volume, &i->reference_ratio, &remapped); /* We might need to update the sink's volume if we are in flat volume mode. */ - pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); + pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume); } pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0); @@ -1217,16 +1352,39 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { /* Notify everyone */ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i); + + if (i->volume_changed) + i->volume_changed(i); + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); return 0; } +/* Called from main context */ +void pa_sink_input_fail_move(pa_sink_input *i) { + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(!i->sink); + + /* Check if someone wants this sink input? */ + if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP) + return; + + if (i->moving) + i->moving(i, NULL); + + pa_sink_input_kill(i); +} + /* Called from main context */ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { int r; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(i->sink); pa_sink_assert_ref(dest); @@ -1237,11 +1395,20 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { if (!pa_sink_input_may_move_to(i, dest)) return -PA_ERR_NOTSUPPORTED; - if ((r = pa_sink_input_start_move(i)) < 0) + pa_sink_input_ref(i); + + if ((r = pa_sink_input_start_move(i)) < 0) { + pa_sink_input_unref(i); return r; + } - if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) + if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) { + pa_sink_input_fail_move(i); + pa_sink_input_unref(i); return r; + } + + pa_sink_input_unref(i); return 0; } @@ -1249,7 +1416,9 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { /* Called from IO thread context */ void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) { pa_bool_t corking, uncorking; + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); if (state == i->thread_info.state) return; @@ -1295,27 +1464,32 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t switch (code) { case PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME: + if (pa_atomic_load(&i->before_ramping_v)) + i->thread_info.future_soft_volume = i->soft_volume; + if (!pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume)) { - i->thread_info.soft_volume = i->soft_volume; + if (!pa_atomic_load(&i->before_ramping_v)) + i->thread_info.soft_volume = i->soft_volume; pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE); } return 0; case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE: + if (pa_atomic_load(&i->before_ramping_m)) + i->thread_info.future_muted = i->muted; + if (i->thread_info.muted != i->muted) { - i->thread_info.muted = i->muted; + if (!pa_atomic_load(&i->before_ramping_m)) + i->thread_info.muted = i->muted; pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE); } return 0; case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { pa_usec_t *r = userdata; - pa_usec_t sink_usec = 0; r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); - - if (i->sink->parent.process_msg(PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_usec, 0, NULL) >= 0) - r[1] += sink_usec; + r[1] += pa_sink_get_latency_within_thread(i->sink); return 0; } @@ -1354,6 +1528,26 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t *r = i->thread_info.requested_sink_latency; return 0; } + + case PA_SINK_INPUT_MESSAGE_SET_ENVELOPE: { + if (!i->thread_info.ramp_info.envelope) + i->thread_info.ramp_info.envelope = pa_envelope_new(&i->sink->sample_spec); + + if (i->thread_info.ramp_info.envelope && i->thread_info.ramp_info.item) { + pa_envelope_remove(i->thread_info.ramp_info.envelope, i->thread_info.ramp_info.item); + i->thread_info.ramp_info.item = NULL; + } + + i->thread_info.ramp_info.item = pa_envelope_add(i->thread_info.ramp_info.envelope, &i->using_def); + i->thread_info.ramp_info.is_ramping = TRUE; + i->thread_info.ramp_info.envelope_dead = FALSE; + i->thread_info.ramp_info.envelope_dying = 0; + + if (i->thread_info.ramp_info.envelope) + pa_envelope_restart(i->thread_info.ramp_info.envelope); + + return 0; + } } return -PA_ERR_NOTIMPLEMENTED; @@ -1362,6 +1556,7 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t /* Called from main thread */ pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); if (i->state == PA_SINK_INPUT_RUNNING || i->state == PA_SINK_INPUT_DRAINED) return pa_atomic_load(&i->thread_info.drained) ? PA_SINK_INPUT_DRAINED : PA_SINK_INPUT_RUNNING; @@ -1372,6 +1567,7 @@ pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) { /* Called from IO context */ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) { pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); if (PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) return pa_memblockq_is_empty(i->thread_info.render_memblockq); @@ -1380,7 +1576,13 @@ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) { } /* Called from IO context */ -void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sample spec */, pa_bool_t rewrite, pa_bool_t flush, pa_bool_t dont_rewind_render) { +void pa_sink_input_request_rewind( + pa_sink_input *i, + size_t nbytes /* in our sample spec */, + pa_bool_t rewrite, + pa_bool_t flush, + pa_bool_t dont_rewind_render) { + size_t lbq; /* If 'rewrite' is TRUE the sink is rewound as far as requested @@ -1395,18 +1597,20 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sam * dont_rewind_render is TRUE then the render memblockq is not * rewound. */ - pa_sink_input_assert_ref(i); + /* nbytes = 0 means maximum rewind request */ - nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes); - -/* pa_log_debug("request rewrite %lu", (unsigned long) nbytes); */ + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert(rewrite || flush); + pa_assert(!dont_rewind_render || !rewrite); /* We don't take rewind requests while we are corked */ if (i->thread_info.state == PA_SINK_INPUT_CORKED) return; - pa_assert(rewrite || flush); - pa_assert(!dont_rewind_render || !rewrite); + nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes); + + /* pa_log_debug("request rewrite %zu", nbytes); */ /* Calculate how much we can rewind locally without having to * touch the sink */ @@ -1426,6 +1630,7 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sam nbytes = pa_resampler_request(i->thread_info.resampler, nbytes); } + /* Remember how much we actually want to rewrite */ if (i->thread_info.rewrite_nbytes != (size_t) -1) { if (rewrite) { /* Make sure to not overwrite over underruns */ @@ -1462,8 +1667,11 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sam /* Called from main context */ pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) { pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(ret); + /* FIXME: Shouldn't access resampler object from main context! */ + pa_silence_memchunk_get( &i->core->silence_cache, i->core->mempool, @@ -1480,6 +1688,7 @@ void pa_sink_input_send_event(pa_sink_input *i, const char *event, pa_proplist * pa_sink_input_send_event_hook_data hook_data; pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); pa_assert(event); if (!i->send_event) @@ -1501,3 +1710,237 @@ finish: if (pl) pa_proplist_free(pl); } + +/* Called from IO context */ +static void sink_input_volume_ramping(pa_sink_input* i, pa_memchunk* chunk) { + pa_assert(i); + pa_assert(chunk); + pa_assert(chunk->memblock); + pa_assert(i->thread_info.ramp_info.is_ramping); + + /* Volume is adjusted with ramping effect here */ + pa_envelope_apply(i->thread_info.ramp_info.envelope, chunk); + + if (pa_envelope_is_finished(i->thread_info.ramp_info.envelope)) { + i->thread_info.ramp_info.is_ramping = FALSE; + if (pa_atomic_load(&i->before_ramping_v)) { + i->thread_info.soft_volume = i->thread_info.future_soft_volume; + pa_atomic_store(&i->before_ramping_v, 0); + } + else if (pa_atomic_load(&i->before_ramping_m)) { + i->thread_info.muted = i->thread_info.future_muted; + pa_atomic_store(&i->before_ramping_m, 0); + } + } +} + +/* + * Called from main context + * This function should be called inside pa_sink_input_set_volume_with_ramping + * should be called after soft_volume of sink_input and sink are all adjusted + */ +static void sink_input_set_ramping_info(pa_sink_input* i, pa_volume_t pre_virtual_volume, pa_volume_t target_virtual_volume, pa_usec_t t) { + + int32_t target_abs_vol, target_apply_vol, pre_apply_vol; + pa_assert(i); + + pa_log_debug("Sink input's soft volume is %d= %f ", pa_cvolume_avg(&i->soft_volume), pa_sw_volume_to_linear(pa_cvolume_avg(&i->soft_volume))); + + /* Calculation formula are target_abs_vol := i->soft_volume + * target_apply_vol := lrint(pa_sw_volume_to_linear(target_abs_vol) * 0x10000) + * pre_apply_vol := ( previous_virtual_volume / target_virtual_volume ) * target_apply_vol + * + * Will do volume adjustment inside pa_sink_input_peek + */ + target_abs_vol = pa_cvolume_avg(&i->soft_volume); + target_apply_vol = (int32_t) lrint(pa_sw_volume_to_linear(target_abs_vol) * 0x10000); + pre_apply_vol = (int32_t) ((pa_sw_volume_to_linear(pre_virtual_volume) / pa_sw_volume_to_linear(target_virtual_volume)) * target_apply_vol); + + i->using_def.n_points = 2; + i->using_def.points_x[0] = 0; + i->using_def.points_x[1] = t; + i->using_def.points_y.i[0] = pre_apply_vol; + i->using_def.points_y.i[1] = target_apply_vol; + i->using_def.points_y.f[0] = ((float) i->using_def.points_y.i[0]) /0x10000; + i->using_def.points_y.f[1] = ((float) i->using_def.points_y.i[1]) /0x10000; + + pa_log_debug("Volume Ramping: Point 1 is %d=%f, Point 2 is %d=%f\n", i->using_def.points_y.i[0], i->using_def.points_y.f[0], + i->using_def.points_y.i[1], i->using_def.points_y.f[1]); + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_ENVELOPE, NULL, 0, NULL) == 0); +} + +/* Called from main context */ +static void sink_input_set_ramping_info_for_mute(pa_sink_input* i, pa_bool_t mute, pa_usec_t t) { + + int32_t cur_vol; + pa_assert(i); + + i->using_def.n_points = 2; + i->using_def.points_x[0] = 0; + i->using_def.points_x[1] = t; + cur_vol = (int32_t) lrint( pa_sw_volume_to_linear(pa_cvolume_avg(&i->soft_volume)) * 0x10000); + + if (mute) { + i->using_def.points_y.i[0] = cur_vol; + i->using_def.points_y.i[1] = 0; + } else { + i->using_def.points_y.i[0] = 0; + i->using_def.points_y.i[1] = cur_vol; + } + + i->using_def.points_y.f[0] = ((float) i->using_def.points_y.i[0]) /0x10000; + i->using_def.points_y.f[1] = ((float) i->using_def.points_y.i[1]) /0x10000; + + pa_log_debug("Mute Ramping: Point 1 is %d=%f, Point 2 is %d=%f\n", i->using_def.points_y.i[0], i->using_def.points_y.f[0], + i->using_def.points_y.i[1], i->using_def.points_y.f[1]); + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_ENVELOPE, NULL, 0, NULL) == 0); +} + +/* Called from IO context */ +static void sink_input_release_envelope(pa_sink_input *i) { + pa_assert(i); + pa_assert(!i->thread_info.ramp_info.is_ramping); + pa_assert(i->thread_info.ramp_info.envelope_dead); + + pa_envelope_free(i->thread_info.ramp_info.envelope); + i->thread_info.ramp_info.envelope = NULL; + i->thread_info.ramp_info.item = NULL; +} + +/* Called from IO context */ +static void sink_input_rewind_ramp_info(pa_sink_input *i, size_t nbytes) { + pa_assert(i); + + if (!i->thread_info.ramp_info.envelope_dead) { + int32_t envelope_length; + + pa_assert(i->thread_info.ramp_info.envelope); + + envelope_length = pa_envelope_length(i->thread_info.ramp_info.envelope); + + if (i->thread_info.ramp_info.envelope_dying > envelope_length) { + if ((int32_t) (i->thread_info.ramp_info.envelope_dying - nbytes) < envelope_length) { + pa_log_debug("Envelope Become Alive"); + pa_envelope_rewind(i->thread_info.ramp_info.envelope, envelope_length - (i->thread_info.ramp_info.envelope_dying - nbytes)); + i->thread_info.ramp_info.is_ramping = TRUE; + } + } else if (i->thread_info.ramp_info.envelope_dying < envelope_length) { + if ((i->thread_info.ramp_info.envelope_dying - (ssize_t) nbytes) <= 0) { + pa_log_debug("Envelope Restart"); + pa_envelope_restart(i->thread_info.ramp_info.envelope); + } + else { + pa_log_debug("Envelope Simple Rewind"); + pa_envelope_rewind(i->thread_info.ramp_info.envelope, nbytes); + } + } + + i->thread_info.ramp_info.envelope_dying -= nbytes; + if (i->thread_info.ramp_info.envelope_dying <= 0) + i->thread_info.ramp_info.envelope_dying = 0; + } +} + +void pa_sink_input_set_volume_with_ramping(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute, pa_usec_t t){ + pa_cvolume v; + pa_volume_t previous_virtual_volume, target_virtual_volume; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(volume); + pa_assert(pa_cvolume_valid(volume)); + pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &i->sample_spec)); + + if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !absolute) { + v = i->sink->reference_volume; + pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map); + + if (pa_cvolume_compatible(volume, &i->sample_spec)) + volume = pa_sw_cvolume_multiply(&v, &v, volume); + else + volume = pa_sw_cvolume_multiply_scalar(&v, &v, pa_cvolume_max(volume)); + } else { + + if (!pa_cvolume_compatible(volume, &i->sample_spec)) { + v = i->volume; + volume = pa_cvolume_scale(&v, pa_cvolume_max(volume)); + } + } + + if (pa_cvolume_equal(volume, &i->volume)) { + i->save_volume = i->save_volume || save; + return; + } + + previous_virtual_volume = pa_cvolume_avg(&i->volume); + target_virtual_volume = pa_cvolume_avg(volume); + + if (t > 0 && target_virtual_volume > 0) + pa_log_debug("SetVolumeWithRamping: Virtual Volume From %u=%f to %u=%f\n", previous_virtual_volume, pa_sw_volume_to_linear(previous_virtual_volume), + target_virtual_volume, pa_sw_volume_to_linear(target_virtual_volume)); + + i->volume = *volume; + i->save_volume = save; + + /* Set this flag before the following code modify i->thread_info.soft_volume */ + if (t > 0 && target_virtual_volume > 0) + pa_atomic_store(&i->before_ramping_v, 1); + + if (i->sink->flags & PA_SINK_FLAT_VOLUME) { + /* We are in flat volume mode, so let's update all sink input + * volumes and update the flat volume of the sink */ + + pa_sink_set_volume(i->sink, NULL, TRUE, save); + + } else { + /* OK, we are in normal volume mode. The volume only affects + * ourselves */ + set_real_ratio(i, volume); + + /* Copy the new soft_volume to the thread_info struct */ + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); + } + + if (t > 0 && target_virtual_volume > 0) + sink_input_set_ramping_info(i, previous_virtual_volume, target_virtual_volume, t); + + /* The volume changed, let's tell people so */ + if (i->volume_changed) + i->volume_changed(i); + + /* The virtual volume changed, let's tell people so */ + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); +} + +void pa_sink_input_set_mute_with_ramping(pa_sink_input *i, pa_bool_t mute, pa_bool_t save, pa_usec_t t){ + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + if (!i->muted == !mute) { + i->save_muted = i->save_muted || mute; + return; + } + + i->muted = mute; + i->save_muted = save; + + /* Set this flag before the following code modify i->thread_info.muted, otherwise distortion will be heard */ + if (t > 0) + pa_atomic_store(&i->before_ramping_m, 1); + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0); + + if (t > 0) + sink_input_set_ramping_info_for_mute(i, mute, t); + + /* The mute status changed, let's tell people so */ + if (i->mute_changed) + i->mute_changed(i); + + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); +}