wavparse: Add TOC support
[platform/upstream/gst-plugins-good.git] / gst / wavparse / gstwavparse.c
index 0e9b06e..8888eea 100644 (file)
@@ -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;