matroska: mux/demux the OpusHead header
authorVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Wed, 17 Dec 2014 17:36:18 +0000 (17:36 +0000)
committerVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Thu, 18 Dec 2014 11:38:49 +0000 (11:38 +0000)
This is meant to be so (https://wiki.xiph.org/MatroskaOpus - while
it is marked as a draft, this part was confirmed to be correct on
IRC), and allows one to determine whether a demuxed stream is
multistream or not, and thus set the multistream caps field
accordingly. In turn, this means downstream does not have to guess.

https://bugzilla.gnome.org/show_bug.cgi?id=740744

gst/matroska/matroska-demux.c
gst/matroska/matroska-ids.c
gst/matroska/matroska-ids.h
gst/matroska/matroska-mux.c

index 03d5286..ddca9cd 100644 (file)
@@ -5278,6 +5278,18 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext *
   } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_OPUS)) {
     caps = gst_caps_new_empty_simple ("audio/x-opus");
     *codec_name = g_strdup ("Opus");
+    context->stream_headers =
+        gst_matroska_parse_opus_stream_headers (context->codec_priv,
+        context->codec_priv_size);
+    if (context->stream_headers) {
+      /* There was a valid header. Multistream headers are more than
+       * 19 bytes, as they include an extra channel mapping table. */
+      gboolean multistream = (context->codec_priv_size > 19);
+      gst_caps_set_simple (caps, "multistream", G_TYPE_BOOLEAN, multistream,
+          NULL);
+    }
+    /* FIXME: mark stream as broken and skip if there are no stream headers */
+    context->send_stream_headers = TRUE;
   } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_ACM)) {
     gst_riff_strf_auds auds;
 
index 60ebd0e..7fdcac8 100644 (file)
@@ -221,6 +221,36 @@ gst_matroska_parse_speex_stream_headers (gpointer codec_data,
 }
 
 GstBufferList *
+gst_matroska_parse_opus_stream_headers (gpointer codec_data,
+    gsize codec_data_size)
+{
+  GstBufferList *list = NULL;
+  GstBuffer *hdr;
+  guint8 *pdata = codec_data;
+
+  GST_MEMDUMP ("opus codec data", codec_data, codec_data_size);
+
+  if (codec_data == NULL || codec_data_size < 19) {
+    GST_WARNING ("not enough codec priv data for opus headers");
+    return NULL;
+  }
+
+  if (memcmp (pdata, "OpusHead", 8) != 0) {
+    GST_WARNING ("no OpusHead marker at start of stream headers");
+    return NULL;
+  }
+
+  list = gst_buffer_list_new ();
+
+  hdr =
+      gst_buffer_new_wrapped (g_memdup (pdata, codec_data_size),
+      codec_data_size);
+  gst_buffer_list_add (list, hdr);
+
+  return list;
+}
+
+GstBufferList *
 gst_matroska_parse_flac_stream_headers (gpointer codec_data,
     gsize codec_data_size)
 {
index 68a68d4..d971ba2 100644 (file)
@@ -640,6 +640,9 @@ GstBufferList * gst_matroska_parse_xiph_stream_headers  (gpointer codec_data,
 GstBufferList * gst_matroska_parse_speex_stream_headers (gpointer codec_data,
                                                          gsize codec_data_size);
 
+GstBufferList * gst_matroska_parse_opus_stream_headers  (gpointer codec_data,
+                                                         gsize codec_data_size);
+
 GstBufferList * gst_matroska_parse_flac_stream_headers  (gpointer codec_data,
                                                          gsize codec_data_size);
 void gst_matroska_track_free (GstMatroskaTrackContext * track);
index 6c3519e..72a8e2c 100644 (file)
@@ -1583,6 +1583,58 @@ speex_streamheader_to_codecdata (const GValue * streamheader,
   return TRUE;
 }
 
+static gboolean
+opus_streamheader_to_codecdata (const GValue * streamheader,
+    GstMatroskaTrackContext * context)
+{
+  GArray *bufarr;
+  GValue *bufval;
+  GstBuffer *buf;
+
+  if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
+    goto wrong_type;
+
+  bufarr = g_value_peek_pointer (streamheader);
+  if (bufarr->len <= 0 || bufarr->len > 255)    /* one header, and count stored in a byte */
+    goto wrong_count;
+  if (bufarr->len != 1 && bufarr->len != 2)
+    goto wrong_count;
+
+  context->xiph_headers_to_skip = bufarr->len;
+
+  bufval = &g_array_index (bufarr, GValue, 0);
+  if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
+    goto wrong_content_type;
+  }
+  buf = g_value_peek_pointer (bufval);
+
+  gst_matroska_mux_free_codec_priv (context);
+
+  context->codec_priv_size = gst_buffer_get_size (buf);
+  context->codec_priv = g_malloc0 (context->codec_priv_size);
+  gst_buffer_extract (buf, 0, context->codec_priv, -1);
+
+  return TRUE;
+
+/* ERRORS */
+wrong_type:
+  {
+    GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
+        G_VALUE_TYPE_NAME (streamheader));
+    return FALSE;
+  }
+wrong_count:
+  {
+    GST_WARNING ("got %u streamheaders, not 1 or 2 as expected", bufarr->len);
+    return FALSE;
+  }
+wrong_content_type:
+  {
+    GST_WARNING ("streamheaders array does not contain GstBuffers");
+    return FALSE;
+  }
+}
+
 static const gchar *
 aac_codec_data_to_codec_id (GstBuffer * buf)
 {
@@ -1834,7 +1886,19 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
       goto refuse_caps;
     }
   } else if (!strcmp (mimetype, "audio/x-opus")) {
+    const GValue *streamheader;
+
     gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_OPUS);
+
+    streamheader = gst_structure_get_value (structure, "streamheader");
+    if (streamheader) {
+      gst_matroska_mux_free_codec_priv (context);
+      if (!opus_streamheader_to_codecdata (streamheader, context)) {
+        GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
+            ("opus stream headers missing or malformed"));
+        goto refuse_caps;
+      }
+    }
   } else if (!strcmp (mimetype, "audio/x-ac3")) {
     gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_AC3);
   } else if (!strcmp (mimetype, "audio/x-eac3")) {