level: subdivide buffers for sample accurate interval handling

Previously we would skip level message when processing buffers > the requested
interval. Also the message frequency would contain quite some jitter due to only
considering them at the end of buffers.

Cleanup the tests while we're at it.
This commit is contained in:
Stefan Sauer 2013-04-03 19:05:38 +02:00
parent ce06005f2a
commit 2e56032031
2 changed files with 177 additions and 131 deletions

View file

@ -244,6 +244,10 @@ gst_level_init (GstLevel * filter)
{ {
filter->CS = NULL; filter->CS = NULL;
filter->peak = NULL; filter->peak = NULL;
filter->last_peak = NULL;
filter->decay_peak = NULL;
filter->decay_peak_base = NULL;
filter->decay_peak_age = NULL;
gst_audio_info_init (&filter->info); gst_audio_info_init (&filter->info);
@ -367,20 +371,18 @@ gst_level_calculate_##TYPE (gpointer data, guint num, guint channels, \
gdouble squaresum = 0.0; /* square sum of the integer samples */ \ gdouble squaresum = 0.0; /* square sum of the integer samples */ \
register gdouble square = 0.0; /* Square */ \ register gdouble square = 0.0; /* Square */ \
register gdouble peaksquare = 0.0; /* Peak Square Sample */ \ register gdouble peaksquare = 0.0; /* Peak Square Sample */ \
gdouble normalizer; /* divisor to get a [-1.0, 1.0] range */ \ gdouble normalizer; /* divisor to get a [-1.0, 1.0] range */ \
\ \
/* *NCS = 0.0; Normalized Cumulative Square */ \ /* *NCS = 0.0; Normalized Cumulative Square */ \
/* *NPS = 0.0; Normalized Peask Square */ \ /* *NPS = 0.0; Normalized Peask Square */ \
\ \
normalizer = (gdouble) (G_GINT64_CONSTANT(1) << (RESOLUTION * 2)); \ for (j = 0; j < num; j += channels) { \
\
for (j = 0; j < num; j += channels) \
{ \
square = ((gdouble) in[j]) * in[j]; \ square = ((gdouble) in[j]) * in[j]; \
if (square > peaksquare) peaksquare = square; \ if (square > peaksquare) peaksquare = square; \
squaresum += square; \ squaresum += square; \
} \ } \
\ \
normalizer = (gdouble) (G_GINT64_CONSTANT(1) << (RESOLUTION * 2)); \
*NCS = squaresum / normalizer; \ *NCS = squaresum / normalizer; \
*NPS = peaksquare / normalizer; \ *NPS = peaksquare / normalizer; \
} }
@ -405,8 +407,7 @@ gst_level_calculate_##TYPE (gpointer data, guint num, guint channels, \
/* *NPS = 0.0; Normalized Peask Square */ \ /* *NPS = 0.0; Normalized Peask Square */ \
\ \
/* orc_level_squaresum_f64(&squaresum,in,num); */ \ /* orc_level_squaresum_f64(&squaresum,in,num); */ \
for (j = 0; j < num; j += channels) \ for (j = 0; j < num; j += channels) { \
{ \
square = ((gdouble) in[j]) * in[j]; \ square = ((gdouble) in[j]) * in[j]; \
if (square > peaksquare) peaksquare = square; \ if (square > peaksquare) peaksquare = square; \
squaresum += square; \ squaresum += square; \
@ -498,6 +499,7 @@ gst_level_start (GstBaseTransform * trans)
GstLevel *filter = GST_LEVEL (trans); GstLevel *filter = GST_LEVEL (trans);
filter->num_frames = 0; filter->num_frames = 0;
filter->message_ts = GST_CLOCK_TIME_NONE;
return TRUE; return TRUE;
} }
@ -580,9 +582,11 @@ gst_level_transform_ip (GstBaseTransform * trans, GstBuffer * in)
gsize in_size; gsize in_size;
gdouble CS; gdouble CS;
guint i; guint i;
guint num_frames = 0; guint num_frames;
guint num_int_samples = 0; /* number of interleaved samples guint num_int_samples = 0; /* number of interleaved samples
* ie. total count for all channels combined */ * ie. total count for all channels combined */
guint block_size, block_int_size; /* we subdivide buffers to not skip message
* intervals */
GstClockTimeDiff falloff_time; GstClockTimeDiff falloff_time;
gint channels, rate, bps; gint channels, rate, bps;
@ -603,75 +607,87 @@ gst_level_transform_ip (GstBaseTransform * trans, GstBuffer * in)
g_return_val_if_fail (num_int_samples % channels == 0, GST_FLOW_ERROR); g_return_val_if_fail (num_int_samples % channels == 0, GST_FLOW_ERROR);
num_frames = num_int_samples / channels; if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (filter->message_ts))) {
for (i = 0; i < channels; ++i) {
if (!GST_BUFFER_FLAG_IS_SET (in, GST_BUFFER_FLAG_GAP)) {
filter->process (in_data, num_int_samples, channels, &CS,
&filter->peak[i]);
GST_LOG_OBJECT (filter,
"channel %d, cumulative sum %f, peak %f, over %d samples/%d channels",
i, CS, filter->peak[i], num_int_samples, channels);
filter->CS[i] += CS;
} else {
filter->peak[i] = 0.0;
}
in_data += bps;
filter->decay_peak_age[i] += GST_FRAMES_TO_CLOCK_TIME (num_frames, rate);
GST_LOG_OBJECT (filter, "filter peak info [%d]: decay peak %f, age %"
GST_TIME_FORMAT, i,
filter->decay_peak[i], GST_TIME_ARGS (filter->decay_peak_age[i]));
/* update running peak */
if (filter->peak[i] > filter->last_peak[i])
filter->last_peak[i] = filter->peak[i];
/* make decay peak fall off if too old */
falloff_time =
GST_CLOCK_DIFF (gst_gdouble_to_guint64 (filter->decay_peak_ttl),
filter->decay_peak_age[i]);
if (falloff_time > 0) {
gdouble falloff_dB;
gdouble falloff;
gdouble length; /* length of falloff time in seconds */
length = (gdouble) falloff_time / (gdouble) GST_SECOND;
falloff_dB = filter->decay_peak_falloff * length;
falloff = pow (10, falloff_dB / -20.0);
GST_LOG_OBJECT (filter,
"falloff: current %f, base %f, interval %" GST_TIME_FORMAT
", dB falloff %f, factor %e",
filter->decay_peak[i], filter->decay_peak_base[i],
GST_TIME_ARGS (falloff_time), falloff_dB, falloff);
filter->decay_peak[i] = filter->decay_peak_base[i] * falloff;
GST_LOG_OBJECT (filter,
"peak is %" GST_TIME_FORMAT " old, decayed with factor %e to %f",
GST_TIME_ARGS (filter->decay_peak_age[i]), falloff,
filter->decay_peak[i]);
} else {
GST_LOG_OBJECT (filter, "peak not old enough, not decaying");
}
/* if the peak of this run is higher, the decay peak gets reset */
if (filter->peak[i] >= filter->decay_peak[i]) {
GST_LOG_OBJECT (filter, "new peak, %f", filter->peak[i]);
filter->decay_peak[i] = filter->peak[i];
filter->decay_peak_base[i] = filter->peak[i];
filter->decay_peak_age[i] = G_GINT64_CONSTANT (0);
}
}
if (G_UNLIKELY (!filter->num_frames)) {
/* remember start timestamp for message */
filter->message_ts = GST_BUFFER_TIMESTAMP (in); filter->message_ts = GST_BUFFER_TIMESTAMP (in);
} }
filter->num_frames += num_frames;
/* do we need to message ? */ num_frames = num_int_samples / channels;
if (filter->num_frames >= filter->interval_frames) { while (num_frames > 0) {
gst_level_post_message (filter); block_size = filter->interval_frames - filter->num_frames;
block_size = MIN (block_size, num_frames);
block_int_size = block_size * channels;
GST_LOG_OBJECT (filter, "run inner loop for %u sample frames",
block_int_size);
for (i = 0; i < channels; ++i) {
if (!GST_BUFFER_FLAG_IS_SET (in, GST_BUFFER_FLAG_GAP)) {
filter->process (in_data, block_int_size, channels, &CS,
&filter->peak[i]);
GST_LOG_OBJECT (filter,
"channel %d, cumulative sum %f, over %d samples/%d channels", i, CS,
block_int_size, channels);
filter->CS[i] += CS;
} else {
filter->peak[i] = 0.0;
}
in_data += bps;
filter->decay_peak_age[i] += GST_FRAMES_TO_CLOCK_TIME (num_frames, rate);
GST_LOG_OBJECT (filter,
"[%d]: peak %f, last peak %f, decay peak %f, age %" GST_TIME_FORMAT,
i, filter->peak[i], filter->last_peak[i], filter->decay_peak[i],
GST_TIME_ARGS (filter->decay_peak_age[i]));
/* update running peak */
if (filter->peak[i] > filter->last_peak[i])
filter->last_peak[i] = filter->peak[i];
/* make decay peak fall off if too old */
falloff_time =
GST_CLOCK_DIFF (gst_gdouble_to_guint64 (filter->decay_peak_ttl),
filter->decay_peak_age[i]);
if (falloff_time > 0) {
gdouble falloff_dB;
gdouble falloff;
gdouble length; /* length of falloff time in seconds */
length = (gdouble) falloff_time / (gdouble) GST_SECOND;
falloff_dB = filter->decay_peak_falloff * length;
falloff = pow (10, falloff_dB / -20.0);
GST_LOG_OBJECT (filter,
"falloff: current %f, base %f, interval %" GST_TIME_FORMAT
", dB falloff %f, factor %e",
filter->decay_peak[i], filter->decay_peak_base[i],
GST_TIME_ARGS (falloff_time), falloff_dB, falloff);
filter->decay_peak[i] = filter->decay_peak_base[i] * falloff;
GST_LOG_OBJECT (filter,
"peak is %" GST_TIME_FORMAT " old, decayed with factor %e to %f",
GST_TIME_ARGS (filter->decay_peak_age[i]), falloff,
filter->decay_peak[i]);
} else {
GST_LOG_OBJECT (filter, "peak not old enough, not decaying");
}
/* if the peak of this run is higher, the decay peak gets reset */
if (filter->peak[i] >= filter->decay_peak[i]) {
GST_LOG_OBJECT (filter, "new peak, %f", filter->peak[i]);
filter->decay_peak[i] = filter->peak[i];
filter->decay_peak_base[i] = filter->peak[i];
filter->decay_peak_age[i] = G_GINT64_CONSTANT (0);
}
}
in_data += ((block_int_size * bps) - bps);
filter->num_frames += block_size;
num_frames -= block_size;
/* do we need to message ? */
if (filter->num_frames >= filter->interval_frames) {
gst_level_post_message (filter);
filter->message_ts += GST_FRAMES_TO_CLOCK_TIME (block_size, rate);
}
} }
gst_buffer_unmap (in, &map); gst_buffer_unmap (in, &map);

View file

@ -42,6 +42,7 @@ GstPad *mysrcpad, *mysinkpad;
"rate = (int) [ 1, MAX ], " \ "rate = (int) [ 1, MAX ], " \
"channels = (int) [ 1, 8 ]" "channels = (int) [ 1, 8 ]"
/* we use rate = 1000 here for easy buffer size calculations */
#define LEVEL_CAPS_STRING \ #define LEVEL_CAPS_STRING \
"audio/x-raw, " \ "audio/x-raw, " \
"format = (string) "GST_AUDIO_NE(S16)", " \ "format = (string) "GST_AUDIO_NE(S16)", " \
@ -66,6 +67,8 @@ static GstElement *
setup_level (void) setup_level (void)
{ {
GstElement *level; GstElement *level;
GstCaps *caps;
GstSegment segment;
GST_DEBUG ("setup_level"); GST_DEBUG ("setup_level");
level = gst_check_setup_element ("level"); level = gst_check_setup_element ("level");
@ -74,6 +77,13 @@ setup_level (void)
gst_pad_set_active (mysrcpad, TRUE); gst_pad_set_active (mysrcpad, TRUE);
gst_pad_set_active (mysinkpad, TRUE); gst_pad_set_active (mysinkpad, TRUE);
caps = gst_caps_from_string (LEVEL_CAPS_STRING);
gst_pad_set_caps (mysrcpad, caps);
gst_caps_unref (caps);
gst_segment_init (&segment, GST_FORMAT_TIME);
gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment));
return level; return level;
} }
@ -89,21 +99,39 @@ cleanup_level (GstElement * level)
gst_check_teardown_element (level); gst_check_teardown_element (level);
} }
/* create a 0.1 sec buffer */
static GstBuffer *
create_buffer (gint16 val_l, gint16 val_r)
{
GstBuffer *buf = gst_buffer_new_and_alloc (400);
GstMapInfo map;
gint j;
gint16 *data;
gst_buffer_map (buf, &map, GST_MAP_WRITE);
data = (gint16 *) map.data;
for (j = 0; j < 100; ++j) {
*(data++) = val_l;
*(data++) = val_r;
}
gst_buffer_unmap (buf, &map);
GST_BUFFER_TIMESTAMP (buf) = G_GUINT64_CONSTANT (0);
return buf;
}
GST_START_TEST (test_int16) GST_START_TEST (test_int16)
{ {
GstElement *level; GstElement *level;
GstBuffer *inbuffer, *outbuffer; GstBuffer *inbuffer, *outbuffer;
GstBus *bus; GstBus *bus;
GstCaps *caps;
GstMessage *message; GstMessage *message;
const GstStructure *structure; const GstStructure *structure;
int i, j; gint i, j;
GstMapInfo map;
gint16 *data;
const GValue *list, *value; const GValue *list, *value;
GstClockTime endtime; GstClockTime endtime;
gdouble dB; gdouble dB;
const gchar *fields[3] = { "rms", "peak", "decay" };
level = setup_level (); level = setup_level ();
g_object_set (level, "message", TRUE, "interval", GST_SECOND / 10, NULL); g_object_set (level, "message", TRUE, "interval", GST_SECOND / 10, NULL);
@ -113,17 +141,7 @@ GST_START_TEST (test_int16)
"could not set to playing"); "could not set to playing");
/* create a fake 0.1 sec buffer with a half-amplitude block signal */ /* create a fake 0.1 sec buffer with a half-amplitude block signal */
inbuffer = gst_buffer_new_and_alloc (400); inbuffer = create_buffer (16536, 16536);
gst_buffer_map (inbuffer, &map, GST_MAP_WRITE);
data = (gint16 *) map.data;
for (j = 0; j < 200; ++j) {
*data = 16536;
++data;
}
gst_buffer_unmap (inbuffer, &map);
caps = gst_caps_from_string (LEVEL_CAPS_STRING);
gst_pad_set_caps (mysrcpad, caps);
gst_caps_unref (caps);
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
/* create a bus to get the level message on */ /* create a bus to get the level message on */
@ -154,7 +172,6 @@ GST_START_TEST (test_int16)
/* block wave of half amplitude has -5.94 dB for rms, peak and decay */ /* block wave of half amplitude has -5.94 dB for rms, peak and decay */
for (i = 0; i < 2; ++i) { for (i = 0; i < 2; ++i) {
const gchar *fields[3] = { "rms", "peak", "decay" };
for (j = 0; j < 3; ++j) { for (j = 0; j < 3; ++j) {
GValueArray *arr; GValueArray *arr;
@ -167,9 +184,6 @@ GST_START_TEST (test_int16)
fail_if (dB > -5.9); fail_if (dB > -5.9);
} }
} }
fail_unless_equals_int (g_list_length (buffers), 1);
fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL);
fail_unless (inbuffer == outbuffer);
/* clean up */ /* clean up */
/* flush current messages,and future state change messages */ /* flush current messages,and future state change messages */
@ -197,12 +211,9 @@ GST_START_TEST (test_int16_panned)
GstElement *level; GstElement *level;
GstBuffer *inbuffer, *outbuffer; GstBuffer *inbuffer, *outbuffer;
GstBus *bus; GstBus *bus;
GstCaps *caps;
GstMessage *message; GstMessage *message;
const GstStructure *structure; const GstStructure *structure;
int j; gint j;
gint16 *data;
GstMapInfo map;
const GValue *list, *value; const GValue *list, *value;
GstClockTime endtime; GstClockTime endtime;
gdouble dB; gdouble dB;
@ -216,19 +227,7 @@ GST_START_TEST (test_int16_panned)
"could not set to playing"); "could not set to playing");
/* create a fake 0.1 sec buffer with a half-amplitude block signal */ /* create a fake 0.1 sec buffer with a half-amplitude block signal */
inbuffer = gst_buffer_new_and_alloc (400); inbuffer = create_buffer (0, 16536);
gst_buffer_map (inbuffer, &map, GST_MAP_WRITE);
data = (gint16 *) map.data;
for (j = 0; j < 100; ++j) {
*data = 0;
++data;
*data = 16536;
++data;
}
gst_buffer_unmap (inbuffer, &map);
caps = gst_caps_from_string (LEVEL_CAPS_STRING);
gst_pad_set_caps (mysrcpad, caps);
gst_caps_unref (caps);
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
/* create a bus to get the level message on */ /* create a bus to get the level message on */
@ -284,9 +283,6 @@ GST_START_TEST (test_int16_panned)
fail_if (dB < -6.0); fail_if (dB < -6.0);
fail_if (dB > -5.9); fail_if (dB > -5.9);
} }
fail_unless_equals_int (g_list_length (buffers), 1);
fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL);
fail_unless (inbuffer == outbuffer);
/* clean up */ /* clean up */
/* flush current messages,and future state change messages */ /* flush current messages,and future state change messages */
@ -300,6 +296,7 @@ GST_START_TEST (test_int16_panned)
gst_element_set_bus (level, NULL); gst_element_set_bus (level, NULL);
ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); ASSERT_OBJECT_REFCOUNT (bus, "bus", 1);
gst_object_unref (bus); gst_object_unref (bus);
ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1);
gst_buffer_unref (outbuffer); gst_buffer_unref (outbuffer);
fail_unless (gst_element_set_state (level, fail_unless (gst_element_set_state (level,
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null");
@ -315,12 +312,9 @@ GST_START_TEST (test_message_on_eos)
GstBuffer *inbuffer, *outbuffer; GstBuffer *inbuffer, *outbuffer;
GstEvent *event; GstEvent *event;
GstBus *bus; GstBus *bus;
GstCaps *caps;
GstMessage *message; GstMessage *message;
const GstStructure *structure; const GstStructure *structure;
int i, j; gint i, j;
GstMapInfo map;
gint16 *data;
const GValue *list, *value; const GValue *list, *value;
GstClockTime endtime; GstClockTime endtime;
gdouble dB; gdouble dB;
@ -333,17 +327,7 @@ GST_START_TEST (test_message_on_eos)
"could not set to playing"); "could not set to playing");
/* create a fake 0.1 sec buffer with a half-amplitude block signal */ /* create a fake 0.1 sec buffer with a half-amplitude block signal */
inbuffer = gst_buffer_new_and_alloc (400); inbuffer = create_buffer (16536, 16536);
gst_buffer_map (inbuffer, &map, GST_MAP_WRITE);
data = (gint16 *) map.data;
for (j = 0; j < 200; ++j) {
*data = 16536;
++data;
}
gst_buffer_unmap (inbuffer, &map);
caps = gst_caps_from_string (LEVEL_CAPS_STRING);
gst_pad_set_caps (mysrcpad, caps);
gst_caps_unref (caps);
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
/* create a bus to get the level message on */ /* create a bus to get the level message on */
@ -395,9 +379,6 @@ GST_START_TEST (test_message_on_eos)
fail_if (dB > -5.9); fail_if (dB > -5.9);
} }
} }
fail_unless_equals_int (g_list_length (buffers), 1);
fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL);
fail_unless (inbuffer == outbuffer);
/* clean up */ /* clean up */
/* flush current messages,and future state change messages */ /* flush current messages,and future state change messages */
@ -420,6 +401,54 @@ GST_START_TEST (test_message_on_eos)
GST_END_TEST; GST_END_TEST;
GST_START_TEST (test_message_interval)
{
GstElement *level;
GstBuffer *inbuffer, *outbuffer;
GstBus *bus;
GstMessage *message;
level = setup_level ();
g_object_set (level, "message", TRUE, "interval", GST_SECOND / 20, NULL);
fail_unless (gst_element_set_state (level,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
"could not set to playing");
/* create a fake 0.1 sec buffer with a half-amplitude block signal */
inbuffer = create_buffer (16536, 16536);
/* create a bus to get the level message on */
bus = gst_bus_new ();
gst_element_set_bus (level, bus);
/* pushing gives away my reference ... */
fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK);
/* ... but it ends up being collected on the global buffer list */
fail_unless_equals_int (g_list_length (buffers), 1);
fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL);
fail_unless (inbuffer == outbuffer);
/* we should get two messages per buffer */
message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1);
fail_unless (message != NULL);
gst_message_unref (message);
message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1);
fail_unless (message != NULL);
gst_message_unref (message);
gst_element_set_bus (level, NULL);
ASSERT_OBJECT_REFCOUNT (bus, "bus", 1);
gst_object_unref (bus);
gst_buffer_unref (outbuffer);
fail_unless (gst_element_set_state (level,
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null");
ASSERT_OBJECT_REFCOUNT (level, "level", 1);
cleanup_level (level);
}
GST_END_TEST;
static Suite * static Suite *
level_suite (void) level_suite (void)
@ -431,6 +460,7 @@ level_suite (void)
tcase_add_test (tc_chain, test_int16); tcase_add_test (tc_chain, test_int16);
tcase_add_test (tc_chain, test_int16_panned); tcase_add_test (tc_chain, test_int16_panned);
tcase_add_test (tc_chain, test_message_on_eos); tcase_add_test (tc_chain, test_message_on_eos);
tcase_add_test (tc_chain, test_message_interval);
return s; return s;
} }