* gst-launch-1.0 gnomevfssrc location=http://www.example.org/sine.wav ! queue ! wavparse ! audioconvert ! alsasink
* ]| Stream data from a network url.
* </refsect2>
- *
- * Last reviewed on 2007-02-14 (0.10.6)
*/
/*
GST_DEBUG_CATEGORY_STATIC (wavparse_debug);
#define GST_CAT_DEFAULT (wavparse_debug)
+#define GST_RIFF_TAG_Fake GST_MAKE_FOURCC ('F','a','k','e')
+
#define GST_BWF_TAG_iXML GST_MAKE_FOURCC ('i','X','M','L')
#define GST_BWF_TAG_qlty GST_MAKE_FOURCC ('q','l','t','y')
#define GST_BWF_TAG_mext GST_MAKE_FOURCC ('m','e','x','t')
#define GST_BWF_TAG_link GST_MAKE_FOURCC ('l','i','n','k')
#define GST_BWF_TAG_axml GST_MAKE_FOURCC ('a','x','m','l')
+/* Data size chunk of RF64,
+ * see http://tech.ebu.ch/docs/tech/tech3306-2009.pdf */
+#define GST_RS64_TAG_DS64 GST_MAKE_FOURCC ('d','s','6','4')
+
static void gst_wavparse_dispose (GObject * object);
static gboolean gst_wavparse_sink_activate (GstPad * sinkpad,
not_wav:
{
GST_ELEMENT_ERROR (element, STREAM, WRONG_TYPE, (NULL),
- ("File is not a WAVE file: %" GST_FOURCC_FORMAT,
- GST_FOURCC_ARGS (doctype)));
+ ("File is not a WAVE file: 0x%" G_GINT32_MODIFIER "x", doctype));
return FALSE;
}
}
*bytepos = gst_util_uint64_scale_ceil (ts, (guint64) wav->bps, GST_SECOND);
return TRUE;
} else if (wav->fact) {
- guint64 bps =
- gst_util_uint64_scale_int (wav->datasize, wav->rate, wav->fact);
+ guint64 bps = gst_util_uint64_scale (wav->datasize, wav->rate, wav->fact);
*bytepos = gst_util_uint64_scale_ceil (ts, bps, GST_SECOND);
return TRUE;
}
gboolean update;
GstSegment seeksegment = { 0, };
gint64 last_stop;
+ guint32 seqnum = 0;
if (event) {
GST_DEBUG_OBJECT (wav, "doing seek with event");
gst_event_parse_seek (event, &rate, &format, &flags,
&cur_type, &cur, &stop_type, &stop);
+ seqnum = gst_event_get_seqnum (event);
/* no negative rates yet */
if (rate < 0.0)
/* BYTE seek event */
event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type, cur,
stop_type, stop);
+ gst_event_set_seqnum (event, seqnum);
res = gst_pad_push_event (wav->sinkpad, event);
}
return res;
* as it completes one iteration (and thus might block when the sink is
* blocking in preroll). */
if (flush) {
+ GstEvent *fevent;
GST_DEBUG_OBJECT (wav, "sending flush start");
- gst_pad_push_event (wav->srcpad, gst_event_new_flush_start ());
+
+ fevent = gst_event_new_flush_start ();
+ gst_event_set_seqnum (fevent, seqnum);
+ gst_pad_push_event (wav->sinkpad, gst_event_ref (fevent));
+ gst_pad_push_event (wav->srcpad, fevent);
} else {
gst_pad_pause_task (wav->sinkpad);
}
if (gst_pad_peer_query_duration (wav->sinkpad, bformat, &upstream_size))
wav->end_offset = MIN (wav->end_offset, upstream_size);
+ if (wav->datasize > 0 && wav->end_offset > wav->datastart + wav->datasize)
+ wav->end_offset = wav->datastart + wav->datasize;
+
/* this is the range of bytes we will use for playback */
wav->offset = MIN (wav->offset, wav->end_offset);
wav->dataleft = wav->end_offset - wav->offset;
/* prepare for streaming again */
if (flush) {
+ GstEvent *fevent;
+
/* if we sent a FLUSH_START, we now send a FLUSH_STOP */
GST_DEBUG_OBJECT (wav, "sending flush stop");
- gst_pad_push_event (wav->srcpad, gst_event_new_flush_stop (TRUE));
+
+ fevent = gst_event_new_flush_stop (TRUE);
+ gst_event_set_seqnum (fevent, seqnum);
+ gst_pad_push_event (wav->sinkpad, gst_event_ref (fevent));
+ gst_pad_push_event (wav->srcpad, fevent);
}
/* now we did the seek and can activate the new segment values */
if (wav->start_segment)
gst_event_unref (wav->start_segment);
wav->start_segment = gst_event_new_segment (&wav->segment);
+ gst_event_set_seqnum (wav->start_segment, seqnum);
/* mark discont if we are going to stream from another position. */
if (last_stop != wav->segment.position) {
return TRUE;
} else if (wav->fact) {
wav->duration =
- gst_util_uint64_scale_int_ceil (GST_SECOND, wav->fact, wav->rate);
+ gst_util_uint64_scale_ceil (GST_SECOND, wav->fact, wav->rate);
GST_INFO_OBJECT (wav, "Got duration (fact) %" GST_TIME_FORMAT,
GST_TIME_ARGS (wav->duration));
return TRUE;
while (size >= 8) {
ltag = GST_READ_UINT32_LE (data + offset);
lsize = GST_READ_UINT32_LE (data + offset + 4);
+
+ if (lsize + 8 > size) {
+ GST_WARNING_OBJECT (wav, "Invalid adtl size: %u + 8 > %u", lsize, size);
+ return FALSE;
+ }
+
switch (ltag) {
case GST_RIFF_TAG_labl:
gst_wavparse_labl_chunk (wav, data + offset, size);
#define MAX_BUFFER_SIZE 4096
+static gboolean
+parse_ds64 (GstWavParse * wav, GstBuffer * buf)
+{
+ GstMapInfo map;
+ guint32 dataSizeLow, dataSizeHigh;
+ guint32 sampleCountLow, sampleCountHigh;
+
+ gst_buffer_map (buf, &map, GST_MAP_READ);
+ dataSizeLow = GST_READ_UINT32_LE (map.data + 2 * 4);
+ dataSizeHigh = GST_READ_UINT32_LE (map.data + 3 * 4);
+ sampleCountLow = GST_READ_UINT32_LE (map.data + 4 * 4);
+ sampleCountHigh = GST_READ_UINT32_LE (map.data + 5 * 4);
+ gst_buffer_unmap (buf, &map);
+ if (dataSizeHigh != 0xFFFFFFFF && dataSizeLow != 0xFFFFFFFF) {
+ wav->datasize = ((guint64) dataSizeHigh << 32) | dataSizeLow;
+ }
+ if (sampleCountHigh != 0xFFFFFFFF && sampleCountLow != 0xFFFFFFFF) {
+ wav->fact = ((guint64) sampleCountHigh << 32) | sampleCountLow;
+ }
+
+ GST_DEBUG_OBJECT (wav, "Got 'ds64' TAG, datasize : %" G_GINT64_FORMAT
+ " fact: %" G_GINT64_FORMAT, wav->datasize, wav->fact);
+ return TRUE;
+}
+
static GstFlowReturn
gst_wavparse_stream_headers (GstWavParse * wav)
{
gboolean gotdata = FALSE;
GstCaps *caps = NULL;
gchar *codec_name = NULL;
- GstEvent **event_p;
gint64 upstream_size = 0;
+ GstStructure *s;
/* search for "_fmt" chunk, which should be first */
while (!wav->got_fmt) {
tag == GST_RIFF_TAG_id3 || tag == GST_RIFF_TAG_IDVX ||
tag == GST_BWF_TAG_iXML || tag == GST_BWF_TAG_qlty ||
tag == GST_BWF_TAG_mext || tag == GST_BWF_TAG_levl ||
- tag == GST_BWF_TAG_link || tag == GST_BWF_TAG_axml) {
+ tag == GST_BWF_TAG_link || tag == GST_BWF_TAG_axml ||
+ tag == GST_RIFF_TAG_Fake) {
GST_DEBUG_OBJECT (wav, "skipping %" GST_FOURCC_FORMAT " chunk",
GST_FOURCC_ARGS (tag));
gst_buffer_unref (buf);
continue;
}
+ if (tag == GST_RS64_TAG_DS64) {
+ if (!parse_ds64 (wav, buf))
+ goto fail;
+ else
+ continue;
+ }
+
if (tag != GST_RIFF_TAG_fmt)
goto invalid_wav;
if (!caps)
goto unknown_format;
+ /* If we got raw audio from upstream, we remove the codec_data field,
+ * which may have been added if the wav header included an extended
+ * chunk. We want to keep it for non raw audio.
+ */
+ s = gst_caps_get_structure (caps, 0);
+ if (s && gst_structure_has_name (s, "audio/x-raw")) {
+ gst_structure_remove_field (s, "codec_data");
+ }
+
/* do more sanity checks of header fields
* (these can be sanitized by gst_riff_create_audio_caps()
*/
}
GST_INFO_OBJECT (wav,
- "Got TAG: %" GST_FOURCC_FORMAT ", offset %" G_GUINT64_FORMAT,
- GST_FOURCC_ARGS (tag), wav->offset);
+ "Got TAG: %" GST_FOURCC_FORMAT ", offset %" G_GUINT64_FORMAT ", size %"
+ G_GUINT32_FORMAT, GST_FOURCC_ARGS (tag), wav->offset, size);
+
+ /* Maximum valid size is INT_MAX */
+ if (size & 0x80000000) {
+ GST_WARNING_OBJECT (wav, "Invalid size, clipping to 0x7fffffff");
+ size = 0x7fffffff;
+ }
+
+ /* Clip to upstream size if known */
+ if (wav->datasize > 0 && size + wav->offset > wav->datasize) {
+ GST_WARNING_OBJECT (wav, "Clipping chunk size to file size");
+ size = wav->datasize - wav->offset;
+ }
/* wav is a st00pid format, we don't know for sure where data starts.
* So we have to go bit by bit until we find the 'data' header
*/
switch (tag) {
case GST_RIFF_TAG_data:{
+ guint64 size64;
+
GST_DEBUG_OBJECT (wav, "Got 'data' TAG, size : %u", size);
+ size64 = size;
if (wav->ignore_length) {
GST_DEBUG_OBJECT (wav, "Ignoring length");
- size = 0;
+ size64 = 0;
}
if (wav->streaming) {
gst_adapter_flush (wav->adapter, 8);
}
wav->offset += 8;
wav->datastart = wav->offset;
+ /* use size from ds64 chunk if available */
+ if (size64 == -1 && wav->datasize > 0) {
+ GST_DEBUG_OBJECT (wav, "Using ds64 datasize");
+ size64 = wav->datasize;
+ }
/* If size is zero, then the data chunk probably actually extends to
the end of the file */
- if (size == 0 && upstream_size) {
- size = upstream_size - wav->datastart;
+ if (size64 == 0 && upstream_size) {
+ size64 = upstream_size - wav->datastart;
}
/* Or the file might be truncated */
else if (upstream_size) {
- size = MIN (size, (upstream_size - wav->datastart));
+ size64 = MIN (size64, (upstream_size - wav->datastart));
}
- wav->datasize = (guint64) size;
- wav->dataleft = (guint64) size;
- wav->end_offset = size + wav->datastart;
+ wav->datasize = size64;
+ wav->dataleft = size64;
+ wav->end_offset = size64 + wav->datastart;
if (!wav->streaming) {
/* We will continue parsing tags 'till end */
- wav->offset += size;
+ wav->offset += size64;
}
- GST_DEBUG_OBJECT (wav, "datasize = %u", size);
+ GST_DEBUG_OBJECT (wav, "datasize = %" G_GUINT64_FORMAT, size64);
break;
}
case GST_RIFF_TAG_fact:{
- if (wav->format != GST_RIFF_WAVE_FORMAT_MPEGL12 &&
+ if (wav->fact == 0 &&
+ wav->format != GST_RIFF_WAVE_FORMAT_MPEGL12 &&
wav->format != GST_RIFF_WAVE_FORMAT_MPEGL3) {
const guint data_size = 4;
wav->fact = GUINT32_FROM_LE (wav->fact);
gst_buffer_unref (buf);
}
- GST_DEBUG_OBJECT (wav, "have fact %u", wav->fact);
+ GST_DEBUG_OBJECT (wav, "have fact %" G_GUINT64_FORMAT, wav->fact);
wav->offset += 8 + GST_ROUND_UP_2 (size);
break;
} else {
break;
}
case GST_RIFF_LIST_adtl:{
- const gint data_size = size;
+ const gint data_size = size - 4;
GST_INFO_OBJECT (wav, "Have 'adtl' LIST, size %u", data_size);
if (wav->streaming) {
const guint8 *data = NULL;
gst_adapter_flush (wav->adapter, 12);
+ wav->offset += 12;
data = gst_adapter_map (wav->adapter, data_size);
gst_wavparse_adtl_chunk (wav, data, data_size);
gst_adapter_unmap (wav->adapter);
gst_buffer_unref (buf);
buf = NULL;
+ wav->offset += 12;
if ((res =
- gst_pad_pull_range (wav->sinkpad, wav->offset + 12,
+ gst_pad_pull_range (wav->sinkpad, wav->offset,
data_size, &buf)) != GST_FLOW_OK)
goto header_read_error;
gst_buffer_map (buf, &map, GST_MAP_READ);
data_size);
gst_buffer_unmap (buf, &map);
}
+ wav->offset += GST_ROUND_UP_2 (data_size);
break;
}
default:
* the right newsegment event downstream. */
gst_wavparse_perform_seek (wav, wav->seek_event);
/* remove pending event */
- event_p = &wav->seek_event;
- gst_event_replace (event_p, NULL);
+ gst_event_replace (&wav->seek_event, NULL);
/* we just started, we are discont */
wav->discont = TRUE;
* that is, buffers not too small either size or time wise
* so we do not end up with too many of them */
/* var abuse */
- upstream_size = 0;
- gst_wavparse_time_to_bytepos (wav, 40 * GST_MSECOND, &upstream_size);
- wav->max_buf_size = upstream_size;
+ if (gst_wavparse_time_to_bytepos (wav, 40 * GST_MSECOND, &upstream_size))
+ wav->max_buf_size = upstream_size;
+ else
+ wav->max_buf_size = 0;
wav->max_buf_size = MAX (wav->max_buf_size, MAX_BUFFER_SIZE);
if (wav->blockalign > 0)
wav->max_buf_size -= (wav->max_buf_size % wav->blockalign);
/* ERROR */
exit:
{
- if (codec_name)
- g_free (codec_name);
- if (header)
- g_free (header);
+ g_free (codec_name);
+ g_free (header);
if (caps)
gst_caps_unref (caps);
return res;
{
GstWavParse *wav = GST_WAVPARSE (element);
gboolean res = FALSE;
- GstEvent **event_p;
GST_DEBUG_OBJECT (wav, "received event %s", GST_EVENT_TYPE_NAME (event));
} else {
GST_DEBUG_OBJECT (wav, "queuing seek for later");
- event_p = &wav->seek_event;
- gst_event_replace (event_p, event);
+ gst_event_replace (&wav->seek_event, event);
/* we always return true */
res = TRUE;
s = gst_caps_get_structure (caps, 0);
if (!gst_structure_has_name (s, "audio/x-dts"))
return FALSE;
- if (prob >= GST_TYPE_FIND_LIKELY)
+ /* typefind behavior for DTS:
+ * MAXIMUM: multiple frame syncs detected, certainly DTS
+ * LIKELY: single frame sync at offset 0. Maybe DTS?
+ * POSSIBLE: single frame sync, not at offset 0. Highly unlikely
+ * to be DTS. */
+ if (prob > GST_TYPE_FIND_LIKELY)
return TRUE;
- /* DTS at non-0 offsets and without second sync may yield POSSIBLE .. */
- if (prob < GST_TYPE_FIND_POSSIBLE)
+ if (prob <= GST_TYPE_FIND_POSSIBLE)
return FALSE;
- /* .. in which case we want at least a valid-looking rate and channels */
+ /* for maybe, check for at least a valid-looking rate and channels */
if (!gst_structure_has_field (s, "channels"))
return FALSE;
/* and for extra assurance we could also check the rate from the DTS frame
return gst_structure_has_field (s, "rate");
}
+static GstTagList *
+gst_wavparse_get_upstream_tags (GstWavParse * wav, GstTagScope scope)
+{
+ GstTagList *tags = NULL;
+ GstEvent *ev;
+ gint i;
+
+ i = 0;
+ while ((ev = gst_pad_get_sticky_event (wav->sinkpad, GST_EVENT_TAG, i++))) {
+ gst_event_parse_tag (ev, &tags);
+ if (tags != NULL && gst_tag_list_get_scope (tags) == scope) {
+ tags = gst_tag_list_copy (tags);
+ gst_tag_list_remove_tag (tags, GST_TAG_CONTAINER_FORMAT);
+ gst_event_unref (ev);
+ break;
+ }
+ tags = NULL;
+ gst_event_unref (ev);
+ }
+ return tags;
+}
+
static void
gst_wavparse_add_src_pad (GstWavParse * wav, GstBuffer * buf)
{
GstStructure *s;
+ GstTagList *tags, *utags;
GST_DEBUG_OBJECT (wav, "adding src pad");
wav->start_segment = NULL;
}
- if (wav->tags) {
- gst_pad_push_event (wav->srcpad, gst_event_new_tag (wav->tags));
+ /* upstream tags, e.g. from id3/ape tag before the wav file; assume for now
+ * that there'll be only one scope/type of tag list from upstream, if any */
+ utags = gst_wavparse_get_upstream_tags (wav, GST_TAG_SCOPE_GLOBAL);
+ if (utags == NULL)
+ utags = gst_wavparse_get_upstream_tags (wav, GST_TAG_SCOPE_STREAM);
+
+ /* if there's a tag upstream it's probably been added to override the
+ * tags from inside the wav header, so keep upstream tags if in doubt */
+ tags = gst_tag_list_merge (utags, wav->tags, GST_TAG_MERGE_KEEP);
+
+ if (wav->tags != NULL) {
+ gst_tag_list_unref (wav->tags);
wav->tags = NULL;
}
+
+ if (utags != NULL)
+ gst_tag_list_unref (utags);
+
+ /* send tags downstream, if any */
+ if (tags != NULL)
+ gst_pad_push_event (wav->srcpad, gst_event_new_tag (tags));
}
static GstFlowReturn
GST_LOG_OBJECT (wav, "Got only %" G_GSIZE_FORMAT " bytes of data", size);
if (size >= wav->blockalign) {
- buf = gst_buffer_make_writable (buf);
- gst_buffer_resize (buf, 0, size - (size % wav->blockalign));
+ if (wav->blockalign > 0) {
+ buf = gst_buffer_make_writable (buf);
+ gst_buffer_resize (buf, 0, size - (size % wav->blockalign));
+ }
} else {
gst_buffer_unref (buf);
goto found_eos;
guint av;
if ((av = gst_adapter_available (wav->adapter)) > 0) {
- wav->dataleft = av;
- wav->end_offset = wav->offset + av;
ret = gst_wavparse_stream_data (wav);
}
}
if (stop > 0) {
end_offset = stop;
- segment.stop -= wav->datastart;
- segment.stop = MAX (stop, 0);
+ stop -= wav->datastart;
+ stop = MAX (stop, 0);
}
if (wav->segment.format == GST_FORMAT_TIME) {
guint64 bps = wav->bps;
/* and set up streaming thread for next one */
wav->offset = offset;
wav->end_offset = end_offset;
- if (wav->end_offset > 0) {
+
+ if (wav->datasize > 0 && (wav->end_offset == -1
+ || wav->end_offset > wav->datastart + wav->datasize))
+ wav->end_offset = wav->datastart + wav->datasize;
+
+ if (wav->end_offset != -1) {
wav->dataleft = wav->end_offset - wav->offset;
} else {
/* infinity; upstream will EOS when done */
static const GstFormat *
gst_wavparse_get_formats (GstPad * pad)
{
- static GstFormat formats[] = {
+ static const GstFormat formats[] = {
GST_FORMAT_TIME,
GST_FORMAT_BYTES,
GST_FORMAT_DEFAULT, /* a "frame", ie a set of samples per Hz */