diff options
author | Wim Taymans <wim.taymans@gmail.com> | 2004-09-28 16:44:12 +0000 |
---|---|---|
committer | Wim Taymans <wim.taymans@gmail.com> | 2004-09-28 16:44:12 +0000 |
commit | 2748ae95482b17103909d2b9f0d65f3fb0948388 (patch) | |
tree | b09f604391722085286899789fb0c3c58d38f88d /ext/speex/gstspeexenc.c | |
parent | 0089ff9ed3851854071c74e82039b95e38940641 (diff) |
ext/speex/: Rewrote speex encoder, make sure it can be embedded in ogg.
Original commit message from CVS:
* 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.
Diffstat (limited to 'ext/speex/gstspeexenc.c')
-rw-r--r-- | ext/speex/gstspeexenc.c | 1068 |
1 files changed, 933 insertions, 135 deletions
diff --git a/ext/speex/gstspeexenc.c b/ext/speex/gstspeexenc.c index 2552c92e..530989c0 100644 --- a/ext/speex/gstspeexenc.c +++ b/ext/speex/gstspeexenc.c @@ -21,42 +21,94 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif +#include <stdlib.h> #include <string.h> +#include <time.h> +#include <math.h> +#include <speex/speex.h> +#include <speex/speex_stereo.h> +#include <gst/gsttaginterface.h> +#include <gst/tag/tag.h> #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 <wim.taymans@chello.be>", + "Encodes audio in Speex format", + "Wim Taymans <wim@fluendo.com>", }; -/* 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 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 * +speex_caps_factory (void) +{ + return gst_caps_new_simple ("audio/x-speex", NULL); +} + +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,63 +191,640 @@ 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; + + gstelement_class->change_state = gst_speexenc_change_state; +} + +static GstPadLinkReturn +gst_speexenc_sinkconnect (GstPad * pad, const GstCaps * caps) +{ + GstSpeexEnc *speexenc; + 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); + + gst_speexenc_setup (speexenc); + + if (speexenc->setup) + return GST_PAD_LINK_OK; + + 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) { - /* create the sink and src pads */ speexenc->sinkpad = - gst_pad_new_from_template (gst_static_pad_template_get - (&speexenc_sink_template), "sink"); + 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_static_pad_template_get - (&speexenc_src_template), "src"); + 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); - speex_bits_init (&speexenc->bits); - speexenc->mode = (SpeexMode *) & speex_nb_mode; - speexenc->bufsize = 0; - speexenc->packet_count = 0; - speexenc->n_packets = 20; + 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 GstPadLinkReturn -gst_speexenc_sinkconnect (GstPad * pad, const GstCaps * caps) + +static gchar * +gst_speexenc_get_tag_value (const GstTagList * list, const gchar * tag, + int index) { - GstSpeexEnc *speexenc; - GstStructure *structure; + gchar *speexvalue = NULL; - speexenc = GST_SPEEXENC (gst_pad_get_parent (pad)); + if (tag == NULL) { + return NULL; + } - structure = gst_caps_get_structure (caps, 0); - 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; + /* 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; - speexenc->state = speex_encoder_init (speexenc->mode); - speex_encoder_ctl (speexenc->state, SPEEX_GET_FRAME_SIZE, - &speexenc->frame_size); + 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; - return GST_PAD_LINK_OK; + 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 GST_PAD_LINK_REFUSED; + 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; + + if (speexenc->channels == 2) { + speex_encode_stereo (speexenc->input, frame_size, &speexenc->bits); + } + speex_encode (speexenc->state, speexenc->input, &speexenc->bits); + + speexenc->frameno++; - gst_pad_push (speexenc->srcpad, GST_DATA (outbuf)); - speexenc->next_ts += frame_size * GST_SECOND / speexenc->rate; + 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); + } } + } + + 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)); + } +} + +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); - size -= frame_size; - data += frame_size; + 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); - if (size) { - memcpy (speexenc->buffer + speexenc->bufsize, data, size * sizeof (gint16)); - speexenc->bufsize += size; + 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); - gst_buffer_unref (buf); + return GST_STATE_SUCCESS; } |