From 6c5b093fa0b32d51be52a2a58cb3be71f1cbda97 Mon Sep 17 00:00:00 2001 From: Jaska Uimonen Date: Fri, 1 Jun 2012 14:37:03 +0300 Subject: [PATCH] volume ramp: additions to the low level infra --- src/pulse/def.h | 22 ++++- src/pulsecore/sample-util.c | 201 ++++++++++++++++++++++++++++++++++++++++++++ src/pulsecore/sample-util.h | 17 ++++ 3 files changed, 239 insertions(+), 1 deletion(-) diff --git a/src/pulse/def.h b/src/pulse/def.h index b939319..cc7405a 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -314,11 +314,15 @@ typedef enum pa_stream_flags { * consider absolute when the sink is in flat volume mode, * relative otherwise. \since 0.9.20 */ - PA_STREAM_PASSTHROUGH = 0x80000U + PA_STREAM_PASSTHROUGH = 0x80000U, /**< Used to tag content that will be rendered by passthrough sinks. * The data will be left as is and not reformatted, resampled. * \since 1.0 */ + PA_STREAM_START_RAMP_MUTED = 0x100000U + /**< Used to tag content that the stream will be started ramp volume + * muted so that you can nicely fade it in */ + } pa_stream_flags_t; /** \cond fulldocs */ @@ -347,6 +351,7 @@ typedef enum pa_stream_flags { #define PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND #define PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME #define PA_STREAM_PASSTHROUGH PA_STREAM_PASSTHROUGH +#define PA_STREAM_START_RAMP_MUTED PA_STREAM_START_RAMP_MUTED /** \endcond */ @@ -1006,6 +1011,21 @@ typedef enum pa_port_available { /** \endcond */ + +/** Volume ramp type +*/ +typedef enum pa_volume_ramp_type { + PA_VOLUME_RAMP_TYPE_LINEAR = 0, /**< linear */ + PA_VOLUME_RAMP_TYPE_LOGARITHMIC = 1, /**< logarithmic */ + PA_VOLUME_RAMP_TYPE_CUBIC = 2, +} pa_volume_ramp_type_t; + +/** \cond fulldocs */ +#define PA_VOLUMER_RAMP_TYPE_LINEAR PA_VOLUMER_RAMP_TYPE_LINEAR +#define PA_VOLUMER_RAMP_TYPE_LOGARITHMIC PA_VOLUMER_RAMP_TYPE_LOGARITHMIC +#define PA_VOLUMER_RAMP_TYPE_CUBIC PA_VOLUMER_RAMP_TYPE_CUBIC +/** \endcond */ + PA_C_DECL_END #endif diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c index 38201b2..fccabc1 100644 --- a/src/pulsecore/sample-util.c +++ b/src/pulsecore/sample-util.c @@ -1050,3 +1050,204 @@ size_t pa_convert_size(size_t size, const pa_sample_spec *from, const pa_sample_ usec = pa_bytes_to_usec_round_up(size, from); return pa_usec_to_bytes_round_up(usec, to); } + +void calc_linear_integer_volume_no_mapping(int32_t [], float [], unsigned); +void calc_linear_float_volume_no_mapping(float [], float [], unsigned); + +void calc_linear_integer_volume_no_mapping(int32_t linear[], float volume[], unsigned nchannels) { + unsigned channel, padding; + + pa_assert(linear); + pa_assert(volume); + + for (channel = 0; channel < nchannels; channel++) + linear[channel] = (int32_t) lrint(volume[channel] * 0x10000U); + + for (padding = 0; padding < VOLUME_PADDING; padding++, channel++) + linear[channel] = linear[padding]; +} + +void calc_linear_float_volume_no_mapping(float linear[], float volume[], unsigned nchannels) { + unsigned channel, padding; + + pa_assert(linear); + pa_assert(volume); + + for (channel = 0; channel < nchannels; channel++) + linear[channel] = volume[channel]; + + for (padding = 0; padding < VOLUME_PADDING; padding++, channel++) + linear[channel] = linear[padding]; +} + +typedef void (*pa_calc_volume_no_mapping_func_t) (void *volumes, float *volume, int channels); + +static const pa_calc_volume_no_mapping_func_t calc_volume_table_no_mapping[] = { + [PA_SAMPLE_U8] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_ALAW] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_ULAW] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_S16LE] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_S16BE] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_FLOAT32LE] = (pa_calc_volume_no_mapping_func_t) calc_linear_float_volume_no_mapping, + [PA_SAMPLE_FLOAT32BE] = (pa_calc_volume_no_mapping_func_t) calc_linear_float_volume_no_mapping, + [PA_SAMPLE_S32LE] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_S32BE] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_S24LE] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_S24BE] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_S24_32LE] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping, + [PA_SAMPLE_S24_32BE] = (pa_calc_volume_no_mapping_func_t) calc_linear_integer_volume_no_mapping +}; + +static const unsigned format_sample_size_table[] = { + [PA_SAMPLE_U8] = 1, + [PA_SAMPLE_ALAW] = 1, + [PA_SAMPLE_ULAW] = 1, + [PA_SAMPLE_S16LE] = 2, + [PA_SAMPLE_S16BE] = 2, + [PA_SAMPLE_FLOAT32LE] = 4, + [PA_SAMPLE_FLOAT32BE] = 4, + [PA_SAMPLE_S32LE] = 4, + [PA_SAMPLE_S32BE] = 4, + [PA_SAMPLE_S24LE] = 3, + [PA_SAMPLE_S24BE] = 3, + [PA_SAMPLE_S24_32LE] = 4, + [PA_SAMPLE_S24_32BE] = 4 +}; + +float calc_volume_ramp_linear(pa_volume_ramp *); +float calc_volume_ramp_logarithmic(pa_volume_ramp *); +float calc_volume_ramp_cubic(pa_volume_ramp *); +void pa_ramp_volume_multiply(float *, const pa_cvolume *, float); + +typedef float (*pa_calc_volume_ramp_func_t) (pa_volume_ramp *); + +static const pa_calc_volume_ramp_func_t calc_volume_ramp_table[] = { + [PA_VOLUME_RAMP_TYPE_LINEAR] = (pa_calc_volume_ramp_func_t) calc_volume_ramp_linear, + [PA_VOLUME_RAMP_TYPE_LOGARITHMIC] = (pa_calc_volume_ramp_func_t) calc_volume_ramp_logarithmic, + [PA_VOLUME_RAMP_TYPE_CUBIC] = (pa_calc_volume_ramp_func_t) calc_volume_ramp_cubic +}; + +float calc_volume_ramp_linear(pa_volume_ramp *ramp) { + + pa_assert(ramp); + pa_assert(ramp->length > 0); + + /* basic linear interpolation */ + return ramp->start + (ramp->length - ramp->left) * (ramp->end - ramp->start) / ramp->length; +} + +float calc_volume_ramp_logarithmic(pa_volume_ramp *ramp) { + + float x_val, s, e; + long temp; + + pa_assert(ramp); + pa_assert(ramp->length > 0); + + if (ramp->end > ramp->start) { + temp = ramp->left; + s = ramp->end; + e = ramp->start; + } + else { + temp = ramp->length - ramp->left; + s = ramp->start; + e = ramp->end; + } + + x_val = temp == 0 ? 0.0 : pow((float)temp, 10); + + /* base 10 logarithmic interpolation */ + return s + x_val * (e - s) / pow((float)ramp->length, 10); +} + +float calc_volume_ramp_cubic(pa_volume_ramp *ramp) { + + float x_val, s, e; + long temp; + + pa_assert(ramp); + pa_assert(ramp->length > 0); + + if (ramp->end > ramp->start) { + temp = ramp->left; + s = ramp->end; + e = ramp->start; + } + else { + temp = ramp->length - ramp->left; + s = ramp->start; + e = ramp->end; + } + + x_val = temp == 0 ? 0.0 : cbrt((float)temp); + + /* cubic interpolation */ + return s + x_val * (e - s) / cbrt((float)ramp->length); +} + +void pa_ramp_volume_multiply(float *dest, const pa_cvolume *volume, float ramp_vol) { + unsigned i; + float conv; + + pa_assert(dest); + pa_assert(volume); + + /* multiplying ramp with volume with pa internal mapping */ + for (i = 0; i < volume->channels; i++) { + conv = volume->values[i] / 0x10000U; + conv = conv * conv * conv; + dest[i] = ramp_vol * conv; + } +} + +void pa_volume_ramp_memchunk( + pa_memchunk*c, + const pa_sample_spec *spec, + pa_volume_ramp *ramp, + const pa_cvolume *volume) { + + void *ptr; + volume_val linear[PA_CHANNELS_MAX + VOLUME_PADDING]; + float vol_adjusted[PA_CHANNELS_MAX + VOLUME_PADDING]; + pa_do_volume_func_t do_volume; + long length_in_frames; + + pa_assert(c); + pa_assert(spec); + pa_assert(pa_frame_aligned(c->length, spec)); + pa_assert(ramp); + + length_in_frames = c->length / format_sample_size_table[spec->format] / spec->channels; + + if (pa_memblock_is_silence(c->memblock)) { + ramp->length -= length_in_frames; + return; + } + + if (spec->format < 0 || spec->format >= PA_SAMPLE_MAX) { + pa_log_warn("Unable to change volume of format"); + return; + } + + do_volume = pa_get_volume_func(spec->format); + pa_assert(do_volume); + + ptr = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index; + + for (int i = 0; i < length_in_frames; i++) { + ramp->curr = calc_volume_ramp_table[ramp->type] (ramp); + pa_ramp_volume_multiply(vol_adjusted, volume, ramp->curr); + calc_volume_table_no_mapping[spec->format] ((void *)linear, vol_adjusted, spec->channels); + + /* we only process one sample per iteration */ + do_volume (ptr, (void *)linear, spec->channels, format_sample_size_table[spec->format] * spec->channels); + + ptr = (uint8_t*)ptr + format_sample_size_table[spec->format] * spec->channels; + + if (ramp->left > 0) + ramp->left--; + } + + pa_memblock_release(c->memblock); +} diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h index cf79d43..215ed3f 100644 --- a/src/pulsecore/sample-util.h +++ b/src/pulsecore/sample-util.h @@ -44,6 +44,17 @@ pa_memblock* pa_silence_memblock(pa_memblock *b, const pa_sample_spec *spec); pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length); +/** A structure encapsulating a volume ramp */ +typedef struct pa_volume_ramp { + pa_volume_ramp_type_t type; + long length; + long left; + float start; + float end; + float curr; + pa_cvolume end_mapped; +} pa_volume_ramp; + typedef struct pa_mix_info { pa_memchunk chunk; pa_cvolume volume; @@ -72,6 +83,12 @@ void pa_volume_memchunk( const pa_sample_spec *spec, const pa_cvolume *volume); +void pa_volume_ramp_memchunk( + pa_memchunk*c, + const pa_sample_spec *spec, + pa_volume_ramp *ramp, + const pa_cvolume *volume); + size_t pa_frame_align(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; pa_bool_t pa_frame_aligned(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; -- 2.7.4