mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-11 09:55:36 +00:00
wavparse: Add TOC support
Add support for: * Cue Chunk * Associated Data List Chunk * Label Chunk https://bugzilla.gnome.org/show_bug.cgi?id=677306
This commit is contained in:
parent
561b131e1a
commit
cc6d533521
2 changed files with 351 additions and 0 deletions
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in a new issue