G_DEFINE_TYPE_WITH_CODE (GstWavParse, gst_wavparse, GST_TYPE_ELEMENT,
DEBUG_INIT);
+typedef struct
+{
+ /* Offset Size Description Value
+ * 0x00 4 ID unique identification value
+ * 0x04 4 Position play order position
+ * 0x08 4 Data Chunk ID RIFF ID of corresponding data chunk
+ * 0x0c 4 Chunk Start Byte Offset of Data Chunk *
+ * 0x10 4 Block Start Byte Offset to sample of First Channel
+ * 0x14 4 Sample Offset Byte Offset to sample byte of First Channel
+ */
+ guint32 id;
+ guint32 position;
+ guint32 data_chunk_id;
+ guint32 chunk_start;
+ guint32 block_start;
+ guint32 sample_offset;
+} GstWavParseCue;
+
+typedef struct
+{
+ /* Offset Size Description Value
+ * 0x00 4 Chunk ID "labl" (0x6C61626C)
+ * 0x04 4 Chunk Data Size depends on contained text
+ * 0x08 4 Cue Point ID 0 - 0xFFFFFFFF
+ * 0x0c Text
+ */
+ guint32 chunk_id;
+ guint32 chunk_data_size;
+ guint32 cue_point_id;
+ gchar *text;
+} GstWavParseLabl;
+
static void
gst_wavparse_class_init (GstWavParseClass * klass)
{
if (wav->tags)
gst_tag_list_free (wav->tags);
wav->tags = NULL;
+ if (wav->toc)
+ gst_toc_unref (wav->toc);
+ wav->toc = NULL;
+ if (wav->cues)
+ g_list_free_full (wav->cues, g_free);
+ wav->cues = NULL;
+ if (wav->labls)
+ g_list_free_full (wav->labls, g_free);
+ wav->labls = NULL;
if (wav->caps)
gst_caps_unref (wav->caps);
wav->caps = NULL;
return TRUE;
}
+/*
+ * gst_wavparse_cue_chunk:
+ * @wav GstWavParse object
+ * @data holder for data
+ * @size holder for data size
+ *
+ * Parse cue chunk from @data to wav->cues.
+ *
+ * Returns: %TRUE when cue chunk is available
+ */
+static gboolean
+gst_wavparse_cue_chunk (GstWavParse * wav, const guint8 * data, guint32 size)
+{
+ guint32 i, ncues;
+ GList *cues = NULL;
+ GstWavParseCue *cue;
+
+ GST_OBJECT_LOCK (wav);
+ if (wav->cues) {
+ GST_OBJECT_UNLOCK (wav);
+ GST_WARNING_OBJECT (wav, "found another cue's");
+ return TRUE;
+ }
+
+ ncues = GST_READ_UINT32_LE (data);
+
+ if (size != 4 + ncues * 24) {
+ GST_WARNING_OBJECT (wav, "broken file");
+ return FALSE;
+ }
+
+ /* parse data */
+ data += 4;
+ for (i = 0; i < ncues; i++) {
+ cue = g_new0 (GstWavParseCue, 1);
+ cue->id = GST_READ_UINT32_LE (data);
+ cue->position = GST_READ_UINT32_LE (data + 4);
+ cue->data_chunk_id = GST_READ_UINT32_LE (data + 8);
+ cue->chunk_start = GST_READ_UINT32_LE (data + 12);
+ cue->block_start = GST_READ_UINT32_LE (data + 16);
+ cue->sample_offset = GST_READ_UINT32_LE (data + 20);
+ cues = g_list_append (cues, cue);
+ data += 24;
+ }
+
+ wav->cues = cues;
+ GST_OBJECT_UNLOCK (wav);
+
+ return TRUE;
+}
+
+/*
+ * gst_wavparse_labl_chunk:
+ * @wav GstWavParse object
+ * @data holder for data
+ * @size holder for data size
+ *
+ * Parse labl from @data to wav->labls.
+ *
+ * Returns: %TRUE when labl chunk is available
+ */
+static gboolean
+gst_wavparse_labl_chunk (GstWavParse * wav, const guint8 * data, guint32 size)
+{
+ GstWavParseLabl *labl;
+
+ labl = g_new0 (GstWavParseLabl, 1);
+
+ /* parse data */
+ labl->chunk_id = GST_READ_UINT32_LE (data);
+ labl->chunk_data_size = GST_READ_UINT32_LE (data + 4);
+ labl->cue_point_id = GST_READ_UINT32_LE (data + 8);
+ labl->text = (gchar *) g_new (gchar *, labl->chunk_data_size + 1);
+ memcpy (labl->text, data + 12, labl->chunk_data_size);
+
+ GST_OBJECT_LOCK (wav);
+ wav->labls = g_list_append (wav->labls, labl);
+ GST_OBJECT_UNLOCK (wav);
+
+ return TRUE;
+}
+
+/*
+ * gst_wavparse_adtl_chunk:
+ * @wav GstWavParse object
+ * @data holder for data
+ * @size holder for data size
+ *
+ * Parse adtl from @data.
+ *
+ * Returns: %TRUE when adtl chunk is available
+ */
+static gboolean
+gst_wavparse_adtl_chunk (GstWavParse * wav, const guint8 * data, guint32 size)
+{
+ guint32 ltag, lsize, offset = 0;
+
+ while (size >= 8) {
+ ltag = GST_READ_UINT32_LE (data + offset);
+ lsize = GST_READ_UINT32_LE (data + offset + 4);
+ switch (ltag) {
+ case GST_RIFF_TAG_labl:
+ gst_wavparse_labl_chunk (wav, data + offset, size);
+ default:
+ break;
+ }
+ offset += 8 + GST_ROUND_UP_2 (lsize);
+ size -= 8 + GST_ROUND_UP_2 (lsize);
+ }
+
+ return TRUE;
+}
+
+/*
+ * gst_wavparse_create_toc:
+ * @wav GstWavParse object
+ *
+ * Create TOC from wav->cues and wav->labls.
+ */
+static gboolean
+gst_wavparse_create_toc (GstWavParse * wav)
+{
+ gint64 start, stop;
+ gchar *id;
+ GList *list;
+ GstWavParseCue *cue;
+ GstWavParseLabl *labl;
+ GstTagList *tags;
+ GstToc *toc;
+ GstTocEntry *entry = NULL, *cur_subentry = NULL, *prev_subentry = NULL;
+
+ GST_OBJECT_LOCK (wav);
+ if (wav->toc) {
+ GST_OBJECT_UNLOCK (wav);
+ GST_WARNING_OBJECT (wav, "found another TOC");
+ return FALSE;
+ }
+
+ toc = gst_toc_new ();
+
+ /* add cue edition */
+ entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, "cue");
+ gst_toc_entry_set_start_stop_times (entry, 0, wav->duration);
+ gst_toc_append_entry (toc, entry);
+
+ /* add chapters in cue edition */
+ list = g_list_first (wav->cues);
+ while (list != NULL) {
+ cue = list->data;
+ prev_subentry = cur_subentry;
+ /* previous chapter stop time = current chapter start time */
+ if (prev_subentry != NULL) {
+ gst_toc_entry_get_start_stop_times (prev_subentry, &start, NULL);
+ stop = gst_util_uint64_scale_round (cue->position, GST_SECOND, wav->rate);
+ gst_toc_entry_set_start_stop_times (prev_subentry, start, stop);
+ }
+ id = g_strdup_printf ("%08x", cue->id);
+ cur_subentry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, id);
+ g_free (id);
+ start = gst_util_uint64_scale_round (cue->position, GST_SECOND, wav->rate);
+ stop = wav->duration;
+ gst_toc_entry_set_start_stop_times (cur_subentry, start, stop);
+ gst_toc_entry_append_sub_entry (entry, cur_subentry);
+ list = g_list_next (list);
+ }
+
+ /* add tags in chapters */
+ list = g_list_first (wav->labls);
+ while (list != NULL) {
+ labl = list->data;
+ id = g_strdup_printf ("%08x", labl->cue_point_id);
+ cur_subentry = gst_toc_find_entry (toc, id);
+ g_free (id);
+ if (cur_subentry != NULL) {
+ tags = gst_tag_list_new_empty ();
+ gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, labl->text,
+ NULL);
+ gst_toc_entry_set_tags (cur_subentry, tags);
+ }
+ list = g_list_next (list);
+ }
+
+ /* send data as TOC */
+ wav->toc = toc;
+
+ /* send TOC event */
+ if (wav->toc) {
+ GST_OBJECT_UNLOCK (wav);
+ gst_pad_push_event (wav->srcpad, gst_event_new_toc (wav->toc, FALSE));
+ }
+
+ return TRUE;
+}
+
#define MAX_BUFFER_SIZE 4096
static GstFlowReturn
}
break;
}
+ case GST_RIFF_LIST_adtl:{
+ const gint data_size = size;
+
+ GST_INFO_OBJECT (wav, "Have 'adtl' LIST, size %u", data_size);
+ if (wav->streaming) {
+ const guint8 *data = NULL;
+
+ gst_adapter_flush (wav->adapter, 12);
+ data = gst_adapter_map (wav->adapter, data_size);
+ gst_wavparse_adtl_chunk (wav, data, data_size);
+ gst_adapter_unmap (wav->adapter);
+ } else {
+ GstMapInfo map;
+
+ gst_buffer_unref (buf);
+ buf = NULL;
+ if ((res =
+ gst_pad_pull_range (wav->sinkpad, wav->offset + 12,
+ data_size, &buf)) != GST_FLOW_OK)
+ goto header_read_error;
+ gst_buffer_map (buf, &map, GST_MAP_READ);
+ gst_wavparse_adtl_chunk (wav, (const guint8 *) map.data,
+ data_size);
+ gst_buffer_unmap (buf, &map);
+ }
+ }
default:
GST_INFO_OBJECT (wav, "Ignoring LIST chunk %" GST_FOURCC_FORMAT,
GST_FOURCC_ARGS (ltag));
}
break;
}
+ case GST_RIFF_TAG_cue:{
+ const guint data_size = size;
+
+ GST_DEBUG_OBJECT (wav, "Have 'cue' TAG, size : %u", data_size);
+ if (wav->streaming) {
+ const guint8 *data = NULL;
+
+ if (!gst_wavparse_peek_chunk (wav, &tag, &size)) {
+ goto exit;
+ }
+ gst_adapter_flush (wav->adapter, 8);
+ wav->offset += 8;
+ data = gst_adapter_map (wav->adapter, data_size);
+ if (!gst_wavparse_cue_chunk (wav, data, data_size)) {
+ goto header_read_error;
+ }
+ gst_adapter_unmap (wav->adapter);
+ } else {
+ GstMapInfo map;
+
+ wav->offset += 8;
+ gst_buffer_unref (buf);
+ buf = NULL;
+ if ((res =
+ 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);
+ if (!gst_wavparse_cue_chunk (wav, (const guint8 *) map.data,
+ data_size)) {
+ goto header_read_error;
+ }
+ gst_buffer_unmap (buf, &map);
+ }
+ size = GST_ROUND_UP_2 (size);
+ if (wav->streaming) {
+ gst_adapter_flush (wav->adapter, size);
+ } else {
+ gst_buffer_unref (buf);
+ }
+ size = GST_ROUND_UP_2 (size);
+ wav->offset += size;
+ break;
+ }
default:
if (!gst_waveparse_ignore_chunk (wav, buf, tag, size))
/* need more data */
gst_segment_init (&wav->segment, GST_FORMAT_TIME);
if (!wav->ignore_length)
wav->segment.duration = wav->duration;
+ if (!wav->toc)
+ gst_wavparse_create_toc (wav);
} else {
/* no bitrate, let downstream peer do the math, we'll feed it bytes. */
gst_segment_init (&wav->segment, GST_FORMAT_BYTES);
}
gst_event_unref (event);
break;
+
+ case GST_EVENT_TOC_SELECT:
+ {
+ char *uid = NULL;
+ GstTocEntry *entry = NULL;
+ GstEvent *seek_event;
+ gint64 start_pos;
+
+ if (!wavparse->toc) {
+ GST_DEBUG_OBJECT (wavparse, "no TOC to select");
+ return FALSE;
+ } else {
+ gst_event_parse_toc_select (event, &uid);
+ if (uid != NULL) {
+ GST_OBJECT_LOCK (wavparse);
+ entry = gst_toc_find_entry (wavparse->toc, uid);
+ if (entry == NULL) {
+ GST_OBJECT_UNLOCK (wavparse);
+ GST_WARNING_OBJECT (wavparse, "no TOC entry with given UID: %s",
+ uid);
+ res = FALSE;
+ } else {
+ gst_toc_entry_get_start_stop_times (entry, &start_pos, NULL);
+ GST_OBJECT_UNLOCK (wavparse);
+ seek_event = gst_event_new_seek (1.0,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH,
+ GST_SEEK_TYPE_SET, start_pos, GST_SEEK_TYPE_SET, -1);
+ res = gst_wavparse_perform_seek (wavparse, seek_event);
+ gst_event_unref (seek_event);
+ }
+ g_free (uid);
+ } else {
+ GST_WARNING_OBJECT (wavparse, "received empty TOC select event");
+ res = FALSE;
+ }
+ }
+ gst_event_unref (event);
+ break;
+ }
+
default:
res = gst_pad_push_event (wavparse->sinkpad, event);
break;