diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/faac/gstfaac.c | 680 |
1 files changed, 680 insertions, 0 deletions
diff --git a/ext/faac/gstfaac.c b/ext/faac/gstfaac.c new file mode 100644 index 00000000..e5dddc17 --- /dev/null +++ b/ext/faac/gstfaac.c @@ -0,0 +1,680 @@ +/* GStreamer FAAC (Free AAC Encoder) plugin + * Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstfaac.h" + +GST_PAD_TEMPLATE_FACTORY (src_template, + "src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_CAPS_NEW ( + "faac_mpeg_templ", + "audio/mpeg", + "systemstream", GST_PROPS_BOOLEAN (FALSE), + "mpegversion", GST_PROPS_LIST ( + GST_PROPS_INT (4), /* we prefer 4 */ + GST_PROPS_INT (2) + ), + "channels", GST_PROPS_INT_RANGE (1, 6), + "samplerate", GST_PROPS_INT_RANGE (8000, 96000) + ) +); + +GST_PAD_TEMPLATE_FACTORY (sink_template, + "sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_CAPS_NEW ( + "faac_int16_templ", + "audio/x-raw-int", + "endianness", GST_PROPS_INT (G_BYTE_ORDER), + "signed", GST_PROPS_BOOLEAN (TRUE), + "width", GST_PROPS_INT (16), + "depth", GST_PROPS_INT (16), + "rate", GST_PROPS_INT_RANGE (8000, 96000), + "channels", GST_PROPS_INT_RANGE (1, 6) + ), + GST_CAPS_NEW ( + "faac_int24_templ", + "audio/x-raw-int", + "endianness", GST_PROPS_INT (G_BYTE_ORDER), + "signed", GST_PROPS_BOOLEAN (TRUE), + "width", GST_PROPS_INT (32), + "depth", GST_PROPS_INT (24), + "rate", GST_PROPS_INT_RANGE (8000, 96000), + "channels", GST_PROPS_INT_RANGE (1, 6) + ), + GST_CAPS_NEW ( + "faac_float_templ", + "audio/x-raw-float", + "endianness", GST_PROPS_INT (G_BYTE_ORDER), + "depth", GST_PROPS_INT (32), /* float */ + "rate", GST_PROPS_INT_RANGE (8000, 96000), + "channels", GST_PROPS_INT_RANGE (1, 6) + ) +); + +enum { + ARG_0, + ARG_BITRATE, + ARG_PROFILE, + ARG_TNS, + ARG_MIDSIDE, + ARG_SHORTCTL + /* FILL ME */ +}; + +static void gst_faac_base_init (GstFaacClass *klass); +static void gst_faac_class_init (GstFaacClass *klass); +static void gst_faac_init (GstFaac *faac); + +static void gst_faac_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gst_faac_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static GstPadLinkReturn + gst_faac_sinkconnect (GstPad *pad, + GstCaps *caps); +static GstPadLinkReturn + gst_faac_srcconnect (GstPad *pad, + GstCaps *caps); +static void gst_faac_chain (GstPad *pad, + GstData *data); +static GstElementStateReturn + gst_faac_change_state (GstElement *element); + +static GstElementClass *parent_class = NULL; +/* static guint gst_faac_signals[LAST_SIGNAL] = { 0 }; */ + +GType +gst_faac_get_type (void) +{ + static GType gst_faac_type = 0; + + if (!gst_faac_type) { + static const GTypeInfo gst_faac_info = { + sizeof (GstFaacClass), + (GBaseInitFunc) gst_faac_base_init, + NULL, + (GClassInitFunc) gst_faac_class_init, + NULL, + NULL, + sizeof(GstFaac), + 0, + (GInstanceInitFunc) gst_faac_init, + }; + + gst_faac_type = g_type_register_static (GST_TYPE_ELEMENT, + "GstFaac", + &gst_faac_info, 0); + } + + return gst_faac_type; +} + +static void +gst_faac_base_init (GstFaacClass *klass) +{ + GstElementDetails gst_faac_details = { + "Free AAC Encoder (FAAC)", + "Codec/Audio/Encoder", + "Free MPEG-2/4 AAC encoder", + "Ronald Bultje <rbultje@ronald.bitfreak.net>", + }; + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (src_template)); + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (sink_template)); + + gst_element_class_set_details (element_class, &gst_faac_details); +} + +#define GST_TYPE_FAAC_PROFILE (gst_faac_profile_get_type ()) +static GType +gst_faac_profile_get_type (void) +{ + static GType gst_faac_profile_type = 0; + + if (!gst_faac_profile_type) { + static GEnumValue gst_faac_profile[] = { + { MAIN, "MAIN", "Main profile" }, + { LOW, "LOW", "Low complexity profile" }, + { SSR, "SSR", "Scalable sampling rate profile" }, + { LTP, "LTP", "Long term prediction profile" }, + { 0, NULL, NULL }, + }; + + gst_faac_profile_type = g_enum_register_static ("GstFaacProfile", + gst_faac_profile); + } + + return gst_faac_profile_type; +} + +#define GST_TYPE_FAAC_SHORTCTL (gst_faac_shortctl_get_type ()) +static GType +gst_faac_shortctl_get_type (void) +{ + static GType gst_faac_shortctl_type = 0; + + if (!gst_faac_shortctl_type) { + static GEnumValue gst_faac_shortctl[] = { + { SHORTCTL_NORMAL, "SHORTCTL_NORMAL", "Normal block type" }, + { SHORTCTL_NOSHORT, "SHORTCTL_NOSHORT", "No short blocks" }, + { SHORTCTL_NOLONG, "SHORTCTL_NOLONG", "No long blocks" }, + { 0, NULL, NULL }, + }; + + gst_faac_shortctl_type = g_enum_register_static ("GstFaacShortCtl", + gst_faac_shortctl); + } + + return gst_faac_shortctl_type; +} + +static void +gst_faac_class_init (GstFaacClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + + /* properties */ + g_object_class_install_property (gobject_class, ARG_BITRATE, + g_param_spec_int ("bitrate", "Bitrate (bps)", "Bitrate in bits/sec", + 8 * 1024, 320 * 1024, 128 * 1024, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_PROFILE, + g_param_spec_enum ("profile", "Profile", "MPEG/AAC encoding profile", + GST_TYPE_FAAC_PROFILE, MAIN, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_TNS, + g_param_spec_boolean ("tns", "TNS", "Use temporal noise shaping", + FALSE, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_MIDSIDE, + g_param_spec_boolean ("midside", "Midside", "Allow mid/side encoding", + TRUE, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, ARG_SHORTCTL, + g_param_spec_enum ("shortctl", "Block type", + "Block type encorcing", + GST_TYPE_FAAC_SHORTCTL, MAIN, G_PARAM_READWRITE)); + + /* virtual functions */ + gstelement_class->change_state = gst_faac_change_state; + + gobject_class->set_property = gst_faac_set_property; + gobject_class->get_property = gst_faac_get_property; +} + +static void +gst_faac_init (GstFaac *faac) +{ + faac->handle = NULL; + faac->samplerate = -1; + faac->channels = -1; + faac->cache = NULL; + faac->cache_time = GST_CLOCK_TIME_NONE; + faac->cache_duration = 0; + + GST_FLAG_SET (faac, GST_ELEMENT_EVENT_AWARE); + + faac->sinkpad = gst_pad_new_from_template ( + GST_PAD_TEMPLATE_GET (sink_template), "sink"); + gst_element_add_pad (GST_ELEMENT (faac), faac->sinkpad); + gst_pad_set_chain_function (faac->sinkpad, gst_faac_chain); + gst_pad_set_link_function (faac->sinkpad, gst_faac_sinkconnect); + + faac->srcpad = gst_pad_new_from_template ( + GST_PAD_TEMPLATE_GET (src_template), "src"); + gst_element_add_pad (GST_ELEMENT (faac), faac->srcpad); + gst_pad_set_link_function (faac->srcpad, gst_faac_srcconnect); + + /* default properties */ + faac->bitrate = 1024 * 128; + faac->profile = MAIN; + faac->shortctl = SHORTCTL_NORMAL; + faac->tns = FALSE; + faac->midside = TRUE; +} + +static GstPadLinkReturn +gst_faac_sinkconnect (GstPad *pad, + GstCaps *caps) +{ + GstFaac *faac = GST_FAAC (gst_pad_get_parent (pad)); + + if (!GST_CAPS_IS_FIXED (caps)) + return GST_PAD_LINK_DELAYED; + + if (faac->handle) { + faacEncClose (faac->handle); + faac->handle = NULL; + } + if (faac->cache) { + gst_buffer_unref (faac->cache); + faac->cache = NULL; + } + + for (; caps != NULL; caps = caps->next) { + faacEncHandle *handle; + gint channels, samplerate, depth; + gulong samples, bytes, fmt = 0, bps = 0; + + gst_caps_get (caps, "channels", &channels, + "rate", &samplerate, + "depth", &depth, NULL); + + /* open a new handle to the encoder */ + if (!(handle = faacEncOpen (samplerate, channels, + &samples, &bytes))) + continue; + + switch (depth) { + case 16: + fmt = FAAC_INPUT_16BIT; + bps = 2; + break; + case 24: + fmt = FAAC_INPUT_32BIT; /* 24-in-32, actually */ + bps = 4; + break; + case 32: + fmt = FAAC_INPUT_FLOAT; /* see template, this is right */ + bps = 4; + break; + } + + if (!fmt) { + faacEncClose (handle); + continue; + } + + faac->format = fmt; + faac->bps = bps; + faac->handle = handle; + faac->bytes = bytes; + faac->samples = samples; + faac->channels = channels; + faac->samplerate = samplerate; + + /* if the other side was already set-up, redo that */ + if (GST_PAD_CAPS (faac->srcpad)) + return gst_faac_srcconnect (faac->srcpad, + gst_pad_get_allowed_caps (faac->srcpad)); + + /* else, that'll be done later */ + return GST_PAD_LINK_OK; + } + + return GST_PAD_LINK_REFUSED; +} + +static GstPadLinkReturn +gst_faac_srcconnect (GstPad *pad, + GstCaps *caps) +{ + GstFaac *faac = GST_FAAC (gst_pad_get_parent (pad)); + GstCaps *t; + + if (!faac->handle || + (faac->samplerate == -1 || faac->channels == -1)) { + return GST_PAD_LINK_DELAYED; + } + + /* we do samplerate/channels ourselves */ + for (t = caps; t != NULL; t = t->next) { + gst_props_remove_entry_by_name (t->properties, "rate"); + gst_props_remove_entry_by_name (t->properties, "channels"); + } + + /* go through list */ + caps = gst_caps_normalize (caps); + for ( ; caps != NULL; caps = caps->next) { + faacEncConfiguration *conf; + gint mpegversion = 0; + GstCaps *newcaps; + GstPadLinkReturn ret; + + gst_caps_get_int (caps, "mpegversion", &mpegversion); + + /* new conf */ + conf = faacEncGetCurrentConfiguration (faac->handle); + conf->mpegVersion = (mpegversion == 4) ? MPEG4 : MPEG2; + conf->aacObjectType = faac->profile; + conf->allowMidside = faac->midside; + conf->useLfe = 0; + conf->useTns = faac->tns; + conf->bitRate = faac->bitrate; + conf->inputFormat = faac->format; + + /* FIXME: this one here means that we do not support direct + * "MPEG audio file" output (like mp3). This means we can + * only mux this into mov/qt (mp4a) or matroska or so. If + * we want to support direct AAC file output, we need ADTS + * headers, and we need to find a way in the caps to detect + * that (that the next element is filesink or any element + * that does want ADTS headers). */ + + conf->outputFormat = 0; /* raw, no ADTS headers */ + conf->shortctl = faac->shortctl; + if (!faacEncSetConfiguration (faac->handle, conf)) { + GST_WARNING ("Faac doesn't support the current conf"); + continue; + } + + newcaps = GST_CAPS_NEW ("faac_mpeg_caps", + "audio/mpeg", + "systemstream", GST_PROPS_BOOLEAN (FALSE), + "mpegversion", GST_PROPS_INT (mpegversion), + "channels", GST_PROPS_INT (faac->channels), + "rate", GST_PROPS_INT (faac->samplerate)); + ret = gst_pad_try_set_caps (faac->srcpad, newcaps); + + switch (ret) { + case GST_PAD_LINK_OK: + case GST_PAD_LINK_DONE: + return GST_PAD_LINK_DONE; + case GST_PAD_LINK_DELAYED: + return GST_PAD_LINK_DELAYED; + default: + break; + } + } + + return GST_PAD_LINK_REFUSED; +} + +static void +gst_faac_chain (GstPad *pad, + GstData *data) +{ + GstFaac *faac = GST_FAAC (gst_pad_get_parent (pad)); + GstBuffer *inbuf, *outbuf, *subbuf; + guint size, ret_size, in_size, frame_size; + + if (GST_IS_EVENT (data)) { + GstEvent *event = GST_EVENT (data); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + /* flush first */ + while (1) { + outbuf = gst_buffer_new_and_alloc (faac->bytes); + if ((ret_size = faacEncEncode (faac->handle, + NULL, 0, + GST_BUFFER_DATA (outbuf), + faac->bytes)) < 0) { + gst_element_error (GST_ELEMENT (faac), "Error during AAC encoding"); + gst_event_unref (event); + gst_buffer_unref (outbuf); + return; + } + + if (ret_size > 0) { + GST_BUFFER_SIZE (outbuf) = ret_size; + GST_BUFFER_TIMESTAMP (outbuf) = 0; + GST_BUFFER_DURATION (outbuf) = 0; + gst_pad_push (faac->srcpad, GST_DATA (outbuf)); + } else { + break; + } + } + + gst_element_set_eos (GST_ELEMENT (faac)); + gst_pad_push (faac->srcpad, data); + return; + default: + gst_pad_event_default (pad, event); + return; + } + } + + inbuf = GST_BUFFER (data); + + if (!faac->handle) { + gst_element_error (GST_ELEMENT (faac), + "No input format negotiated"); + gst_buffer_unref (inbuf); + return; + } + + if (!GST_PAD_CAPS (faac->srcpad)) { + if (gst_faac_srcconnect (faac->srcpad, + gst_pad_get_allowed_caps (faac->srcpad)) <= 0) { + gst_element_error (GST_ELEMENT (faac), + "Failed to negotiate MPEG/AAC format with next element"); + gst_buffer_unref (inbuf); + return; + } + } + + size = GST_BUFFER_SIZE (inbuf); + in_size = size; + if (faac->cache) + in_size += GST_BUFFER_SIZE (faac->cache); + frame_size = faac->samples * faac->bps; + + while (1) { + /* do we have enough data for one frame? */ + if (in_size / faac->bps < faac->samples) { + if (in_size > size) { + /* this is panic! we got a buffer, but still don't have enough + * data. Merge them and retry in the next cycle... */ + faac->cache = gst_buffer_merge (faac->cache, inbuf); + } else if (in_size == size) { + /* this shouldn't happen, but still... */ + faac->cache = inbuf; + } else if (in_size > 0) { + faac->cache = gst_buffer_create_sub (inbuf, size - in_size, + in_size); + GST_BUFFER_DURATION (faac->cache) = + GST_BUFFER_DURATION (inbuf) * GST_BUFFER_SIZE (faac->cache) / size; + GST_BUFFER_TIMESTAMP (faac->cache) = + GST_BUFFER_TIMESTAMP (inbuf) + (GST_BUFFER_DURATION (inbuf) * + (size - in_size) / size); + gst_buffer_unref (inbuf); + } else { + gst_buffer_unref (inbuf); + } + + return; + } + + /* create the frame */ + if (in_size > size) { + /* merge */ + subbuf = gst_buffer_create_sub (inbuf, 0, frame_size - (in_size - size)); + GST_BUFFER_DURATION (subbuf) = + GST_BUFFER_DURATION (inbuf) * GST_BUFFER_SIZE (subbuf) / size; + subbuf = gst_buffer_merge (faac->cache, subbuf); + faac->cache = NULL; + } else { + subbuf = gst_buffer_create_sub (inbuf, size - in_size, frame_size); + GST_BUFFER_DURATION (subbuf) = + GST_BUFFER_DURATION (inbuf) * GST_BUFFER_SIZE (subbuf) / size; + GST_BUFFER_TIMESTAMP (subbuf) = + GST_BUFFER_TIMESTAMP (inbuf) + (GST_BUFFER_DURATION (inbuf) * + (size - in_size) / size); + } + + outbuf = gst_buffer_new_and_alloc (faac->bytes); + if ((ret_size = faacEncEncode (faac->handle, + (gint32 *) GST_BUFFER_DATA (subbuf), + GST_BUFFER_SIZE (subbuf) / faac->bps, + GST_BUFFER_DATA (outbuf), + faac->bytes)) < 0) { + gst_element_error (GST_ELEMENT (faac), "Error during AAC encoding"); + gst_buffer_unref (inbuf); + gst_buffer_unref (subbuf); + return; + } + + if (ret_size > 0) { + GST_BUFFER_SIZE (outbuf) = ret_size; + if (faac->cache_time != GST_CLOCK_TIME_NONE) { + GST_BUFFER_TIMESTAMP (outbuf) = faac->cache_time; + faac->cache_time = GST_CLOCK_TIME_NONE; + } else + GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (subbuf); + GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (subbuf); + if (faac->cache_duration) { + GST_BUFFER_DURATION (outbuf) += faac->cache_duration; + faac->cache_duration = 0; + } + gst_pad_push (faac->srcpad, GST_DATA (outbuf)); + } else { + /* FIXME: what I'm doing here isn't fully correct, but there + * really isn't a better way yet. + * Problem is that libfaac caches buffers (for encoding + * purposes), so the timestamp of the outgoing buffer isn't + * the same as the timestamp of the data that I pushed in. + * However, I don't know the delay between those two so I + * cannot really say aything about it. This is a bad guess. */ + + gst_buffer_unref (outbuf); + if (faac->cache_time != GST_CLOCK_TIME_NONE) + faac->cache_time = GST_BUFFER_TIMESTAMP (subbuf); + faac->cache_duration += GST_BUFFER_DURATION (subbuf); + } + + in_size -= frame_size; + gst_buffer_unref (subbuf); + } +} + +static void +gst_faac_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GstFaac *faac = GST_FAAC (object); + + switch (prop_id) { + case ARG_BITRATE: + faac->bitrate = g_value_get_int (value); + break; + case ARG_PROFILE: + faac->profile = g_value_get_enum (value); + break; + case ARG_TNS: + faac->tns = g_value_get_boolean (value); + break; + case ARG_MIDSIDE: + faac->midside = g_value_get_boolean (value); + break; + case ARG_SHORTCTL: + faac->shortctl = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_faac_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GstFaac *faac = GST_FAAC (object); + + switch (prop_id) { + case ARG_BITRATE: + g_value_set_int (value, faac->bitrate); + break; + case ARG_PROFILE: + g_value_set_enum (value, faac->profile); + break; + case ARG_TNS: + g_value_set_boolean (value, faac->tns); + break; + case ARG_MIDSIDE: + g_value_set_boolean (value, faac->midside); + break; + case ARG_SHORTCTL: + g_value_set_enum (value, faac->shortctl); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstElementStateReturn +gst_faac_change_state (GstElement *element) +{ + GstFaac *faac = GST_FAAC (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_PAUSED_TO_READY: + if (faac->handle) { + faacEncClose (faac->handle); + faac->handle = NULL; + } + if (faac->cache) { + gst_buffer_unref (faac->cache); + faac->cache = NULL; + } + faac->cache_time = GST_CLOCK_TIME_NONE; + faac->cache_duration = 0; + faac->samplerate = -1; + faac->channels = -1; + break; + default: + break; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); + + return GST_STATE_SUCCESS; +} + +static gboolean +plugin_init (GstPlugin *plugin) +{ + return gst_element_register (plugin, "faac", + GST_RANK_NONE, + GST_TYPE_FAAC); +} + +GST_PLUGIN_DEFINE ( + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "faac", + "Free AAC Encoder (FAAC)", + plugin_init, + VERSION, + "LGPL", + GST_COPYRIGHT, + GST_PACKAGE, + GST_ORIGIN +) |