From 8b1500d7ff67c55df6f6842035537802ecd01cf3 Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Wed, 19 Jul 2023 03:11:52 +0300 Subject: [PATCH] volume: support arbitrarily-large positive gains The current limit is `x10`, which allows just `+20 dB` of gain. While it may seem sufficient, this came up as a problem in a real-world, non-specially-engineered situation, in strawberry's EBU R 128 loudness normalization. (https://github.com/strawberrymusicplayer/strawberry/pull/1216) There is an audio track (that was not intentionally engineered that way), that has integrated loudness of `-38 LUFS`, and if we want to normalize it's loudness to e.g. `-16 LUFS`, which is a very reasonable thing to do, we need to apply gain of `+22 dB`, which is larger than `+20 dB`, and we fail... I think it should allow at least `+96 dB` of gain, and therefore should be at `10^(96/20) ~= 63096`. But, i don't see why we need to put any specific restriction on that parameter in the first place, other than the fact that the fixed-point multiplication scheme does not support volume larger than 15x-ish. So let's just implement a floating-point fall-back path that does not involve fixed-point multiplication and lift the restriction altogether? Part-of: --- .../docs/plugins/gst_plugins_cache.json | 2 +- .../gst-plugins-base/gst/volume/gstvolume.c | 183 ++- .../gst-plugins-base/gst/volume/gstvolume.h | 10 +- .../tests/check/elements/volume.c | 1101 ++++++++++++++++- 4 files changed, 1270 insertions(+), 26 deletions(-) diff --git a/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json index 61001e894b..8afc849732 100644 --- a/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json @@ -15709,7 +15709,7 @@ "construct-only": false, "controllable": true, "default": "1", - "max": "10", + "max": "1.79769e+308", "min": "0", "mutable": "null", "readable": true, diff --git a/subprojects/gst-plugins-base/gst/volume/gstvolume.c b/subprojects/gst-plugins-base/gst/volume/gstvolume.c index a9ffded12a..a0427479b5 100644 --- a/subprojects/gst-plugins-base/gst/volume/gstvolume.c +++ b/subprojects/gst-plugins-base/gst/volume/gstvolume.c @@ -58,8 +58,7 @@ #include "gstvolume.h" /* some defines for audio processing */ -/* the volume factor is a range from 0.0 to (arbitrary) VOLUME_MAX_DOUBLE = 10.0 - * we map 1.0 to VOLUME_UNITY_INT* +/* we map VOLUME_UNITY_INT* to volume = 1.0 */ #define VOLUME_UNITY_INT8 8 /* internal int for unity 2^(8-5) */ #define VOLUME_UNITY_INT8_BIT_SHIFT 3 /* number of bits to shift for unity */ @@ -69,7 +68,6 @@ #define VOLUME_UNITY_INT24_BIT_SHIFT 19 /* number of bits to shift for unity */ #define VOLUME_UNITY_INT32 134217728 /* internal int for unity 2^(32-5) */ #define VOLUME_UNITY_INT32_BIT_SHIFT 27 -#define VOLUME_MAX_DOUBLE 10.0 #define VOLUME_MAX_INT8 G_MAXINT8 #define VOLUME_MIN_INT8 G_MININT8 #define VOLUME_MAX_INT16 G_MAXINT16 @@ -78,6 +76,8 @@ #define VOLUME_MIN_INT24 -8388608 #define VOLUME_MAX_INT32 G_MAXINT32 #define VOLUME_MIN_INT32 G_MININT32 +#define VOLUME_MAX_INT64 G_MAXINT64 +#define VOLUME_MIN_INT64 G_MININT64 #define GST_CAT_DEFAULT gst_volume_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -140,24 +140,32 @@ static void volume_process_int32 (GstVolume * self, gpointer bytes, guint n_bytes); static void volume_process_int32_clamp (GstVolume * self, gpointer bytes, guint n_bytes); +static void volume_process_int32_via_double_with_clamp (GstVolume * self, + gpointer bytes, guint n_bytes); static void volume_process_controlled_int32_clamp (GstVolume * self, gpointer bytes, gdouble * volume, guint channels, guint n_bytes); static void volume_process_int24 (GstVolume * self, gpointer bytes, guint n_bytes); static void volume_process_int24_clamp (GstVolume * self, gpointer bytes, guint n_bytes); +static void volume_process_int24_via_float_with_clamp (GstVolume * self, + gpointer bytes, guint n_bytes); static void volume_process_controlled_int24_clamp (GstVolume * self, gpointer bytes, gdouble * volume, guint channels, guint n_bytes); static void volume_process_int16 (GstVolume * self, gpointer bytes, guint n_bytes); static void volume_process_int16_clamp (GstVolume * self, gpointer bytes, guint n_bytes); +static void volume_process_int16_via_float_with_clamp (GstVolume * self, + gpointer bytes, guint n_bytes); static void volume_process_controlled_int16_clamp (GstVolume * self, gpointer bytes, gdouble * volume, guint channels, guint n_bytes); static void volume_process_int8 (GstVolume * self, gpointer bytes, guint n_bytes); static void volume_process_int8_clamp (GstVolume * self, gpointer bytes, guint n_bytes); +static void volume_process_int8_via_float_with_clamp (GstVolume * self, + gpointer bytes, guint n_bytes); static void volume_process_controlled_int8_clamp (GstVolume * self, gpointer bytes, gdouble * volume, guint channels, guint n_bytes); @@ -181,7 +189,12 @@ volume_choose_func (GstVolume * self, const GstAudioInfo * info) case GST_AUDIO_FORMAT_S32: /* only clamp if the gain is greater than 1.0 */ if (self->current_vol_i32 > VOLUME_UNITY_INT32) { - self->process = volume_process_int32_clamp; + /* Fixed-point multiplication only supports small subset of volumes */ + if (self->current_vol_i32 > VOLUME_MAX_INT32) { + self->process = volume_process_int32_via_double_with_clamp; + } else { + self->process = volume_process_int32_clamp; + } } else { self->process = volume_process_int32; } @@ -190,7 +203,12 @@ volume_choose_func (GstVolume * self, const GstAudioInfo * info) case GST_AUDIO_FORMAT_S24: /* only clamp if the gain is greater than 1.0 */ if (self->current_vol_i24 > VOLUME_UNITY_INT24) { - self->process = volume_process_int24_clamp; + /* Fixed-point multiplication only supports small subset of volumes */ + if (self->current_vol_i24 > VOLUME_MAX_INT24) { + self->process = volume_process_int24_via_float_with_clamp; + } else { + self->process = volume_process_int24_clamp; + } } else { self->process = volume_process_int24; } @@ -199,7 +217,12 @@ volume_choose_func (GstVolume * self, const GstAudioInfo * info) case GST_AUDIO_FORMAT_S16: /* only clamp if the gain is greater than 1.0 */ if (self->current_vol_i16 > VOLUME_UNITY_INT16) { - self->process = volume_process_int16_clamp; + /* Fixed-point multiplication only supports small subset of volumes */ + if (self->current_vol_i16 > VOLUME_MAX_INT16) { + self->process = volume_process_int16_via_float_with_clamp; + } else { + self->process = volume_process_int16_clamp; + } } else { self->process = volume_process_int16; } @@ -208,7 +231,12 @@ volume_choose_func (GstVolume * self, const GstAudioInfo * info) case GST_AUDIO_FORMAT_S8: /* only clamp if the gain is greater than 1.0 */ if (self->current_vol_i8 > VOLUME_UNITY_INT8) { - self->process = volume_process_int8_clamp; + /* Fixed-point multiplication only supports small subset of volumes */ + if (self->current_vol_i8 > VOLUME_MAX_INT8) { + self->process = volume_process_int8_via_float_with_clamp; + } else { + self->process = volume_process_int8_clamp; + } } else { self->process = volume_process_int8; } @@ -252,16 +280,49 @@ volume_update_volume (GstVolume * self, const GstAudioInfo * info, self->current_mute = FALSE; self->current_volume = volume; - self->current_vol_i8 = - (gint) ((gdouble) volume * (gdouble) VOLUME_UNITY_INT8); - self->current_vol_i16 = - (gint) ((gdouble) volume * (gdouble) VOLUME_UNITY_INT16); - self->current_vol_i24 = - (gint) ((gdouble) volume * (gdouble) VOLUME_UNITY_INT24); - self->current_vol_i32 = - (gint) ((gdouble) volume * (gdouble) VOLUME_UNITY_INT32); + gdouble current_fp_vol_i8 = (gdouble) volume * (gdouble) VOLUME_UNITY_INT8; + gdouble current_fp_vol_i16 = + (gdouble) volume * (gdouble) VOLUME_UNITY_INT16; + gdouble current_fp_vol_i24 = + (gdouble) volume * (gdouble) VOLUME_UNITY_INT24; + gdouble current_fp_vol_i32 = + (gdouble) volume * (gdouble) VOLUME_UNITY_INT32; - passthrough = (self->current_vol_i16 == VOLUME_UNITY_INT16); + /* Perform "saturating" FP->int conversion. + * We want to be able to tell when current_fp_vol_i32 is *larger* than + * VOLUME_MAX_INT32, *and* don't have UB on FP->int cast overflow, + * `2 * VOLUME_MAX_INT32` achieves both of these points. + */ + self->current_vol_i8 = (glong) (CLAMP (current_fp_vol_i8, (gdouble) 0, + (gdouble) 2 * VOLUME_MAX_INT32)); + self->current_vol_i16 = (glong) (CLAMP (current_fp_vol_i16, (gdouble) 0, + (gdouble) 2 * VOLUME_MAX_INT32)); + self->current_vol_i24 = (glong) (CLAMP (current_fp_vol_i24, (gdouble) 0, + (gdouble) 2 * VOLUME_MAX_INT32)); + self->current_vol_i32 = (glong) (CLAMP (current_fp_vol_i32, (gdouble) 0, + (gdouble) 2 * VOLUME_MAX_INT32)); + + switch (GST_AUDIO_INFO_FORMAT (info)) { + case GST_AUDIO_FORMAT_F32: + case GST_AUDIO_FORMAT_F64: + passthrough = (self->current_volume == 1.0); + break; + case GST_AUDIO_FORMAT_S8: + passthrough = (self->current_vol_i8 == VOLUME_UNITY_INT8); + break; + case GST_AUDIO_FORMAT_S16: + passthrough = (self->current_vol_i16 == VOLUME_UNITY_INT16); + break; + case GST_AUDIO_FORMAT_S24: + passthrough = (self->current_vol_i24 == VOLUME_UNITY_INT24); + break; + case GST_AUDIO_FORMAT_S32: + passthrough = (self->current_vol_i32 == VOLUME_UNITY_INT32); + break; + default: + passthrough = FALSE; + break; + } } /* If a controller is used, never use passthrough mode @@ -319,9 +380,13 @@ gst_volume_class_init (GstVolumeClass * klass) DEFAULT_PROP_MUTE, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); + /* The volume factor is a range from 0.0 to G_MAXDOUBLE. + * The choice of G_MAXDOUBLE is somewhat arbitrary, + * but it should be *very* inclusive, e.g. gain of +48 dB is very reasonable. + */ g_object_class_install_property (gobject_class, PROP_VOLUME, g_param_spec_double ("volume", "Volume", "volume factor, 1.0=100%", - 0.0, VOLUME_MAX_DOUBLE, DEFAULT_PROP_VOLUME, + 0.0, G_MAXDOUBLE, DEFAULT_PROP_VOLUME, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); gst_element_class_set_static_metadata (element_class, "Volume", @@ -437,6 +502,26 @@ volume_process_int32_clamp (GstVolume * self, gpointer bytes, guint n_bytes) volume_orc_process_int32_clamp (data, self->current_vol_i32, num_samples); } +// TODO: Add ORC implementation for this +static void +volume_process_int32_via_double_with_clamp (GstVolume * self, + gpointer bytes, guint n_bytes) +{ + gint32 *data = (gint32 *) bytes; + guint i; + guint num_samples = n_bytes / sizeof (gint32); + /* Cast between the 32-bit integer and 32-bit floating point is lossy, + * but not between 64-bit floating point, so use it instead. */ + /* WARNING: (gint32)((gfloat)VOLUME_MAX_INT32)) is UB! */ + gdouble vol = self->current_volume; + gdouble val; + + for (i = 0; i < num_samples; i++) { + val = *data * vol; + *data++ = (gint32) CLAMP (val, VOLUME_MIN_INT32, VOLUME_MAX_INT32); + } +} + static void volume_process_controlled_int32_clamp (GstVolume * self, gpointer bytes, gdouble * volume, guint channels, guint n_bytes) @@ -460,6 +545,7 @@ volume_process_controlled_int32_clamp (GstVolume * self, gpointer bytes, } #if (G_BYTE_ORDER == G_LITTLE_ENDIAN) +/* NOTE: does not sign-extend, result is an 24-bit unsigned integer! */ #define get_unaligned_i24(_x) ( (((guint8*)_x)[0]) | ((((guint8*)_x)[1]) << 8) | ((((gint8*)_x)[2]) << 16) ) #define write_unaligned_u24(_x,samp) \ @@ -470,6 +556,7 @@ G_STMT_START { \ } G_STMT_END #else /* BIG ENDIAN */ +/* NOTE: does not sign-extend, result is an 24-bit unsigned integer! */ #define get_unaligned_i24(_x) ( (((guint8*)_x)[2]) | ((((guint8*)_x)[1]) << 8) | ((((gint8*)_x)[0]) << 16) ) #define write_unaligned_u24(_x,samp) \ G_STMT_START { \ @@ -479,6 +566,9 @@ G_STMT_START { \ } G_STMT_END #endif +#define sign_extend_i24(_x) \ + (((gint32)(((guint32)(_x)) << (32 - 24))) >> (32 - 24)) + static void volume_process_int24 (GstVolume * self, gpointer bytes, guint n_bytes) { @@ -525,6 +615,30 @@ volume_process_int24_clamp (GstVolume * self, gpointer bytes, guint n_bytes) } } +static void +volume_process_int24_via_float_with_clamp (GstVolume * self, + gpointer bytes, guint n_bytes) +{ + gint8 *data = (gint8 *) bytes; /* treat the data as a byte stream */ + guint i, num_samples; + guint32 samp; + gfloat vol = self->current_volume; + gfloat val; + + num_samples = n_bytes / (sizeof (gint8) * 3); + for (i = 0; i < num_samples; i++) { + samp = get_unaligned_i24 (data); /* NOT SIGN-EXTENDED!!! */ + + val = (gfloat) (sign_extend_i24 (samp)); + val *= vol; + /* NOTE: we *MUST* first cast FP to int, and only then to unsigned. */ + samp = (guint32) ((gint32) CLAMP (val, VOLUME_MIN_INT24, VOLUME_MAX_INT24)); + + /* write the value back into the stream */ + write_unaligned_u24 (data, samp); + } +} + static void volume_process_controlled_int24_clamp (GstVolume * self, gpointer bytes, gdouble * volume, guint channels, guint n_bytes) @@ -568,6 +682,23 @@ volume_process_int16_clamp (GstVolume * self, gpointer bytes, guint n_bytes) volume_orc_process_int16_clamp (data, self->current_vol_i16, num_samples); } +// TODO: Add ORC implementation for this +static void +volume_process_int16_via_float_with_clamp (GstVolume * self, + gpointer bytes, guint n_bytes) +{ + gint16 *data = (gint16 *) bytes; + guint i; + guint num_samples = n_bytes / sizeof (gint16); + gfloat vol = self->current_volume; + gfloat val; + + for (i = 0; i < num_samples; i++) { + val = *data * vol; + *data++ = (gint16) CLAMP (val, VOLUME_MIN_INT16, VOLUME_MAX_INT16); + } +} + static void volume_process_controlled_int16_clamp (GstVolume * self, gpointer bytes, gdouble * volume, guint channels, guint n_bytes) @@ -616,6 +747,24 @@ volume_process_int8_clamp (GstVolume * self, gpointer bytes, guint n_bytes) volume_orc_process_int8_clamp (data, self->current_vol_i8, num_samples); } +// TODO: Add ORC implementation for this +static void +volume_process_int8_via_float_with_clamp (GstVolume * self, + gpointer bytes, guint n_bytes) +{ + gint8 *data = (gint8 *) bytes; + guint i; + guint num_samples = n_bytes / sizeof (gint8); + gfloat vol = self->current_volume; + gfloat val; + + for (i = 0; i < num_samples; i++) { + val = *data; + val *= vol; + *data++ = (gint8) CLAMP (val, VOLUME_MIN_INT8, VOLUME_MAX_INT8); + } +} + static void volume_process_controlled_int8_clamp (GstVolume * self, gpointer bytes, gdouble * volume, guint channels, guint n_bytes) diff --git a/subprojects/gst-plugins-base/gst/volume/gstvolume.h b/subprojects/gst-plugins-base/gst/volume/gstvolume.h index b8709dc3a3..5ba858a0cb 100644 --- a/subprojects/gst-plugins-base/gst/volume/gstvolume.h +++ b/subprojects/gst-plugins-base/gst/volume/gstvolume.h @@ -52,11 +52,11 @@ struct _GstVolume { gboolean current_mute; gdouble current_volume; - gint current_vol_i32; - gint current_vol_i24; /* the _i(nt) values get synchronized with the */ - gint current_vol_i16; /* the _i(nt) values get synchronized with the */ - gint current_vol_i8; /* the _i(nt) values get synchronized with the */ - + gint64 current_vol_i32; + gint64 current_vol_i24; /* the _i(nt) values get synchronized with the */ + gint64 current_vol_i16; /* the _i(nt) values get synchronized with the */ + gint64 current_vol_i8; /* the _i(nt) values get synchronized with the */ + GList *tracklist; gboolean negotiated; diff --git a/subprojects/gst-plugins-base/tests/check/elements/volume.c b/subprojects/gst-plugins-base/tests/check/elements/volume.c index 053f29b535..1da0aa5663 100644 --- a/subprojects/gst-plugins-base/tests/check/elements/volume.c +++ b/subprojects/gst-plugins-base/tests/check/elements/volume.c @@ -371,6 +371,159 @@ GST_START_TEST (test_ten_s8) GST_END_TEST; +GST_START_TEST (test_fifteen_s8) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint8 in[4] = { 1, -1, G_MAXINT8, G_MININT8 }; + gint8 out[4] = { 15, -15, G_MAXINT8, G_MININT8 }; + /* notice the clamped sample */ + gint8 *res; + GstMapInfo map; + + volume = setup_volume (); + // Maximal volume that is supported by the fixed point multiplication. + g_object_set (G_OBJECT (volume), "volume", 15.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (4); + gst_buffer_fill (inbuffer, 0, in, 4); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 4) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S8); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint8 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, 4) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_sixteen_s8) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint8 in[4] = { 1, -1, G_MAXINT8, G_MININT8 }; + gint8 out[4] = { 16, -16, G_MAXINT8, G_MININT8 }; + /* notice the clamped sample */ + gint8 *res; + GstMapInfo map; + + volume = setup_volume (); + // No longer using fixed point multiplication. + g_object_set (G_OBJECT (volume), "volume", 16.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (4); + gst_buffer_fill (inbuffer, 0, in, 4); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 4) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S8); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint8 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, 4) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_max_s8) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint8 in[4] = { 1, -1, G_MAXINT8, G_MININT8 }; + gint8 out[4] = { G_MAXINT8, G_MININT8, G_MAXINT8, G_MININT8 }; + /* notice the clamped sample */ + gint8 *res; + GstMapInfo map; + + volume = setup_volume (); + // No longer using fixed point multiplication. + g_object_set (G_OBJECT (volume), "volume", G_MAXDOUBLE, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (4); + gst_buffer_fill (inbuffer, 0, in, 4); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 4) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S8); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint8 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, 4) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + GST_START_TEST (test_mute_s8) { GstElement *volume; @@ -605,6 +758,155 @@ GST_START_TEST (test_ten_s16) GST_END_TEST; +GST_START_TEST (test_fifteen_s16) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint16 in[4] = { 1, -1, G_MAXINT16, G_MININT16 }; + gint16 out[4] = { 15, -15, G_MAXINT16, G_MININT16 }; + /* notice the clamped sample */ + gint16 *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 15.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (8); + gst_buffer_fill (inbuffer, 0, in, 8); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 8) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S16); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint16 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (map.data, out, 8) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_sixteen_s16) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint16 in[4] = { 1, -1, G_MAXINT16, G_MININT16 }; + gint16 out[4] = { 16, -16, G_MAXINT16, G_MININT16 }; + /* notice the clamped sample */ + gint16 *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 16.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (8); + gst_buffer_fill (inbuffer, 0, in, 8); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 8) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S16); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint16 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (map.data, out, 8) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_max_s16) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint16 in[4] = { 1, -1, G_MAXINT16, G_MININT16 }; + gint16 out[4] = { G_MAXINT16, G_MININT16, G_MAXINT16, G_MININT16 }; + /* notice the clamped sample */ + gint16 *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", G_MAXDOUBLE, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (8); + gst_buffer_fill (inbuffer, 0, in, 8); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 8) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S16); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint16 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (map.data, out, 8) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; GST_START_TEST (test_mute_s16) { @@ -879,6 +1181,316 @@ GST_START_TEST (test_ten_s24) GST_END_TEST; +GST_START_TEST (test_fifteen_s24) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint32 in_32[4] = { 1, -1, 8388607, -8388608 }; + guint8 in[12]; + GstMapInfo map; + gint32 res_32[4]; + gint32 out_32[4] = { 15, -15, 8388607, -8388608 }; + /* notice the clamped sample */ + + write_unaligned_u24 (in, in_32[0]); + write_unaligned_u24 (in + 3, in_32[1]); + write_unaligned_u24 (in + 6, in_32[2]); + write_unaligned_u24 (in + 9, in_32[3]); + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 15.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (12); + gst_buffer_fill (inbuffer, 0, in, 12); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S24); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + + res_32[0] = get_unaligned_i24 (map.data); + res_32[1] = get_unaligned_i24 ((map.data + 3)); + res_32[2] = get_unaligned_i24 ((map.data + 6)); + res_32[3] = get_unaligned_i24 ((map.data + 9)); + + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out_32[0], + out_32[1], out_32[2], out_32[3], res_32[0], res_32[1], res_32[2], + res_32[3]); + fail_unless (memcmp (res_32, out_32, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_sixteen_s24) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint32 in_32[4] = { 1, -1, 8388607, -8388608 }; + guint8 in[12]; + GstMapInfo map; + gint32 res_32[4]; + gint32 out_32[4] = { 16, -16, 8388607, -8388608 }; + /* notice the clamped sample */ + + write_unaligned_u24 (in, in_32[0]); + write_unaligned_u24 (in + 3, in_32[1]); + write_unaligned_u24 (in + 6, in_32[2]); + write_unaligned_u24 (in + 9, in_32[3]); + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 16.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (12); + gst_buffer_fill (inbuffer, 0, in, 12); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S24); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + + res_32[0] = get_unaligned_i24 (map.data); + res_32[1] = get_unaligned_i24 ((map.data + 3)); + res_32[2] = get_unaligned_i24 ((map.data + 6)); + res_32[3] = get_unaligned_i24 ((map.data + 9)); + + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out_32[0], + out_32[1], out_32[2], out_32[3], res_32[0], res_32[1], res_32[2], + res_32[3]); + fail_unless (memcmp (res_32, out_32, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_4095_s24) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint32 in_32[4] = { 1, -1, 8388607, -8388608 }; + guint8 in[12]; + GstMapInfo map; + gint32 res_32[4]; + gint32 out_32[4] = { 4095, -4095, 8388607, -8388608 }; + /* notice the clamped sample */ + + write_unaligned_u24 (in, in_32[0]); + write_unaligned_u24 (in + 3, in_32[1]); + write_unaligned_u24 (in + 6, in_32[2]); + write_unaligned_u24 (in + 9, in_32[3]); + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 4095.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (12); + gst_buffer_fill (inbuffer, 0, in, 12); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S24); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + + res_32[0] = get_unaligned_i24 (map.data); + res_32[1] = get_unaligned_i24 ((map.data + 3)); + res_32[2] = get_unaligned_i24 ((map.data + 6)); + res_32[3] = get_unaligned_i24 ((map.data + 9)); + + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out_32[0], + out_32[1], out_32[2], out_32[3], res_32[0], res_32[1], res_32[2], + res_32[3]); + fail_unless (memcmp (res_32, out_32, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_4096_s24) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint32 in_32[4] = { 1, -1, 8388607, -8388608 }; + guint8 in[12]; + GstMapInfo map; + gint32 res_32[4]; + gint32 out_32[4] = { 4096, -4096, 8388607, -8388608 }; + /* notice the clamped sample */ + + write_unaligned_u24 (in, in_32[0]); + write_unaligned_u24 (in + 3, in_32[1]); + write_unaligned_u24 (in + 6, in_32[2]); + write_unaligned_u24 (in + 9, in_32[3]); + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 4096.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (12); + gst_buffer_fill (inbuffer, 0, in, 12); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S24); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + + res_32[0] = get_unaligned_i24 (map.data); + res_32[1] = get_unaligned_i24 ((map.data + 3)); + res_32[2] = get_unaligned_i24 ((map.data + 6)); + res_32[3] = get_unaligned_i24 ((map.data + 9)); + + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out_32[0], + out_32[1], out_32[2], out_32[3], res_32[0], res_32[1], res_32[2], + res_32[3]); + fail_unless (memcmp (res_32, out_32, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_max_s24) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint32 in_32[4] = { 1, -1, 8388607, -8388608 }; + guint8 in[12]; + GstMapInfo map; + gint32 res_32[4]; + gint32 out_32[4] = { 8388607, -8388608, 8388607, -8388608 }; + /* notice the clamped sample */ + + write_unaligned_u24 (in, in_32[0]); + write_unaligned_u24 (in + 3, in_32[1]); + write_unaligned_u24 (in + 6, in_32[2]); + write_unaligned_u24 (in + 9, in_32[3]); + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", G_MAXDOUBLE, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (12); + gst_buffer_fill (inbuffer, 0, in, 12); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S24); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + + res_32[0] = get_unaligned_i24 (map.data); + res_32[1] = get_unaligned_i24 ((map.data + 3)); + res_32[2] = get_unaligned_i24 ((map.data + 6)); + res_32[3] = get_unaligned_i24 ((map.data + 9)); + + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out_32[0], + out_32[1], out_32[2], out_32[3], res_32[0], res_32[1], res_32[2], + res_32[3]); + fail_unless (memcmp (res_32, out_32, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + GST_START_TEST (test_mute_s24) { GstElement *volume; @@ -1121,6 +1733,156 @@ GST_START_TEST (test_ten_s32) GST_END_TEST; +GST_START_TEST (test_fifteen_s32) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint32 in[4] = { 1, -1, G_MAXINT32, G_MININT32 }; + gint32 out[4] = { 15, -15, G_MAXINT32, G_MININT32 }; + /* notice the clamped sample */ + gint32 *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 15.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S32); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint32 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (map.data, out, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_sixteen_s32) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint32 in[4] = { 1, -1, G_MAXINT32, G_MININT32 }; + gint32 out[4] = { 16, -16, G_MAXINT32, G_MININT32 }; + /* notice the clamped sample */ + gint32 *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 16.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S32); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint32 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (map.data, out, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_max_s32) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gint32 in[4] = { 1, -1, G_MAXINT32, G_MININT32 }; + gint32 out[4] = { G_MAXINT32, G_MININT32, G_MAXINT32, G_MININT32 }; + /* notice the clamped sample */ + gint32 *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", G_MAXDOUBLE, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_S32); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint32 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", out[0], + out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (map.data, out, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + GST_START_TEST (test_mute_s32) { GstElement *volume; @@ -1361,6 +2123,163 @@ GST_START_TEST (test_ten_f32) GST_END_TEST; +GST_START_TEST (test_fifteen_f32) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gfloat in[4] = { 1.0, -1.0, 2.0, -2.0 }; + gfloat out[4] = { 15.0, -15.0, 30.0, -30.0 }; /* nothing is clamped */ + gfloat *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 15.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_F32); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + GST_INFO + ("expected %+1.4f %+1.4f %+1.4f %+1.4f real %+1.4f %+1.4f %+1.4f %+1.4f", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless_equals_float (res[0], out[0]); + fail_unless_equals_float (res[1], out[1]); + fail_unless_equals_float (res[2], out[2]); + fail_unless_equals_float (res[3], out[3]); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_sixteen_f32) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gfloat in[4] = { 1.0, -1.0, 2.0, -2.0 }; + gfloat out[4] = { 16.0, -16.0, 32.0, -32.0 }; /* nothing is clamped */ + gfloat *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 16.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_F32); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + GST_INFO + ("expected %+1.4f %+1.4f %+1.4f %+1.4f real %+1.4f %+1.4f %+1.4f %+1.4f", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless_equals_float (res[0], out[0]); + fail_unless_equals_float (res[1], out[1]); + fail_unless_equals_float (res[2], out[2]); + fail_unless_equals_float (res[3], out[3]); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_max_f32) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gfloat in[4] = { 1.0, -1.0, 2.0, -2.0 }; + gfloat out[4] = { INFINITY, -INFINITY, INFINITY, + -INFINITY + }; /* nothing is clamped */ + gfloat *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", G_MAXDOUBLE, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_F32); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + GST_INFO + ("expected %+1.4f %+1.4f %+1.4f %+1.4f real %+1.4f %+1.4f %+1.4f %+1.4f", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (map.data, out, 16) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; GST_START_TEST (test_mute_f32) { @@ -1605,6 +2524,163 @@ GST_START_TEST (test_ten_f64) GST_END_TEST; +GST_START_TEST (test_fifteen_f64) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gdouble in[4] = { 1.0, -1.0, 2.0, -2.0 }; + gdouble out[4] = { 15.0, -15.0, 30.0, -30.0 }; /* nothing is clamped */ + gdouble *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 15.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (32); + gst_buffer_fill (inbuffer, 0, in, 32); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 32) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_F64); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + GST_INFO + ("expected %+1.4f %+1.4f %+1.4f %+1.4f real %+1.4f %+1.4f %+1.4f %+1.4f", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless_equals_float (res[0], out[0]); + fail_unless_equals_float (res[1], out[1]); + fail_unless_equals_float (res[2], out[2]); + fail_unless_equals_float (res[3], out[3]); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_sixteen_f64) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gdouble in[4] = { 1.0, -1.0, 2.0, -2.0 }; + gdouble out[4] = { 16.0, -16.0, 32.0, -32.0 }; /* nothing is clamped */ + gdouble *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", 16.0, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (32); + gst_buffer_fill (inbuffer, 0, in, 32); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 32) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_F64); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + GST_INFO + ("expected %+1.4f %+1.4f %+1.4f %+1.4f real %+1.4f %+1.4f %+1.4f %+1.4f", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless_equals_float (res[0], out[0]); + fail_unless_equals_float (res[1], out[1]); + fail_unless_equals_float (res[2], out[2]); + fail_unless_equals_float (res[3], out[3]); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; + +GST_START_TEST (test_max_f64) +{ + GstElement *volume; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *caps; + gdouble in[4] = { 1.0, -1.0, 2.0, -2.0 }; + gdouble out[4] = { INFINITY, -INFINITY, INFINITY, + -INFINITY + }; /* nothing is clamped */ + gdouble *res; + GstMapInfo map; + + volume = setup_volume (); + g_object_set (G_OBJECT (volume), "volume", G_MAXDOUBLE, NULL); + fail_unless (gst_element_set_state (volume, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (32); + gst_buffer_fill (inbuffer, 0, in, 32); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 32) == 0); + caps = gst_caps_from_string (VOLUME_CAPS_STRING_F64); + gst_check_setup_events (mysrcpad, volume, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + /* FIXME: reffing the inbuffer should make the transformation not be + * inplace + gst_buffer_ref (inbuffer); + */ + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being modified inplace and + * collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + GST_INFO + ("expected %+1.4f %+1.4f %+1.4f %+1.4f real %+1.4f %+1.4f %+1.4f %+1.4f", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + fail_unless (memcmp (map.data, out, 32) == 0); + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_volume (volume); +} + +GST_END_TEST; GST_START_TEST (test_mute_f64) { @@ -1778,7 +2854,7 @@ GST_START_TEST (test_controller_processing) GstElement *volume; GstBuffer *inbuffer, *outbuffer; GstCaps *caps; - gint16 *out, in[2] = { 16384, -256 }; + gint16 *out, in[2] = { +32767, -32768 }; GstMapInfo map; GstSegment seg; @@ -1845,7 +2921,7 @@ GST_START_TEST (test_controller_defaults_at_ts0) gst_direct_control_binding_new (GST_OBJECT_CAST (volume), "volume", cs)); /* make a control curve that does not start at ts=0, the element will use - * the current property value (default) until the control curve starts + * the current property value (default) until the control curve starts */ tvcs = (GstTimedValueControlSource *) cs; gst_timed_value_control_source_set (tvcs, GST_SECOND / 100, 0.1); @@ -1876,7 +2952,6 @@ GST_START_TEST (test_controller_defaults_at_ts0) GST_END_TEST; - static Suite * volume_suite (void) { @@ -1889,31 +2964,51 @@ volume_suite (void) tcase_add_test (tc_chain, test_half_s8); tcase_add_test (tc_chain, test_double_s8); tcase_add_test (tc_chain, test_ten_s8); + tcase_add_test (tc_chain, test_fifteen_s8); + tcase_add_test (tc_chain, test_sixteen_s8); + tcase_add_test (tc_chain, test_max_s8); tcase_add_test (tc_chain, test_mute_s8); tcase_add_test (tc_chain, test_unity_s16); tcase_add_test (tc_chain, test_half_s16); tcase_add_test (tc_chain, test_double_s16); tcase_add_test (tc_chain, test_ten_s16); + tcase_add_test (tc_chain, test_fifteen_s16); + tcase_add_test (tc_chain, test_sixteen_s16); + tcase_add_test (tc_chain, test_max_s16); tcase_add_test (tc_chain, test_mute_s16); tcase_add_test (tc_chain, test_unity_s24); tcase_add_test (tc_chain, test_half_s24); tcase_add_test (tc_chain, test_double_s24); tcase_add_test (tc_chain, test_ten_s24); + tcase_add_test (tc_chain, test_fifteen_s24); + tcase_add_test (tc_chain, test_sixteen_s24); + tcase_add_test (tc_chain, test_4095_s24); + tcase_add_test (tc_chain, test_4096_s24); + tcase_add_test (tc_chain, test_max_s24); tcase_add_test (tc_chain, test_mute_s24); tcase_add_test (tc_chain, test_unity_s32); tcase_add_test (tc_chain, test_half_s32); tcase_add_test (tc_chain, test_double_s32); tcase_add_test (tc_chain, test_ten_s32); + tcase_add_test (tc_chain, test_fifteen_s32); + tcase_add_test (tc_chain, test_sixteen_s32); + tcase_add_test (tc_chain, test_max_s32); tcase_add_test (tc_chain, test_mute_s32); tcase_add_test (tc_chain, test_unity_f32); tcase_add_test (tc_chain, test_half_f32); tcase_add_test (tc_chain, test_double_f32); tcase_add_test (tc_chain, test_ten_f32); + tcase_add_test (tc_chain, test_fifteen_f32); + tcase_add_test (tc_chain, test_sixteen_f32); + tcase_add_test (tc_chain, test_max_f32); tcase_add_test (tc_chain, test_mute_f32); tcase_add_test (tc_chain, test_unity_f64); tcase_add_test (tc_chain, test_half_f64); tcase_add_test (tc_chain, test_double_f64); tcase_add_test (tc_chain, test_ten_f64); + tcase_add_test (tc_chain, test_fifteen_f64); + tcase_add_test (tc_chain, test_sixteen_f64); + tcase_add_test (tc_chain, test_max_f64); tcase_add_test (tc_chain, test_mute_f64); tcase_add_test (tc_chain, test_wrong_caps); tcase_add_test (tc_chain, test_passthrough);