Add CELT encoder and decoder elements based on the Speex elements.

Original commit message from CVS:
* configure.ac:
* ext/Makefile.am:
* ext/celt/Makefile.am:
* ext/celt/gstcelt.c: (plugin_init):
* ext/celt/gstceltdec.c: (gst_celt_dec_base_init),
(gst_celt_dec_class_init), (gst_celt_dec_reset),
(gst_celt_dec_init), (celt_dec_convert),
(celt_get_sink_query_types), (celt_dec_sink_query),
(celt_get_src_query_types), (celt_dec_src_query),
(celt_dec_src_event), (celt_dec_sink_event),
(celt_dec_chain_parse_header), (celt_dec_chain_parse_comments),
(celt_dec_chain_parse_data), (celt_dec_chain),
(celt_dec_change_state):
* ext/celt/gstceltdec.h:
* ext/celt/gstceltenc.c: (gst_celt_enc_setup_interfaces),
(gst_celt_enc_base_init), (gst_celt_enc_class_init),
(gst_celt_enc_finalize), (gst_celt_enc_sink_setcaps),
(gst_celt_enc_sink_getcaps), (gst_celt_enc_convert_src),
(gst_celt_enc_convert_sink), (gst_celt_enc_get_latency),
(gst_celt_enc_get_query_types), (gst_celt_enc_src_query),
(gst_celt_enc_sink_query), (gst_celt_enc_init),
(gst_celt_enc_create_metadata_buffer), (gst_celt_enc_setup),
(gst_celt_enc_buffer_from_data), (gst_celt_enc_push_buffer),
(gst_celt_enc_set_header_on_caps), (gst_celt_enc_sinkevent),
(gst_celt_enc_chain), (gst_celt_enc_get_property),
(gst_celt_enc_set_property), (gst_celt_enc_change_state):
* ext/celt/gstceltenc.h:
Add CELT encoder and decoder elements based on the Speex elements.
This commit is contained in:
Sebastian Dröge 2008-08-02 17:29:44 +00:00
parent 44706c8370
commit f783ef73b7
9 changed files with 1968 additions and 0 deletions

View file

@ -1,3 +1,34 @@
2008-08-02 Sebastian Dröge <sebastian.droege@collabora.co.uk>
* configure.ac:
* ext/Makefile.am:
* ext/celt/Makefile.am:
* ext/celt/gstcelt.c: (plugin_init):
* ext/celt/gstceltdec.c: (gst_celt_dec_base_init),
(gst_celt_dec_class_init), (gst_celt_dec_reset),
(gst_celt_dec_init), (celt_dec_convert),
(celt_get_sink_query_types), (celt_dec_sink_query),
(celt_get_src_query_types), (celt_dec_src_query),
(celt_dec_src_event), (celt_dec_sink_event),
(celt_dec_chain_parse_header), (celt_dec_chain_parse_comments),
(celt_dec_chain_parse_data), (celt_dec_chain),
(celt_dec_change_state):
* ext/celt/gstceltdec.h:
* ext/celt/gstceltenc.c: (gst_celt_enc_setup_interfaces),
(gst_celt_enc_base_init), (gst_celt_enc_class_init),
(gst_celt_enc_finalize), (gst_celt_enc_sink_setcaps),
(gst_celt_enc_sink_getcaps), (gst_celt_enc_convert_src),
(gst_celt_enc_convert_sink), (gst_celt_enc_get_latency),
(gst_celt_enc_get_query_types), (gst_celt_enc_src_query),
(gst_celt_enc_sink_query), (gst_celt_enc_init),
(gst_celt_enc_create_metadata_buffer), (gst_celt_enc_setup),
(gst_celt_enc_buffer_from_data), (gst_celt_enc_push_buffer),
(gst_celt_enc_set_header_on_caps), (gst_celt_enc_sinkevent),
(gst_celt_enc_chain), (gst_celt_enc_get_property),
(gst_celt_enc_set_property), (gst_celt_enc_change_state):
* ext/celt/gstceltenc.h:
Add CELT encoder and decoder elements based on the Speex elements.
2008-08-02 Sebastian Dröge <sebastian.droege@collabora.co.uk>
Patch by: Tal Shalif <tshalif at nargila dot org>

View file

@ -369,6 +369,17 @@ AG_GST_CHECK_FEATURE(CDAUDIO, [cdaudio], cdaudio, [
AC_SUBST(CDAUDIO_LIBS)
])
dnl *** celt ***
translit(dnm, m, l) AM_CONDITIONAL(USE_CELT, true)
AG_GST_CHECK_FEATURE(CELT, [celt], celt, [
PKG_CHECK_MODULES(CELT, celt >= 0.4.0, [
HAVE_CELT="yes"], [
HAVE_CELT="no"
AC_MSG_RESULT(no)
])
AC_SUBST(CELT_CFLAGS)
AC_SUBST(CELT_LIBS)
])
dnl *** dc1394 ***
translit(dnm, m, l) AM_CONDITIONAL(USE_DC1394, true)
@ -1101,6 +1112,7 @@ AM_CONDITIONAL(USE_ALSA, false)
AM_CONDITIONAL(USE_AMRWB, false)
AM_CONDITIONAL(USE_BZ2, false)
AM_CONDITIONAL(USE_CDAUDIO, false)
AM_CONDITIONAL(USE_CELT, false)
AM_CONDITIONAL(USE_DC1394, false)
AM_CONDITIONAL(USE_DIRECTFB, false)
AM_CONDITIONAL(USE_DTS, false)
@ -1254,6 +1266,7 @@ ext/amrwb/Makefile
ext/alsaspdif/Makefile
ext/bz2/Makefile
ext/cdaudio/Makefile
ext/celt/Makefile
ext/dc1394/Makefile
ext/dirac/Makefile
ext/directfb/Makefile

View file

@ -46,6 +46,12 @@ else
CDAUDIO_DIR=
endif
if USE_CELT
CELT_DIR=celt
else
CELT_DIR=
endif
if USE_DC1394
DC1394_DIR=dc1394
else
@ -303,6 +309,7 @@ SUBDIRS=\
$(AUDIORESAMPLE_DIR) \
$(BZ2_DIR) \
$(CDAUDIO_DIR) \
$(CELT_DIR) \
$(DC1394_DIR) \
$(DIRAC_DIR) \
$(DIRECTFB_DIR) \

15
ext/celt/Makefile.am Normal file
View file

@ -0,0 +1,15 @@
plugin_LTLIBRARIES = libgstcelt.la
libgstcelt_la_SOURCES = gstcelt.c gstceltdec.c gstceltenc.c
libgstcelt_la_CFLAGS = \
$(GST_PLUGINS_BASE_CFLAGS) \
$(GST_CFLAGS) \
$(CELT_CFLAGS)
libgstcelt_la_LIBADD = \
$(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_MAJORMINOR) \
$(GST_BASE_LIBS) \
$(GST_LIBS) \
$(CELT_LIBS)
libgstcelt_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(LIBM)
noinst_HEADERS = gstceltenc.h gstceltdec.h

50
ext/celt/gstcelt.c Normal file
View file

@ -0,0 +1,50 @@
/* 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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "gstceltdec.h"
#include "gstceltenc.h"
#include <gst/tag/tag.h>
static gboolean
plugin_init (GstPlugin * plugin)
{
if (!gst_element_register (plugin, "celtenc", GST_RANK_NONE,
GST_TYPE_CELT_ENC))
return FALSE;
if (!gst_element_register (plugin, "celtdec", GST_RANK_PRIMARY,
GST_TYPE_CELT_DEC))
return FALSE;
gst_tag_register_musicbrainz_tags ();
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"celt",
"CELT plugin library",
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)

744
ext/celt/gstceltdec.c Normal file
View file

@ -0,0 +1,744 @@
/* GStreamer
* Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
* Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
* 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.
*/
/*
* Based on the speexdec element.
*/
/**
* SECTION:element-celtdec
* @short_description: a decoder that decodes CELT to raw audio
* @see_also: celtenc, oggdemux
*
* <refsect2>
* <para>
* This element decodes a CELT stream to raw integer audio.
* </para>
* <title>Example pipelines</title>
* <para>
* <programlisting>
* gst-launch -v filesrc location=celt.ogg ! oggdemux ! celtdec ! audioconvert ! audioresample ! alsasink
* </programlisting>
* Decode an Ogg/Celt file. To create an Ogg/Celt file refer to the documentation of celtenc.
* </para>
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "gstceltdec.h"
#include <string.h>
#include <gst/tag/tag.h>
GST_DEBUG_CATEGORY_STATIC (celtdec_debug);
#define GST_CAT_DEFAULT celtdec_debug
static GstStaticPadTemplate celt_dec_src_factory =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int, "
"rate = (int) [ 32000, 64000 ], "
"channels = (int) [ 1, 2 ], "
"endianness = (int) BYTE_ORDER, "
"signed = (boolean) true, " "width = (int) 16, " "depth = (int) 16")
);
static GstStaticPadTemplate celt_dec_sink_factory =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-celt")
);
GST_BOILERPLATE (GstCeltDec, gst_celt_dec, GstElement, GST_TYPE_ELEMENT);
static gboolean celt_dec_sink_event (GstPad * pad, GstEvent * event);
static GstFlowReturn celt_dec_chain (GstPad * pad, GstBuffer * buf);
static GstStateChangeReturn celt_dec_change_state (GstElement * element,
GstStateChange transition);
static gboolean celt_dec_src_event (GstPad * pad, GstEvent * event);
static gboolean celt_dec_src_query (GstPad * pad, GstQuery * query);
static gboolean celt_dec_sink_query (GstPad * pad, GstQuery * query);
static const GstQueryType *celt_get_src_query_types (GstPad * pad);
static const GstQueryType *celt_get_sink_query_types (GstPad * pad);
static gboolean celt_dec_convert (GstPad * pad,
GstFormat src_format, gint64 src_value,
GstFormat * dest_format, gint64 * dest_value);
static GstFlowReturn celt_dec_chain_parse_data (GstCeltDec * dec,
GstBuffer * buf, GstClockTime timestamp, GstClockTime duration);
static void
gst_celt_dec_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&celt_dec_src_factory));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&celt_dec_sink_factory));
gst_element_class_set_details_simple (element_class, "Celt audio decoder",
"Codec/Decoder/Audio",
"decode celt streams to audio",
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
}
static void
gst_celt_dec_class_init (GstCeltDecClass * klass)
{
GstElementClass *gstelement_class;
gstelement_class = (GstElementClass *) klass;
gstelement_class->change_state = GST_DEBUG_FUNCPTR (celt_dec_change_state);
GST_DEBUG_CATEGORY_INIT (celtdec_debug, "celtdec", 0,
"celt decoding element");
}
static void
gst_celt_dec_reset (GstCeltDec * dec)
{
gst_segment_init (&dec->segment, GST_FORMAT_UNDEFINED);
dec->granulepos = -1;
dec->packetno = 0;
dec->frame_size = 0;
dec->frame_duration = 0;
if (dec->state) {
celt_decoder_destroy (dec->state);
dec->state = NULL;
}
if (dec->mode) {
celt_mode_destroy (dec->mode);
dec->mode = NULL;
}
memset (&dec->header, 0, sizeof (dec->header));
}
static void
gst_celt_dec_init (GstCeltDec * dec, GstCeltDecClass * g_class)
{
dec->sinkpad =
gst_pad_new_from_static_template (&celt_dec_sink_factory, "sink");
gst_pad_set_chain_function (dec->sinkpad, GST_DEBUG_FUNCPTR (celt_dec_chain));
gst_pad_set_event_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (celt_dec_sink_event));
gst_pad_set_query_type_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (celt_get_sink_query_types));
gst_pad_set_query_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (celt_dec_sink_query));
gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
dec->srcpad = gst_pad_new_from_static_template (&celt_dec_src_factory, "src");
gst_pad_use_fixed_caps (dec->srcpad);
gst_pad_set_event_function (dec->srcpad,
GST_DEBUG_FUNCPTR (celt_dec_src_event));
gst_pad_set_query_type_function (dec->srcpad,
GST_DEBUG_FUNCPTR (celt_get_src_query_types));
gst_pad_set_query_function (dec->srcpad,
GST_DEBUG_FUNCPTR (celt_dec_src_query));
gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
gst_celt_dec_reset (dec);
}
static gboolean
celt_dec_convert (GstPad * pad,
GstFormat src_format, gint64 src_value,
GstFormat * dest_format, gint64 * dest_value)
{
gboolean res = TRUE;
GstCeltDec *dec;
guint64 scale = 1;
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
if (dec->packetno < 1) {
res = FALSE;
goto cleanup;
}
if (src_format == *dest_format) {
*dest_value = src_value;
res = TRUE;
goto cleanup;
}
if (pad == dec->sinkpad &&
(src_format == GST_FORMAT_BYTES || *dest_format == GST_FORMAT_BYTES)) {
res = FALSE;
goto cleanup;
}
switch (src_format) {
case GST_FORMAT_TIME:
switch (*dest_format) {
case GST_FORMAT_BYTES:
scale = sizeof (gint16) * dec->header.nb_channels;
case GST_FORMAT_DEFAULT:
*dest_value =
gst_util_uint64_scale_int (scale * src_value,
dec->header.sample_rate, GST_SECOND);
break;
default:
res = FALSE;
break;
}
break;
case GST_FORMAT_DEFAULT:
switch (*dest_format) {
case GST_FORMAT_BYTES:
*dest_value = src_value * sizeof (gint16) * dec->header.nb_channels;
break;
case GST_FORMAT_TIME:
*dest_value =
gst_util_uint64_scale_int (src_value, GST_SECOND,
dec->header.sample_rate);
break;
default:
res = FALSE;
break;
}
break;
case GST_FORMAT_BYTES:
switch (*dest_format) {
case GST_FORMAT_DEFAULT:
*dest_value = src_value / (sizeof (gint16) * dec->header.nb_channels);
break;
case GST_FORMAT_TIME:
*dest_value = gst_util_uint64_scale_int (src_value, GST_SECOND,
dec->header.sample_rate * sizeof (gint16) *
dec->header.nb_channels);
break;
default:
res = FALSE;
break;
}
break;
default:
res = FALSE;
break;
}
cleanup:
gst_object_unref (dec);
return res;
}
static const GstQueryType *
celt_get_sink_query_types (GstPad * pad)
{
static const GstQueryType celt_dec_sink_query_types[] = {
GST_QUERY_CONVERT,
0
};
return celt_dec_sink_query_types;
}
static gboolean
celt_dec_sink_query (GstPad * pad, GstQuery * query)
{
GstCeltDec *dec;
gboolean res;
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
res = celt_dec_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val);
if (res) {
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
}
break;
}
default:
res = gst_pad_query_default (pad, query);
break;
}
gst_object_unref (dec);
return res;
}
static const GstQueryType *
celt_get_src_query_types (GstPad * pad)
{
static const GstQueryType celt_dec_src_query_types[] = {
GST_QUERY_POSITION,
GST_QUERY_DURATION,
0
};
return celt_dec_src_query_types;
}
static gboolean
celt_dec_src_query (GstPad * pad, GstQuery * query)
{
GstCeltDec *dec;
gboolean res = FALSE;
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:{
GstSegment segment;
GstFormat format;
gint64 cur;
gst_query_parse_position (query, &format, NULL);
GST_PAD_STREAM_LOCK (dec->sinkpad);
segment = dec->segment;
GST_PAD_STREAM_UNLOCK (dec->sinkpad);
if (segment.format != GST_FORMAT_TIME) {
GST_DEBUG_OBJECT (dec, "segment not initialised yet");
break;
}
if ((res = celt_dec_convert (dec->srcpad, GST_FORMAT_TIME,
segment.last_stop, &format, &cur))) {
gst_query_set_position (query, format, cur);
}
break;
}
case GST_QUERY_DURATION:{
GstFormat format = GST_FORMAT_TIME;
gint64 dur;
/* get duration from demuxer */
if (!gst_pad_query_peer_duration (dec->sinkpad, &format, &dur))
break;
gst_query_parse_duration (query, &format, NULL);
/* and convert it into the requested format */
if ((res = celt_dec_convert (dec->srcpad, GST_FORMAT_TIME,
dur, &format, &dur))) {
gst_query_set_duration (query, format, dur);
}
break;
}
default:
res = gst_pad_query_default (pad, query);
break;
}
gst_object_unref (dec);
return res;
}
static gboolean
celt_dec_src_event (GstPad * pad, GstEvent * event)
{
gboolean res = FALSE;
GstCeltDec *dec = GST_CELT_DEC (gst_pad_get_parent (pad));
GST_LOG_OBJECT (dec, "handling %s event", GST_EVENT_TYPE_NAME (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:{
GstFormat format, tformat;
gdouble rate;
GstEvent *real_seek;
GstSeekFlags flags;
GstSeekType cur_type, stop_type;
gint64 cur, stop;
gint64 tcur, tstop;
gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur,
&stop_type, &stop);
/* we have to ask our peer to seek to time here as we know
* nothing about how to generate a granulepos from the src
* formats or anything.
*
* First bring the requested format to time
*/
tformat = GST_FORMAT_TIME;
if (!(res = celt_dec_convert (pad, format, cur, &tformat, &tcur)))
break;
if (!(res = celt_dec_convert (pad, format, stop, &tformat, &tstop)))
break;
/* then seek with time on the peer */
real_seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
flags, cur_type, tcur, stop_type, tstop);
GST_LOG_OBJECT (dec, "seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (tcur));
res = gst_pad_push_event (dec->sinkpad, real_seek);
gst_event_unref (event);
break;
}
default:
res = gst_pad_event_default (pad, event);
break;
}
gst_object_unref (dec);
return res;
}
static gboolean
celt_dec_sink_event (GstPad * pad, GstEvent * event)
{
GstCeltDec *dec;
gboolean ret = FALSE;
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
GST_LOG_OBJECT (dec, "handling %s event", GST_EVENT_TYPE_NAME (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_NEWSEGMENT:{
GstFormat format;
gdouble rate, arate;
gint64 start, stop, time;
gboolean update;
gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
&start, &stop, &time);
if (format != GST_FORMAT_TIME)
goto newseg_wrong_format;
if (rate <= 0.0)
goto newseg_wrong_rate;
if (update) {
/* time progressed without data, see if we can fill the gap with
* some concealment data */
if (dec->segment.last_stop < start) {
GstClockTime duration;
duration = start - dec->segment.last_stop;
celt_dec_chain_parse_data (dec, NULL, dec->segment.last_stop,
duration);
}
}
/* now configure the values */
gst_segment_set_newsegment_full (&dec->segment, update,
rate, arate, GST_FORMAT_TIME, start, stop, time);
dec->granulepos = -1;
GST_DEBUG_OBJECT (dec, "segment now: cur = %" GST_TIME_FORMAT " [%"
GST_TIME_FORMAT " - %" GST_TIME_FORMAT "]",
GST_TIME_ARGS (dec->segment.last_stop),
GST_TIME_ARGS (dec->segment.start),
GST_TIME_ARGS (dec->segment.stop));
ret = gst_pad_push_event (dec->srcpad, event);
break;
}
default:
ret = gst_pad_event_default (pad, event);
break;
}
gst_object_unref (dec);
return ret;
/* ERRORS */
newseg_wrong_format:
{
GST_DEBUG_OBJECT (dec, "received non TIME newsegment");
gst_object_unref (dec);
return FALSE;
}
newseg_wrong_rate:
{
GST_DEBUG_OBJECT (dec, "negative rates not supported yet");
gst_object_unref (dec);
return FALSE;
}
}
static GstFlowReturn
celt_dec_chain_parse_header (GstCeltDec * dec, GstBuffer * buf)
{
GstCaps *caps;
gint error = CELT_OK;
/* get the header */
celt_header_from_packet ((const unsigned char *) GST_BUFFER_DATA (buf),
GST_BUFFER_SIZE (buf), &dec->header);
if (memcmp (dec->header.codec_id, "CELT ", 8) != 0)
goto invalid_header;
dec->mode =
celt_mode_create (dec->header.sample_rate, dec->header.nb_channels,
dec->header.frame_size, &error);
if (!dec->mode)
goto mode_init_failed;
/* initialize the decoder */
dec->state = celt_decoder_create (dec->mode);
if (!dec->state)
goto init_failed;
celt_mode_info (dec->mode, CELT_GET_FRAME_SIZE, &dec->frame_size);
dec->frame_duration = gst_util_uint64_scale_int (dec->frame_size,
GST_SECOND, dec->header.sample_rate);
/* set caps */
caps = gst_caps_new_simple ("audio/x-raw-int",
"rate", G_TYPE_INT, dec->header.sample_rate,
"channels", G_TYPE_INT, dec->header.nb_channels,
"signed", G_TYPE_BOOLEAN, TRUE,
"endianness", G_TYPE_INT, G_BYTE_ORDER,
"width", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16, NULL);
if (!gst_pad_set_caps (dec->srcpad, caps))
goto nego_failed;
gst_caps_unref (caps);
return GST_FLOW_OK;
/* ERRORS */
invalid_header:
{
GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE,
(NULL), ("Invalid header"));
return GST_FLOW_ERROR;
}
mode_init_failed:
{
GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE,
(NULL), ("Mode initialization failed: %d", error));
return GST_FLOW_ERROR;
}
init_failed:
{
GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE,
(NULL), ("couldn't initialize decoder"));
return GST_FLOW_ERROR;
}
nego_failed:
{
GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE,
(NULL), ("couldn't negotiate format"));
gst_caps_unref (caps);
return GST_FLOW_NOT_NEGOTIATED;
}
}
static GstFlowReturn
celt_dec_chain_parse_comments (GstCeltDec * dec, GstBuffer * buf)
{
GstTagList *list;
gchar *ver, *encoder = NULL;
list = gst_tag_list_from_vorbiscomment_buffer (buf, NULL, 0, &encoder);
if (!list) {
GST_WARNING_OBJECT (dec, "couldn't decode comments");
list = gst_tag_list_new ();
}
if (encoder) {
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
GST_TAG_ENCODER, encoder, NULL);
}
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
GST_TAG_AUDIO_CODEC, "Celt", NULL);
ver = g_strndup (dec->header.codec_version, 20);
g_strstrip (ver);
if (ver != NULL && *ver != '\0') {
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
GST_TAG_ENCODER_VERSION, ver, NULL);
}
if (dec->header.bytes_per_packet > 0) {
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
GST_TAG_BITRATE, (guint) dec->header.bytes_per_packet * 8, NULL);
}
GST_INFO_OBJECT (dec, "tags: %" GST_PTR_FORMAT, list);
gst_element_found_tags_for_pad (GST_ELEMENT (dec), dec->srcpad, list);
g_free (encoder);
g_free (ver);
return GST_FLOW_OK;
}
static GstFlowReturn
celt_dec_chain_parse_data (GstCeltDec * dec, GstBuffer * buf,
GstClockTime timestamp, GstClockTime duration)
{
GstFlowReturn res = GST_FLOW_OK;
gint size;
guint8 *data;
GstBuffer *outbuf;
gint16 *out_data;
gint error = CELT_OK;
if (timestamp != -1) {
dec->segment.last_stop = timestamp;
dec->granulepos = -1;
}
if (buf) {
data = GST_BUFFER_DATA (buf);
size = GST_BUFFER_SIZE (buf);
GST_DEBUG_OBJECT (dec, "received buffer of size %u", size);
/* copy timestamp */
} else {
/* concealment data, pass NULL as the bits parameters */
GST_DEBUG_OBJECT (dec, "creating concealment data");
data = NULL;
size = 0;
}
res = gst_pad_alloc_buffer_and_set_caps (dec->srcpad,
GST_BUFFER_OFFSET_NONE, dec->frame_size * dec->header.nb_channels * 2,
GST_PAD_CAPS (dec->srcpad), &outbuf);
if (res != GST_FLOW_OK) {
GST_DEBUG_OBJECT (dec, "buf alloc flow: %s", gst_flow_get_name (res));
return res;
}
out_data = (gint16 *) GST_BUFFER_DATA (outbuf);
GST_LOG_OBJECT (dec, "decoding frame");
error = celt_decode (dec->state, data, size, out_data);
if (error != CELT_OK) {
GST_WARNING_OBJECT (dec, "Decoding error: %d", error);
return GST_FLOW_ERROR;
}
if (dec->granulepos == -1) {
if (dec->segment.format != GST_FORMAT_TIME) {
GST_WARNING_OBJECT (dec, "segment not initialized or not TIME format");
dec->granulepos = 0;
} else {
dec->granulepos = gst_util_uint64_scale_int (dec->segment.last_stop,
dec->header.sample_rate, GST_SECOND);
}
GST_DEBUG_OBJECT (dec, "granulepos=%" G_GINT64_FORMAT, dec->granulepos);
}
GST_BUFFER_OFFSET (outbuf) = dec->granulepos;
GST_BUFFER_OFFSET_END (outbuf) = dec->granulepos + dec->frame_size;
GST_BUFFER_TIMESTAMP (outbuf) = gst_util_uint64_scale_int (dec->granulepos,
GST_SECOND, dec->header.sample_rate);
GST_BUFFER_DURATION (outbuf) = dec->frame_duration;
dec->granulepos += dec->frame_size;
dec->segment.last_stop += dec->frame_duration;
GST_LOG_OBJECT (dec, "pushing buffer with ts=%" GST_TIME_FORMAT ", dur=%"
GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
GST_TIME_ARGS (dec->frame_duration));
res = gst_pad_push (dec->srcpad, outbuf);
if (res != GST_FLOW_OK)
GST_DEBUG_OBJECT (dec, "flow: %s", gst_flow_get_name (res));
return res;
}
static GstFlowReturn
celt_dec_chain (GstPad * pad, GstBuffer * buf)
{
GstFlowReturn res;
GstCeltDec *dec;
dec = GST_CELT_DEC (gst_pad_get_parent (pad));
if (dec->packetno == 0)
res = celt_dec_chain_parse_header (dec, buf);
else if (dec->packetno == 1)
res = celt_dec_chain_parse_comments (dec, buf);
else if (dec->packetno <= 1 + dec->header.extra_headers)
res = GST_FLOW_OK;
else
res =
celt_dec_chain_parse_data (dec, buf, GST_BUFFER_TIMESTAMP (buf),
GST_BUFFER_DURATION (buf));
dec->packetno++;
gst_buffer_unref (buf);
gst_object_unref (dec);
return res;
}
static GstStateChangeReturn
celt_dec_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret;
GstCeltDec *dec = GST_CELT_DEC (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
case GST_STATE_CHANGE_READY_TO_PAUSED:
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
default:
break;
}
ret = parent_class->change_state (element, transition);
if (ret != GST_STATE_CHANGE_SUCCESS)
return ret;
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_celt_dec_reset (dec);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
break;
default:
break;
}
return ret;
}

73
ext/celt/gstceltdec.h Normal file
View file

@ -0,0 +1,73 @@
/* 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_CELT_DEC_H__
#define __GST_CELT_DEC_H__
#include <gst/gst.h>
#include <celt/celt.h>
#include <celt/celt_header.h>
G_BEGIN_DECLS
#define GST_TYPE_CELT_DEC \
(gst_celt_dec_get_type())
#define GST_CELT_DEC(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CELT_DEC,GstCeltDec))
#define GST_CELT_DEC_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CELT_DEC,GstCeltDecClass))
#define GST_IS_CELT_DEC(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CELT_DEC))
#define GST_IS_CELT_DEC_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CELT_DEC))
typedef struct _GstCeltDec GstCeltDec;
typedef struct _GstCeltDecClass GstCeltDecClass;
#define DEC_MAX_FRAME_SIZE 2000
struct _GstCeltDec {
GstElement element;
/* pads */
GstPad *sinkpad;
GstPad *srcpad;
CELTDecoder *state;
CELTMode *mode;
CELTHeader header;
gint frame_size;
GstClockTime frame_duration;
guint64 packetno;
GstSegment segment; /* STREAM LOCK */
gint64 granulepos; /* -1 = needs to be set from current time */
};
struct _GstCeltDecClass {
GstElementClass parent_class;
};
GType gst_celt_dec_get_type (void);
G_END_DECLS
#endif /* __GST_CELT_DEC_H__ */

943
ext/celt/gstceltenc.c Normal file
View file

@ -0,0 +1,943 @@
/* GStreamer Celt Encoder
* 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.
*/
/*
* Based on the speexenc element
*/
/**
* SECTION:element-celtenc
* @short_description: an encoder that encodes raw audio to CELT
* @see_also: celtdec, oggmux
*
* <refsect2>
* <para>
* This element raw audio to CELT.
* </para>
* <title>Example pipelines</title>
* <para>
* <programlisting>
* gst-launch -v audiotestsrc wave=sine num-buffers=100 ! audioconvert ! celtenc ! oggmux ! filesink location=sine.ogg
* </programlisting>
* Encode a test sine signal to Ogg/CELT.
* </para>
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <celt/celt.h>
#include <celt/celt_header.h>
#include <gst/gsttagsetter.h>
#include <gst/tag/tag.h>
#include "gstceltenc.h"
GST_DEBUG_CATEGORY_STATIC (celtenc_debug);
#define GST_CAT_DEFAULT celtenc_debug
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int, "
"rate = (int) [ 32000, 64000 ], "
"channels = (int) [ 1, 2 ], "
"endianness = (int) BYTE_ORDER, "
"signed = (boolean) TRUE, " "width = (int) 16, " "depth = (int) 16")
);
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-celt, "
"rate = (int) [ 32000, 64000 ], " "channels = (int) [ 1, 2 ]")
);
static const GstElementDetails celtenc_details =
GST_ELEMENT_DETAILS ("Celt audio encoder",
"Codec/Encoder/Audio",
"Encodes audio in Celt format",
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
#define DEFAULT_BITRATE 128
#define DEFAULT_FRAMESIZE 256
enum
{
PROP_0,
PROP_BITRATE,
PROP_FRAMESIZE
};
static void gst_celt_enc_finalize (GObject * object);
static gboolean gst_celt_enc_sinkevent (GstPad * pad, GstEvent * event);
static GstFlowReturn gst_celt_enc_chain (GstPad * pad, GstBuffer * buf);
static gboolean gst_celt_enc_setup (GstCeltEnc * enc);
static void gst_celt_enc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_celt_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static GstStateChangeReturn gst_celt_enc_change_state (GstElement * element,
GstStateChange transition);
static void
gst_celt_enc_setup_interfaces (GType celtenc_type)
{
static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL };
g_type_add_interface_static (celtenc_type, GST_TYPE_TAG_SETTER,
&tag_setter_info);
GST_DEBUG_CATEGORY_INIT (celtenc_debug, "celtenc", 0, "Celt encoder");
}
GST_BOILERPLATE_FULL (GstCeltEnc, gst_celt_enc, GstElement, GST_TYPE_ELEMENT,
gst_celt_enc_setup_interfaces);
static void
gst_celt_enc_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&src_factory));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&sink_factory));
gst_element_class_set_details_simple (element_class, "Celt audio encoder",
"Codec/Encoder/Audio",
"Encodes audio in Celt format",
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
}
static void
gst_celt_enc_class_init (GstCeltEncClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gobject_class->set_property = gst_celt_enc_set_property;
gobject_class->get_property = gst_celt_enc_get_property;
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BITRATE,
g_param_spec_int ("bitrate", "Encoding Bit-rate",
"Specify an encoding bit-rate (in bps). (0 = automatic)",
0, 150, DEFAULT_BITRATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_FRAMESIZE,
g_param_spec_int ("framesize", "Frame Size",
"The number of samples per frame", 64, 256, DEFAULT_FRAMESIZE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_celt_enc_finalize);
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_celt_enc_change_state);
}
static void
gst_celt_enc_finalize (GObject * object)
{
GstCeltEnc *enc;
enc = GST_CELT_ENC (object);
g_object_unref (enc->adapter);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_celt_enc_sink_setcaps (GstPad * pad, GstCaps * caps)
{
GstCeltEnc *enc;
GstStructure *structure;
enc = GST_CELT_ENC (GST_PAD_PARENT (pad));
enc->setup = FALSE;
structure = gst_caps_get_structure (caps, 0);
gst_structure_get_int (structure, "channels", &enc->channels);
gst_structure_get_int (structure, "rate", &enc->rate);
gst_celt_enc_setup (enc);
return enc->setup;
}
static GstCaps *
gst_celt_enc_sink_getcaps (GstPad * pad)
{
GstCaps *caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
GstCaps *peercaps = NULL;
GstCeltEnc *enc = GST_CELT_ENC (gst_pad_get_parent_element (pad));
peercaps = gst_pad_peer_get_caps (enc->srcpad);
if (peercaps) {
if (!gst_caps_is_empty (peercaps) && !gst_caps_is_any (peercaps)) {
GstStructure *ps = gst_caps_get_structure (peercaps, 0);
GstStructure *s = gst_caps_get_structure (caps, 0);
gint rate, channels;
if (gst_structure_get_int (ps, "rate", &rate)) {
gst_structure_fixate_field_nearest_int (s, "rate", rate);
}
if (gst_structure_get_int (ps, "channels", &channels)) {
gst_structure_fixate_field_nearest_int (s, "channels", channels);
}
}
gst_caps_unref (peercaps);
}
gst_object_unref (enc);
return caps;
}
static gboolean
gst_celt_enc_convert_src (GstPad * pad, GstFormat src_format, gint64 src_value,
GstFormat * dest_format, gint64 * dest_value)
{
gboolean res = TRUE;
GstCeltEnc *enc;
gint64 avg;
enc = GST_CELT_ENC (GST_PAD_PARENT (pad));
if (enc->samples_in == 0 || enc->bytes_out == 0 || enc->rate == 0)
return FALSE;
avg = (enc->bytes_out * enc->rate) / (enc->samples_in);
switch (src_format) {
case GST_FORMAT_BYTES:
switch (*dest_format) {
case GST_FORMAT_TIME:
*dest_value = src_value * GST_SECOND / avg;
break;
default:
res = FALSE;
}
break;
case GST_FORMAT_TIME:
switch (*dest_format) {
case GST_FORMAT_BYTES:
*dest_value = src_value * avg / GST_SECOND;
break;
default:
res = FALSE;
}
break;
default:
res = FALSE;
}
return res;
}
static gboolean
gst_celt_enc_convert_sink (GstPad * pad, GstFormat src_format,
gint64 src_value, GstFormat * dest_format, gint64 * dest_value)
{
gboolean res = TRUE;
guint scale = 1;
gint bytes_per_sample;
GstCeltEnc *enc;
enc = GST_CELT_ENC (GST_PAD_PARENT (pad));
bytes_per_sample = enc->channels * 2;
switch (src_format) {
case GST_FORMAT_BYTES:
switch (*dest_format) {
case GST_FORMAT_DEFAULT:
if (bytes_per_sample == 0)
return FALSE;
*dest_value = src_value / bytes_per_sample;
break;
case GST_FORMAT_TIME:
{
gint byterate = bytes_per_sample * enc->rate;
if (byterate == 0)
return FALSE;
*dest_value = src_value * GST_SECOND / byterate;
break;
}
default:
res = FALSE;
}
break;
case GST_FORMAT_DEFAULT:
switch (*dest_format) {
case GST_FORMAT_BYTES:
*dest_value = src_value * bytes_per_sample;
break;
case GST_FORMAT_TIME:
if (enc->rate == 0)
return FALSE;
*dest_value = src_value * GST_SECOND / enc->rate;
break;
default:
res = FALSE;
}
break;
case GST_FORMAT_TIME:
switch (*dest_format) {
case GST_FORMAT_BYTES:
scale = bytes_per_sample;
/* fallthrough */
case GST_FORMAT_DEFAULT:
*dest_value = src_value * scale * enc->rate / GST_SECOND;
break;
default:
res = FALSE;
}
break;
default:
res = FALSE;
}
return res;
}
static gint64
gst_celt_enc_get_latency (GstCeltEnc * enc)
{
return gst_util_uint64_scale (enc->frame_size, GST_SECOND, enc->rate);
}
static const GstQueryType *
gst_celt_enc_get_query_types (GstPad * pad)
{
static const GstQueryType gst_celt_enc_src_query_types[] = {
GST_QUERY_POSITION,
GST_QUERY_DURATION,
GST_QUERY_CONVERT,
GST_QUERY_LATENCY,
0
};
return gst_celt_enc_src_query_types;
}
static gboolean
gst_celt_enc_src_query (GstPad * pad, GstQuery * query)
{
gboolean res = TRUE;
GstCeltEnc *enc;
enc = GST_CELT_ENC (gst_pad_get_parent (pad));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:
{
GstFormat fmt, req_fmt;
gint64 pos, val;
gst_query_parse_position (query, &req_fmt, NULL);
if ((res = gst_pad_query_peer_position (enc->sinkpad, &req_fmt, &val))) {
gst_query_set_position (query, req_fmt, val);
break;
}
fmt = GST_FORMAT_TIME;
if (!(res = gst_pad_query_peer_position (enc->sinkpad, &fmt, &pos)))
break;
if ((res =
gst_pad_query_peer_convert (enc->sinkpad, fmt, pos, &req_fmt,
&val)))
gst_query_set_position (query, req_fmt, val);
break;
}
case GST_QUERY_DURATION:
{
GstFormat fmt, req_fmt;
gint64 dur, val;
gst_query_parse_duration (query, &req_fmt, NULL);
if ((res = gst_pad_query_peer_duration (enc->sinkpad, &req_fmt, &val))) {
gst_query_set_duration (query, req_fmt, val);
break;
}
fmt = GST_FORMAT_TIME;
if (!(res = gst_pad_query_peer_duration (enc->sinkpad, &fmt, &dur)))
break;
if ((res =
gst_pad_query_peer_convert (enc->sinkpad, fmt, dur, &req_fmt,
&val))) {
gst_query_set_duration (query, req_fmt, val);
}
break;
}
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if (!(res = gst_celt_enc_convert_src (pad, src_fmt, src_val, &dest_fmt,
&dest_val)))
goto error;
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
break;
}
case GST_QUERY_LATENCY:
{
gboolean live;
GstClockTime min_latency, max_latency;
gint64 latency;
if ((res = gst_pad_peer_query (pad, query))) {
gst_query_parse_latency (query, &live, &min_latency, &max_latency);
latency = gst_celt_enc_get_latency (enc);
/* add our latency */
min_latency += latency;
if (max_latency != -1)
max_latency += latency;
gst_query_set_latency (query, live, min_latency, max_latency);
}
break;
}
default:
res = gst_pad_peer_query (pad, query);
break;
}
error:
gst_object_unref (enc);
return res;
}
static gboolean
gst_celt_enc_sink_query (GstPad * pad, GstQuery * query)
{
gboolean res = TRUE;
GstCeltEnc *enc;
enc = GST_CELT_ENC (GST_PAD_PARENT (pad));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if (!(res =
gst_celt_enc_convert_sink (pad, src_fmt, src_val, &dest_fmt,
&dest_val)))
goto error;
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
break;
}
default:
res = gst_pad_query_default (pad, query);
break;
}
error:
return res;
}
static void
gst_celt_enc_init (GstCeltEnc * enc, GstCeltEncClass * klass)
{
enc->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad);
gst_pad_set_event_function (enc->sinkpad,
GST_DEBUG_FUNCPTR (gst_celt_enc_sinkevent));
gst_pad_set_chain_function (enc->sinkpad,
GST_DEBUG_FUNCPTR (gst_celt_enc_chain));
gst_pad_set_setcaps_function (enc->sinkpad,
GST_DEBUG_FUNCPTR (gst_celt_enc_sink_setcaps));
gst_pad_set_getcaps_function (enc->sinkpad,
GST_DEBUG_FUNCPTR (gst_celt_enc_sink_getcaps));
gst_pad_set_query_function (enc->sinkpad,
GST_DEBUG_FUNCPTR (gst_celt_enc_sink_query));
enc->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
gst_pad_set_query_function (enc->srcpad,
GST_DEBUG_FUNCPTR (gst_celt_enc_src_query));
gst_pad_set_query_type_function (enc->srcpad,
GST_DEBUG_FUNCPTR (gst_celt_enc_get_query_types));
gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad);
enc->channels = -1;
enc->rate = -1;
enc->bitrate = DEFAULT_BITRATE;
enc->frame_size = DEFAULT_FRAMESIZE;
enc->setup = FALSE;
enc->header_sent = FALSE;
enc->adapter = gst_adapter_new ();
}
static GstBuffer *
gst_celt_enc_create_metadata_buffer (GstCeltEnc * enc)
{
const GstTagList *user_tags;
GstTagList *merged_tags;
GstBuffer *comments = NULL;
user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc));
GST_DEBUG_OBJECT (enc, "upstream tags = %" GST_PTR_FORMAT, enc->tags);
GST_DEBUG_OBJECT (enc, "user-set tags = %" GST_PTR_FORMAT, user_tags);
/* gst_tag_list_merge() will handle NULL for either or both lists fine */
merged_tags = gst_tag_list_merge (user_tags, enc->tags,
gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (enc)));
if (merged_tags == NULL)
merged_tags = gst_tag_list_new ();
GST_DEBUG_OBJECT (enc, "merged tags = %" GST_PTR_FORMAT, merged_tags);
comments = gst_tag_list_to_vorbiscomment_buffer (merged_tags, NULL,
0, "Encoded with GStreamer Celtenc");
gst_tag_list_free (merged_tags);
GST_BUFFER_OFFSET (comments) = enc->bytes_out;
GST_BUFFER_OFFSET_END (comments) = 0;
return comments;
}
static gboolean
gst_celt_enc_setup (GstCeltEnc * enc)
{
gint error = CELT_OK;
gint bytes_per_packet;
enc->setup = FALSE;
/* Fix bitrate */
if (enc->channels == 1) {
if (enc->bitrate <= 0)
enc->bitrate = 64;
else if (enc->bitrate < 32)
enc->bitrate = 32;
else if (enc->bitrate > 110)
enc->bitrate = 110;
} else {
if (enc->bitrate <= 0)
enc->bitrate = 128;
else if (enc->bitrate < 64)
enc->bitrate = 64;
else if (enc->bitrate > 150)
enc->bitrate = 150;
}
enc->mode =
celt_mode_create (enc->rate, enc->channels, enc->frame_size, &error);
if (!enc->mode)
goto mode_initialization_failed;
celt_mode_info (enc->mode, CELT_GET_FRAME_SIZE, &enc->frame_size);
bytes_per_packet =
(enc->bitrate * 1000 * enc->frame_size / enc->rate + 4) / 8;
celt_header_init (&enc->header, enc->mode);
enc->header.nb_channels = enc->channels;
enc->state = celt_encoder_create (enc->mode);
if (!enc->state)
goto encoder_creation_failed;
GST_LOG_OBJECT (enc, "we have frame size %d", enc->frame_size);
enc->setup = TRUE;
return TRUE;
mode_initialization_failed:
GST_ERROR_OBJECT (enc, "Mode initialization failed: %d", error);
return FALSE;
encoder_creation_failed:
GST_ERROR_OBJECT (enc, "Encoder creation failed");
return FALSE;
}
/* prepare a buffer for transmission */
static GstBuffer *
gst_celt_enc_buffer_from_data (GstCeltEnc * enc, guchar * data,
gint data_len, guint64 granulepos)
{
GstBuffer *outbuf;
outbuf = gst_buffer_new_and_alloc (data_len);
memcpy (GST_BUFFER_DATA (outbuf), data, data_len);
GST_BUFFER_OFFSET (outbuf) = enc->bytes_out;
GST_BUFFER_OFFSET_END (outbuf) = granulepos;
GST_LOG_OBJECT (enc, "encoded buffer of %d bytes", GST_BUFFER_SIZE (outbuf));
return outbuf;
}
/* push out the buffer and do internal bookkeeping */
static GstFlowReturn
gst_celt_enc_push_buffer (GstCeltEnc * enc, GstBuffer * buffer)
{
guint size;
size = GST_BUFFER_SIZE (buffer);
enc->bytes_out += size;
GST_DEBUG_OBJECT (enc, "pushing output buffer of size %u", size);
return gst_pad_push (enc->srcpad, buffer);
}
static GstCaps *
gst_celt_enc_set_header_on_caps (GstCaps * caps, GstBuffer * buf1,
GstBuffer * buf2)
{
GstStructure *structure = NULL;
GstBuffer *buf;
GValue array = { 0 };
GValue value = { 0 };
caps = gst_caps_make_writable (caps);
structure = gst_caps_get_structure (caps, 0);
g_assert (gst_buffer_is_metadata_writable (buf1));
g_assert (gst_buffer_is_metadata_writable (buf2));
/* mark buffers */
GST_BUFFER_FLAG_SET (buf1, GST_BUFFER_FLAG_IN_CAPS);
GST_BUFFER_FLAG_SET (buf2, GST_BUFFER_FLAG_IN_CAPS);
/* put buffers in a fixed list */
g_value_init (&array, GST_TYPE_ARRAY);
g_value_init (&value, GST_TYPE_BUFFER);
buf = gst_buffer_copy (buf1);
gst_value_set_buffer (&value, buf);
gst_buffer_unref (buf);
gst_value_array_append_value (&array, &value);
g_value_unset (&value);
g_value_init (&value, GST_TYPE_BUFFER);
buf = gst_buffer_copy (buf2);
gst_value_set_buffer (&value, buf);
gst_buffer_unref (buf);
gst_value_array_append_value (&array, &value);
gst_structure_set_value (structure, "streamheader", &array);
g_value_unset (&value);
g_value_unset (&array);
return caps;
}
static gboolean
gst_celt_enc_sinkevent (GstPad * pad, GstEvent * event)
{
gboolean res = TRUE;
GstCeltEnc *enc;
enc = GST_CELT_ENC (gst_pad_get_parent (pad));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
enc->eos = TRUE;
res = gst_pad_event_default (pad, event);
break;
case GST_EVENT_TAG:
{
GstTagList *list;
gst_event_parse_tag (event, &list);
if (enc->tags) {
gst_tag_list_insert (enc->tags, list,
gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (enc)));
}
res = gst_pad_event_default (pad, event);
break;
}
default:
res = gst_pad_event_default (pad, event);
break;
}
gst_object_unref (enc);
return res;
}
static GstFlowReturn
gst_celt_enc_chain (GstPad * pad, GstBuffer * buf)
{
GstCeltEnc *enc;
GstFlowReturn ret = GST_FLOW_OK;
enc = GST_CELT_ENC (GST_PAD_PARENT (pad));
if (!enc->setup)
goto not_setup;
if (!enc->header_sent) {
/* Celt streams 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.
We merely need to make the headers, then pass them to libcelt
one at a time; libcelt handles the additional Ogg bitstream
constraints */
GstBuffer *buf1, *buf2;
GstCaps *caps;
guchar data[100];
/* create header buffer */
celt_header_to_packet (&enc->header, data, 100);
buf1 = gst_celt_enc_buffer_from_data (enc, data, 100, 0);
/* create comment buffer */
buf2 = gst_celt_enc_create_metadata_buffer (enc);
/* mark and put on caps */
caps = gst_pad_get_caps (enc->srcpad);
caps = gst_celt_enc_set_header_on_caps (caps, buf1, buf2);
gst_caps_set_simple (caps,
"rate", G_TYPE_INT, enc->rate,
"channels", G_TYPE_INT, enc->channels, NULL);
/* negotiate with these caps */
GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, caps);
gst_pad_set_caps (enc->srcpad, caps);
gst_buffer_set_caps (buf1, caps);
gst_buffer_set_caps (buf2, caps);
gst_caps_unref (caps);
/* push out buffers */
ret = gst_celt_enc_push_buffer (enc, buf1);
if (ret != GST_FLOW_OK) {
gst_buffer_unref (buf2);
goto done;
}
ret = gst_celt_enc_push_buffer (enc, buf2);
if (ret != GST_FLOW_OK)
goto done;
enc->header_sent = TRUE;
}
{
gint frame_size = enc->frame_size;
gint bytes = frame_size * 2 * enc->channels;
gint bytes_per_packet =
(enc->bitrate * 1000 * enc->frame_size / enc->rate + 4) / 8;
GST_DEBUG_OBJECT (enc, "received buffer of %u bytes",
GST_BUFFER_SIZE (buf));
/* push buffer to adapter */
gst_adapter_push (enc->adapter, buf);
buf = NULL;
while (gst_adapter_available (enc->adapter) >= bytes) {
gint16 *data;
gint outsize;
GstBuffer *outbuf;
ret = gst_pad_alloc_buffer_and_set_caps (enc->srcpad,
GST_BUFFER_OFFSET_NONE, bytes_per_packet, GST_PAD_CAPS (enc->srcpad),
&outbuf);
if (GST_FLOW_OK != ret)
goto done;
data = (gint16 *) gst_adapter_take (enc->adapter, bytes);
enc->samples_in += frame_size;
GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", frame_size,
bytes);
outsize =
celt_encode (enc->state, data, GST_BUFFER_DATA (outbuf),
bytes_per_packet);
g_free (data);
if (outsize < 0) {
GST_ERROR_OBJECT (enc, "Encoding failed: %d", outsize);
ret = GST_FLOW_ERROR;
goto done;
}
enc->frameno++;
GST_BUFFER_TIMESTAMP (outbuf) =
gst_util_uint64_scale_int (enc->frameno * frame_size, GST_SECOND,
enc->rate);
GST_BUFFER_DURATION (outbuf) =
gst_util_uint64_scale_int (frame_size, GST_SECOND, enc->rate);
/* set gp time and granulepos; see gst-plugins-base/ext/ogg/README */
GST_BUFFER_OFFSET_END (outbuf) = ((enc->frameno + 1) * frame_size);
GST_BUFFER_OFFSET (outbuf) =
gst_util_uint64_scale_int (GST_BUFFER_OFFSET_END (outbuf), GST_SECOND,
enc->rate);
ret = gst_celt_enc_push_buffer (enc, outbuf);
if ((GST_FLOW_OK != ret) && (GST_FLOW_NOT_LINKED != ret))
goto done;
}
}
done:
if (buf)
gst_buffer_unref (buf);
return ret;
/* ERRORS */
not_setup:
{
GST_ELEMENT_ERROR (enc, CORE, NEGOTIATION, (NULL),
("encoder not initialized (input is not audio?)"));
ret = GST_FLOW_NOT_NEGOTIATED;
goto done;
}
}
static void
gst_celt_enc_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstCeltEnc *enc;
enc = GST_CELT_ENC (object);
switch (prop_id) {
case PROP_BITRATE:
g_value_set_int (value, enc->bitrate);
break;
case PROP_FRAMESIZE:
g_value_set_int (value, enc->frame_size);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_celt_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstCeltEnc *enc;
enc = GST_CELT_ENC (object);
switch (prop_id) {
case PROP_BITRATE:
enc->bitrate = g_value_get_int (value);
break;
case PROP_FRAMESIZE:
enc->frame_size = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstStateChangeReturn
gst_celt_enc_change_state (GstElement * element, GstStateChange transition)
{
GstCeltEnc *enc = GST_CELT_ENC (element);
GstStateChangeReturn res;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
enc->tags = gst_tag_list_new ();
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
enc->frameno = 0;
enc->samples_in = 0;
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
/* fall through */
default:
break;
}
res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (res == GST_STATE_CHANGE_FAILURE)
return res;
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
enc->setup = FALSE;
enc->header_sent = FALSE;
if (enc->state) {
celt_encoder_destroy (enc->state);
enc->state = NULL;
}
if (enc->mode) {
celt_mode_destroy (enc->mode);
enc->mode = NULL;
}
memset (&enc->header, 0, sizeof (enc->header));
break;
case GST_STATE_CHANGE_READY_TO_NULL:
gst_tag_list_free (enc->tags);
enc->tags = NULL;
default:
break;
}
return res;
}

92
ext/celt/gstceltenc.h Normal file
View file

@ -0,0 +1,92 @@
/* GStreamer Celt Encoder
* 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_CELT_ENC_H__
#define __GST_CELT_ENC_H__
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include <celt/celt.h>
#include <celt/celt_header.h>
G_BEGIN_DECLS
#define GST_TYPE_CELT_ENC \
(gst_celt_enc_get_type())
#define GST_CELT_ENC(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CELT_ENC,GstCeltEnc))
#define GST_CELT_ENC_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CELT_ENC,GstCeltEncClass))
#define GST_IS_CELT_ENC(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CELT_ENC))
#define GST_IS_CELT_ENC_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CELT_ENC))
#define MAX_FRAME_SIZE 2000*2
#define MAX_FRAME_BYTES 2000
typedef struct _GstCeltEnc GstCeltEnc;
typedef struct _GstCeltEncClass GstCeltEncClass;
struct _GstCeltEnc {
GstElement element;
/* pads */
GstPad *sinkpad,
*srcpad;
CELTHeader header;
CELTMode *mode;
CELTEncoder *state;
GstAdapter *adapter;
gint bitrate;
gint frame_size;
gint channels;
gint rate;
gboolean setup;
gboolean header_sent;
gboolean eos;
guint64 samples_in;
guint64 bytes_out;
GstTagList *tags;
guint64 frameno;
};
struct _GstCeltEncClass {
GstElementClass parent_class;
/* signals */
void (*frame_encoded) (GstElement *element);
};
GType gst_celt_enc_get_type (void);
G_END_DECLS
#endif /* __GST_CELT_ENC_H__ */