From bff6e3c62863ae620b99d871b9f0f1321cf0d750 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Sun, 20 Nov 2011 09:52:46 +0000 Subject: [PATCH] opus: make opusparse set headers on caps Header-on-caps code moved to a new shared location to avoid duplicating the code. --- ext/opus/Makefile.am | 4 +- ext/opus/gstopusenc.c | 138 ++------------------------------- ext/opus/gstopusheader.c | 163 +++++++++++++++++++++++++++++++++++++++ ext/opus/gstopusheader.h | 32 ++++++++ ext/opus/gstopusparse.c | 118 +++++++++++++++++++++++++++- ext/opus/gstopusparse.h | 4 + 6 files changed, 322 insertions(+), 137 deletions(-) create mode 100644 ext/opus/gstopusheader.c create mode 100644 ext/opus/gstopusheader.h diff --git a/ext/opus/Makefile.am b/ext/opus/Makefile.am index 6fe723ecce..88845a3cab 100644 --- a/ext/opus/Makefile.am +++ b/ext/opus/Makefile.am @@ -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 diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index 2f8dc5bfbe..f13490ee74 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -1,6 +1,7 @@ /* GStreamer Opus Encoder * Copyright (C) <1999> Erik Walthinsen * Copyright (C) <2008> Sebastian Dröge + * Copyright (C) <2011> Vincent Penquerc'h * * 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 #include -#include -#include #include +#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 index 0000000000..b430b7df47 --- /dev/null +++ b/ext/opus/gstopusheader.c @@ -0,0 +1,163 @@ +/* GStreamer Opus Encoder + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2008> Sebastian Dröge + * Copyright (C) <2011> Vincent Penquerc'h + * + * 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 +#include +#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 index 0000000000..4679083661 --- /dev/null +++ b/ext/opus/gstopusheader.h @@ -0,0 +1,32 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2008> Sebastian Dröge + * + * 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 + +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__ */ diff --git a/ext/opus/gstopusparse.c b/ext/opus/gstopusparse.c index c94687589b..f62ff66964 100644 --- a/ext/opus/gstopusparse.c +++ b/ext/opus/gstopusparse.c @@ -38,6 +38,7 @@ #endif #include +#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; +} diff --git a/ext/opus/gstopusparse.h b/ext/opus/gstopusparse.h index 5f9f884d39..60ea5c536b 100644 --- a/ext/opus/gstopusparse.h +++ b/ext/opus/gstopusparse.h @@ -42,6 +42,10 @@ typedef struct _GstOpusParseClass GstOpusParseClass; struct _GstOpusParse { GstBaseParse element; + + gboolean header_sent; + GSList *headers; + GstClockTime next_ts; }; struct _GstOpusParseClass {