mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-03 16:09:39 +00:00
635b0c1129
Adds 'stream-format' to output caps to inform if the stream is adts or raw. This makes it possible for muxers to reject input streams on caps if they don't support adts instead of generating a invalid file. Fixes #604925
854 lines
24 KiB
C
854 lines
24 KiB
C
/* GStreamer FAAC (Free AAC Encoder) plugin
|
|
* Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
|
|
* Copyright (C) 2009 Mark Nauwelaerts <mnauw@users.sourceforge.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.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-faac
|
|
* @see_also: faad
|
|
*
|
|
* faac encodes raw audio to AAC (MPEG-4 part 10) streams.
|
|
*
|
|
* The #GstFaac:outputformat property determines whether or not the
|
|
* AAC data needs additional framing provided by a container
|
|
* (such as Matroska or Quicktime).
|
|
* This is required for raw data, whereas ADTS formatted AAC already provides
|
|
* framing and needs no container.
|
|
*
|
|
* The #GstFaac:profile property determines the AAC profile, where the default
|
|
* LC (Low Complexity) profile is most widely used, supported and suitable for
|
|
* general use. The other profiles are very rarely used and often not supported.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* <programlisting>
|
|
* gst-launch audiotestsrc wave=sine num-buffers=100 ! audioconvert ! faac ! matroskamux ! filesink location=sine.mkv
|
|
* </programlisting>
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "gstfaac.h"
|
|
|
|
#define SINK_CAPS \
|
|
"audio/x-raw-int, " \
|
|
"endianness = (int) BYTE_ORDER, " \
|
|
"signed = (boolean) true, " \
|
|
"width = (int) 16, " \
|
|
"depth = (int) 16, " \
|
|
"rate = (int) [ 8000, 96000 ], " \
|
|
"channels = (int) [ 1, 6 ] "
|
|
|
|
/* these don't seem to work? */
|
|
#if 0
|
|
"audio/x-raw-int, "
|
|
"endianness = (int) BYTE_ORDER, "
|
|
"signed = (boolean) true, "
|
|
"width = (int) 32, "
|
|
"depth = (int) { 24, 32 }, "
|
|
"rate = (int) [ 8000, 96000], "
|
|
"channels = (int) [ 1, 6]; "
|
|
"audio/x-raw-float, "
|
|
"endianness = (int) BYTE_ORDER, "
|
|
"width = (int) 32, "
|
|
"rate = (int) [ 8000, 96000], " "channels = (int) [ 1, 6]"
|
|
#endif
|
|
#define SRC_CAPS \
|
|
"audio/mpeg, " \
|
|
"mpegversion = (int) { 4, 2 }, " \
|
|
"channels = (int) [ 1, 6 ], " \
|
|
"rate = (int) [ 8000, 96000 ], " \
|
|
"stream-format = (string) { adts, none } "
|
|
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (SRC_CAPS));
|
|
|
|
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (SINK_CAPS));
|
|
|
|
static const GstElementDetails gst_faac_details =
|
|
GST_ELEMENT_DETAILS ("AAC audio encoder",
|
|
"Codec/Encoder/Audio",
|
|
"Free MPEG-2/4 AAC encoder",
|
|
"Ronald Bultje <rbultje@ronald.bitfreak.net>");
|
|
|
|
enum
|
|
{
|
|
ARG_0,
|
|
ARG_OUTPUTFORMAT,
|
|
ARG_BITRATE,
|
|
ARG_PROFILE,
|
|
ARG_TNS,
|
|
ARG_MIDSIDE,
|
|
ARG_SHORTCTL
|
|
};
|
|
|
|
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_finalize (GObject * object);
|
|
static void gst_faac_reset (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 gboolean gst_faac_sink_event (GstPad * pad, GstEvent * event);
|
|
static gboolean gst_faac_configure_source_pad (GstFaac * faac);
|
|
static gboolean gst_faac_sink_setcaps (GstPad * pad, GstCaps * caps);
|
|
static GstFlowReturn gst_faac_push_buffers (GstFaac * faac, gboolean force);
|
|
static GstFlowReturn gst_faac_chain (GstPad * pad, GstBuffer * data);
|
|
static GstStateChangeReturn gst_faac_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
|
|
static GstElementClass *parent_class = NULL;
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (faac_debug);
|
|
#define GST_CAT_DEFAULT faac_debug
|
|
|
|
#define FAAC_DEFAULT_MPEGVERSION 4
|
|
#define FAAC_DEFAULT_OUTPUTFORMAT 0 /* RAW */
|
|
#define FAAC_DEFAULT_BITRATE 128 * 1000
|
|
#define FAAC_DEFAULT_PROFILE LOW
|
|
#define FAAC_DEFAULT_TNS FALSE
|
|
#define FAAC_DEFAULT_MIDSIDE TRUE
|
|
#define FAAC_DEFAULT_SHORTCTL SHORTCTL_NORMAL
|
|
|
|
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,
|
|
};
|
|
const GInterfaceInfo preset_interface_info = {
|
|
NULL, /* interface_init */
|
|
NULL, /* interface_finalize */
|
|
NULL /* interface_data */
|
|
};
|
|
|
|
gst_faac_type = g_type_register_static (GST_TYPE_ELEMENT,
|
|
"GstFaac", &gst_faac_info, 0);
|
|
|
|
g_type_add_interface_static (gst_faac_type, GST_TYPE_PRESET,
|
|
&preset_interface_info);
|
|
}
|
|
|
|
return gst_faac_type;
|
|
}
|
|
|
|
static void
|
|
gst_faac_base_init (GstFaacClass * klass)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&src_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&sink_template));
|
|
|
|
gst_element_class_set_details (element_class, &gst_faac_details);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (faac_debug, "faac", 0, "AAC encoding");
|
|
}
|
|
|
|
#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, "LC", "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;
|
|
}
|
|
|
|
#define GST_TYPE_FAAC_OUTPUTFORMAT (gst_faac_outputformat_get_type ())
|
|
static GType
|
|
gst_faac_outputformat_get_type (void)
|
|
{
|
|
static GType gst_faac_outputformat_type = 0;
|
|
|
|
if (!gst_faac_outputformat_type) {
|
|
static GEnumValue gst_faac_outputformat[] = {
|
|
{0, "OUTPUTFORMAT_RAW", "Raw AAC"},
|
|
{1, "OUTPUTFORMAT_ADTS", "ADTS headers"},
|
|
{0, NULL, NULL},
|
|
};
|
|
|
|
gst_faac_outputformat_type = g_enum_register_static ("GstFaacOutputFormat",
|
|
gst_faac_outputformat);
|
|
}
|
|
|
|
return gst_faac_outputformat_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_peek_parent (klass);
|
|
|
|
gobject_class->set_property = gst_faac_set_property;
|
|
gobject_class->get_property = gst_faac_get_property;
|
|
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_faac_finalize);
|
|
|
|
/* properties */
|
|
g_object_class_install_property (gobject_class, ARG_BITRATE,
|
|
g_param_spec_int ("bitrate", "Bitrate (bps)", "Bitrate in bits/sec",
|
|
8 * 1000, 320 * 1000, FAAC_DEFAULT_BITRATE, 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, FAAC_DEFAULT_PROFILE, G_PARAM_READWRITE));
|
|
g_object_class_install_property (gobject_class, ARG_TNS,
|
|
g_param_spec_boolean ("tns", "TNS", "Use temporal noise shaping",
|
|
FAAC_DEFAULT_TNS, G_PARAM_READWRITE));
|
|
g_object_class_install_property (gobject_class, ARG_MIDSIDE,
|
|
g_param_spec_boolean ("midside", "Midside", "Allow mid/side encoding",
|
|
FAAC_DEFAULT_MIDSIDE, 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, FAAC_DEFAULT_SHORTCTL, G_PARAM_READWRITE));
|
|
g_object_class_install_property (gobject_class, ARG_OUTPUTFORMAT,
|
|
g_param_spec_enum ("outputformat", "Output format",
|
|
"Format of output frames",
|
|
GST_TYPE_FAAC_OUTPUTFORMAT, FAAC_DEFAULT_OUTPUTFORMAT,
|
|
G_PARAM_READWRITE));
|
|
|
|
/* virtual functions */
|
|
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_faac_change_state);
|
|
}
|
|
|
|
static void
|
|
gst_faac_init (GstFaac * faac)
|
|
{
|
|
faac->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
|
|
gst_pad_set_chain_function (faac->sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_faac_chain));
|
|
gst_pad_set_setcaps_function (faac->sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_faac_sink_setcaps));
|
|
gst_pad_set_event_function (faac->sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_faac_sink_event));
|
|
gst_element_add_pad (GST_ELEMENT (faac), faac->sinkpad);
|
|
|
|
faac->srcpad = gst_pad_new_from_static_template (&src_template, "src");
|
|
gst_pad_use_fixed_caps (faac->srcpad);
|
|
gst_element_add_pad (GST_ELEMENT (faac), faac->srcpad);
|
|
|
|
faac->adapter = gst_adapter_new ();
|
|
|
|
/* default properties */
|
|
faac->bitrate = FAAC_DEFAULT_BITRATE;
|
|
faac->profile = FAAC_DEFAULT_PROFILE;
|
|
faac->shortctl = FAAC_DEFAULT_SHORTCTL;
|
|
faac->outputformat = FAAC_DEFAULT_OUTPUTFORMAT;
|
|
faac->tns = FAAC_DEFAULT_TNS;
|
|
faac->midside = FAAC_DEFAULT_MIDSIDE;
|
|
|
|
gst_faac_reset (faac);
|
|
}
|
|
|
|
static void
|
|
gst_faac_reset (GstFaac * faac)
|
|
{
|
|
faac->handle = NULL;
|
|
faac->samplerate = -1;
|
|
faac->channels = -1;
|
|
faac->offset = 0;
|
|
gst_adapter_clear (faac->adapter);
|
|
}
|
|
|
|
static void
|
|
gst_faac_finalize (GObject * object)
|
|
{
|
|
GstFaac *faac = (GstFaac *) object;
|
|
|
|
g_object_unref (faac->adapter);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_faac_close_encoder (GstFaac * faac)
|
|
{
|
|
if (faac->handle)
|
|
faacEncClose (faac->handle);
|
|
faac->handle = NULL;
|
|
gst_adapter_clear (faac->adapter);
|
|
faac->offset = 0;
|
|
}
|
|
|
|
static gboolean
|
|
gst_faac_sink_setcaps (GstPad * pad, GstCaps * caps)
|
|
{
|
|
GstFaac *faac = GST_FAAC (gst_pad_get_parent (pad));
|
|
GstStructure *structure = gst_caps_get_structure (caps, 0);
|
|
faacEncHandle *handle;
|
|
gint channels, samplerate, width;
|
|
gulong samples, bytes, fmt = 0, bps = 0;
|
|
gboolean result = FALSE;
|
|
|
|
if (!gst_caps_is_fixed (caps))
|
|
goto refuse_caps;
|
|
|
|
if (!gst_structure_get_int (structure, "channels", &channels) ||
|
|
!gst_structure_get_int (structure, "rate", &samplerate)) {
|
|
goto refuse_caps;
|
|
}
|
|
|
|
if (gst_structure_has_name (structure, "audio/x-raw-int")) {
|
|
gst_structure_get_int (structure, "width", &width);
|
|
switch (width) {
|
|
case 16:
|
|
fmt = FAAC_INPUT_16BIT;
|
|
bps = 2;
|
|
break;
|
|
case 24:
|
|
case 32:
|
|
fmt = FAAC_INPUT_32BIT;
|
|
bps = 4;
|
|
break;
|
|
default:
|
|
g_return_val_if_reached (FALSE);
|
|
}
|
|
} else if (gst_structure_has_name (structure, "audio/x-raw-float")) {
|
|
fmt = FAAC_INPUT_FLOAT;
|
|
bps = 4;
|
|
}
|
|
|
|
if (!fmt)
|
|
goto refuse_caps;
|
|
|
|
/* If the encoder is initialized, do not
|
|
reinitialize it again if not necessary */
|
|
if (faac->handle) {
|
|
if (samplerate == faac->samplerate && channels == faac->channels &&
|
|
fmt == faac->format)
|
|
return TRUE;
|
|
|
|
/* clear out pending frames */
|
|
gst_faac_push_buffers (faac, TRUE);
|
|
|
|
gst_faac_close_encoder (faac);
|
|
}
|
|
|
|
if (!(handle = faacEncOpen (samplerate, channels, &samples, &bytes)))
|
|
goto setup_failed;
|
|
|
|
/* ok, record and set up */
|
|
faac->format = fmt;
|
|
faac->bps = bps;
|
|
faac->handle = handle;
|
|
faac->bytes = bytes;
|
|
faac->samples = samples;
|
|
faac->channels = channels;
|
|
faac->samplerate = samplerate;
|
|
|
|
/* finish up */
|
|
result = gst_faac_configure_source_pad (faac);
|
|
|
|
done:
|
|
gst_object_unref (faac);
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
setup_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (faac, LIBRARY, SETTINGS, (NULL), (NULL));
|
|
goto done;
|
|
}
|
|
refuse_caps:
|
|
{
|
|
GST_WARNING_OBJECT (faac, "refused caps %" GST_PTR_FORMAT, caps);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_faac_configure_source_pad (GstFaac * faac)
|
|
{
|
|
GstCaps *allowed_caps;
|
|
GstCaps *srccaps;
|
|
gboolean ret = FALSE;
|
|
gint n, ver, mpegversion = 2;
|
|
faacEncConfiguration *conf;
|
|
guint maxbitrate;
|
|
|
|
mpegversion = FAAC_DEFAULT_MPEGVERSION;
|
|
|
|
allowed_caps = gst_pad_get_allowed_caps (faac->srcpad);
|
|
GST_DEBUG_OBJECT (faac, "allowed caps: %" GST_PTR_FORMAT, allowed_caps);
|
|
|
|
if (allowed_caps) {
|
|
if (gst_caps_is_empty (allowed_caps))
|
|
goto empty_caps;
|
|
|
|
if (!gst_caps_is_any (allowed_caps)) {
|
|
for (n = 0; n < gst_caps_get_size (allowed_caps); n++) {
|
|
GstStructure *s = gst_caps_get_structure (allowed_caps, n);
|
|
|
|
if (gst_structure_get_int (s, "mpegversion", &ver) &&
|
|
(ver == 4 || ver == 2)) {
|
|
mpegversion = ver;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
gst_caps_unref (allowed_caps);
|
|
}
|
|
|
|
/* we negotiated caps update current configuration */
|
|
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 / faac->channels;
|
|
conf->inputFormat = faac->format;
|
|
conf->outputFormat = faac->outputformat;
|
|
conf->shortctl = faac->shortctl;
|
|
|
|
/* check, warn and correct if the max bitrate for the given samplerate is
|
|
* exceeded. Maximum of 6144 bit for a channel */
|
|
maxbitrate =
|
|
(unsigned int) (6144.0 * (double) faac->samplerate / (double) 1024.0 +
|
|
.5);
|
|
if (conf->bitRate > maxbitrate) {
|
|
GST_ELEMENT_WARNING (faac, RESOURCE, SETTINGS, (NULL),
|
|
("bitrate %lu exceeds maximum allowed bitrate of %u for samplerate %d. "
|
|
"Setting bitrate to %u", conf->bitRate, maxbitrate,
|
|
faac->samplerate, maxbitrate));
|
|
conf->bitRate = maxbitrate;
|
|
}
|
|
|
|
if (!faacEncSetConfiguration (faac->handle, conf))
|
|
goto set_failed;
|
|
|
|
/* now create a caps for it all */
|
|
srccaps = gst_caps_new_simple ("audio/mpeg",
|
|
"mpegversion", G_TYPE_INT, mpegversion,
|
|
"channels", G_TYPE_INT, faac->channels,
|
|
"rate", G_TYPE_INT, faac->samplerate,
|
|
"stream-format", G_TYPE_STRING, (faac->outputformat ? "adts" : "none"),
|
|
NULL);
|
|
|
|
if (!faac->outputformat) {
|
|
GstBuffer *codec_data;
|
|
guint8 *config = NULL;
|
|
gulong config_len = 0;
|
|
|
|
/* get the config string */
|
|
GST_DEBUG_OBJECT (faac, "retrieving decoder info");
|
|
faacEncGetDecoderSpecificInfo (faac->handle, &config, &config_len);
|
|
|
|
/* copy it into a buffer */
|
|
codec_data = gst_buffer_new_and_alloc (config_len);
|
|
memcpy (GST_BUFFER_DATA (codec_data), config, config_len);
|
|
free (config);
|
|
|
|
/* add to caps */
|
|
gst_caps_set_simple (srccaps,
|
|
"codec_data", GST_TYPE_BUFFER, codec_data, NULL);
|
|
|
|
gst_buffer_unref (codec_data);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (faac, "src pad caps: %" GST_PTR_FORMAT, srccaps);
|
|
|
|
ret = gst_pad_set_caps (faac->srcpad, srccaps);
|
|
gst_caps_unref (srccaps);
|
|
|
|
return ret;
|
|
|
|
/* ERROR */
|
|
empty_caps:
|
|
{
|
|
gst_caps_unref (allowed_caps);
|
|
return FALSE;
|
|
}
|
|
set_failed:
|
|
{
|
|
GST_WARNING_OBJECT (faac, "Faac doesn't support the current configuration");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_faac_push_buffers (GstFaac * faac, gboolean force)
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
gint av, frame_size, size, ret_size;
|
|
GstBuffer *outbuf;
|
|
guint64 timestamp, distance;
|
|
const guint8 *data;
|
|
|
|
/* samples already considers channel count */
|
|
frame_size = faac->samples * faac->bps;
|
|
|
|
while (G_LIKELY (ret == GST_FLOW_OK)) {
|
|
|
|
av = gst_adapter_available (faac->adapter);
|
|
|
|
GST_LOG_OBJECT (faac, "pushing: force: %d, frame_size: %d, av: %d, "
|
|
"offset: %d", force, frame_size, av, faac->offset);
|
|
|
|
/* idea:
|
|
* - start of adapter corresponds with what has already been encoded
|
|
* (i.e. really returned by faac)
|
|
* - start + offset is what needs to be fed to faac next
|
|
* That way we can timestamp the output based
|
|
* on adapter provided timestamp (and duration is a fixed frame duration) */
|
|
|
|
/* not enough data for one frame and no flush forcing */
|
|
if (!force && (av < frame_size + faac->offset))
|
|
break;
|
|
|
|
if (G_LIKELY (av - faac->offset >= frame_size)) {
|
|
GST_LOG_OBJECT (faac, "encoding a frame");
|
|
data = gst_adapter_peek (faac->adapter, faac->offset + frame_size);
|
|
data += faac->offset;
|
|
size = frame_size;
|
|
} else if (av - faac->offset > 0) {
|
|
GST_LOG_OBJECT (faac, "encoding leftover");
|
|
data = gst_adapter_peek (faac->adapter, av);
|
|
data += faac->offset;
|
|
size = av - faac->offset;
|
|
} else {
|
|
GST_LOG_OBJECT (faac, "emptying encoder");
|
|
data = NULL;
|
|
size = 0;
|
|
}
|
|
|
|
outbuf = gst_buffer_new_and_alloc (faac->bytes);
|
|
|
|
if (G_UNLIKELY ((ret_size = faacEncEncode (faac->handle, (gint32 *) data,
|
|
size / faac->bps, GST_BUFFER_DATA (outbuf),
|
|
faac->bytes)) < 0)) {
|
|
gst_buffer_unref (outbuf);
|
|
goto encode_failed;
|
|
}
|
|
|
|
GST_LOG_OBJECT (faac, "encoder return: %d", ret_size);
|
|
|
|
/* consumed, advanced view */
|
|
faac->offset += size;
|
|
g_assert (faac->offset <= av);
|
|
|
|
if (G_UNLIKELY (!ret_size)) {
|
|
gst_buffer_unref (outbuf);
|
|
if (size)
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* in case encoder returns more data than is expected (which seems possible)
|
|
* ignore the extra part */
|
|
if (G_UNLIKELY (av == 0 && faac->offset == 0)) {
|
|
GST_DEBUG_OBJECT (faac, "encoder returned unexpected data; discarding");
|
|
gst_buffer_unref (outbuf);
|
|
continue;
|
|
}
|
|
|
|
/* after some caching, finally some data */
|
|
/* adapter gives time */
|
|
timestamp = gst_adapter_prev_timestamp (faac->adapter, &distance);
|
|
if (G_LIKELY ((av = gst_adapter_available (faac->adapter)) >= frame_size)) {
|
|
/* must have then come from a complete frame */
|
|
gst_adapter_flush (faac->adapter, frame_size);
|
|
faac->offset -= frame_size;
|
|
size = frame_size;
|
|
} else {
|
|
/* otherwise leftover */
|
|
gst_adapter_clear (faac->adapter);
|
|
faac->offset = 0;
|
|
size = av;
|
|
}
|
|
|
|
GST_BUFFER_SIZE (outbuf) = ret_size;
|
|
if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (timestamp)))
|
|
GST_BUFFER_TIMESTAMP (outbuf) = timestamp +
|
|
GST_FRAMES_TO_CLOCK_TIME (distance / faac->channels / faac->bps,
|
|
faac->samplerate);
|
|
GST_BUFFER_DURATION (outbuf) =
|
|
GST_FRAMES_TO_CLOCK_TIME (size / faac->channels / faac->bps,
|
|
faac->samplerate);
|
|
/* perhaps check/set DISCONT based on timestamps ? */
|
|
|
|
GST_LOG_OBJECT (faac, "Pushing out buffer time: %" GST_TIME_FORMAT
|
|
" duration: %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
|
|
GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)));
|
|
|
|
gst_buffer_set_caps (outbuf, GST_PAD_CAPS (faac->srcpad));
|
|
ret = gst_pad_push (faac->srcpad, outbuf);
|
|
}
|
|
|
|
/* in case encoder returns less than expected, clear our view as well */
|
|
if (G_UNLIKELY (force)) {
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if ((av = gst_adapter_available (faac->adapter)))
|
|
GST_WARNING_OBJECT (faac, "encoder left %d bytes; discarding", av);
|
|
#endif
|
|
gst_adapter_clear (faac->adapter);
|
|
faac->offset = 0;
|
|
}
|
|
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
encode_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (faac, LIBRARY, ENCODE, (NULL), (NULL));
|
|
gst_buffer_unref (outbuf);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_faac_sink_event (GstPad * pad, GstEvent * event)
|
|
{
|
|
GstFaac *faac;
|
|
gboolean ret;
|
|
|
|
faac = GST_FAAC (gst_pad_get_parent (pad));
|
|
|
|
GST_LOG_OBJECT (faac, "received %s", GST_EVENT_TYPE_NAME (event));
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_EOS:
|
|
{
|
|
if (faac->handle) {
|
|
/* flush first */
|
|
GST_DEBUG_OBJECT (faac, "Pushing out remaining buffers because of EOS");
|
|
gst_faac_push_buffers (faac, TRUE);
|
|
}
|
|
|
|
ret = gst_pad_event_default (pad, event);
|
|
break;
|
|
}
|
|
default:
|
|
ret = gst_pad_event_default (pad, event);
|
|
break;
|
|
|
|
}
|
|
gst_object_unref (faac);
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_faac_chain (GstPad * pad, GstBuffer * inbuf)
|
|
{
|
|
GstFlowReturn result = GST_FLOW_OK;
|
|
GstFaac *faac;
|
|
|
|
faac = GST_FAAC (gst_pad_get_parent (pad));
|
|
|
|
if (!faac->handle)
|
|
goto no_handle;
|
|
|
|
GST_LOG_OBJECT (faac, "Got buffer time: %" GST_TIME_FORMAT " duration: %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)),
|
|
GST_TIME_ARGS (GST_BUFFER_DURATION (inbuf)));
|
|
|
|
gst_adapter_push (faac->adapter, inbuf);
|
|
|
|
result = gst_faac_push_buffers (faac, FALSE);
|
|
|
|
done:
|
|
gst_object_unref (faac);
|
|
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
no_handle:
|
|
{
|
|
GST_ELEMENT_ERROR (faac, CORE, NEGOTIATION, (NULL),
|
|
("format wasn't negotiated before chain function"));
|
|
gst_buffer_unref (inbuf);
|
|
result = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_faac_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstFaac *faac = GST_FAAC (object);
|
|
|
|
GST_OBJECT_LOCK (faac);
|
|
|
|
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;
|
|
case ARG_OUTPUTFORMAT:
|
|
faac->outputformat = g_value_get_enum (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
|
|
GST_OBJECT_UNLOCK (faac);
|
|
}
|
|
|
|
static void
|
|
gst_faac_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstFaac *faac = GST_FAAC (object);
|
|
|
|
GST_OBJECT_LOCK (faac);
|
|
|
|
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;
|
|
case ARG_OUTPUTFORMAT:
|
|
g_value_set_enum (value, faac->outputformat);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
|
|
GST_OBJECT_UNLOCK (faac);
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_faac_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
|
GstFaac *faac = GST_FAAC (element);
|
|
|
|
/* upwards state changes */
|
|
switch (transition) {
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
/* downwards state changes */
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
{
|
|
gst_faac_close_encoder (faac);
|
|
gst_faac_reset (faac);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
return gst_element_register (plugin, "faac", GST_RANK_SECONDARY,
|
|
GST_TYPE_FAAC);
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
"faac",
|
|
"Free AAC Encoder (FAAC)",
|
|
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|