opus: make opusparse set headers on caps
authorVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Sun, 20 Nov 2011 09:52:46 +0000 (09:52 +0000)
committerVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Mon, 21 Nov 2011 11:51:20 +0000 (11:51 +0000)
Header-on-caps code moved to a new shared location to avoid
duplicating the code.

ext/opus/Makefile.am
ext/opus/gstopusenc.c
ext/opus/gstopusheader.c [new file with mode: 0644]
ext/opus/gstopusheader.h [new file with mode: 0644]
ext/opus/gstopusparse.c
ext/opus/gstopusparse.h

index 6fe723e..88845a3 100644 (file)
@@ -1,6 +1,6 @@
 plugin_LTLIBRARIES = libgstopus.la
 
-libgstopus_la_SOURCES = gstopus.c gstopusdec.c gstopusenc.c gstopusparse.c
+libgstopus_la_SOURCES = gstopus.c gstopusdec.c gstopusenc.c gstopusparse.c gstopusheader.c
 libgstopus_la_CFLAGS = \
         -DGST_USE_UNSTABLE_API \
        $(GST_PLUGINS_BASE_CFLAGS) \
@@ -15,4 +15,4 @@ libgstopus_la_LIBADD = \
 libgstopus_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(LIBM)
 libgstopus_la_LIBTOOLFLAGS = --tag=disable-static
 
-noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h
+noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h gstopusheader.h
index 2f8dc5b..f13490e 100644 (file)
@@ -1,6 +1,7 @@
 /* GStreamer Opus Encoder
  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
  * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -46,9 +47,8 @@
 #include <opus/opus.h>
 
 #include <gst/gsttagsetter.h>
-#include <gst/tag/tag.h>
-#include <gst/base/gstbytewriter.h>
 #include <gst/audio/audio.h>
+#include "gstopusheader.h"
 #include "gstopusenc.h"
 
 GST_DEBUG_CATEGORY_STATIC (opusenc_debug);
@@ -414,61 +414,6 @@ gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info)
   return TRUE;
 }
 
-static GstBuffer *
-gst_opus_enc_create_id_buffer (GstOpusEnc * enc)
-{
-  GstBuffer *buffer;
-  GstByteWriter bw;
-
-  gst_byte_writer_init (&bw);
-
-  /* See http://wiki.xiph.org/OggOpus */
-  gst_byte_writer_put_data (&bw, (const guint8 *) "OpusHead", 8);
-  gst_byte_writer_put_uint8 (&bw, 0);   /* version number */
-  gst_byte_writer_put_uint8 (&bw, enc->n_channels);
-  gst_byte_writer_put_uint16_le (&bw, 0);       /* pre-skip *//* TODO: endianness ? */
-  gst_byte_writer_put_uint32_le (&bw, enc->sample_rate);
-  gst_byte_writer_put_uint16_le (&bw, 0);       /* output gain *//* TODO: endianness ? */
-  gst_byte_writer_put_uint8 (&bw, 0);   /* channel mapping *//* TODO: what is this ? */
-
-  buffer = gst_byte_writer_reset_and_get_buffer (&bw);
-
-  GST_BUFFER_OFFSET (buffer) = 0;
-  GST_BUFFER_OFFSET_END (buffer) = 0;
-
-  return buffer;
-}
-
-static GstBuffer *
-gst_opus_enc_create_metadata_buffer (GstOpusEnc * enc)
-{
-  const GstTagList *tags;
-  GstTagList *empty_tags = NULL;
-  GstBuffer *comments = NULL;
-
-  tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc));
-
-  GST_DEBUG_OBJECT (enc, "tags = %" GST_PTR_FORMAT, tags);
-
-  if (tags == NULL) {
-    /* FIXME: better fix chain of callers to not write metadata at all,
-     * if there is none */
-    empty_tags = gst_tag_list_new ();
-    tags = empty_tags;
-  }
-  comments =
-      gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags",
-      8, "Encoded with GStreamer Opusenc");
-
-  GST_BUFFER_OFFSET (comments) = 0;
-  GST_BUFFER_OFFSET_END (comments) = 0;
-
-  if (empty_tags)
-    gst_tag_list_free (empty_tags);
-
-  return comments;
-}
-
 static gboolean
 gst_opus_enc_setup (GstOpusEnc * enc)
 {
@@ -644,63 +589,6 @@ done:
   return ret;
 }
 
-/*
- * (really really) FIXME: move into core (dixit tpm)
- */
-/**
- * _gst_caps_set_buffer_array:
- * @caps: a #GstCaps
- * @field: field in caps to set
- * @buf: header buffers
- *
- * Adds given buffers to an array of buffers set as the given @field
- * on the given @caps.  List of buffer arguments must be NULL-terminated.
- *
- * Returns: input caps with a streamheader field added, or NULL if some error
- */
-static GstCaps *
-_gst_caps_set_buffer_array (GstCaps * caps, const gchar * field,
-    GstBuffer * buf, ...)
-{
-  GstStructure *structure = NULL;
-  va_list va;
-  GValue array = { 0 };
-  GValue value = { 0 };
-
-  g_return_val_if_fail (caps != NULL, NULL);
-  g_return_val_if_fail (gst_caps_is_fixed (caps), NULL);
-  g_return_val_if_fail (field != NULL, NULL);
-
-  caps = gst_caps_make_writable (caps);
-  structure = gst_caps_get_structure (caps, 0);
-
-  g_value_init (&array, GST_TYPE_ARRAY);
-
-  va_start (va, buf);
-  /* put buffers in a fixed list */
-  while (buf) {
-    g_assert (gst_buffer_is_writable (buf));
-
-    /* mark buffer */
-    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
-
-    g_value_init (&value, GST_TYPE_BUFFER);
-    buf = gst_buffer_copy (buf);
-    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
-    gst_value_set_buffer (&value, buf);
-    gst_buffer_unref (buf);
-    gst_value_array_append_value (&array, &value);
-    g_value_unset (&value);
-
-    buf = va_arg (va, GstBuffer *);
-  }
-
-  gst_structure_set_value (structure, field, &array);
-  g_value_unset (&array);
-
-  return caps;
-}
-
 static GstFlowReturn
 gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf)
 {
@@ -711,32 +599,20 @@ gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf)
   GST_DEBUG_OBJECT (enc, "handle_frame");
 
   if (!enc->header_sent) {
-    /* Opus streams in Ogg begin with two headers; the initial header (with
-       most of the codec setup parameters) which is mandated by the Ogg
-       bitstream spec.  The second header holds any comment fields. */
-    GstBuffer *buf1, *buf2;
     GstCaps *caps;
 
-    /* create header buffers */
-    buf1 = gst_opus_enc_create_id_buffer (enc);
-    buf2 = gst_opus_enc_create_metadata_buffer (enc);
+    g_slist_foreach (enc->headers, (GFunc) gst_buffer_unref, NULL);
+    enc->headers = NULL;
+
+    gst_opus_header_create_caps (&caps, &enc->headers, enc->n_channels,
+        enc->sample_rate, gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc)));
 
-    /* mark and put on caps */
-    caps = gst_caps_from_string ("audio/x-opus");
-    caps = _gst_caps_set_buffer_array (caps, "streamheader", buf1, buf2, NULL);
 
     /* negotiate with these caps */
     GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, caps);
 
     gst_pad_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), caps);
 
-    /* push out buffers */
-    /* store buffers for later pre_push sending */
-    g_slist_foreach (enc->headers, (GFunc) gst_buffer_unref, NULL);
-    enc->headers = NULL;
-    GST_DEBUG_OBJECT (enc, "storing header buffers");
-    enc->headers = g_slist_prepend (enc->headers, buf2);
-    enc->headers = g_slist_prepend (enc->headers, buf1);
     enc->header_sent = TRUE;
   }
 
diff --git a/ext/opus/gstopusheader.c b/ext/opus/gstopusheader.c
new file mode 100644 (file)
index 0000000..b430b7d
--- /dev/null
@@ -0,0 +1,163 @@
+/* GStreamer Opus Encoder
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <gst/tag/tag.h>
+#include <gst/base/gstbytewriter.h>
+#include "gstopusheader.h"
+
+static GstBuffer *
+gst_opus_enc_create_id_buffer (gint nchannels, gint sample_rate)
+{
+  GstBuffer *buffer;
+  GstByteWriter bw;
+
+  gst_byte_writer_init (&bw);
+
+  /* See http://wiki.xiph.org/OggOpus */
+  gst_byte_writer_put_data (&bw, (const guint8 *) "OpusHead", 8);
+  gst_byte_writer_put_uint8 (&bw, 0);   /* version number */
+  gst_byte_writer_put_uint8 (&bw, nchannels);
+  gst_byte_writer_put_uint16_le (&bw, 0);       /* pre-skip *//* TODO: endianness ? */
+  gst_byte_writer_put_uint32_le (&bw, sample_rate);
+  gst_byte_writer_put_uint16_le (&bw, 0);       /* output gain *//* TODO: endianness ? */
+  gst_byte_writer_put_uint8 (&bw, 0);   /* channel mapping *//* TODO: what is this ? */
+
+  buffer = gst_byte_writer_reset_and_get_buffer (&bw);
+
+  GST_BUFFER_OFFSET (buffer) = 0;
+  GST_BUFFER_OFFSET_END (buffer) = 0;
+
+  return buffer;
+}
+
+static GstBuffer *
+gst_opus_enc_create_metadata_buffer (const GstTagList * tags)
+{
+  GstTagList *empty_tags = NULL;
+  GstBuffer *comments = NULL;
+
+  GST_DEBUG ("tags = %" GST_PTR_FORMAT, tags);
+
+  if (tags == NULL) {
+    /* FIXME: better fix chain of callers to not write metadata at all,
+     * if there is none */
+    empty_tags = gst_tag_list_new ();
+    tags = empty_tags;
+  }
+  comments =
+      gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags",
+      8, "Encoded with GStreamer Opusenc");
+
+  GST_BUFFER_OFFSET (comments) = 0;
+  GST_BUFFER_OFFSET_END (comments) = 0;
+
+  if (empty_tags)
+    gst_tag_list_free (empty_tags);
+
+  return comments;
+}
+
+/*
+ * (really really) FIXME: move into core (dixit tpm)
+ */
+/**
+ * _gst_caps_set_buffer_array:
+ * @caps: a #GstCaps
+ * @field: field in caps to set
+ * @buf: header buffers
+ *
+ * Adds given buffers to an array of buffers set as the given @field
+ * on the given @caps.  List of buffer arguments must be NULL-terminated.
+ *
+ * Returns: input caps with a streamheader field added, or NULL if some error
+ */
+static GstCaps *
+_gst_caps_set_buffer_array (GstCaps * caps, const gchar * field,
+    GstBuffer * buf, ...)
+{
+  GstStructure *structure = NULL;
+  va_list va;
+  GValue array = { 0 };
+  GValue value = { 0 };
+
+  g_return_val_if_fail (caps != NULL, NULL);
+  g_return_val_if_fail (gst_caps_is_fixed (caps), NULL);
+  g_return_val_if_fail (field != NULL, NULL);
+
+  caps = gst_caps_make_writable (caps);
+  structure = gst_caps_get_structure (caps, 0);
+
+  g_value_init (&array, GST_TYPE_ARRAY);
+
+  va_start (va, buf);
+  /* put buffers in a fixed list */
+  while (buf) {
+    g_assert (gst_buffer_is_writable (buf));
+
+    /* mark buffer */
+    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
+
+    g_value_init (&value, GST_TYPE_BUFFER);
+    buf = gst_buffer_copy (buf);
+    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
+    gst_value_set_buffer (&value, buf);
+    gst_buffer_unref (buf);
+    gst_value_array_append_value (&array, &value);
+    g_value_unset (&value);
+
+    buf = va_arg (va, GstBuffer *);
+  }
+
+  gst_structure_set_value (structure, field, &array);
+  g_value_unset (&array);
+
+  return caps;
+}
+
+void
+gst_opus_header_create_caps (GstCaps ** caps, GSList ** headers, gint nchannels,
+    gint sample_rate, const GstTagList * tags)
+{
+  GstBuffer *buf1, *buf2;
+
+  g_return_if_fail (caps);
+  g_return_if_fail (headers && !*headers);
+  g_return_if_fail (nchannels > 0);
+  g_return_if_fail (sample_rate >= 0);  /* 0 -> unset */
+
+  /* Opus streams in Ogg begin with two headers; the initial header (with
+     most of the codec setup parameters) which is mandated by the Ogg
+     bitstream spec.  The second header holds any comment fields. */
+
+  /* create header buffers */
+  buf1 = gst_opus_enc_create_id_buffer (nchannels, sample_rate);
+  buf2 = gst_opus_enc_create_metadata_buffer (tags);
+
+  /* mark and put on caps */
+  *caps = gst_caps_from_string ("audio/x-opus");
+  *caps = _gst_caps_set_buffer_array (*caps, "streamheader", buf1, buf2, NULL);
+
+  *headers = g_slist_prepend (*headers, buf2);
+  *headers = g_slist_prepend (*headers, buf1);
+}
diff --git a/ext/opus/gstopusheader.h b/ext/opus/gstopusheader.h
new file mode 100644 (file)
index 0000000..4679083
--- /dev/null
@@ -0,0 +1,32 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_OPUS_HEADER_H__
+#define __GST_OPUS_HEADER_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+extern void gst_opus_header_create_caps (GstCaps **caps, GSList **headers, gint nchannels, gint sample_rate, const GstTagList *tags);
+
+G_END_DECLS
+
+#endif /* __GST_OPUS_HEADER_H__ */
index c946875..f62ff66 100644 (file)
@@ -38,6 +38,7 @@
 #endif
 
 #include <opus/opus.h>
+#include "gstopusheader.h"
 #include "gstopusparse.h"
 
 GST_DEBUG_CATEGORY_STATIC (opusparse_debug);
@@ -62,8 +63,11 @@ GST_STATIC_PAD_TEMPLATE ("sink",
 G_DEFINE_TYPE (GstOpusParse, gst_opus_parse, GST_TYPE_BASE_PARSE);
 
 static gboolean gst_opus_parse_start (GstBaseParse * parse);
+static gboolean gst_opus_parse_stop (GstBaseParse * parse);
 static gboolean gst_opus_parse_check_valid_frame (GstBaseParse * base,
     GstBaseParseFrame * frame, guint * frame_size, gint * skip);
+static GstFlowReturn gst_opus_parse_parse_frame (GstBaseParse * base,
+    GstBaseParseFrame * frame);
 
 static void
 gst_opus_parse_class_init (GstOpusParseClass * klass)
@@ -75,8 +79,10 @@ gst_opus_parse_class_init (GstOpusParseClass * klass)
   element_class = (GstElementClass *) klass;
 
   bpclass->start = GST_DEBUG_FUNCPTR (gst_opus_parse_start);
+  bpclass->stop = GST_DEBUG_FUNCPTR (gst_opus_parse_stop);
   bpclass->check_valid_frame =
       GST_DEBUG_FUNCPTR (gst_opus_parse_check_valid_frame);
+  bpclass->parse_frame = GST_DEBUG_FUNCPTR (gst_opus_parse_parse_frame);
 
   gst_element_class_add_pad_template (element_class,
       gst_static_pad_template_get (&opus_parse_src_factory));
@@ -94,17 +100,29 @@ gst_opus_parse_class_init (GstOpusParseClass * klass)
 static void
 gst_opus_parse_init (GstOpusParse * parse)
 {
+  parse->header_sent = FALSE;
 }
 
 static gboolean
 gst_opus_parse_start (GstBaseParse * base)
 {
   GstOpusParse *parse = GST_OPUS_PARSE (base);
-  GstCaps *caps;
 
-  caps = gst_caps_from_string ("audio/x-opus");
-  gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (GST_BASE_PARSE (parse)), caps);
-  gst_caps_unref (caps);
+  parse->header_sent = FALSE;
+  parse->next_ts = 0;
+
+  return TRUE;
+}
+
+static gboolean
+gst_opus_parse_stop (GstBaseParse * base)
+{
+  GstOpusParse *parse = GST_OPUS_PARSE (base);
+
+  g_slist_foreach (parse->headers, (GFunc) gst_buffer_unref, NULL);
+  parse->headers = NULL;
+
+  parse->header_sent = FALSE;
 
   return TRUE;
 }
@@ -149,6 +167,19 @@ gst_opus_parse_check_valid_frame (GstBaseParse * base,
 
   GST_DEBUG_OBJECT (parse, "Got Opus packet, %d bytes");
 
+  if (!parse->header_sent) {
+    GstCaps *caps;
+
+    g_slist_foreach (parse->headers, (GFunc) gst_buffer_unref, NULL);
+    parse->headers = NULL;
+
+    gst_opus_header_create_caps (&caps, &parse->headers, channels, 0, NULL);
+
+    gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps);
+
+    parse->header_sent = TRUE;
+  }
+
   *skip = 8;
   *frame_size = packet_size;
   ret = TRUE;
@@ -156,3 +187,82 @@ gst_opus_parse_check_valid_frame (GstBaseParse * base,
 beach:
   return ret;
 }
+
+/* Adapted copy of the one in gstoggstream.c... */
+static guint64
+packet_duration_opus (const guint8 * data, size_t len)
+{
+  static const guint64 durations[32] = {
+    10000, 20000, 40000, 60000, /* Silk NB */
+    10000, 20000, 40000, 60000, /* Silk MB */
+    10000, 20000, 40000, 60000, /* Silk WB */
+    10000, 20000,               /* Hybrid SWB */
+    10000, 20000,               /* Hybrid FB */
+    2500, 5000, 10000, 20000,   /* CELT NB */
+    2500, 5000, 10000, 20000,   /* CELT NB */
+    2500, 5000, 10000, 20000,   /* CELT NB */
+    2500, 5000, 10000, 20000,   /* CELT NB */
+  };
+
+  gint64 duration;
+  gint64 frame_duration;
+  gint nframes;
+  guint8 toc;
+
+  if (len < 1)
+    return 0;
+
+  toc = data[0];
+
+  frame_duration = durations[toc >> 3] * 1000;
+  switch (toc & 3) {
+    case 0:
+      nframes = 1;
+      break;
+    case 1:
+      nframes = 2;
+      break;
+    case 2:
+      nframes = 2;
+      break;
+    case 3:
+      if (len < 2) {
+        GST_WARNING ("Code 3 Opus packet has less than 2 bytes");
+        return 0;
+      }
+      nframes = data[1] & 63;
+      break;
+  }
+
+  duration = nframes * frame_duration;
+  if (duration > 120 * GST_MSECOND) {
+    GST_WARNING ("Opus packet duration > 120 ms, invalid");
+    return 0;
+  }
+  GST_LOG ("Opus packet: frame size %.1f ms, %d frames, duration %.1f ms",
+      frame_duration / 1000000.f, nframes, duration / 1000000.f);
+  return duration;
+}
+
+static GstFlowReturn
+gst_opus_parse_parse_frame (GstBaseParse * base, GstBaseParseFrame * frame)
+{
+  guint64 duration;
+  GstOpusParse *parse;
+
+  parse = GST_OPUS_PARSE (base);
+
+  GST_BUFFER_TIMESTAMP (frame->buffer) = parse->next_ts;
+
+  duration =
+      packet_duration_opus (GST_BUFFER_DATA (frame->buffer),
+      GST_BUFFER_SIZE (frame->buffer));
+  parse->next_ts += duration;
+
+  GST_BUFFER_DURATION (frame->buffer) = duration;
+  GST_BUFFER_OFFSET_END (frame->buffer) =
+      gst_util_uint64_scale (parse->next_ts, 48000, GST_SECOND);
+  GST_BUFFER_OFFSET (frame->buffer) = parse->next_ts;
+
+  return GST_FLOW_OK;
+}
index 5f9f884..60ea5c5 100644 (file)
@@ -42,6 +42,10 @@ typedef struct _GstOpusParseClass GstOpusParseClass;
 
 struct _GstOpusParse {
   GstBaseParse       element;
+
+  gboolean header_sent;
+  GSList *headers;
+  GstClockTime next_ts;
 };
 
 struct _GstOpusParseClass {