From cc6d533521841bc949136e969d96230ad7a30bd2 Mon Sep 17 00:00:00 2001 From: Anton Belka Date: Sun, 8 Jul 2012 20:36:22 +0300 Subject: [PATCH] wavparse: Add TOC support Add support for: * Cue Chunk * Associated Data List Chunk * Label Chunk https://bugzilla.gnome.org/show_bug.cgi?id=677306 --- gst/wavparse/gstwavparse.c | 348 +++++++++++++++++++++++++++++++++++++++++++++ gst/wavparse/gstwavparse.h | 3 + 2 files changed, 351 insertions(+) diff --git a/gst/wavparse/gstwavparse.c b/gst/wavparse/gstwavparse.c index 0e9b06e..8888eea 100644 --- a/gst/wavparse/gstwavparse.c +++ b/gst/wavparse/gstwavparse.c @@ -113,6 +113,38 @@ GST_STATIC_PAD_TEMPLATE ("sink", 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) { @@ -198,6 +230,15 @@ gst_wavparse_reset (GstWavParse * wav) 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; @@ -1115,6 +1156,200 @@ gst_waveparse_ignore_chunk (GstWavParse * wav, GstBuffer * buf, guint32 tag, 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 @@ -1505,6 +1740,32 @@ gst_wavparse_stream_headers (GstWavParse * wav) } 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)); @@ -1515,6 +1776,50 @@ gst_wavparse_stream_headers (GstWavParse * wav) } 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 */ @@ -1545,6 +1850,8 @@ gst_wavparse_stream_headers (GstWavParse * wav) 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); @@ -2549,6 +2856,47 @@ gst_wavparse_srcpad_event (GstPad * pad, GstObject * parent, GstEvent * event) } 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; diff --git a/gst/wavparse/gstwavparse.h b/gst/wavparse/gstwavparse.h index 82838e4..ad8e02b 100644 --- a/gst/wavparse/gstwavparse.h +++ b/gst/wavparse/gstwavparse.h @@ -66,6 +66,9 @@ struct _GstWavParse { * the format for sure */ GstCaps *caps; GstTagList *tags; + GstToc *toc; + GList *cues; + GList *labls; GstEvent *start_segment; /* WAVE decoding state */ -- 2.7.4