#include <pulsecore/play-memblockq.h>
#include <pulsecore/namereg.h>
#include <pulsecore/core-util.h>
+#include <pulse/timeval.h>
#include "sink-input.h"
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);
}
}
+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);
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_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;
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) {
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;
i->volume = data->volume;
i->volume_factor = data->volume_factor;
+ 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);
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);
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! */
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);
* "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);
/* 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;
* 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;
while (tchunk.length > 0) {
pa_memchunk wchunk;
+ pa_bool_t nvfs = need_volume_factor_sink;
wchunk = tchunk;
pa_memblock_ref(wchunk.memblock);
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);
}
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 */
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) {
/* 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;
/* Called from main context */
void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) {
- 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(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;
+ /* Do not allow for volume changes for non-audio types */
+ if (i->flags & PA_SINK_INPUT_PASSTHROUGH)
return;
- }
-
- i->volume = *volume;
- i->save_volume = save;
-
- 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);
- }
-
- /* The volume changed, let's tell people so */
- if (i->volume_changed)
- i->volume_changed(i);
-
- pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ /* 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 */
/* Called from main context */
void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save) {
- pa_sink_input_assert_ref(i);
- pa_assert_ctl_context();
- 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);
-
- /* 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);
+ /* 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 */
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);
}
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;
/* 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);
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);
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);
i->save_sink = save;
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++;
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;
*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;
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);
+}