wildmidi: cleanups and fix seeking

Remove time_per_frame, it's too inaccurate.
Fix convert for -1 values.
Use scaling to convert samples to time.
Fix segment handling.
Fix seeking.
Fix buffer clipping (not yet enabled)
Protect the WildMidi library with the object lock to avoid races when changing
properties.
Fix offsets on output buffers, for raw audio the offset contains the sample
position not byte position.
Add better DISCONT handling.

Fixes #519001
This commit is contained in:
Wim Taymans 2009-09-04 17:27:52 +02:00 committed by Wim Taymans
parent e806e7aaeb
commit f874e30998
2 changed files with 189 additions and 173 deletions

View file

@ -284,7 +284,6 @@ gst_wildmidi_init (GstWildmidi * filter, GstWildmidiClass * g_class)
filter->adapter = gst_adapter_new ();
filter->bytes_per_frame = WILDMIDI_BPS;
filter->time_per_frame = GST_SECOND / WILDMIDI_RATE;
}
static void
@ -307,14 +306,14 @@ gst_wildmidi_src_convert (GstWildmidi * wildmidi,
gboolean res = TRUE;
gint64 frames;
if (src_format == *dest_format) {
if (src_format == *dest_format || src_value == -1) {
*dest_value = src_value;
goto done;
}
switch (src_format) {
case GST_FORMAT_TIME:
frames = src_value / wildmidi->time_per_frame;
frames = gst_util_uint64_scale_int (src_value, WILDMIDI_RATE, GST_SECOND);
break;
case GST_FORMAT_BYTES:
frames = src_value / (wildmidi->bytes_per_frame);
@ -329,7 +328,8 @@ gst_wildmidi_src_convert (GstWildmidi * wildmidi,
switch (*dest_format) {
case GST_FORMAT_TIME:
*dest_value = frames * wildmidi->time_per_frame;
*dest_value =
gst_util_uint64_scale_int (frames, GST_SECOND, WILDMIDI_RATE);
break;
case GST_FORMAT_BYTES:
*dest_value = frames * wildmidi->bytes_per_frame;
@ -362,11 +362,13 @@ gst_wildmidi_src_query (GstPad * pad, GstQuery * query)
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_DURATION:
gst_query_set_duration (query, GST_FORMAT_TIME,
wildmidi->o_len * wildmidi->time_per_frame);
gst_util_uint64_scale_int (wildmidi->o_len, GST_SECOND,
WILDMIDI_RATE));
break;
case GST_QUERY_POSITION:
gst_query_set_position (query, GST_FORMAT_TIME,
wildmidi->o_segment->last_stop * wildmidi->time_per_frame);
gst_util_uint64_scale_int (wildmidi->o_segment->last_stop, GST_SECOND,
WILDMIDI_RATE));
break;
case GST_QUERY_CONVERT:
gst_query_parse_convert (query, &src_format, &src_value,
@ -402,53 +404,115 @@ gst_wildmidi_src_query (GstPad * pad, GstQuery * query)
return res;
}
static GstSegment *
gst_wildmidi_get_segment (GstWildmidi * wildmidi, GstFormat format,
gboolean update)
static GstEvent *
gst_wildmidi_get_new_segment_event (GstWildmidi * wildmidi, GstFormat format)
{
gint64 start, stop, time;
GstSegment *segment = gst_segment_new ();
gst_wildmidi_src_convert (wildmidi,
wildmidi->o_segment->format, wildmidi->o_segment->start, &format, &start);
if (wildmidi->o_segment->stop == GST_CLOCK_TIME_NONE) {
stop = GST_CLOCK_TIME_NONE;
} else {
gst_wildmidi_src_convert (wildmidi,
wildmidi->o_segment->format, wildmidi->o_segment->stop, &format, &stop);
}
gst_wildmidi_src_convert (wildmidi,
wildmidi->o_segment->format, wildmidi->o_segment->time, &format, &time);
gst_segment_set_newsegment_full (segment, update,
wildmidi->o_segment->rate, wildmidi->o_segment->applied_rate,
format, start, stop, time);
segment->last_stop = time;
return segment;
}
static GstEvent *
gst_wildmidi_get_new_segment_event (GstWildmidi * wildmidi, GstFormat format,
gboolean update)
{
GstSegment *segment;
GstEvent *event;
GstFormat src_format;
segment = gst_wildmidi_get_segment (wildmidi, format, update);
segment = wildmidi->o_segment;
src_format = segment->format;
event = gst_event_new_new_segment_full (update,
segment->rate, segment->applied_rate, segment->format,
segment->start, segment->stop, segment->time);
/* convert the segment values to the target format */
gst_wildmidi_src_convert (wildmidi, src_format, segment->start, &format,
&start);
gst_wildmidi_src_convert (wildmidi, src_format, segment->stop, &format,
&stop);
gst_wildmidi_src_convert (wildmidi, src_format, segment->time, &format,
&time);
gst_segment_free (segment);
event = gst_event_new_new_segment_full (FALSE,
segment->rate, segment->applied_rate, format, start, stop, time);
return event;
}
static gboolean
gst_wildmidi_do_seek (GstWildmidi * wildmidi, GstEvent * event)
{
gdouble rate;
GstFormat src_format, dst_format;
GstSeekFlags flags;
GstSeekType start_type, stop_type;
gint64 start, stop;
gboolean flush, update, accurate;
gboolean res;
unsigned long int sample;
GstSegment *segment;
if (!wildmidi->song)
return FALSE;
gst_event_parse_seek (event, &rate, &src_format, &flags,
&start_type, &start, &stop_type, &stop);
/* convert the input format to samples */
dst_format = GST_FORMAT_DEFAULT;
res = TRUE;
if (start_type != GST_SEEK_TYPE_NONE) {
res =
gst_wildmidi_src_convert (wildmidi, src_format, start, &dst_format,
&start);
}
if (res && stop_type != GST_SEEK_TYPE_NONE) {
res =
gst_wildmidi_src_convert (wildmidi, src_format, stop, &dst_format,
&stop);
}
/* unsupported format */
if (!res)
return res;
flush = ((flags & GST_SEEK_FLAG_FLUSH) == GST_SEEK_FLAG_FLUSH);
accurate = ((flags & GST_SEEK_FLAG_ACCURATE) == GST_SEEK_FLAG_ACCURATE);
if (flush) {
GST_DEBUG ("performing flush");
gst_pad_push_event (wildmidi->srcpad, gst_event_new_flush_start ());
} else {
gst_pad_stop_task (wildmidi->sinkpad);
}
segment = wildmidi->o_segment;
GST_PAD_STREAM_LOCK (wildmidi->sinkpad);
if (flush) {
gst_pad_push_event (wildmidi->srcpad, gst_event_new_flush_stop ());
}
/* update the segment now */
gst_segment_set_seek (segment, rate, dst_format, flags,
start_type, start, stop_type, stop, &update);
/* we need to seek to last_stop in the segment now, sample will be updated */
sample = segment->last_stop;
GST_OBJECT_LOCK (wildmidi);
if (accurate) {
WildMidi_SampledSeek (wildmidi->song, &sample);
} else {
WildMidi_FastSeek (wildmidi->song, &sample);
}
GST_OBJECT_UNLOCK (wildmidi);
segment->start = segment->time = segment->last_stop = sample;
gst_pad_push_event (wildmidi->srcpad,
gst_wildmidi_get_new_segment_event (wildmidi, GST_FORMAT_TIME));
gst_pad_start_task (wildmidi->sinkpad,
(GstTaskFunction) gst_wildmidi_loop, wildmidi->sinkpad);
wildmidi->discont = TRUE;
GST_PAD_STREAM_UNLOCK (wildmidi->sinkpad);
GST_DEBUG ("seek done");
return TRUE;
}
static gboolean
gst_wildmidi_src_event (GstPad * pad, GstEvent * event)
{
@ -459,66 +523,7 @@ gst_wildmidi_src_event (GstPad * pad, GstEvent * event)
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
{
gdouble rate;
GstFormat src_format, dst_format;
GstSeekFlags flags;
GstSeekType start_type, stop_type;
gint64 orig_start, start, stop;
gboolean flush, update;
if (!wildmidi->song)
break;
gst_event_parse_seek (event, &rate, &src_format, &flags,
&start_type, &orig_start, &stop_type, &stop);
dst_format = GST_FORMAT_DEFAULT;
gst_wildmidi_src_convert (wildmidi, src_format, orig_start,
&dst_format, &start);
gst_wildmidi_src_convert (wildmidi, src_format, stop, &dst_format, &stop);
flush = ((flags & GST_SEEK_FLAG_FLUSH) == GST_SEEK_FLAG_FLUSH);
if (flush) {
GST_DEBUG ("performing flush");
gst_pad_push_event (wildmidi->srcpad, gst_event_new_flush_start ());
} else {
gst_pad_stop_task (wildmidi->sinkpad);
}
GST_PAD_STREAM_LOCK (wildmidi->sinkpad);
if (flush) {
gst_pad_push_event (wildmidi->srcpad, gst_event_new_flush_stop ());
}
gst_segment_set_seek (wildmidi->o_segment, rate, dst_format, flags,
start_type, start, stop_type, stop, &update);
if ((flags && GST_SEEK_FLAG_SEGMENT) == GST_SEEK_FLAG_SEGMENT) {
GST_DEBUG_OBJECT (wildmidi, "received segment seek %d, %d",
(gint) start_type, (gint) stop_type);
} else {
GST_DEBUG_OBJECT (wildmidi, "received normal seek %d",
(gint) start_type);
update = FALSE;
}
gst_pad_push_event (wildmidi->srcpad,
gst_wildmidi_get_new_segment_event (wildmidi, GST_FORMAT_TIME,
update));
wildmidi->o_seek = TRUE;
gst_pad_start_task (wildmidi->sinkpad,
(GstTaskFunction) gst_wildmidi_loop, wildmidi->sinkpad);
GST_PAD_STREAM_UNLOCK (wildmidi->sinkpad);
GST_DEBUG ("seek done");
}
res = TRUE;
res = gst_wildmidi_do_seek (wildmidi, event);
break;
default:
break;
@ -551,33 +556,41 @@ gst_wildmidi_activatepull (GstPad * pad, gboolean active)
static GstBuffer *
gst_wildmidi_clip_buffer (GstWildmidi * wildmidi, GstBuffer * buffer)
{
gint64 start, stop;
gint64 new_start, new_stop;
gint64 offset, length;
GstBuffer *out;
guint64 bpf;
/* clipping disabled for now */
return buffer;
start = GST_BUFFER_OFFSET (buffer);
stop = GST_BUFFER_OFFSET_END (buffer);
if (!gst_segment_clip (wildmidi->o_segment, GST_FORMAT_DEFAULT,
GST_BUFFER_OFFSET (buffer), GST_BUFFER_OFFSET_END (buffer),
&new_start, &new_stop)) {
start, stop, &new_start, &new_stop)) {
gst_buffer_unref (buffer);
return NULL;
}
if (GST_BUFFER_OFFSET (buffer) == new_start &&
GST_BUFFER_OFFSET_END (buffer) == new_stop)
if (start == new_start && stop == new_stop)
return buffer;
offset = new_start - GST_BUFFER_OFFSET (buffer);
offset = new_start - start;
length = new_stop - new_start;
out = gst_buffer_create_sub (buffer, offset * wildmidi->bytes_per_frame,
length * wildmidi->bytes_per_frame);
bpf = wildmidi->bytes_per_frame;
out = gst_buffer_create_sub (buffer, offset * bpf, length * bpf);
GST_BUFFER_OFFSET (out) = new_start;
GST_BUFFER_OFFSET_END (out) = new_stop;
GST_BUFFER_TIMESTAMP (out) = new_start * wildmidi->time_per_frame;
GST_BUFFER_DURATION (out) = (new_stop - new_start) * wildmidi->time_per_frame;
GST_BUFFER_TIMESTAMP (out) =
gst_util_uint64_scale_int (new_start, GST_SECOND, WILDMIDI_RATE);
GST_BUFFER_DURATION (out) =
gst_util_uint64_scale_int (new_stop, GST_SECOND, WILDMIDI_RATE) -
GST_BUFFER_TIMESTAMP (out);
gst_buffer_unref (buffer);
@ -591,44 +604,50 @@ gst_wildmidi_get_buffer (GstWildmidi * wildmidi)
size_t bytes_read;
gint64 samples;
GstBuffer *buffer;
GstSegment *segment;
guint8 *data;
guint size;
guint bpf;
buffer = gst_buffer_new_and_alloc (256 * wildmidi->bytes_per_frame);
bpf = wildmidi->bytes_per_frame;
bytes_read =
WildMidi_GetOutput (wildmidi->song, (char *) GST_BUFFER_DATA (buffer),
(unsigned long int) GST_BUFFER_SIZE (buffer));
buffer = gst_buffer_new_and_alloc (256 * bpf);
data = GST_BUFFER_DATA (buffer);
size = GST_BUFFER_SIZE (buffer);
GST_OBJECT_LOCK (wildmidi);
bytes_read = WildMidi_GetOutput (wildmidi->song, (char *) data,
(unsigned long int) size);
GST_OBJECT_UNLOCK (wildmidi);
if (bytes_read == 0) {
gst_buffer_unref (buffer);
return NULL;
}
GST_BUFFER_OFFSET (buffer) =
wildmidi->o_segment->last_stop * wildmidi->bytes_per_frame;
/* adjust buffer size */
size = GST_BUFFER_SIZE (buffer) = bytes_read;
segment = wildmidi->o_segment;
GST_BUFFER_OFFSET (buffer) = segment->last_stop;
GST_BUFFER_TIMESTAMP (buffer) =
wildmidi->o_segment->last_stop * wildmidi->time_per_frame;
gst_util_uint64_scale_int (segment->last_stop, GST_SECOND, WILDMIDI_RATE);
if (bytes_read < GST_BUFFER_SIZE (buffer)) {
GstBuffer *old = buffer;
samples = size / bpf;
segment->last_stop += samples;
buffer = gst_buffer_create_sub (buffer, 0, bytes_read);
gst_buffer_unref (old);
}
samples = GST_BUFFER_SIZE (buffer) / wildmidi->bytes_per_frame;
wildmidi->o_segment->last_stop += samples;
GST_BUFFER_OFFSET_END (buffer) =
wildmidi->o_segment->last_stop * wildmidi->bytes_per_frame;
GST_BUFFER_DURATION (buffer) = samples * wildmidi->time_per_frame;
GST_BUFFER_OFFSET_END (buffer) = segment->last_stop;
GST_BUFFER_DURATION (buffer) =
gst_util_uint64_scale_int (segment->last_stop, GST_SECOND,
WILDMIDI_RATE) - GST_BUFFER_TIMESTAMP (buffer);
GST_DEBUG_OBJECT (wildmidi,
"generated buffer %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT
"buffer ts: %" GST_TIME_FORMAT ", dur: %" GST_TIME_FORMAT
" (%d samples)",
GST_TIME_ARGS ((guint64) GST_BUFFER_TIMESTAMP (buffer)),
GST_TIME_ARGS (((guint64) (GST_BUFFER_TIMESTAMP (buffer) +
GST_BUFFER_DURATION (buffer)))), samples);
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)), samples);
return gst_wildmidi_clip_buffer (wildmidi, buffer);
}
@ -647,13 +666,11 @@ gst_wildmidi_parse_song (GstWildmidi * wildmidi)
data = gst_adapter_take (wildmidi->adapter, size);
/* this method takes our memory block */
GST_OBJECT_LOCK (wildmidi);
wildmidi->song = WildMidi_OpenBuffer (data, size);
if (!wildmidi->song) {
GST_ELEMENT_ERROR (wildmidi, STREAM, DECODE, (NULL),
("Unable to parse midi"));
return GST_FLOW_ERROR;
}
if (!wildmidi->song)
goto open_failed;
WildMidi_LoadSamples (wildmidi->song);
@ -663,21 +680,33 @@ gst_wildmidi_parse_song (GstWildmidi * wildmidi)
wildmidi->high_quality);
info = WildMidi_GetInfo (wildmidi->song);
GST_OBJECT_UNLOCK (wildmidi);
wildmidi->o_len = info->approx_total_samples;
outcaps = gst_caps_copy (gst_pad_get_pad_template_caps (wildmidi->srcpad));
gst_pad_set_caps (wildmidi->srcpad, outcaps);
gst_caps_unref (outcaps);
/* we keep an internal segment in samples */
gst_segment_set_newsegment (wildmidi->o_segment, FALSE, 1.0,
GST_FORMAT_DEFAULT, 0, GST_CLOCK_TIME_NONE, 0);
gst_pad_push_event (wildmidi->srcpad,
gst_wildmidi_get_new_segment_event (wildmidi, GST_FORMAT_TIME, FALSE));
gst_wildmidi_get_new_segment_event (wildmidi, GST_FORMAT_TIME));
GST_DEBUG_OBJECT (wildmidi, "Parsing song done");
return GST_FLOW_OK;
/* ERRORS */
open_failed:
{
GST_OBJECT_UNLOCK (wildmidi);
GST_ELEMENT_ERROR (wildmidi, STREAM, DECODE, (NULL),
("Unable to parse midi data"));
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
@ -686,27 +715,12 @@ gst_wildmidi_do_play (GstWildmidi * wildmidi)
GstBuffer *out;
GstFlowReturn ret;
if (wildmidi->o_seek) {
unsigned long int sample;
/* perform a seek internally */
sample = wildmidi->o_segment->time;
if (wildmidi->accurate_seek) {
WildMidi_SampledSeek (wildmidi->song, &sample);
} else {
WildMidi_FastSeek (wildmidi->song, &sample);
}
wildmidi->o_segment->last_stop = wildmidi->o_segment->time = sample;
}
if (!(out = gst_wildmidi_get_buffer (wildmidi)))
goto eos;
if (wildmidi->o_seek) {
if (wildmidi->discont) {
GST_BUFFER_FLAG_SET (out, GST_BUFFER_FLAG_DISCONT);
wildmidi->o_seek = FALSE;
wildmidi->discont = FALSE;
}
gst_buffer_set_caps (out, GST_PAD_CAPS (wildmidi->srcpad));
@ -718,7 +732,6 @@ gst_wildmidi_do_play (GstWildmidi * wildmidi)
eos:
{
GST_LOG_OBJECT (wildmidi, "Song ended");
wildmidi->o_seek = FALSE;
return GST_FLOW_UNEXPECTED;
}
}
@ -782,7 +795,7 @@ gst_wildmidi_loop (GstPad * sinkpad)
wildmidi->state = GST_WILDMIDI_STATE_PARSE;
} else if (ret != GST_FLOW_OK) {
GST_ELEMENT_ERROR (wildmidi, STREAM, DECODE, (NULL),
("Unable to load song"));
("Unable to read song"));
goto pause;
} else {
GST_DEBUG_OBJECT (wildmidi, "pushing buffer");
@ -792,15 +805,11 @@ gst_wildmidi_loop (GstPad * sinkpad)
break;
}
case GST_WILDMIDI_STATE_PARSE:
{
if (!wildmidi->song) {
ret = gst_wildmidi_parse_song (wildmidi);
if (ret != GST_FLOW_OK)
goto pause;
}
ret = gst_wildmidi_parse_song (wildmidi);
if (ret != GST_FLOW_OK)
goto pause;
wildmidi->state = GST_WILDMIDI_STATE_PLAY;
break;
}
case GST_WILDMIDI_STATE_PLAY:
ret = gst_wildmidi_do_play (wildmidi);
if (ret != GST_FLOW_OK)
@ -848,6 +857,7 @@ gst_wildmidi_change_state (GstElement * element, GstStateChange transition)
case GST_STATE_CHANGE_READY_TO_PAUSED:
wildmidi->offset = 0;
wildmidi->state = GST_WILDMIDI_STATE_LOAD;
wildmidi->discont = FALSE;
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
@ -861,9 +871,11 @@ gst_wildmidi_change_state (GstElement * element, GstStateChange transition)
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_OBJECT_LOCK (wildmidi);
if (wildmidi->song)
WildMidi_Close (wildmidi->song);
wildmidi->song = NULL;
GST_OBJECT_UNLOCK (wildmidi);
gst_adapter_clear (wildmidi->adapter);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
@ -887,16 +899,20 @@ gst_wildmidi_set_property (GObject * object, guint prop_id,
switch (prop_id) {
case ARG_LINEAR_VOLUME:
GST_OBJECT_LOCK (object);
wildmidi->linear_volume = g_value_get_boolean (value);
if (wildmidi->song)
WildMidi_SetOption (wildmidi->song, WM_MO_LINEAR_VOLUME,
wildmidi->linear_volume);
GST_OBJECT_UNLOCK (object);
break;
case ARG_HIGH_QUALITY:
GST_OBJECT_LOCK (object);
wildmidi->high_quality = g_value_get_boolean (value);
if (wildmidi->song)
WildMidi_SetOption (wildmidi->song, WM_MO_EXPENSIVE_INTERPOLATION,
wildmidi->high_quality);
GST_OBJECT_UNLOCK (object);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -916,10 +932,14 @@ gst_wildmidi_get_property (GObject * object, guint prop_id,
switch (prop_id) {
case ARG_LINEAR_VOLUME:
GST_OBJECT_LOCK (object);
g_value_set_boolean (value, wildmidi->linear_volume);
GST_OBJECT_UNLOCK (object);
break;
case ARG_HIGH_QUALITY:
GST_OBJECT_LOCK (object);
g_value_set_boolean (value, wildmidi->high_quality);
GST_OBJECT_UNLOCK (object);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);

View file

@ -66,16 +66,12 @@ struct _GstWildmidi
guint64 offset;
/* output data */
gboolean o_new_segment, o_seek;
gboolean discont;
GstSegment o_segment[1];
gint64 o_len;
/* format of the stream */
gint64 bytes_per_frame;
GstClockTime time_per_frame;
/* options */
gboolean accurate_seek;
guint bytes_per_frame;
/* wildmidi settings */
gboolean high_quality;