mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 11:10:37 +00:00
c9e20af4cb
lame does internal resampling, but the base class only cares about the number of raw samples, so tell finish frames about that, not the number of samples in the outgoing frame.:
933 lines
29 KiB
C
933 lines
29 KiB
C
/* GStreamer
|
|
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
|
* Copyright (C) <2004> Wim Taymans <wim@fluendo.com>
|
|
* Copyright (C) <2005> Thomas Vander Stichele <thomas at apestaart dot org>
|
|
* Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-lamemp3enc
|
|
* @see_also: lame, mad, vorbisenc
|
|
*
|
|
* This element encodes raw integer audio into an MPEG-1 layer 3 (MP3) stream.
|
|
* Note that <ulink url="http://en.wikipedia.org/wiki/MP3">MP3</ulink> is not
|
|
* a free format, there are licensing and patent issues to take into
|
|
* consideration. See <ulink url="http://www.vorbis.com/">Ogg/Vorbis</ulink>
|
|
* for a royalty free (and often higher quality) alternative.
|
|
*
|
|
* <refsect2>
|
|
* <title>Output sample rate</title>
|
|
* If no fixed output sample rate is negotiated on the element's src pad,
|
|
* the element will choose an optimal sample rate to resample to internally.
|
|
* For example, a 16-bit 44.1 KHz mono audio stream encoded at 48 kbit will
|
|
* get resampled to 32 KHz. Use filter caps on the src pad to force a
|
|
* particular sample rate.
|
|
* </refsect2>
|
|
* <refsect2>
|
|
* <title>Example pipelines</title>
|
|
* |[
|
|
* gst-launch -v audiotestsrc wave=sine num-buffers=100 ! audioconvert ! lamemp3enc ! filesink location=sine.mp3
|
|
* ]| Encode a test sine signal to MP3.
|
|
* |[
|
|
* gst-launch -v alsasrc ! audioconvert ! lamemp3enc target=bitrate bitrate=192 ! filesink location=alsasrc.mp3
|
|
* ]| Record from a sound card using ALSA and encode to MP3 with an average bitrate of 192kbps
|
|
* |[
|
|
* gst-launch -v filesrc location=music.wav ! decodebin ! audioconvert ! audioresample ! lamemp3enc target=quality quality=0 ! id3v2mux ! filesink location=music.mp3
|
|
* ]| Transcode from a .wav file to MP3 (the id3v2mux element is optional) with best VBR quality
|
|
* |[
|
|
* gst-launch -v cdda://5 ! audioconvert ! lamemp3enc target=bitrate cbr=true bitrate=192 ! filesink location=track5.mp3
|
|
* ]| Encode Audio CD track 5 to MP3 with a constant bitrate of 192kbps
|
|
* |[
|
|
* gst-launch -v audiotestsrc num-buffers=10 ! audio/x-raw,rate=44100,channels=1 ! lamemp3enc target=bitrate cbr=true bitrate=48 ! filesink location=test.mp3
|
|
* ]| Encode to a fixed sample rate
|
|
* </refsect2>
|
|
*
|
|
* Since: 0.10.12
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include "gstlamemp3enc.h"
|
|
#include <gst/gst-i18n-plugin.h>
|
|
|
|
/* lame < 3.98 */
|
|
#ifndef HAVE_LAME_SET_VBR_QUALITY
|
|
#define lame_set_VBR_quality(flags,q) lame_set_VBR_q((flags),(int)(q))
|
|
#endif
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (debug);
|
|
#define GST_CAT_DEFAULT debug
|
|
|
|
/* elementfactory information */
|
|
|
|
/* LAMEMP3ENC can do MPEG-1, MPEG-2, and MPEG-2.5, so it has 9 possible
|
|
* sample rates it supports */
|
|
static GstStaticPadTemplate gst_lamemp3enc_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/x-raw, "
|
|
"format = (string) " GST_AUDIO_NE (S16) ", "
|
|
"layout = (string) interleaved, "
|
|
"rate = (int) { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }, "
|
|
"channels = (int) 1; "
|
|
"audio/x-raw, "
|
|
"format = (string) " GST_AUDIO_NE (S16) ", "
|
|
"layout = (string) interleaved, "
|
|
"rate = (int) { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }, "
|
|
"channels = (int) 2, " "channel-mask = (bitmask) 0x3")
|
|
);
|
|
|
|
static GstStaticPadTemplate gst_lamemp3enc_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/mpeg, "
|
|
"mpegversion = (int) 1, "
|
|
"layer = (int) 3, "
|
|
"rate = (int) { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }, "
|
|
"channels = (int) [ 1, 2 ]")
|
|
);
|
|
|
|
/********** Define useful types for non-programmatic interfaces **********/
|
|
enum
|
|
{
|
|
LAMEMP3ENC_TARGET_QUALITY = 0,
|
|
LAMEMP3ENC_TARGET_BITRATE
|
|
};
|
|
|
|
#define GST_TYPE_LAMEMP3ENC_TARGET (gst_lamemp3enc_target_get_type())
|
|
static GType
|
|
gst_lamemp3enc_target_get_type (void)
|
|
{
|
|
static GType lame_target_type = 0;
|
|
static GEnumValue lame_targets[] = {
|
|
{LAMEMP3ENC_TARGET_QUALITY, "Quality", "quality"},
|
|
{LAMEMP3ENC_TARGET_BITRATE, "Bitrate", "bitrate"},
|
|
{0, NULL, NULL}
|
|
};
|
|
|
|
if (!lame_target_type) {
|
|
lame_target_type =
|
|
g_enum_register_static ("GstLameMP3EncTarget", lame_targets);
|
|
}
|
|
return lame_target_type;
|
|
}
|
|
|
|
enum
|
|
{
|
|
LAMEMP3ENC_ENCODING_ENGINE_QUALITY_FAST = 0,
|
|
LAMEMP3ENC_ENCODING_ENGINE_QUALITY_STANDARD,
|
|
LAMEMP3ENC_ENCODING_ENGINE_QUALITY_HIGH
|
|
};
|
|
|
|
#define GST_TYPE_LAMEMP3ENC_ENCODING_ENGINE_QUALITY (gst_lamemp3enc_encoding_engine_quality_get_type())
|
|
static GType
|
|
gst_lamemp3enc_encoding_engine_quality_get_type (void)
|
|
{
|
|
static GType lame_encoding_engine_quality_type = 0;
|
|
static GEnumValue lame_encoding_engine_quality[] = {
|
|
{0, "Fast", "fast"},
|
|
{1, "Standard", "standard"},
|
|
{2, "High", "high"},
|
|
{0, NULL, NULL}
|
|
};
|
|
|
|
if (!lame_encoding_engine_quality_type) {
|
|
lame_encoding_engine_quality_type =
|
|
g_enum_register_static ("GstLameMP3EncEncodingEngineQuality",
|
|
lame_encoding_engine_quality);
|
|
}
|
|
return lame_encoding_engine_quality_type;
|
|
}
|
|
|
|
/********** Standard stuff for signals and arguments **********/
|
|
|
|
enum
|
|
{
|
|
ARG_0,
|
|
ARG_TARGET,
|
|
ARG_BITRATE,
|
|
ARG_CBR,
|
|
ARG_QUALITY,
|
|
ARG_ENCODING_ENGINE_QUALITY,
|
|
ARG_MONO
|
|
};
|
|
|
|
#define DEFAULT_TARGET LAMEMP3ENC_TARGET_QUALITY
|
|
#define DEFAULT_BITRATE 128
|
|
#define DEFAULT_CBR FALSE
|
|
#define DEFAULT_QUALITY 4
|
|
#define DEFAULT_ENCODING_ENGINE_QUALITY LAMEMP3ENC_ENCODING_ENGINE_QUALITY_STANDARD
|
|
#define DEFAULT_MONO FALSE
|
|
|
|
static gboolean gst_lamemp3enc_start (GstAudioEncoder * enc);
|
|
static gboolean gst_lamemp3enc_stop (GstAudioEncoder * enc);
|
|
static gboolean gst_lamemp3enc_set_format (GstAudioEncoder * enc,
|
|
GstAudioInfo * info);
|
|
static GstFlowReturn gst_lamemp3enc_handle_frame (GstAudioEncoder * enc,
|
|
GstBuffer * in_buf);
|
|
static void gst_lamemp3enc_flush (GstAudioEncoder * enc);
|
|
|
|
static void gst_lamemp3enc_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_lamemp3enc_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
static gboolean gst_lamemp3enc_setup (GstLameMP3Enc * lame, GstTagList ** tags);
|
|
|
|
#define gst_lamemp3enc_parent_class parent_class
|
|
G_DEFINE_TYPE (GstLameMP3Enc, gst_lamemp3enc, GST_TYPE_AUDIO_ENCODER);
|
|
|
|
static void
|
|
gst_lamemp3enc_release_memory (GstLameMP3Enc * lame)
|
|
{
|
|
if (lame->lgf) {
|
|
lame_close (lame->lgf);
|
|
lame->lgf = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_lamemp3enc_finalize (GObject * obj)
|
|
{
|
|
gst_lamemp3enc_release_memory (GST_LAMEMP3ENC (obj));
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gst_lamemp3enc_class_init (GstLameMP3EncClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstAudioEncoderClass *base_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
base_class = (GstAudioEncoderClass *) klass;
|
|
|
|
gobject_class->set_property = gst_lamemp3enc_set_property;
|
|
gobject_class->get_property = gst_lamemp3enc_get_property;
|
|
gobject_class->finalize = gst_lamemp3enc_finalize;
|
|
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&gst_lamemp3enc_src_template));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&gst_lamemp3enc_sink_template));
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class,
|
|
"L.A.M.E. mp3 encoder", "Codec/Encoder/Audio",
|
|
"High-quality free MP3 encoder",
|
|
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
|
|
|
base_class->start = GST_DEBUG_FUNCPTR (gst_lamemp3enc_start);
|
|
base_class->stop = GST_DEBUG_FUNCPTR (gst_lamemp3enc_stop);
|
|
base_class->set_format = GST_DEBUG_FUNCPTR (gst_lamemp3enc_set_format);
|
|
base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_lamemp3enc_handle_frame);
|
|
base_class->flush = GST_DEBUG_FUNCPTR (gst_lamemp3enc_flush);
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TARGET,
|
|
g_param_spec_enum ("target", "Target",
|
|
"Optimize for quality or bitrate", GST_TYPE_LAMEMP3ENC_TARGET,
|
|
DEFAULT_TARGET,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BITRATE,
|
|
g_param_spec_int ("bitrate", "Bitrate (kb/s)",
|
|
"Bitrate in kbit/sec (Only valid if target is bitrate, for CBR one "
|
|
"of 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, "
|
|
"256 or 320)", 8, 320, DEFAULT_BITRATE,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_CBR,
|
|
g_param_spec_boolean ("cbr", "CBR", "Enforce constant bitrate encoding "
|
|
"(Only valid if target is bitrate)", DEFAULT_CBR,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_QUALITY,
|
|
g_param_spec_float ("quality", "Quality",
|
|
"VBR Quality from 0 to 10, 0 being the best "
|
|
"(Only valid if target is quality)", 0.0, 9.999,
|
|
DEFAULT_QUALITY,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass),
|
|
ARG_ENCODING_ENGINE_QUALITY, g_param_spec_enum ("encoding-engine-quality",
|
|
"Encoding Engine Quality", "Quality/speed of the encoding engine, "
|
|
"this does not affect the bitrate!",
|
|
GST_TYPE_LAMEMP3ENC_ENCODING_ENGINE_QUALITY,
|
|
DEFAULT_ENCODING_ENGINE_QUALITY,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MONO,
|
|
g_param_spec_boolean ("mono", "Mono", "Enforce mono encoding",
|
|
DEFAULT_MONO,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
gst_lamemp3enc_init (GstLameMP3Enc * lame)
|
|
{
|
|
}
|
|
|
|
static gboolean
|
|
gst_lamemp3enc_start (GstAudioEncoder * enc)
|
|
{
|
|
GstLameMP3Enc *lame = GST_LAMEMP3ENC (enc);
|
|
|
|
GST_DEBUG_OBJECT (lame, "start");
|
|
|
|
if (!lame->adapter)
|
|
lame->adapter = gst_adapter_new ();
|
|
gst_adapter_clear (lame->adapter);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_lamemp3enc_stop (GstAudioEncoder * enc)
|
|
{
|
|
GstLameMP3Enc *lame = GST_LAMEMP3ENC (enc);
|
|
|
|
GST_DEBUG_OBJECT (lame, "stop");
|
|
|
|
if (lame->adapter) {
|
|
g_object_unref (lame->adapter);
|
|
lame->adapter = NULL;
|
|
}
|
|
|
|
gst_lamemp3enc_release_memory (lame);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_lamemp3enc_set_format (GstAudioEncoder * enc, GstAudioInfo * info)
|
|
{
|
|
GstLameMP3Enc *lame;
|
|
gint out_samplerate;
|
|
gint version;
|
|
GstCaps *othercaps;
|
|
GstClockTime latency;
|
|
GstTagList *tags = NULL;
|
|
|
|
lame = GST_LAMEMP3ENC (enc);
|
|
|
|
/* parameters already parsed for us */
|
|
lame->samplerate = GST_AUDIO_INFO_RATE (info);
|
|
lame->num_channels = GST_AUDIO_INFO_CHANNELS (info);
|
|
|
|
/* but we might be asked to reconfigure, so reset */
|
|
gst_lamemp3enc_release_memory (lame);
|
|
|
|
GST_DEBUG_OBJECT (lame, "setting up lame");
|
|
if (!gst_lamemp3enc_setup (lame, &tags))
|
|
goto setup_failed;
|
|
|
|
out_samplerate = lame_get_out_samplerate (lame->lgf);
|
|
if (out_samplerate == 0)
|
|
goto zero_output_rate;
|
|
if (out_samplerate != lame->samplerate) {
|
|
GST_WARNING_OBJECT (lame,
|
|
"output samplerate %d is different from incoming samplerate %d",
|
|
out_samplerate, lame->samplerate);
|
|
}
|
|
lame->out_samplerate = out_samplerate;
|
|
|
|
version = lame_get_version (lame->lgf);
|
|
if (version == 0)
|
|
version = 2;
|
|
else if (version == 1)
|
|
version = 1;
|
|
else if (version == 2)
|
|
version = 3;
|
|
|
|
othercaps =
|
|
gst_caps_new_simple ("audio/mpeg",
|
|
"mpegversion", G_TYPE_INT, 1,
|
|
"mpegaudioversion", G_TYPE_INT, version,
|
|
"layer", G_TYPE_INT, 3,
|
|
"channels", G_TYPE_INT, lame->mono ? 1 : lame->num_channels,
|
|
"rate", G_TYPE_INT, out_samplerate, NULL);
|
|
|
|
/* and use these caps */
|
|
gst_audio_encoder_set_output_format (GST_AUDIO_ENCODER (enc), othercaps);
|
|
gst_caps_unref (othercaps);
|
|
|
|
/* base class feedback:
|
|
* - we will handle buffers, just hand us all available
|
|
* - report latency */
|
|
latency = gst_util_uint64_scale_int (lame_get_framesize (lame->lgf),
|
|
GST_SECOND, lame->samplerate);
|
|
gst_audio_encoder_set_latency (enc, latency, latency);
|
|
|
|
if (tags) {
|
|
gst_audio_encoder_merge_tags (enc, tags, GST_TAG_MERGE_REPLACE);
|
|
gst_tag_list_unref (tags);
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
zero_output_rate:
|
|
{
|
|
if (tags)
|
|
gst_tag_list_unref (tags);
|
|
GST_ELEMENT_ERROR (lame, LIBRARY, SETTINGS, (NULL),
|
|
("LAME mp3 audio decided on a zero sample rate"));
|
|
return FALSE;
|
|
}
|
|
setup_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (lame, LIBRARY, SETTINGS,
|
|
(_("Failed to configure LAME mp3 audio encoder. Check your encoding parameters.")), (NULL));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* <php-emulation-mode>three underscores for ___rate is really really really
|
|
* private as opposed to one underscore<php-emulation-mode> */
|
|
/* call this MACRO outside of the NULL state so that we have a higher chance
|
|
* of actually having a pipeline and bus to get the message through */
|
|
|
|
#define CHECK_AND_FIXUP_BITRATE(obj,param,rate) \
|
|
G_STMT_START { \
|
|
gint ___rate = rate; \
|
|
gint maxrate = 320; \
|
|
gint multiplier = 64; \
|
|
if (rate == 0) { \
|
|
___rate = rate; \
|
|
} else if (rate <= 64) { \
|
|
maxrate = 64; multiplier = 8; \
|
|
if ((rate % 8) != 0) ___rate = GST_ROUND_UP_8 (rate); \
|
|
} else if (rate <= 128) { \
|
|
maxrate = 128; multiplier = 16; \
|
|
if ((rate % 16) != 0) ___rate = GST_ROUND_UP_16 (rate); \
|
|
} else if (rate <= 256) { \
|
|
maxrate = 256; multiplier = 32; \
|
|
if ((rate % 32) != 0) ___rate = GST_ROUND_UP_32 (rate); \
|
|
} else if (rate <= 320) { \
|
|
maxrate = 320; multiplier = 64; \
|
|
if ((rate % 64) != 0) ___rate = GST_ROUND_UP_64 (rate); \
|
|
} \
|
|
if (___rate != rate) { \
|
|
GST_ELEMENT_WARNING (obj, LIBRARY, SETTINGS, \
|
|
(_("The requested bitrate %d kbit/s for property '%s' " \
|
|
"is not allowed. " \
|
|
"The bitrate was changed to %d kbit/s."), rate, \
|
|
param, ___rate), \
|
|
("A bitrate below %d should be a multiple of %d.", \
|
|
maxrate, multiplier)); \
|
|
rate = ___rate; \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
static void
|
|
gst_lamemp3enc_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstLameMP3Enc *lame;
|
|
|
|
lame = GST_LAMEMP3ENC (object);
|
|
|
|
switch (prop_id) {
|
|
case ARG_TARGET:
|
|
lame->target = g_value_get_enum (value);
|
|
break;
|
|
case ARG_BITRATE:
|
|
lame->bitrate = g_value_get_int (value);
|
|
break;
|
|
case ARG_CBR:
|
|
lame->cbr = g_value_get_boolean (value);
|
|
break;
|
|
case ARG_QUALITY:
|
|
lame->quality = g_value_get_float (value);
|
|
break;
|
|
case ARG_ENCODING_ENGINE_QUALITY:
|
|
lame->encoding_engine_quality = g_value_get_enum (value);
|
|
break;
|
|
case ARG_MONO:
|
|
lame->mono = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_lamemp3enc_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstLameMP3Enc *lame;
|
|
|
|
lame = GST_LAMEMP3ENC (object);
|
|
|
|
switch (prop_id) {
|
|
case ARG_TARGET:
|
|
g_value_set_enum (value, lame->target);
|
|
break;
|
|
case ARG_BITRATE:
|
|
g_value_set_int (value, lame->bitrate);
|
|
break;
|
|
case ARG_CBR:
|
|
g_value_set_boolean (value, lame->cbr);
|
|
break;
|
|
case ARG_QUALITY:
|
|
g_value_set_float (value, lame->quality);
|
|
break;
|
|
case ARG_ENCODING_ENGINE_QUALITY:
|
|
g_value_set_enum (value, lame->encoding_engine_quality);
|
|
break;
|
|
case ARG_MONO:
|
|
g_value_set_boolean (value, lame->mono);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* **** credits go to mpegaudioparse **** */
|
|
|
|
static const guint mp3types_bitrates[2][3][16] = {
|
|
{
|
|
{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448,},
|
|
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,},
|
|
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,}
|
|
},
|
|
{
|
|
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,},
|
|
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,},
|
|
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,}
|
|
},
|
|
};
|
|
|
|
static const guint mp3types_freqs[3][3] = { {44100, 48000, 32000},
|
|
{22050, 24000, 16000},
|
|
{11025, 12000, 8000}
|
|
};
|
|
|
|
static inline guint
|
|
mp3_type_frame_length_from_header (GstLameMP3Enc * lame, guint32 header,
|
|
guint * put_version, guint * put_layer, guint * put_channels,
|
|
guint * put_bitrate, guint * put_samplerate, guint * put_mode,
|
|
guint * put_crc)
|
|
{
|
|
guint length;
|
|
gulong mode, samplerate, bitrate, layer, channels, padding, crc;
|
|
gulong version;
|
|
gint lsf, mpg25;
|
|
|
|
if (header & (1 << 20)) {
|
|
lsf = (header & (1 << 19)) ? 0 : 1;
|
|
mpg25 = 0;
|
|
} else {
|
|
lsf = 1;
|
|
mpg25 = 1;
|
|
}
|
|
|
|
version = 1 + lsf + mpg25;
|
|
|
|
layer = 4 - ((header >> 17) & 0x3);
|
|
|
|
crc = (header >> 16) & 0x1;
|
|
|
|
bitrate = (header >> 12) & 0xF;
|
|
bitrate = mp3types_bitrates[lsf][layer - 1][bitrate] * 1000;
|
|
/* The caller has ensured we have a valid header, so bitrate can't be
|
|
zero here. */
|
|
g_assert (bitrate != 0);
|
|
|
|
samplerate = (header >> 10) & 0x3;
|
|
samplerate = mp3types_freqs[lsf + mpg25][samplerate];
|
|
|
|
padding = (header >> 9) & 0x1;
|
|
|
|
mode = (header >> 6) & 0x3;
|
|
channels = (mode == 3) ? 1 : 2;
|
|
|
|
switch (layer) {
|
|
case 1:
|
|
length = 4 * ((bitrate * 12) / samplerate + padding);
|
|
break;
|
|
case 2:
|
|
length = (bitrate * 144) / samplerate + padding;
|
|
break;
|
|
default:
|
|
case 3:
|
|
length = (bitrate * 144) / (samplerate << lsf) + padding;
|
|
break;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (lame, "Calculated mp3 frame length of %u bytes", length);
|
|
GST_DEBUG_OBJECT (lame, "samplerate = %lu, bitrate = %lu, version = %lu, "
|
|
"layer = %lu, channels = %lu", samplerate, bitrate, version,
|
|
layer, channels);
|
|
|
|
if (put_version)
|
|
*put_version = version;
|
|
if (put_layer)
|
|
*put_layer = layer;
|
|
if (put_channels)
|
|
*put_channels = channels;
|
|
if (put_bitrate)
|
|
*put_bitrate = bitrate;
|
|
if (put_samplerate)
|
|
*put_samplerate = samplerate;
|
|
if (put_mode)
|
|
*put_mode = mode;
|
|
if (put_crc)
|
|
*put_crc = crc;
|
|
|
|
return length;
|
|
}
|
|
|
|
static gboolean
|
|
mp3_sync_check (GstLameMP3Enc * lame, unsigned long head)
|
|
{
|
|
GST_DEBUG_OBJECT (lame, "checking mp3 header 0x%08lx", head);
|
|
/* if it's not a valid sync */
|
|
if ((head & 0xffe00000) != 0xffe00000) {
|
|
GST_WARNING_OBJECT (lame, "invalid sync");
|
|
return FALSE;
|
|
}
|
|
/* if it's an invalid MPEG version */
|
|
if (((head >> 19) & 3) == 0x1) {
|
|
GST_WARNING_OBJECT (lame, "invalid MPEG version: 0x%lx", (head >> 19) & 3);
|
|
return FALSE;
|
|
}
|
|
/* if it's an invalid layer */
|
|
if (!((head >> 17) & 3)) {
|
|
GST_WARNING_OBJECT (lame, "invalid layer: 0x%lx", (head >> 17) & 3);
|
|
return FALSE;
|
|
}
|
|
/* if it's an invalid bitrate */
|
|
if (((head >> 12) & 0xf) == 0x0) {
|
|
GST_WARNING_OBJECT (lame, "invalid bitrate: 0x%lx."
|
|
"Free format files are not supported yet", (head >> 12) & 0xf);
|
|
return FALSE;
|
|
}
|
|
if (((head >> 12) & 0xf) == 0xf) {
|
|
GST_WARNING_OBJECT (lame, "invalid bitrate: 0x%lx", (head >> 12) & 0xf);
|
|
return FALSE;
|
|
}
|
|
/* if it's an invalid samplerate */
|
|
if (((head >> 10) & 0x3) == 0x3) {
|
|
GST_WARNING_OBJECT (lame, "invalid samplerate: 0x%lx", (head >> 10) & 0x3);
|
|
return FALSE;
|
|
}
|
|
|
|
if ((head & 0x3) == 0x2) {
|
|
/* Ignore this as there are some files with emphasis 0x2 that can
|
|
* be played fine. See BGO #537235 */
|
|
GST_WARNING_OBJECT (lame, "invalid emphasis: 0x%lx", head & 0x3);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* **** end mpegaudioparse **** */
|
|
|
|
static GstFlowReturn
|
|
gst_lamemp3enc_finish_frames (GstLameMP3Enc * lame)
|
|
{
|
|
gint av;
|
|
guint header;
|
|
GstFlowReturn result = GST_FLOW_OK;
|
|
|
|
/* limited parsing, we don't expect to lose sync here */
|
|
while ((result == GST_FLOW_OK) &&
|
|
((av = gst_adapter_available (lame->adapter)) > 4)) {
|
|
guint rate, version, layer, size;
|
|
GstBuffer *mp3_buf;
|
|
const guint8 *data;
|
|
guint samples_per_frame;
|
|
|
|
data = gst_adapter_map (lame->adapter, 4);
|
|
header = GST_READ_UINT32_BE (data);
|
|
gst_adapter_unmap (lame->adapter);
|
|
|
|
if (!mp3_sync_check (lame, header))
|
|
goto invalid_header;
|
|
|
|
size = mp3_type_frame_length_from_header (lame, header, &version, &layer,
|
|
NULL, NULL, &rate, NULL, NULL);
|
|
|
|
if (G_UNLIKELY (layer != 3 || rate != lame->out_samplerate)) {
|
|
GST_DEBUG_OBJECT (lame,
|
|
"unexpected mp3 header with rate %u, version %u, layer %u",
|
|
rate, version, layer);
|
|
goto invalid_header;
|
|
}
|
|
|
|
if (size > av) {
|
|
/* pretty likely to occur when lame is holding back on us */
|
|
GST_LOG_OBJECT (lame, "frame size %u (> %d)", size, av);
|
|
break;
|
|
}
|
|
|
|
/* Account for the internal resampling, finish frame really wants to
|
|
* know about the number of incoming samples
|
|
*/
|
|
samples_per_frame = (version == 1) ? 1152 : 576;
|
|
samples_per_frame *= lame->samplerate;
|
|
samples_per_frame /= lame->out_samplerate;
|
|
|
|
/* should be ok now */
|
|
mp3_buf = gst_adapter_take_buffer (lame->adapter, size);
|
|
/* number of samples for MPEG-1, layer 3 */
|
|
result = gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER (lame),
|
|
mp3_buf, samples_per_frame);
|
|
}
|
|
|
|
exit:
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
invalid_header:
|
|
{
|
|
GST_ELEMENT_ERROR (lame, STREAM, ENCODE,
|
|
("invalid lame mp3 sync header %08X", header), (NULL));
|
|
result = GST_FLOW_ERROR;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_lamemp3enc_flush_full (GstLameMP3Enc * lame, gboolean push)
|
|
{
|
|
GstBuffer *buf;
|
|
GstMapInfo map;
|
|
gint size;
|
|
GstFlowReturn result = GST_FLOW_OK;
|
|
gint av;
|
|
|
|
if (!lame->lgf)
|
|
return GST_FLOW_OK;
|
|
|
|
buf = gst_buffer_new_and_alloc (7200);
|
|
gst_buffer_map (buf, &map, GST_MAP_WRITE);
|
|
size = lame_encode_flush (lame->lgf, map.data, 7200);
|
|
|
|
if (size > 0) {
|
|
gst_buffer_unmap (buf, &map);
|
|
gst_buffer_resize (buf, 0, size);
|
|
GST_DEBUG_OBJECT (lame, "collecting final %d bytes", size);
|
|
gst_adapter_push (lame->adapter, buf);
|
|
} else {
|
|
gst_buffer_unmap (buf, &map);
|
|
GST_DEBUG_OBJECT (lame, "no final packet (size=%d, push=%d)", size, push);
|
|
gst_buffer_unref (buf);
|
|
result = GST_FLOW_OK;
|
|
}
|
|
|
|
if (push) {
|
|
result = gst_lamemp3enc_finish_frames (lame);
|
|
} else {
|
|
/* never mind */
|
|
gst_adapter_clear (lame->adapter);
|
|
}
|
|
|
|
/* either way, we expect nothing left */
|
|
if ((av = gst_adapter_available (lame->adapter))) {
|
|
/* should this be more fatal ?? */
|
|
GST_WARNING_OBJECT (lame, "unparsed %d bytes left after flushing", av);
|
|
/* clean up anyway */
|
|
gst_adapter_clear (lame->adapter);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
gst_lamemp3enc_flush (GstAudioEncoder * enc)
|
|
{
|
|
gst_lamemp3enc_flush_full (GST_LAMEMP3ENC (enc), FALSE);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_lamemp3enc_handle_frame (GstAudioEncoder * enc, GstBuffer * in_buf)
|
|
{
|
|
GstLameMP3Enc *lame;
|
|
gint mp3_buffer_size, mp3_size;
|
|
GstBuffer *mp3_buf;
|
|
GstFlowReturn result;
|
|
gint num_samples;
|
|
GstMapInfo in_map, mp3_map;
|
|
|
|
lame = GST_LAMEMP3ENC (enc);
|
|
|
|
/* squeeze remaining and push */
|
|
if (G_UNLIKELY (in_buf == NULL))
|
|
return gst_lamemp3enc_flush_full (lame, TRUE);
|
|
|
|
gst_buffer_map (in_buf, &in_map, GST_MAP_READ);
|
|
|
|
num_samples = in_map.size / 2;
|
|
|
|
/* allocate space for output */
|
|
mp3_buffer_size = 1.25 * num_samples + 7200;
|
|
mp3_buf = gst_buffer_new_allocate (NULL, mp3_buffer_size, NULL);
|
|
gst_buffer_map (mp3_buf, &mp3_map, GST_MAP_WRITE);
|
|
|
|
/* lame seems to be too stupid to get mono interleaved going */
|
|
if (lame->num_channels == 1) {
|
|
mp3_size = lame_encode_buffer (lame->lgf,
|
|
(short int *) in_map.data,
|
|
(short int *) in_map.data, num_samples, mp3_map.data, mp3_buffer_size);
|
|
} else {
|
|
mp3_size = lame_encode_buffer_interleaved (lame->lgf,
|
|
(short int *) in_map.data,
|
|
num_samples / lame->num_channels, mp3_map.data, mp3_buffer_size);
|
|
}
|
|
gst_buffer_unmap (in_buf, &in_map);
|
|
|
|
GST_LOG_OBJECT (lame, "encoded %" G_GSIZE_FORMAT " bytes of audio "
|
|
"to %d bytes of mp3", in_map.size, mp3_size);
|
|
|
|
if (G_LIKELY (mp3_size > 0)) {
|
|
/* unfortunately lame does not provide frame delineated output,
|
|
* so collect output and parse into frames ... */
|
|
gst_buffer_unmap (mp3_buf, &mp3_map);
|
|
gst_buffer_resize (mp3_buf, 0, mp3_size);
|
|
gst_adapter_push (lame->adapter, mp3_buf);
|
|
result = gst_lamemp3enc_finish_frames (lame);
|
|
} else {
|
|
gst_buffer_unmap (mp3_buf, &mp3_map);
|
|
if (mp3_size < 0) {
|
|
/* eat error ? */
|
|
g_warning ("error %d", mp3_size);
|
|
}
|
|
gst_buffer_unref (mp3_buf);
|
|
result = GST_FLOW_OK;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* set up the encoder state */
|
|
static gboolean
|
|
gst_lamemp3enc_setup (GstLameMP3Enc * lame, GstTagList ** tags)
|
|
{
|
|
gboolean res;
|
|
|
|
#define CHECK_ERROR(command) G_STMT_START {\
|
|
if ((command) < 0) { \
|
|
GST_ERROR_OBJECT (lame, "setup failed: " G_STRINGIFY (command)); \
|
|
if (*tags) { \
|
|
gst_tag_list_unref (*tags); \
|
|
*tags = NULL; \
|
|
} \
|
|
return FALSE; \
|
|
} \
|
|
}G_STMT_END
|
|
|
|
int retval;
|
|
GstCaps *allowed_caps;
|
|
|
|
GST_DEBUG_OBJECT (lame, "starting setup");
|
|
|
|
lame->lgf = lame_init ();
|
|
|
|
if (lame->lgf == NULL)
|
|
return FALSE;
|
|
|
|
*tags = gst_tag_list_new_empty ();
|
|
|
|
/* copy the parameters over */
|
|
lame_set_in_samplerate (lame->lgf, lame->samplerate);
|
|
|
|
/* let lame choose default samplerate unless outgoing sample rate is fixed */
|
|
allowed_caps = gst_pad_get_allowed_caps (GST_AUDIO_ENCODER_SRC_PAD (lame));
|
|
|
|
if (allowed_caps != NULL) {
|
|
GstStructure *structure;
|
|
gint samplerate;
|
|
|
|
structure = gst_caps_get_structure (allowed_caps, 0);
|
|
|
|
if (gst_structure_get_int (structure, "rate", &samplerate)) {
|
|
GST_DEBUG_OBJECT (lame, "Setting sample rate to %d as fixed in src caps",
|
|
samplerate);
|
|
lame_set_out_samplerate (lame->lgf, samplerate);
|
|
} else {
|
|
GST_DEBUG_OBJECT (lame, "Letting lame choose sample rate");
|
|
lame_set_out_samplerate (lame->lgf, 0);
|
|
}
|
|
gst_caps_unref (allowed_caps);
|
|
allowed_caps = NULL;
|
|
} else {
|
|
GST_DEBUG_OBJECT (lame, "No peer yet, letting lame choose sample rate");
|
|
lame_set_out_samplerate (lame->lgf, 0);
|
|
}
|
|
|
|
CHECK_ERROR (lame_set_num_channels (lame->lgf, lame->num_channels));
|
|
CHECK_ERROR (lame_set_bWriteVbrTag (lame->lgf, 0));
|
|
|
|
if (lame->target == LAMEMP3ENC_TARGET_QUALITY) {
|
|
CHECK_ERROR (lame_set_VBR (lame->lgf, vbr_default));
|
|
CHECK_ERROR (lame_set_VBR_quality (lame->lgf, lame->quality));
|
|
} else {
|
|
if (lame->cbr) {
|
|
CHECK_AND_FIXUP_BITRATE (lame, "bitrate", lame->bitrate);
|
|
CHECK_ERROR (lame_set_VBR (lame->lgf, vbr_off));
|
|
CHECK_ERROR (lame_set_brate (lame->lgf, lame->bitrate));
|
|
} else {
|
|
CHECK_ERROR (lame_set_VBR (lame->lgf, vbr_abr));
|
|
CHECK_ERROR (lame_set_VBR_mean_bitrate_kbps (lame->lgf, lame->bitrate));
|
|
}
|
|
gst_tag_list_add (*tags, GST_TAG_MERGE_REPLACE, GST_TAG_BITRATE,
|
|
lame->bitrate * 1000, NULL);
|
|
}
|
|
|
|
if (lame->encoding_engine_quality == LAMEMP3ENC_ENCODING_ENGINE_QUALITY_FAST)
|
|
CHECK_ERROR (lame_set_quality (lame->lgf, 7));
|
|
else if (lame->encoding_engine_quality ==
|
|
LAMEMP3ENC_ENCODING_ENGINE_QUALITY_HIGH)
|
|
CHECK_ERROR (lame_set_quality (lame->lgf, 2));
|
|
/* else default */
|
|
|
|
if (lame->mono)
|
|
CHECK_ERROR (lame_set_mode (lame->lgf, MONO));
|
|
|
|
/* initialize the lame encoder */
|
|
if ((retval = lame_init_params (lame->lgf)) >= 0) {
|
|
/* FIXME: it would be nice to print out the mode here */
|
|
GST_INFO
|
|
("lame encoder setup (target %s, quality %f, bitrate %d, %d Hz, %d channels)",
|
|
(lame->target == LAMEMP3ENC_TARGET_QUALITY) ? "quality" : "bitrate",
|
|
lame->quality, lame->bitrate, lame->samplerate, lame->num_channels);
|
|
res = TRUE;
|
|
} else {
|
|
GST_ERROR_OBJECT (lame, "lame_init_params returned %d", retval);
|
|
res = FALSE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (lame, "done with setup");
|
|
return res;
|
|
#undef CHECK_ERROR
|
|
}
|
|
|
|
gboolean
|
|
gst_lamemp3enc_register (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (debug, "lamemp3enc", 0, "lame mp3 encoder");
|
|
|
|
if (!gst_element_register (plugin, "lamemp3enc", GST_RANK_PRIMARY,
|
|
GST_TYPE_LAMEMP3ENC))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|