diff --git a/ChangeLog b/ChangeLog index a9abcabd29..75b76db660 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,30 @@ +2004-09-28 Wim Taymans + + * ext/speex/gstspeex.c: (plugin_init): + * ext/speex/gstspeexdec.c: (gst_speex_dec_base_init), + (gst_speex_dec_class_init), (speex_dec_get_formats), + (speex_get_event_masks), (speex_get_query_types), + (gst_speex_dec_init), (speex_dec_convert), (speex_dec_src_query), + (speex_dec_src_event), (speex_dec_event), (speex_dec_chain), + (gst_speexdec_get_property), (gst_speexdec_set_property), + (speex_dec_change_state): + * ext/speex/gstspeexdec.h: + * ext/speex/gstspeexenc.c: (gst_speexenc_get_formats), + (gst_speexenc_get_type), (speex_caps_factory), (raw_caps_factory), + (gst_speexenc_base_init), (gst_speexenc_class_init), + (gst_speexenc_sinkconnect), (gst_speexenc_convert_src), + (gst_speexenc_convert_sink), (gst_speexenc_get_query_types), + (gst_speexenc_src_query), (gst_speexenc_init), + (gst_speexenc_get_tag_value), (comment_init), (comment_add), + (gst_speexenc_metadata_set1), (gst_speexenc_set_metadata), + (gst_speexenc_setup), (gst_speexenc_buffer_from_data), + (gst_speexenc_push_buffer), (gst_speexenc_set_header_on_caps), + (gst_speexenc_chain), (gst_speexenc_get_property), + (gst_speexenc_set_property), (gst_speexenc_change_state): + * ext/speex/gstspeexenc.h: + Rewrote speex encoder, make sure it can be embedded in ogg. + Implemented speex decoder. + 2004-09-28 Christian Schaller * configure.ac: diff --git a/ext/speex/gstspeex.c b/ext/speex/gstspeex.c index cde74576a9..c80211c4f1 100644 --- a/ext/speex/gstspeex.c +++ b/ext/speex/gstspeex.c @@ -26,6 +26,12 @@ static gboolean plugin_init (GstPlugin * plugin) { + if (!gst_library_load ("gstbytestream")) + return FALSE; + + if (!gst_library_load ("gsttags")) + return FALSE; + if (!gst_element_register (plugin, "speexenc", GST_RANK_NONE, GST_TYPE_SPEEXENC)) return FALSE; diff --git a/ext/speex/gstspeexdec.c b/ext/speex/gstspeexdec.c index 660f332b5d..da246aab66 100644 --- a/ext/speex/gstspeexdec.c +++ b/ext/speex/gstspeexdec.c @@ -1,5 +1,5 @@ /* GStreamer - * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) 2004 Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -17,177 +17,579 @@ * Boston, MA 02111-1307, USA. */ - #ifdef HAVE_CONFIG_H -#include "config.h" +# include "config.h" #endif -#include #include "gstspeexdec.h" +#include +#include -/* elementfactory information */ -GstElementDetails gst_speexdec_details = { - "speex audio decoder", +GST_DEBUG_CATEGORY (speexdec_debug); +#define GST_CAT_DEFAULT speexdec_debug + +static GstElementDetails speex_dec_details = { + "SpeexDec", "Codec/Decoder/Audio", - ".speex", - "Wim Taymans ", + "decode speex streams to audio", + "Wim Taymans ", }; -/* SpeexDec signals and args */ +/* Filter signals and args */ enum { /* FILL ME */ LAST_SIGNAL }; +#define DEFAULT_ENH TRUE + enum { - ARG_0 - /* FILL ME */ + ARG_0, + ARG_ENH }; -static void gst_speexdec_base_init (gpointer g_class); -static void gst_speexdec_class_init (GstSpeexDec * klass); -static void gst_speexdec_init (GstSpeexDec * speexdec); - -static void gst_speexdec_chain (GstPad * pad, GstData * _data); -static GstPadLinkReturn gst_speexdec_sinkconnect (GstPad * pad, - const GstCaps * caps); - -static GstElementClass *parent_class = NULL; - -/*static guint gst_speexdec_signals[LAST_SIGNAL] = { 0 }; */ - -GType -gst_speexdec_get_type (void) -{ - static GType speexdec_type = 0; - - if (!speexdec_type) { - static const GTypeInfo speexdec_info = { - sizeof (GstSpeexDecClass), - gst_speexdec_base_init, - NULL, - (GClassInitFunc) gst_speexdec_class_init, - NULL, - NULL, - sizeof (GstSpeexDec), - 0, - (GInstanceInitFunc) gst_speexdec_init, - }; - - speexdec_type = - g_type_register_static (GST_TYPE_ELEMENT, "GstSpeexDec", &speexdec_info, - 0); - } - return speexdec_type; -} - -static GstStaticPadTemplate speexdec_sink_template = -GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-speex, " - "rate = (int) [ 1000, 48000 ], " "channels = (int) 1") - ); - -static GstStaticPadTemplate speexdec_src_template = +static GstStaticPadTemplate speex_dec_src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-int, " + "rate = (int) [ 6000, 48000 ], " + "channels = (int) [ 1, 2 ], " "endianness = (int) BYTE_ORDER, " - "signed = (boolean) true, " - "width = (int) 16, " - "depth = (int) 16, " - "rate = (int) [ 1000, 48000 ], " "channels = (int) 1") + "signed = (boolean) true, " "width = (int) 16, " "depth = (int) 16") ); +static GstStaticPadTemplate speex_dec_sink_factory = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-speex") + ); + +GST_BOILERPLATE (GstSpeexDec, gst_speex_dec, GstElement, GST_TYPE_ELEMENT); + +static void speex_dec_chain (GstPad * pad, GstData * data); +static GstElementStateReturn speex_dec_change_state (GstElement * element); +static const GstFormat *speex_dec_get_formats (GstPad * pad); + +static gboolean speex_dec_src_event (GstPad * pad, GstEvent * event); +static gboolean speex_dec_src_query (GstPad * pad, + GstQueryType query, GstFormat * format, gint64 * value); +static gboolean speex_dec_convert (GstPad * pad, + GstFormat src_format, gint64 src_value, + GstFormat * dest_format, gint64 * dest_value); + +static void gst_speexdec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_speexdec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + static void -gst_speexdec_base_init (gpointer g_class) +gst_speex_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 (&speexdec_src_template)); + gst_static_pad_template_get (&speex_dec_src_factory)); gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&speexdec_sink_template)); - - gst_element_class_set_details (element_class, &gst_speexdec_details); + gst_static_pad_template_get (&speex_dec_sink_factory)); + gst_element_class_set_details (element_class, &speex_dec_details); } static void -gst_speexdec_class_init (GstSpeexDec * klass) +gst_speex_dec_class_init (GstSpeexDecClass * klass) { + GObjectClass *gobject_class; GstElementClass *gstelement_class; + gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; - parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ENH, + g_param_spec_boolean ("enh", "Enh", "Enable perceptual enhancement", + DEFAULT_ENH, G_PARAM_READWRITE)); + + gstelement_class->change_state = speex_dec_change_state; + + gobject_class->set_property = gst_speexdec_set_property; + gobject_class->get_property = gst_speexdec_get_property; + + GST_DEBUG_CATEGORY_INIT (speexdec_debug, "speexdec", 0, + "speex decoding element"); +} + +static const GstFormat * +speex_dec_get_formats (GstPad * pad) +{ + static GstFormat src_formats[] = { + GST_FORMAT_BYTES, + GST_FORMAT_DEFAULT, /* samples in the audio case */ + GST_FORMAT_TIME, + 0 + }; + static GstFormat sink_formats[] = { + GST_FORMAT_BYTES, + GST_FORMAT_TIME, + GST_FORMAT_DEFAULT, /* samples */ + 0 + }; + + return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats); +} + +static const GstEventMask * +speex_get_event_masks (GstPad * pad) +{ + static const GstEventMask speex_dec_src_event_masks[] = { + {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH}, + {0,} + }; + + return speex_dec_src_event_masks; +} + +static const GstQueryType * +speex_get_query_types (GstPad * pad) +{ + static const GstQueryType speex_dec_src_query_types[] = { + GST_QUERY_TOTAL, + GST_QUERY_POSITION, + 0 + }; + + return speex_dec_src_query_types; } static void -gst_speexdec_init (GstSpeexDec * speexdec) +gst_speex_dec_init (GstSpeexDec * dec) { - GST_DEBUG ("gst_speexdec_init: initializing"); - - /* create the sink and src pads */ - speexdec->sinkpad = + dec->sinkpad = gst_pad_new_from_template (gst_static_pad_template_get - (&speexdec_sink_template), "sink"); - gst_element_add_pad (GST_ELEMENT (speexdec), speexdec->sinkpad); - gst_pad_set_chain_function (speexdec->sinkpad, gst_speexdec_chain); - gst_pad_set_link_function (speexdec->sinkpad, gst_speexdec_sinkconnect); + (&speex_dec_sink_factory), "sink"); + gst_pad_set_chain_function (dec->sinkpad, speex_dec_chain); + gst_pad_set_formats_function (dec->sinkpad, speex_dec_get_formats); + gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad); - speexdec->srcpad = + dec->srcpad = gst_pad_new_from_template (gst_static_pad_template_get - (&speexdec_src_template), "src"); - gst_pad_use_explicit_caps (speexdec->srcpad); - gst_element_add_pad (GST_ELEMENT (speexdec), speexdec->srcpad); + (&speex_dec_src_factory), "src"); + gst_pad_use_explicit_caps (dec->srcpad); + gst_pad_set_event_mask_function (dec->srcpad, speex_get_event_masks); + gst_pad_set_event_function (dec->srcpad, speex_dec_src_event); + gst_pad_set_query_type_function (dec->srcpad, speex_get_query_types); + gst_pad_set_query_function (dec->srcpad, speex_dec_src_query); + gst_pad_set_formats_function (dec->srcpad, speex_dec_get_formats); + gst_pad_set_convert_function (dec->srcpad, speex_dec_convert); + gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad); + dec->enh = DEFAULT_ENH; + + GST_FLAG_SET (dec, GST_ELEMENT_EVENT_AWARE); } -static GstPadLinkReturn -gst_speexdec_sinkconnect (GstPad * pad, const GstCaps * caps) +static gboolean +speex_dec_convert (GstPad * pad, + GstFormat src_format, gint64 src_value, + GstFormat * dest_format, gint64 * dest_value) { - GstSpeexDec *speexdec; - gint rate; - GstStructure *structure; + gboolean res = TRUE; + GstSpeexDec *dec; + guint64 scale = 1; - speexdec = GST_SPEEXDEC (gst_pad_get_parent (pad)); + dec = GST_SPEEXDEC (gst_pad_get_parent (pad)); - structure = gst_caps_get_structure (caps, 0); - gst_structure_get_int (structure, "rate", &rate); + if (dec->packetno < 1) + return FALSE; - if (gst_pad_set_explicit_caps (speexdec->srcpad, - gst_caps_new_simple ("audio/x-raw-int", - "endianness", G_TYPE_INT, G_BYTE_ORDER, - "signed", G_TYPE_BOOLEAN, TRUE, - "width", G_TYPE_INT, 16, - "depth", G_TYPE_INT, 16, - "rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, 1, NULL))) { - return GST_PAD_LINK_OK; + switch (src_format) { + case GST_FORMAT_TIME: + switch (*dest_format) { + case GST_FORMAT_BYTES: + scale = sizeof (float) * dec->header->nb_channels; + case GST_FORMAT_DEFAULT: + *dest_value = scale * (src_value * dec->header->rate / GST_SECOND); + break; + default: + res = FALSE; + } + break; + case GST_FORMAT_DEFAULT: + switch (*dest_format) { + case GST_FORMAT_BYTES: + *dest_value = src_value * sizeof (float) * dec->header->nb_channels; + break; + case GST_FORMAT_TIME: + *dest_value = src_value * GST_SECOND / dec->header->rate; + break; + default: + res = FALSE; + } + break; + case GST_FORMAT_BYTES: + switch (*dest_format) { + case GST_FORMAT_DEFAULT: + *dest_value = src_value / (sizeof (float) * dec->header->nb_channels); + break; + case GST_FORMAT_TIME: + *dest_value = src_value * GST_SECOND / + (dec->header->rate * sizeof (float) * dec->header->nb_channels); + break; + default: + res = FALSE; + } + break; + default: + res = FALSE; } - return GST_PAD_LINK_REFUSED; + + return res; +} + +static gboolean +speex_dec_src_query (GstPad * pad, GstQueryType query, GstFormat * format, + gint64 * value) +{ + gint64 samples_out = 0; + GstSpeexDec *dec = GST_SPEEXDEC (gst_pad_get_parent (pad)); + GstFormat my_format = GST_FORMAT_DEFAULT; + + if (query == GST_QUERY_POSITION) { + samples_out = dec->samples_out; + } else { + /* query peer in default format */ + if (!gst_pad_query (GST_PAD_PEER (dec->sinkpad), query, &my_format, + &samples_out)) + return FALSE; + } + + /* and convert to the final format */ + if (!gst_pad_convert (pad, GST_FORMAT_DEFAULT, samples_out, format, value)) + return FALSE; + + GST_LOG_OBJECT (dec, + "query %u: peer returned samples_out: %llu - we return %llu (format %u)\n", + query, samples_out, *value, *format); + return TRUE; +} + +static gboolean +speex_dec_src_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + GstSpeexDec *dec = GST_SPEEXDEC (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK:{ + guint64 value; + GstFormat my_format = GST_FORMAT_DEFAULT; + + /* convert to samples_out */ + res = speex_dec_convert (pad, GST_EVENT_SEEK_FORMAT (event), + GST_EVENT_SEEK_OFFSET (event), &my_format, &value); + if (res) { + GstEvent *real_seek = gst_event_new_seek ( + (GST_EVENT_SEEK_TYPE (event) & ~GST_SEEK_FORMAT_MASK) | + GST_FORMAT_DEFAULT, + value); + + res = gst_pad_send_event (GST_PAD_PEER (dec->sinkpad), real_seek); + } + gst_event_unref (event); + break; + } + default: + res = gst_pad_event_default (pad, event); + break; + } + + return res; } static void -gst_speexdec_chain (GstPad * pad, GstData * _data) +speex_dec_event (GstSpeexDec * dec, GstEvent * event) { - GstBuffer *buf = GST_BUFFER (_data); - GstSpeexDec *speexdec; - gchar *data; - guint size; + guint64 value, time, bytes; - g_return_if_fail (pad != NULL); - g_return_if_fail (GST_IS_PAD (pad)); - g_return_if_fail (buf != NULL); - /*g_return_if_fail(GST_IS_BUFFER(buf)); */ + GST_LOG_OBJECT (dec, "handling event"); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_DISCONTINUOUS: + if (gst_event_discont_get_value (event, GST_FORMAT_DEFAULT, + (gint64 *) & value)) { + dec->samples_out = value; + GST_DEBUG_OBJECT (dec, + "setting samples_out to %" G_GUINT64_FORMAT " after discont", + value); + } else { + GST_WARNING_OBJECT (dec, + "discont event didn't include offset, we might set it wrong now"); + } + if (dec->packetno < 2) { + if (dec->samples_out != 0) + GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), + ("can't handle discont before parsing first 2 packets")); + dec->packetno = 0; + gst_pad_push (dec->srcpad, GST_DATA (gst_event_new_discontinuous (FALSE, + GST_FORMAT_TIME, (guint64) 0, GST_FORMAT_DEFAULT, + (guint64) 0, GST_FORMAT_BYTES, (guint64) 0, 0))); + } else { + GstFormat time_format, default_format, bytes_format; - speexdec = GST_SPEEXDEC (gst_pad_get_parent (pad)); + time_format = GST_FORMAT_TIME; + default_format = GST_FORMAT_DEFAULT; + bytes_format = GST_FORMAT_BYTES; - data = GST_BUFFER_DATA (buf); - size = GST_BUFFER_SIZE (buf); - - gst_buffer_unref (buf); + dec->packetno = 2; + /* if one of them works, all of them work */ + if (speex_dec_convert (dec->srcpad, GST_FORMAT_DEFAULT, + dec->samples_out, &time_format, &time) + && speex_dec_convert (dec->srcpad, GST_FORMAT_DEFAULT, + dec->samples_out, &bytes_format, &bytes)) { + gst_pad_push (dec->srcpad, + GST_DATA (gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME, + time, GST_FORMAT_DEFAULT, dec->samples_out, + GST_FORMAT_BYTES, bytes, 0))); + } else { + GST_ERROR_OBJECT (dec, + "failed to parse data for DISCONT event, not sending any"); + } + } + gst_data_unref (GST_DATA (event)); + break; + default: + gst_pad_event_default (dec->sinkpad, event); + break; + } +} + +static void +speex_dec_chain (GstPad * pad, GstData * data) +{ + GstBuffer *buf; + GstSpeexDec *dec; + + dec = GST_SPEEXDEC (gst_pad_get_parent (pad)); + if (GST_IS_EVENT (data)) { + speex_dec_event (dec, GST_EVENT (data)); + return; + } + + buf = GST_BUFFER (data); + + if (dec->packetno == 0) { + GstCaps *caps; + + /* get the header */ + dec->header = + speex_packet_to_header (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + gst_data_unref (data); + if (!dec->header) { + GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE, + (NULL), ("couldn't read header")); + return; + } + if (dec->header->mode >= SPEEX_NB_MODES) { + GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE, + (NULL), + ("Mode number %d does not (yet/any longer) exist in this version", + dec->header->mode)); + return; + } + + dec->mode = speex_mode_list[dec->header->mode]; + + /* initialize the decoder */ + dec->state = speex_decoder_init (dec->mode); + if (!dec->state) { + GST_ELEMENT_ERROR (GST_ELEMENT (dec), STREAM, DECODE, + (NULL), ("couldn't initialize decoder")); + gst_data_unref (data); + return; + } + + speex_decoder_ctl (dec->state, SPEEX_SET_ENH, &dec->enh); + speex_decoder_ctl (dec->state, SPEEX_GET_FRAME_SIZE, &dec->frame_size); + + if (dec->header->nb_channels != 1) { + dec->callback.callback_id = SPEEX_INBAND_STEREO; + dec->callback.func = speex_std_stereo_request_handler; + dec->callback.data = &dec->stereo; + speex_decoder_ctl (dec->state, SPEEX_SET_HANDLER, &dec->callback); + } + + speex_decoder_ctl (dec->state, SPEEX_SET_SAMPLING_RATE, &dec->header->rate); + + speex_bits_init (&dec->bits); + + /* set caps */ + caps = gst_caps_new_simple ("audio/x-raw-int", + "rate", G_TYPE_INT, dec->header->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_explicit_caps (dec->srcpad, caps)) { + gst_caps_free (caps); + return; + } + gst_caps_free (caps); + } else if (dec->packetno == 1) { + gchar *encoder = NULL; + + /* FIXME parse comments */ + GstTagList *list = gst_tag_list_from_vorbiscomment_buffer (buf, "", 1, + &encoder); + + gst_data_unref (data); + + 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); + g_free (encoder); + } + /* + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_ENCODER_VERSION, dec->vi.version, NULL); + + if (dec->vi.bitrate_upper > 0) + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_MAXIMUM_BITRATE, (guint) vd->vi.bitrate_upper, NULL); + if (vd->vi.bitrate_nominal > 0) + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_NOMINAL_BITRATE, (guint) vd->vi.bitrate_nominal, NULL); + if (vd->vi.bitrate_lower > 0) + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_MINIMUM_BITRATE, (guint) vd->vi.bitrate_lower, NULL); + */ + gst_element_found_tags_for_pad (GST_ELEMENT (dec), dec->srcpad, 0, list); + } else { + gint i; + + /* send data to the bitstream */ + speex_bits_read_from (&dec->bits, GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf)); + gst_data_unref (data); + + /* now decode each frame */ + for (i = 0; i < dec->header->frames_per_packet; i++) { + gint ret; + GstBuffer *outbuf; + gint16 *out_data; + + ret = speex_decode (dec->state, &dec->bits, dec->output); + if (ret == -1) { + /* FIXME emit warning */ + break; + } else if (ret == -2) { + /* FIXME emit warning */ + break; + } + if (speex_bits_remaining (&dec->bits) < 0) { + fprintf (stderr, "Decoding overflow: corrupted stream?\n"); + break; + } + if (dec->header->nb_channels == 2) + speex_decode_stereo (dec->output, dec->frame_size, &dec->stereo); + + outbuf = gst_pad_alloc_buffer (dec->srcpad, GST_BUFFER_OFFSET_NONE, + dec->frame_size * dec->header->nb_channels * 2); + out_data = (gint16 *) GST_BUFFER_DATA (outbuf); + + /*PCM saturation (just in case) */ + for (i = 0; i < dec->frame_size * dec->header->nb_channels; i++) { + if (dec->output[i] > 32000.0) + out_data[i] = 32000; + else if (dec->output[i] < -32000.0) + out_data[i] = -32000; + else + out_data[i] = (gint16) dec->output[i]; + } + + GST_BUFFER_OFFSET (outbuf) = dec->samples_out; + GST_BUFFER_OFFSET_END (outbuf) = dec->samples_out + dec->frame_size; + GST_BUFFER_TIMESTAMP (outbuf) = + dec->samples_out * GST_SECOND / dec->header->rate; + GST_BUFFER_DURATION (outbuf) = + dec->frame_size * GST_SECOND / dec->header->rate; + gst_pad_push (dec->srcpad, GST_DATA (outbuf)); + dec->samples_out += dec->frame_size; + } + } + dec->packetno++; +} + +static void +gst_speexdec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstSpeexDec *speexdec; + + /* it's not null if we got it, but it might not be ours */ + g_return_if_fail (GST_IS_SPEEXDEC (object)); + + speexdec = GST_SPEEXDEC (object); + + switch (prop_id) { + case ARG_ENH: + g_value_set_boolean (value, speexdec->enh); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_speexdec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSpeexDec *speexdec; + + /* it's not null if we got it, but it might not be ours */ + g_return_if_fail (GST_IS_SPEEXDEC (object)); + + speexdec = GST_SPEEXDEC (object); + + switch (prop_id) { + case ARG_ENH: + speexdec->enh = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static GstElementStateReturn +speex_dec_change_state (GstElement * element) +{ + GstSpeexDec *vd = GST_SPEEXDEC (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_NULL_TO_READY: + break; + case GST_STATE_READY_TO_PAUSED: + break; + case GST_STATE_PAUSED_TO_PLAYING: + break; + case GST_STATE_PLAYING_TO_PAUSED: + break; + case GST_STATE_PAUSED_TO_READY: + vd->packetno = 0; + vd->samples_out = 0; + break; + case GST_STATE_READY_TO_NULL: + break; + default: + break; + } + + return parent_class->change_state (element); } diff --git a/ext/speex/gstspeexdec.h b/ext/speex/gstspeexdec.h index c679dc188e..dcb04a2c7c 100644 --- a/ext/speex/gstspeexdec.h +++ b/ext/speex/gstspeexdec.h @@ -23,7 +23,10 @@ #include -#include +#include +#include +#include +#include #ifdef __cplusplus extern "C" { @@ -31,7 +34,7 @@ extern "C" { #define GST_TYPE_SPEEXDEC \ - (gst_speexdec_get_type()) + (gst_speex_dec_get_type()) #define GST_SPEEXDEC(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SPEEXDEC,GstSpeexDec)) #define GST_SPEEXDEC_CLASS(klass) \ @@ -44,18 +47,35 @@ extern "C" { typedef struct _GstSpeexDec GstSpeexDec; typedef struct _GstSpeexDecClass GstSpeexDecClass; +#define DEC_MAX_FRAME_SIZE 2000 + struct _GstSpeexDec { GstElement element; /* pads */ GstPad *sinkpad,*srcpad; + + void *state; + SpeexStereoState stereo; + SpeexMode *mode; + SpeexHeader *header; + SpeexCallback callback; + SpeexBits bits; + + gfloat output[DEC_MAX_FRAME_SIZE]; + + gboolean enh; + + gint frame_size; + guint64 samples_out; + guint64 packetno; }; struct _GstSpeexDecClass { GstElementClass parent_class; }; -GType gst_speexdec_get_type(void); +GType gst_speex_dec_get_type(void); #ifdef __cplusplus diff --git a/ext/speex/gstspeexenc.c b/ext/speex/gstspeexenc.c index 2552c92ee7..530989c0a6 100644 --- a/ext/speex/gstspeexenc.c +++ b/ext/speex/gstspeexenc.c @@ -21,42 +21,94 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif +#include #include +#include +#include +#include +#include +#include +#include #include "gstspeexenc.h" +GST_DEBUG_CATEGORY (speexenc_debug); +#define GST_CAT_DEFAULT speexenc_debug + +static GstPadTemplate *gst_speexenc_src_template, *gst_speexenc_sink_template; + /* elementfactory information */ -GstElementDetails gst_speexenc_details = { - "speex audio encoder", +GstElementDetails speexenc_details = { + "Speex encoder", "Codec/Encoder/Audio", - ".speex", - "Wim Taymans ", + "Encodes audio in Speex format", + "Wim Taymans ", }; -/* SpeexEnc signals and args */ +/* GstSpeexEnc signals and args */ enum { - FRAME_ENCODED, /* FILL ME */ LAST_SIGNAL }; +#define DEFAULT_QUALITY 8.0 +#define DEFAULT_BITRATE 0 +#define DEFAULT_VBR FALSE +#define DEFAULT_ABR 0 +#define DEFAULT_VAD FALSE +#define DEFAULT_DTX FALSE +#define DEFAULT_COMPLEXITY 3 +#define DEFAULT_NFRAMES 1 + enum { - ARG_0 - /* FILL ME */ + ARG_0, + ARG_QUALITY, + ARG_BITRATE, + ARG_VBR, + ARG_ABR, + ARG_VAD, + ARG_DTX, + ARG_COMPLEXITY, + ARG_NFRAMES, + ARG_LAST_MESSAGE }; +static const GstFormat * +gst_speexenc_get_formats (GstPad * pad) +{ + static const GstFormat src_formats[] = { + GST_FORMAT_BYTES, + GST_FORMAT_TIME, + 0 + }; + static const GstFormat sink_formats[] = { + GST_FORMAT_BYTES, + GST_FORMAT_DEFAULT, + GST_FORMAT_TIME, + 0 + }; + + return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats); +} + static void gst_speexenc_base_init (gpointer g_class); -static void gst_speexenc_class_init (GstSpeexEnc * klass); +static void gst_speexenc_class_init (GstSpeexEncClass * klass); static void gst_speexenc_init (GstSpeexEnc * speexenc); static void gst_speexenc_chain (GstPad * pad, GstData * _data); -static GstPadLinkReturn gst_speexenc_sinkconnect (GstPad * pad, - const GstCaps * caps); +static gboolean gst_speexenc_setup (GstSpeexEnc * speexenc); + +static void gst_speexenc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_speexenc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static GstElementStateReturn gst_speexenc_change_state (GstElement * element); static GstElementClass *parent_class = NULL; -static guint gst_speexenc_signals[LAST_SIGNAL] = { 0 }; + +/*static guint gst_speexenc_signals[LAST_SIGNAL] = { 0 }; */ GType gst_speexenc_get_type (void) @@ -75,49 +127,63 @@ gst_speexenc_get_type (void) 0, (GInstanceInitFunc) gst_speexenc_init, }; + static const GInterfaceInfo tag_setter_info = { + NULL, + NULL, + NULL + }; speexenc_type = g_type_register_static (GST_TYPE_ELEMENT, "GstSpeexEnc", &speexenc_info, 0); + + g_type_add_interface_static (speexenc_type, GST_TYPE_TAG_SETTER, + &tag_setter_info); + + GST_DEBUG_CATEGORY_INIT (speexenc_debug, "speexenc", 0, "Speex encoder"); } return speexenc_type; } -static GstStaticPadTemplate speexenc_sink_template = -GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-raw-int, " - "endianness = (int) BYTE_ORDER, " - "signed = (boolean) true, " - "width = (int) 16, " - "depth = (int) 16, " - "rate = (int) [ 1000, 48000 ], " "channels = (int) 1") - ); +static GstCaps * +speex_caps_factory (void) +{ + return gst_caps_new_simple ("audio/x-speex", NULL); +} -static GstStaticPadTemplate speexenc_src_template = -GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-speex, " - "rate = (int) [ 1000, 48000 ], " "channels = (int) 1") - ); +static GstCaps * +raw_caps_factory (void) +{ + return + gst_caps_new_simple ("audio/x-raw-int", + "rate", GST_TYPE_INT_RANGE, 6000, 48000, + "channels", GST_TYPE_INT_RANGE, 1, 2, + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "signed", G_TYPE_BOOLEAN, TRUE, + "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16, NULL); +} static void gst_speexenc_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + GstCaps *raw_caps, *speex_caps; - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&speexenc_sink_template)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&speexenc_src_template)); + raw_caps = raw_caps_factory (); + speex_caps = speex_caps_factory (); - gst_element_class_set_details (element_class, &gst_speexenc_details); + gst_speexenc_sink_template = gst_pad_template_new ("sink", GST_PAD_SINK, + GST_PAD_ALWAYS, raw_caps); + gst_speexenc_src_template = gst_pad_template_new ("src", GST_PAD_SRC, + GST_PAD_ALWAYS, speex_caps); + gst_element_class_add_pad_template (element_class, + gst_speexenc_sink_template); + gst_element_class_add_pad_template (element_class, gst_speexenc_src_template); + gst_element_class_set_details (element_class, &speexenc_details); } static void -gst_speexenc_class_init (GstSpeexEnc * klass) +gst_speexenc_class_init (GstSpeexEncClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; @@ -125,36 +191,44 @@ gst_speexenc_class_init (GstSpeexEnc * klass) gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_QUALITY, + g_param_spec_float ("quality", "Quality", "Encoding quality", + 0.0, 10.0, DEFAULT_QUALITY, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BITRATE, + g_param_spec_int ("bitrate", "Encoding Bit-rate", + "Specify an encoding bit-rate (in bps). ", + 0, G_MAXINT, DEFAULT_BITRATE, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VBR, + g_param_spec_boolean ("vbr", "VBR", + "Enable variable bit-rate", DEFAULT_VBR, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ABR, + g_param_spec_int ("abr", "ABR", + "Enable average bit-rate (0 = disabled)", + 0, G_MAXINT, DEFAULT_ABR, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VAD, + g_param_spec_boolean ("vad", "VAD", + "Enable voice activity detection", DEFAULT_VAD, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DTX, + g_param_spec_boolean ("dtx", "DTX", + "Enable discontinuous transmission", DEFAULT_DTX, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_COMPLEXITY, + g_param_spec_int ("complexity", "Complexity", + "Set encoding complexity", + 0, G_MAXINT, DEFAULT_COMPLEXITY, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_NFRAMES, + g_param_spec_int ("nframes", "NFrames", + "Number of frames per buffer", + 0, G_MAXINT, DEFAULT_NFRAMES, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LAST_MESSAGE, + g_param_spec_string ("last-message", "last-message", + "The last status message", NULL, G_PARAM_READABLE)); + parent_class = g_type_class_ref (GST_TYPE_ELEMENT); - gst_speexenc_signals[FRAME_ENCODED] = - g_signal_new ("frame-encoded", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstSpeexEncClass, frame_encoded), - NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); -} + gobject_class->set_property = gst_speexenc_set_property; + gobject_class->get_property = gst_speexenc_get_property; - -static void -gst_speexenc_init (GstSpeexEnc * speexenc) -{ - /* create the sink and src pads */ - speexenc->sinkpad = - gst_pad_new_from_template (gst_static_pad_template_get - (&speexenc_sink_template), "sink"); - gst_element_add_pad (GST_ELEMENT (speexenc), speexenc->sinkpad); - gst_pad_set_chain_function (speexenc->sinkpad, gst_speexenc_chain); - gst_pad_set_link_function (speexenc->sinkpad, gst_speexenc_sinkconnect); - - speexenc->srcpad = - gst_pad_new_from_template (gst_static_pad_template_get - (&speexenc_src_template), "src"); - gst_element_add_pad (GST_ELEMENT (speexenc), speexenc->srcpad); - - speex_bits_init (&speexenc->bits); - speexenc->mode = (SpeexMode *) & speex_nb_mode; - speexenc->bufsize = 0; - speexenc->packet_count = 0; - speexenc->n_packets = 20; + gstelement_class->change_state = gst_speexenc_change_state; } static GstPadLinkReturn @@ -164,24 +238,593 @@ gst_speexenc_sinkconnect (GstPad * pad, const GstCaps * caps) GstStructure *structure; speexenc = GST_SPEEXENC (gst_pad_get_parent (pad)); + speexenc->setup = FALSE; structure = gst_caps_get_structure (caps, 0); + gst_structure_get_int (structure, "channels", &speexenc->channels); gst_structure_get_int (structure, "rate", &speexenc->rate); - if (gst_pad_try_set_caps (speexenc->srcpad, - gst_caps_new_simple ("audio/x-speex", - "rate", G_TYPE_INT, speexenc->rate, - "channels", G_TYPE_INT, 1, NULL))) { - speex_init_header (&speexenc->header, speexenc->rate, 1, speexenc->mode); - speexenc->header.frames_per_packet = speexenc->n_packets; - speexenc->state = speex_encoder_init (speexenc->mode); - speex_encoder_ctl (speexenc->state, SPEEX_GET_FRAME_SIZE, - &speexenc->frame_size); + gst_speexenc_setup (speexenc); + if (speexenc->setup) return GST_PAD_LINK_OK; - } - return GST_PAD_LINK_REFUSED; + return GST_PAD_LINK_REFUSED; +} + +static gboolean +gst_speexenc_convert_src (GstPad * pad, GstFormat src_format, gint64 src_value, + GstFormat * dest_format, gint64 * dest_value) +{ + gboolean res = TRUE; + GstSpeexEnc *speexenc; + gint64 avg; + + speexenc = GST_SPEEXENC (gst_pad_get_parent (pad)); + + if (speexenc->samples_in == 0 || + speexenc->bytes_out == 0 || speexenc->rate == 0) + return FALSE; + + avg = (speexenc->bytes_out * speexenc->rate) / (speexenc->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_speexenc_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; + GstSpeexEnc *speexenc; + + speexenc = GST_SPEEXENC (gst_pad_get_parent (pad)); + + bytes_per_sample = speexenc->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 * speexenc->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 (speexenc->rate == 0) + return FALSE; + *dest_value = src_value * GST_SECOND / speexenc->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 * speexenc->rate / GST_SECOND; + break; + default: + res = FALSE; + } + break; + default: + res = FALSE; + } + return res; +} + +static const GstQueryType * +gst_speexenc_get_query_types (GstPad * pad) +{ + static const GstQueryType gst_speexenc_src_query_types[] = { + GST_QUERY_TOTAL, + GST_QUERY_POSITION, + 0 + }; + + return gst_speexenc_src_query_types; +} + +static gboolean +gst_speexenc_src_query (GstPad * pad, GstQueryType type, + GstFormat * format, gint64 * value) +{ + gboolean res = TRUE; + GstSpeexEnc *speexenc; + + speexenc = GST_SPEEXENC (gst_pad_get_parent (pad)); + + switch (type) { + case GST_QUERY_TOTAL: + { + switch (*format) { + case GST_FORMAT_BYTES: + case GST_FORMAT_TIME: + { + gint64 peer_value; + const GstFormat *peer_formats; + + res = FALSE; + + peer_formats = gst_pad_get_formats (GST_PAD_PEER (speexenc->sinkpad)); + + while (peer_formats && *peer_formats && !res) { + + GstFormat peer_format = *peer_formats; + + /* do the probe */ + if (gst_pad_query (GST_PAD_PEER (speexenc->sinkpad), + GST_QUERY_TOTAL, &peer_format, &peer_value)) { + GstFormat conv_format; + + /* convert to TIME */ + conv_format = GST_FORMAT_TIME; + res = gst_pad_convert (speexenc->sinkpad, + peer_format, peer_value, &conv_format, value); + /* and to final format */ + res &= gst_pad_convert (pad, + GST_FORMAT_TIME, *value, format, value); + } + peer_formats++; + } + break; + } + default: + res = FALSE; + break; + } + break; + } + case GST_QUERY_POSITION: + switch (*format) { + default: + { + /* we only know about our samples, convert to requested format */ + res = gst_pad_convert (pad, + GST_FORMAT_BYTES, speexenc->bytes_out, format, value); + break; + } + } + break; + default: + res = FALSE; + break; + } + return res; +} + +static void +gst_speexenc_init (GstSpeexEnc * speexenc) +{ + speexenc->sinkpad = + gst_pad_new_from_template (gst_speexenc_sink_template, "sink"); + gst_element_add_pad (GST_ELEMENT (speexenc), speexenc->sinkpad); + gst_pad_set_chain_function (speexenc->sinkpad, gst_speexenc_chain); + gst_pad_set_link_function (speexenc->sinkpad, gst_speexenc_sinkconnect); + gst_pad_set_convert_function (speexenc->sinkpad, + GST_DEBUG_FUNCPTR (gst_speexenc_convert_sink)); + gst_pad_set_formats_function (speexenc->sinkpad, + GST_DEBUG_FUNCPTR (gst_speexenc_get_formats)); + + speexenc->srcpad = + gst_pad_new_from_template (gst_speexenc_src_template, "src"); + gst_pad_set_query_function (speexenc->srcpad, + GST_DEBUG_FUNCPTR (gst_speexenc_src_query)); + gst_pad_set_query_type_function (speexenc->srcpad, + GST_DEBUG_FUNCPTR (gst_speexenc_get_query_types)); + gst_pad_set_convert_function (speexenc->srcpad, + GST_DEBUG_FUNCPTR (gst_speexenc_convert_src)); + gst_pad_set_formats_function (speexenc->srcpad, + GST_DEBUG_FUNCPTR (gst_speexenc_get_formats)); + gst_element_add_pad (GST_ELEMENT (speexenc), speexenc->srcpad); + + speexenc->channels = -1; + speexenc->rate = -1; + + speexenc->quality = DEFAULT_QUALITY; + speexenc->bitrate = DEFAULT_BITRATE; + speexenc->vbr = DEFAULT_VBR; + speexenc->abr = DEFAULT_ABR; + speexenc->vad = DEFAULT_VAD; + speexenc->dtx = DEFAULT_DTX; + speexenc->complexity = DEFAULT_COMPLEXITY; + speexenc->nframes = DEFAULT_NFRAMES; + + speexenc->setup = FALSE; + speexenc->eos = FALSE; + speexenc->header_sent = FALSE; + + speexenc->tags = gst_tag_list_new (); + speexenc->adapter = gst_adapter_new (); + + /* we're chained and we can deal with events */ + GST_FLAG_SET (speexenc, GST_ELEMENT_EVENT_AWARE); +} + + +static gchar * +gst_speexenc_get_tag_value (const GstTagList * list, const gchar * tag, + int index) +{ + gchar *speexvalue = NULL; + + if (tag == NULL) { + return NULL; + } + + /* get tag name right */ + if ((strcmp (tag, GST_TAG_TRACK_NUMBER) == 0) + || (strcmp (tag, GST_TAG_ALBUM_VOLUME_NUMBER) == 0) + || (strcmp (tag, GST_TAG_TRACK_COUNT) == 0) + || (strcmp (tag, GST_TAG_ALBUM_VOLUME_COUNT) == 0)) { + guint track_no; + + if (!gst_tag_list_get_uint_index (list, tag, index, &track_no)) + g_assert_not_reached (); + speexvalue = g_strdup_printf ("%u", track_no); + } else if (strcmp (tag, GST_TAG_DATE) == 0) { + /* FIXME: how are dates represented in speex files? */ + GDate *date; + guint u; + + if (!gst_tag_list_get_uint_index (list, tag, index, &u)) + g_assert_not_reached (); + date = g_date_new_julian (u); + speexvalue = + g_strdup_printf ("%04d-%02d-%02d", (gint) g_date_get_year (date), + (gint) g_date_get_month (date), (gint) g_date_get_day (date)); + g_date_free (date); + } else if (gst_tag_get_type (tag) == G_TYPE_STRING) { + if (!gst_tag_list_get_string_index (list, tag, index, &speexvalue)) + g_assert_not_reached (); + } + + return speexvalue; +} + +/* + * Comments will be stored in the Vorbis style. + * It is describled in the "Structure" section of + * http://www.xiph.org/ogg/vorbis/doc/v-comment.html + * + * The comment header is decoded as follows: + * 1) [vendor_length] = read an unsigned integer of 32 bits + * 2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets + * 3) [user_comment_list_length] = read an unsigned integer of 32 bits + * 4) iterate [user_comment_list_length] times { + * 5) [length] = read an unsigned integer of 32 bits + * 6) this iteration's user comment = read a UTF-8 vector as [length] octets + * } + * 7) [framing_bit] = read a single bit as boolean + * 8) if ( [framing_bit] unset or end of packet ) then ERROR + * 9) done. + * + * If you have troubles, please write to ymnk@jcraft.com. + */ +#define readint(buf, base) (((buf[base+3]<<24) & 0xff000000)| \ + ((buf[base+2]<<16) & 0xff0000)| \ + ((buf[base+1]<< 8) & 0xff00)| \ + (buf[base ] & 0xff)) +#define writeint(buf, base, val) do{ buf[base+3] = ((val)>>24) & 0xff; \ + buf[base+2] = ((val)>>16) & 0xff; \ + buf[base+1] = ((val)>> 8) & 0xff; \ + buf[base ] = (val) & 0xff; \ + }while(0) + +static void +comment_init (char **comments, int *length, char *vendor_string) +{ + int vendor_length = strlen (vendor_string); + int user_comment_list_length = 0; + int len = 4 + vendor_length + 4; + char *p = (char *) malloc (len); + + if (p == NULL) { + } + writeint (p, 0, vendor_length); + memcpy (p + 4, vendor_string, vendor_length); + writeint (p, 4 + vendor_length, user_comment_list_length); + *length = len; + *comments = p; +} +static void +comment_add (char **comments, int *length, const char *tag, char *val) +{ + char *p = *comments; + int vendor_length = readint (p, 0); + int user_comment_list_length = readint (p, 4 + vendor_length); + int tag_len = (tag ? strlen (tag) : 0); + int val_len = strlen (val); + int len = (*length) + 4 + tag_len + val_len; + + p = (char *) realloc (p, len); + + writeint (p, *length, tag_len + val_len); /* length of comment */ + if (tag) + memcpy (p + *length + 4, tag, tag_len); /* comment */ + memcpy (p + *length + 4 + tag_len, val, val_len); /* comment */ + writeint (p, 4 + vendor_length, user_comment_list_length + 1); + + *comments = p; + *length = len; +} + +#undef readint +#undef writeint + +static void +gst_speexenc_metadata_set1 (const GstTagList * list, const gchar * tag, + gpointer speexenc) +{ + const gchar *speextag = NULL; + gchar *speexvalue = NULL; + guint i, count; + GstSpeexEnc *enc = GST_SPEEXENC (speexenc); + + speextag = gst_tag_to_vorbis_tag (tag); + if (speextag == NULL) { + return; + } + + count = gst_tag_list_get_tag_size (list, tag); + for (i = 0; i < count; i++) { + speexvalue = gst_speexenc_get_tag_value (list, tag, i); + + if (speexvalue != NULL) { + comment_add (&enc->comments, &enc->comment_len, speextag, speexvalue); + } + } +} + +static void +gst_speexenc_set_metadata (GstSpeexEnc * speexenc) +{ + GstTagList *copy; + const GstTagList *user_tags; + + user_tags = gst_tag_setter_get_list (GST_TAG_SETTER (speexenc)); + if (!(speexenc->tags || user_tags)) + return; + + comment_init (&speexenc->comments, &speexenc->comment_len, + "Encoded with GStreamer Speexenc"); + copy = + gst_tag_list_merge (user_tags, speexenc->tags, + gst_tag_setter_get_merge_mode (GST_TAG_SETTER (speexenc))); + gst_tag_list_foreach (copy, gst_speexenc_metadata_set1, speexenc); + gst_tag_list_free (copy); +} + +static gboolean +gst_speexenc_setup (GstSpeexEnc * speexenc) +{ + speexenc->setup = FALSE; + + switch (speexenc->mode) { + case GST_SPEEXENC_MODE_UWB: + speexenc->speex_mode = &speex_uwb_mode; + break; + case GST_SPEEXENC_MODE_WB: + speexenc->speex_mode = &speex_wb_mode; + break; + case GST_SPEEXENC_MODE_NB: + speexenc->speex_mode = &speex_nb_mode; + break; + case GST_SPEEXENC_MODE_AUTO: + default: + break; + } + + if (speexenc->rate > 25000) { + if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) { + speexenc->speex_mode = &speex_uwb_mode; + } else { + if (speexenc->speex_mode != &speex_uwb_mode) { + speexenc->last_message = + g_strdup_printf + ("Warning: suggest to use ultra wide band mode for this rate"); + g_object_notify (G_OBJECT (speexenc), "last_message"); + } + } + } else if (speexenc->rate > 12500) { + if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) { + speexenc->speex_mode = &speex_wb_mode; + } else { + if (speexenc->speex_mode != &speex_wb_mode) { + speexenc->last_message = + g_strdup_printf + ("Warning: suggest to use wide band mode for this rate"); + g_object_notify (G_OBJECT (speexenc), "last_message"); + } + } + } else { + if (speexenc->mode == GST_SPEEXENC_MODE_AUTO) { + speexenc->speex_mode = &speex_nb_mode; + } else { + if (speexenc->speex_mode != &speex_nb_mode) { + speexenc->last_message = + g_strdup_printf + ("Warning: suggest to use narrow band mode for this rate"); + g_object_notify (G_OBJECT (speexenc), "last_message"); + } + } + } + + if (speexenc->rate != 8000 && speexenc->rate != 16000 + && speexenc->rate != 32000) { + speexenc->last_message = + g_strdup_printf ("Warning: speex is optimized for 8, 16 and 32 KHz"); + g_object_notify (G_OBJECT (speexenc), "last_message"); + } + + speex_init_header (&speexenc->header, speexenc->rate, 1, + speexenc->speex_mode); + speexenc->header.frames_per_packet = speexenc->nframes; + speexenc->header.vbr = speexenc->vbr; + speexenc->header.nb_channels = speexenc->channels; + + /*Initialize Speex encoder */ + speexenc->state = speex_encoder_init (speexenc->speex_mode); + + speex_encoder_ctl (speexenc->state, SPEEX_GET_FRAME_SIZE, + &speexenc->frame_size); + speex_encoder_ctl (speexenc->state, SPEEX_SET_COMPLEXITY, + &speexenc->complexity); + speex_encoder_ctl (speexenc->state, SPEEX_SET_SAMPLING_RATE, &speexenc->rate); + + if (speexenc->vbr) + speex_encoder_ctl (speexenc->state, SPEEX_SET_VBR_QUALITY, + &speexenc->quality); + else { + gint tmp = floor (speexenc->quality); + + speex_encoder_ctl (speexenc->state, SPEEX_SET_QUALITY, &tmp); + } + if (speexenc->bitrate) { + if (speexenc->quality >= 0.0 && speexenc->vbr) { + speexenc->last_message = + g_strdup_printf ("Warning: bitrate option is overriding quality"); + g_object_notify (G_OBJECT (speexenc), "last_message"); + } + speex_encoder_ctl (speexenc->state, SPEEX_SET_BITRATE, &speexenc->bitrate); + } + if (speexenc->vbr) { + gint tmp = 1; + + speex_encoder_ctl (speexenc->state, SPEEX_SET_VBR, &tmp); + } else if (speexenc->vad) { + gint tmp = 1; + + speex_encoder_ctl (speexenc->state, SPEEX_SET_VAD, &tmp); + } + + if (speexenc->dtx) { + gint tmp = 1; + + speex_encoder_ctl (speexenc->state, SPEEX_SET_DTX, &tmp); + } + + if (speexenc->dtx && !(speexenc->vbr || speexenc->abr || speexenc->vad)) { + speexenc->last_message = + g_strdup_printf ("Warning: dtx is useless without vad, vbr or abr"); + g_object_notify (G_OBJECT (speexenc), "last_message"); + } else if ((speexenc->vbr || speexenc->abr) && (speexenc->vad)) { + speexenc->last_message = + g_strdup_printf ("Warning: vad is already implied by vbr or abr"); + g_object_notify (G_OBJECT (speexenc), "last_message"); + } + + if (speexenc->abr) { + speex_encoder_ctl (speexenc->state, SPEEX_SET_ABR, &speexenc->abr); + } + + speex_encoder_ctl (speexenc->state, SPEEX_GET_LOOKAHEAD, + &speexenc->lookahead); + + speexenc->setup = TRUE; + + return TRUE; +} + +/* prepare a buffer for transmission */ +static GstBuffer * +gst_speexenc_buffer_from_data (GstSpeexEnc * speexenc, 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) = speexenc->bytes_out; + GST_BUFFER_OFFSET_END (outbuf) = granulepos; + + GST_DEBUG ("encoded buffer of %d bytes", GST_BUFFER_SIZE (outbuf)); + return outbuf; +} + +/* push out the buffer and do internal bookkeeping */ +static void +gst_speexenc_push_buffer (GstSpeexEnc * speexenc, GstBuffer * buffer) +{ + speexenc->bytes_out += GST_BUFFER_SIZE (buffer); + + if (GST_PAD_IS_USABLE (speexenc->srcpad)) { + gst_pad_push (speexenc->srcpad, GST_DATA (buffer)); + } else { + gst_buffer_unref (buffer); + } +} + +static void +gst_speexenc_set_header_on_caps (GstCaps * caps, GstBuffer * buf1, + GstBuffer * buf2) +{ + GstStructure *structure = gst_caps_get_structure (caps, 0); + GValue list = { 0 }; + GValue value = { 0 }; + + /* mark buffers */ + GST_BUFFER_FLAG_SET (buf1, GST_BUFFER_IN_CAPS); + GST_BUFFER_FLAG_SET (buf2, GST_BUFFER_IN_CAPS); + + /* put buffers in a fixed list */ + g_value_init (&list, GST_TYPE_FIXED_LIST); + g_value_init (&value, GST_TYPE_BUFFER); + g_value_set_boxed (&value, buf1); + gst_value_list_append_value (&list, &value); + g_value_unset (&value); + g_value_init (&value, GST_TYPE_BUFFER); + g_value_set_boxed (&value, buf2); + gst_value_list_append_value (&list, &value); + gst_structure_set_value (structure, "streamheader", &list); + g_value_unset (&value); + g_value_unset (&list); } static void @@ -189,103 +832,258 @@ gst_speexenc_chain (GstPad * pad, GstData * _data) { GstBuffer *buf = GST_BUFFER (_data); GstSpeexEnc *speexenc; - GstBuffer *outbuf; - gint16 *data; - guint8 *header_data; - gint size; - float input[1000]; - gint frame_size; - gint i; g_return_if_fail (pad != NULL); g_return_if_fail (GST_IS_PAD (pad)); g_return_if_fail (buf != NULL); - speexenc = GST_SPEEXENC (GST_OBJECT_PARENT (pad)); + speexenc = GST_SPEEXENC (gst_pad_get_parent (pad)); - if (!GST_PAD_CAPS (speexenc->srcpad)) { + if (GST_IS_EVENT (buf)) { + GstEvent *event = GST_EVENT (buf); - if (!gst_pad_try_set_caps (speexenc->srcpad, - gst_caps_new_simple ("audio/x-speex", - "rate", G_TYPE_INT, speexenc->rate, - "channels", G_TYPE_INT, 1, NULL))) { - GST_ELEMENT_ERROR (speexenc, CORE, NEGOTIATION, (NULL), (NULL)); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + speexenc->eos = TRUE; + gst_event_unref (event); + break; + case GST_EVENT_TAG: + if (speexenc->tags) { + gst_tag_list_insert (speexenc->tags, gst_event_tag_get_list (event), + gst_tag_setter_get_merge_mode (GST_TAG_SETTER (speexenc))); + } else { + g_assert_not_reached (); + } + gst_pad_event_default (pad, event); + return; + default: + gst_pad_event_default (pad, event); + return; + } + } else { + if (!speexenc->setup) { + gst_buffer_unref (buf); + GST_ELEMENT_ERROR (speexenc, CORE, NEGOTIATION, (NULL), + ("encoder not initialized (input is not audio?)")); return; } - } - if (speexenc->packet_count == 0) { - header_data = speex_header_to_packet (&speexenc->header, &size); + if (!speexenc->header_sent) { + /* Speex 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 libspeex + one at a time; libspeex handles the additional Ogg bitstream + constraints */ + GstBuffer *buf1, *buf2; + GstCaps *caps; + guchar *data; + gint data_len; - outbuf = gst_buffer_new (); - GST_BUFFER_DATA (outbuf) = header_data; - GST_BUFFER_SIZE (outbuf) = size; + gst_speexenc_set_metadata (speexenc); - gst_pad_push (speexenc->srcpad, GST_DATA (outbuf)); - } + /* create header buffer */ + data = speex_header_to_packet (&speexenc->header, &data_len); + buf1 = gst_speexenc_buffer_from_data (speexenc, data, data_len, 0); - data = (gint16 *) GST_BUFFER_DATA (buf); - size = GST_BUFFER_SIZE (buf) / sizeof (gint16); + /* create comment buffer */ + buf2 = + gst_speexenc_buffer_from_data (speexenc, speexenc->comments, + speexenc->comment_len, 0); - frame_size = speexenc->frame_size; + /* mark and put on caps */ + caps = gst_pad_get_caps (speexenc->srcpad); + gst_speexenc_set_header_on_caps (caps, buf1, buf2); - if (speexenc->bufsize && (speexenc->bufsize + size >= frame_size)) { - memcpy (speexenc->buffer + speexenc->bufsize, data, - (frame_size - speexenc->bufsize) * sizeof (gint16)); + /* negotiate with these caps */ + GST_DEBUG ("here are the caps: %" GST_PTR_FORMAT, caps); + gst_pad_try_set_caps (speexenc->srcpad, caps); - for (i = 0; i < frame_size; i++) - input[i] = speexenc->buffer[i]; + /* push out buffers */ + gst_speexenc_push_buffer (speexenc, buf1); + gst_speexenc_push_buffer (speexenc, buf2); - speex_encode (speexenc->state, input, &speexenc->bits); - speexenc->packet_count++; - - if (speexenc->packet_count % speexenc->n_packets == 0) { - GstBuffer *outbuf; - - outbuf = gst_buffer_new_and_alloc (frame_size * speexenc->n_packets); - GST_BUFFER_SIZE (outbuf) = speex_bits_write (&speexenc->bits, - GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf)); - GST_BUFFER_TIMESTAMP (outbuf) = speexenc->next_ts; + speex_bits_init (&speexenc->bits); speex_bits_reset (&speexenc->bits); - gst_pad_push (speexenc->srcpad, GST_DATA (outbuf)); - speexenc->next_ts += frame_size * GST_SECOND / speexenc->rate; + speexenc->header_sent = TRUE; } - size -= (speexenc->frame_size - speexenc->bufsize); - data += (speexenc->frame_size - speexenc->bufsize); + { + gint frame_size = speexenc->frame_size; + gint bytes = frame_size * 2 * speexenc->channels; - speexenc->bufsize = 0; - } + /* push buffer to adapter */ + gst_adapter_push (speexenc->adapter, buf); - while (size >= frame_size) { - for (i = 0; i < frame_size; i++) - input[i] = data[i]; + while (gst_adapter_available (speexenc->adapter) >= bytes) { + gint16 *data; + gint i; + gint outsize, written; + GstBuffer *outbuf; - speex_encode (speexenc->state, input, &speexenc->bits); - speexenc->packet_count++; + data = (gint16 *) gst_adapter_peek (speexenc->adapter, bytes); - if (speexenc->packet_count % speexenc->n_packets == 0) { - GstBuffer *outbuf; + for (i = 0; i < frame_size * speexenc->channels; i++) { + speexenc->input[i] = (gfloat) data[i]; + } + gst_adapter_flush (speexenc->adapter, bytes); - outbuf = gst_buffer_new_and_alloc (frame_size * speexenc->n_packets); - GST_BUFFER_SIZE (outbuf) = speex_bits_write (&speexenc->bits, - GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf)); - GST_BUFFER_TIMESTAMP (outbuf) = speexenc->next_ts; - speex_bits_reset (&speexenc->bits); + speexenc->samples_in += frame_size; - gst_pad_push (speexenc->srcpad, GST_DATA (outbuf)); - speexenc->next_ts += frame_size * GST_SECOND / speexenc->rate; + if (speexenc->channels == 2) { + speex_encode_stereo (speexenc->input, frame_size, &speexenc->bits); + } + speex_encode (speexenc->state, speexenc->input, &speexenc->bits); + + speexenc->frameno++; + + if ((speexenc->frameno % speexenc->nframes) != 0) + continue; + + speex_bits_insert_terminator (&speexenc->bits); + outsize = speex_bits_nbytes (&speexenc->bits); + outbuf = + gst_pad_alloc_buffer (speexenc->srcpad, GST_BUFFER_OFFSET_NONE, + outsize); + written = + speex_bits_write (&speexenc->bits, GST_BUFFER_DATA (outbuf), + outsize); + g_assert (written == outsize); + speex_bits_reset (&speexenc->bits); + + GST_BUFFER_OFFSET (outbuf) = speexenc->bytes_out; + GST_BUFFER_OFFSET_END (outbuf) = + speexenc->frameno * frame_size - speexenc->lookahead; + + gst_speexenc_push_buffer (speexenc, outbuf); + } } - - size -= frame_size; - data += frame_size; } - if (size) { - memcpy (speexenc->buffer + speexenc->bufsize, data, size * sizeof (gint16)); - speexenc->bufsize += size; + if (speexenc->eos) { + /* clean up and exit. */ + gst_pad_push (speexenc->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS))); + gst_element_set_eos (GST_ELEMENT (speexenc)); } - - gst_buffer_unref (buf); +} + +static void +gst_speexenc_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstSpeexEnc *speexenc; + + /* it's not null if we got it, but it might not be ours */ + g_return_if_fail (GST_IS_SPEEXENC (object)); + + speexenc = GST_SPEEXENC (object); + + switch (prop_id) { + case ARG_QUALITY: + g_value_set_float (value, speexenc->quality); + break; + case ARG_BITRATE: + g_value_set_int (value, speexenc->bitrate); + break; + case ARG_VBR: + g_value_set_boolean (value, speexenc->vbr); + break; + case ARG_ABR: + g_value_set_int (value, speexenc->abr); + break; + case ARG_VAD: + g_value_set_boolean (value, speexenc->vad); + break; + case ARG_DTX: + g_value_set_boolean (value, speexenc->dtx); + break; + case ARG_COMPLEXITY: + g_value_set_int (value, speexenc->complexity); + break; + case ARG_NFRAMES: + g_value_set_int (value, speexenc->nframes); + break; + case ARG_LAST_MESSAGE: + g_value_set_string (value, speexenc->last_message); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_speexenc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSpeexEnc *speexenc; + + /* it's not null if we got it, but it might not be ours */ + g_return_if_fail (GST_IS_SPEEXENC (object)); + + speexenc = GST_SPEEXENC (object); + + switch (prop_id) { + case ARG_QUALITY: + speexenc->quality = g_value_get_float (value); + break; + case ARG_BITRATE: + speexenc->bitrate = g_value_get_int (value); + break; + case ARG_VBR: + speexenc->vbr = g_value_get_boolean (value); + break; + case ARG_ABR: + speexenc->abr = g_value_get_int (value); + break; + case ARG_VAD: + speexenc->vad = g_value_get_boolean (value); + break; + case ARG_DTX: + speexenc->dtx = g_value_get_boolean (value); + break; + case ARG_COMPLEXITY: + speexenc->complexity = g_value_get_int (value); + break; + case ARG_NFRAMES: + speexenc->nframes = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstElementStateReturn +gst_speexenc_change_state (GstElement * element) +{ + GstSpeexEnc *speexenc = GST_SPEEXENC (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_NULL_TO_READY: + break; + case GST_STATE_READY_TO_PAUSED: + speexenc->eos = FALSE; + speexenc->frameno = 0; + speexenc->samples_in = 0; + break; + case GST_STATE_PAUSED_TO_PLAYING: + case GST_STATE_PLAYING_TO_PAUSED: + break; + case GST_STATE_PAUSED_TO_READY: + speexenc->setup = FALSE; + speexenc->header_sent = FALSE; + gst_tag_list_free (speexenc->tags); + speexenc->tags = gst_tag_list_new (); + break; + case GST_STATE_READY_TO_NULL: + default: + break; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); + + return GST_STATE_SUCCESS; } diff --git a/ext/speex/gstspeexenc.h b/ext/speex/gstspeexenc.h index 4677f6a1d3..a8a83805b2 100644 --- a/ext/speex/gstspeexenc.h +++ b/ext/speex/gstspeexenc.h @@ -23,6 +23,7 @@ #include +#include #include #include @@ -43,6 +44,17 @@ extern "C" { #define GST_IS_SPEEXENC_CLASS(obj) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SPEEXENC)) +#define MAX_FRAME_SIZE 2000*2 +#define MAX_FRAME_BYTES 2000 + +typedef enum +{ + GST_SPEEXENC_MODE_AUTO, + GST_SPEEXENC_MODE_UWB, + GST_SPEEXENC_MODE_WB, + GST_SPEEXENC_MODE_NB, +} GstSpeexMode; + typedef struct _GstSpeexEnc GstSpeexEnc; typedef struct _GstSpeexEncClass GstSpeexEncClass; @@ -58,14 +70,43 @@ struct _GstSpeexEnc { SpeexBits bits; SpeexHeader header; - SpeexMode *mode; + SpeexMode *speex_mode; void *state; - gint frame_size; - gint16 buffer[2000]; - gint bufsize; - guint64 next_ts; + GstSpeexMode mode; + GstAdapter *adapter; - gint rate; + gfloat quality; + gint bitrate; + gboolean vbr; + gint abr; + gboolean vad; + gboolean dtx; + gint complexity; + gint nframes; + + gint lookahead; + + gint channels; + gint rate; + + gboolean setup; + gboolean header_sent; + gboolean eos; + + guint64 samples_in; + guint64 bytes_out; + + GstTagList *tags; + + gchar *last_message; + + gint frame_size; + guint64 frameno; + + gchar *comments; + gint comment_len; + + gfloat input[MAX_FRAME_SIZE]; }; struct _GstSpeexEncClass {