mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-10 19:31:12 +00:00
73330c2ab8
mad expects extra bytes at the end of a buffer (see discussion in http://www.mars.org/mailman/public/mad-dev/2001-May/000262.html), and since we inject these without the base class' knowledge, we need to hide the bodies better. This fixes an assert at EOS when decoding an mp3 manually without an intervening mpegaudioparse.
585 lines
17 KiB
C
585 lines
17 KiB
C
/* GStreamer
|
|
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
|
*
|
|
* 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-mad
|
|
* @see_also: lame
|
|
*
|
|
* MP3 audio decoder.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example pipelines</title>
|
|
* |[
|
|
* gst-launch filesrc location=music.mp3 ! mad ! audioconvert ! audioresample ! autoaudiosink
|
|
* ]| Decode the mp3 file and play
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "gstmad.h"
|
|
#include <gst/audio/audio.h>
|
|
|
|
enum
|
|
{
|
|
ARG_0,
|
|
ARG_HALF,
|
|
ARG_IGNORE_CRC
|
|
};
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (mad_debug);
|
|
#define GST_CAT_DEFAULT mad_debug
|
|
|
|
static GstStaticPadTemplate mad_src_template_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/x-raw-int, "
|
|
"endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", "
|
|
"signed = (boolean) true, "
|
|
"width = (int) 32, "
|
|
"depth = (int) 32, "
|
|
"rate = (int) { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }, "
|
|
"channels = (int) [ 1, 2 ]")
|
|
);
|
|
|
|
/* FIXME: make three caps, for mpegversion 1, 2 and 2.5 */
|
|
static GstStaticPadTemplate mad_sink_template_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/mpeg, "
|
|
"mpegversion = (int) 1, "
|
|
"layer = (int) [ 1, 3 ], "
|
|
"rate = (int) { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }, "
|
|
"channels = (int) [ 1, 2 ]")
|
|
);
|
|
|
|
|
|
static gboolean gst_mad_start (GstAudioDecoder * dec);
|
|
static gboolean gst_mad_stop (GstAudioDecoder * dec);
|
|
static gboolean gst_mad_parse (GstAudioDecoder * dec, GstAdapter * adapter,
|
|
gint * offset, gint * length);
|
|
static GstFlowReturn gst_mad_handle_frame (GstAudioDecoder * dec,
|
|
GstBuffer * buffer);
|
|
static gboolean gst_mad_event (GstAudioDecoder * dec, GstEvent * event);
|
|
static void gst_mad_flush (GstAudioDecoder * dec, gboolean hard);
|
|
|
|
static void gst_mad_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_mad_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
|
|
GST_BOILERPLATE (GstMad, gst_mad, GstAudioDecoder, GST_TYPE_AUDIO_DECODER);
|
|
|
|
static void
|
|
gst_mad_base_init (gpointer g_class)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
|
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&mad_sink_template_factory);
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&mad_src_template_factory);
|
|
gst_element_class_set_details_simple (element_class, "mad mp3 decoder",
|
|
"Codec/Decoder/Audio",
|
|
"Uses mad code to decode mp3 streams", "Wim Taymans <wim@fluendo.com>");
|
|
}
|
|
|
|
static void
|
|
gst_mad_class_init (GstMadClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
GstAudioDecoderClass *base_class = (GstAudioDecoderClass *) klass;
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
base_class->start = GST_DEBUG_FUNCPTR (gst_mad_start);
|
|
base_class->stop = GST_DEBUG_FUNCPTR (gst_mad_stop);
|
|
base_class->parse = GST_DEBUG_FUNCPTR (gst_mad_parse);
|
|
base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_mad_handle_frame);
|
|
base_class->flush = GST_DEBUG_FUNCPTR (gst_mad_flush);
|
|
base_class->event = GST_DEBUG_FUNCPTR (gst_mad_event);
|
|
|
|
gobject_class->set_property = gst_mad_set_property;
|
|
gobject_class->get_property = gst_mad_get_property;
|
|
|
|
/* init properties */
|
|
/* currently, string representations are used, we might want to change that */
|
|
/* FIXME: descriptions need to be more technical,
|
|
* default values and ranges need to be selected right */
|
|
g_object_class_install_property (gobject_class, ARG_HALF,
|
|
g_param_spec_boolean ("half", "Half", "Generate PCM at 1/2 sample rate",
|
|
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, ARG_IGNORE_CRC,
|
|
g_param_spec_boolean ("ignore-crc", "Ignore CRC", "Ignore CRC errors",
|
|
TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
gst_mad_init (GstMad * mad, GstMadClass * klass)
|
|
{
|
|
GstAudioDecoder *dec;
|
|
|
|
dec = GST_AUDIO_DECODER (mad);
|
|
gst_audio_decoder_set_tolerance (dec, 20 * GST_MSECOND);
|
|
|
|
mad->half = FALSE;
|
|
mad->ignore_crc = TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mad_start (GstAudioDecoder * dec)
|
|
{
|
|
GstMad *mad = GST_MAD (dec);
|
|
guint options = 0;
|
|
|
|
GST_DEBUG_OBJECT (dec, "start");
|
|
mad_stream_init (&mad->stream);
|
|
mad_frame_init (&mad->frame);
|
|
mad_synth_init (&mad->synth);
|
|
mad->rate = 0;
|
|
mad->channels = 0;
|
|
mad->caps_set = FALSE;
|
|
mad->frame.header.samplerate = 0;
|
|
if (mad->ignore_crc)
|
|
options |= MAD_OPTION_IGNORECRC;
|
|
if (mad->half)
|
|
options |= MAD_OPTION_HALFSAMPLERATE;
|
|
mad_stream_options (&mad->stream, options);
|
|
mad->header.mode = -1;
|
|
mad->header.emphasis = -1;
|
|
mad->eos = FALSE;
|
|
|
|
/* call upon legacy upstream byte support (e.g. seeking) */
|
|
gst_audio_decoder_set_byte_time (dec, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mad_stop (GstAudioDecoder * dec)
|
|
{
|
|
GstMad *mad = GST_MAD (dec);
|
|
|
|
GST_DEBUG_OBJECT (dec, "stop");
|
|
mad_synth_finish (&mad->synth);
|
|
mad_frame_finish (&mad->frame);
|
|
mad_stream_finish (&mad->stream);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline gint32
|
|
scale (mad_fixed_t sample)
|
|
{
|
|
#if MAD_F_FRACBITS < 28
|
|
/* round */
|
|
sample += (1L << (28 - MAD_F_FRACBITS - 1));
|
|
#endif
|
|
|
|
/* clip */
|
|
if (sample >= MAD_F_ONE)
|
|
sample = MAD_F_ONE - 1;
|
|
else if (sample < -MAD_F_ONE)
|
|
sample = -MAD_F_ONE;
|
|
|
|
#if MAD_F_FRACBITS < 28
|
|
/* quantize */
|
|
sample >>= (28 - MAD_F_FRACBITS);
|
|
#endif
|
|
|
|
/* convert from 29 bits to 32 bits */
|
|
return (gint32) (sample << 3);
|
|
}
|
|
|
|
/* internal function to check if the header has changed and thus the
|
|
* caps need to be reset. Only call during normal mode, not resyncing */
|
|
static void
|
|
gst_mad_check_caps_reset (GstMad * mad)
|
|
{
|
|
guint nchannels;
|
|
guint rate;
|
|
|
|
nchannels = MAD_NCHANNELS (&mad->frame.header);
|
|
|
|
#if MAD_VERSION_MINOR <= 12
|
|
rate = mad->header.sfreq;
|
|
#else
|
|
rate = mad->frame.header.samplerate;
|
|
#endif
|
|
|
|
/* rate and channels are not supposed to change in a continuous stream,
|
|
* so check this first before doing anything */
|
|
|
|
/* only set caps if they weren't already set for this continuous stream */
|
|
if (mad->channels != nchannels || mad->rate != rate) {
|
|
GstCaps *caps;
|
|
|
|
if (mad->caps_set) {
|
|
GST_DEBUG_OBJECT (mad, "Header changed from %d Hz/%d ch to %d Hz/%d ch, "
|
|
"failed sync after seek ?", mad->rate, mad->channels, rate,
|
|
nchannels);
|
|
/* we're conservative on stream changes. However, our *initial* caps
|
|
* might have been wrong as well - mad ain't perfect in syncing. So,
|
|
* we count caps changes and change if we pass a limit treshold (3). */
|
|
if (nchannels != mad->pending_channels || rate != mad->pending_rate) {
|
|
mad->times_pending = 0;
|
|
mad->pending_channels = nchannels;
|
|
mad->pending_rate = rate;
|
|
}
|
|
if (++mad->times_pending < 3)
|
|
return;
|
|
}
|
|
|
|
if (mad->stream.options & MAD_OPTION_HALFSAMPLERATE)
|
|
rate >>= 1;
|
|
|
|
/* we set the caps even when the pad is not connected so they
|
|
* can be gotten for streaminfo */
|
|
caps = gst_caps_new_simple ("audio/x-raw-int",
|
|
"endianness", G_TYPE_INT, G_BYTE_ORDER,
|
|
"signed", G_TYPE_BOOLEAN, TRUE,
|
|
"width", G_TYPE_INT, 32,
|
|
"depth", G_TYPE_INT, 32,
|
|
"rate", G_TYPE_INT, rate, "channels", G_TYPE_INT, nchannels, NULL);
|
|
|
|
gst_pad_set_caps (GST_AUDIO_DECODER_SRC_PAD (mad), caps);
|
|
gst_caps_unref (caps);
|
|
|
|
mad->caps_set = TRUE;
|
|
mad->channels = nchannels;
|
|
mad->rate = rate;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mad_parse (GstAudioDecoder * dec, GstAdapter * adapter,
|
|
gint * _offset, gint * len)
|
|
{
|
|
GstMad *mad;
|
|
GstFlowReturn ret = GST_FLOW_UNEXPECTED;
|
|
gint av, size, offset, prev_offset, consumed = 0;
|
|
const guint8 *data;
|
|
|
|
mad = GST_MAD (dec);
|
|
|
|
if (mad->eos) {
|
|
/* This is one steaming hack right there.
|
|
* mad will not decode the last frame if it is not followed by
|
|
* a number of 0 bytes, due to some buffer overflow, which can
|
|
* not be fixed for reasons I did not inquire into, see
|
|
* http://www.mars.org/mailman/public/mad-dev/2001-May/000262.html
|
|
*/
|
|
GstBuffer *guard = gst_buffer_new_and_alloc (MAD_BUFFER_GUARD);
|
|
memset (GST_BUFFER_DATA (guard), 0, GST_BUFFER_SIZE (guard));
|
|
GST_DEBUG_OBJECT (mad, "Discreetly stuffing %u zero bytes in the adapter",
|
|
GST_BUFFER_SIZE (guard));
|
|
gst_adapter_push (adapter, guard);
|
|
}
|
|
|
|
/* we basically let mad library do parsing,
|
|
* and translate that back to baseclass.
|
|
* if a frame is found (and also decoded), subsequent handle_frame
|
|
* only needs to synthesize it */
|
|
|
|
prev_offset = -1;
|
|
offset = 0;
|
|
av = gst_adapter_available (adapter);
|
|
while (offset < av) {
|
|
size = MIN (MAD_BUFFER_MDLEN * 3, av - offset);
|
|
data = gst_adapter_peek (adapter, av);
|
|
|
|
/* check for mad asking too much */
|
|
if (offset == prev_offset) {
|
|
if (G_UNLIKELY (offset + size < av)) {
|
|
/* mad should not do this, so really fatal */
|
|
GST_ELEMENT_ERROR (mad, STREAM, DECODE, (NULL),
|
|
("mad claims to need more data than %u bytes", size));
|
|
ret = GST_FLOW_ERROR;
|
|
goto exit;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* only feed that much to mad at a time */
|
|
mad_stream_buffer (&mad->stream, data + offset, size);
|
|
prev_offset = offset;
|
|
|
|
while (offset - prev_offset < size) {
|
|
consumed = 0;
|
|
|
|
GST_LOG_OBJECT (mad, "decoding the header now");
|
|
if (mad_header_decode (&mad->frame.header, &mad->stream) == -1) {
|
|
if (mad->stream.error == MAD_ERROR_BUFLEN) {
|
|
GST_LOG_OBJECT (mad,
|
|
"not enough data in tempbuffer (%d), breaking to get more", size);
|
|
break;
|
|
} else {
|
|
GST_WARNING_OBJECT (mad, "mad_header_decode had an error: %s",
|
|
mad_stream_errorstr (&mad->stream));
|
|
}
|
|
}
|
|
|
|
GST_LOG_OBJECT (mad, "parsing and decoding one frame now");
|
|
if (mad_frame_decode (&mad->frame, &mad->stream) == -1) {
|
|
GST_LOG_OBJECT (mad, "got error %d", mad->stream.error);
|
|
|
|
/* not enough data, need to wait for next buffer? */
|
|
if (mad->stream.error == MAD_ERROR_BUFLEN) {
|
|
if (mad->stream.next_frame == data) {
|
|
GST_LOG_OBJECT (mad,
|
|
"not enough data in tempbuffer (%d), breaking to get more",
|
|
size);
|
|
break;
|
|
} else {
|
|
GST_LOG_OBJECT (mad, "sync error, flushing unneeded data");
|
|
goto flush;
|
|
}
|
|
} else if (mad->stream.error == MAD_ERROR_BADDATAPTR) {
|
|
/* Flush data */
|
|
goto flush;
|
|
} else {
|
|
GST_WARNING_OBJECT (mad, "mad_frame_decode had an error: %s",
|
|
mad_stream_errorstr (&mad->stream));
|
|
if (!MAD_RECOVERABLE (mad->stream.error)) {
|
|
/* well, all may be well enough bytes later on ... */
|
|
GST_AUDIO_DECODER_ERROR (mad, 1, STREAM, DECODE, (NULL),
|
|
("mad error: %s", mad_stream_errorstr (&mad->stream)), ret);
|
|
/* so make sure we really move along ... */
|
|
if (!offset)
|
|
offset++;
|
|
goto exit;
|
|
} else {
|
|
const guint8 *before_sync, *after_sync;
|
|
|
|
mad_frame_mute (&mad->frame);
|
|
mad_synth_mute (&mad->synth);
|
|
before_sync = mad->stream.ptr.byte;
|
|
if (mad_stream_sync (&mad->stream) != 0)
|
|
GST_WARNING_OBJECT (mad, "mad_stream_sync failed");
|
|
after_sync = mad->stream.ptr.byte;
|
|
/* a succesful resync should make us drop bytes as consumed, so
|
|
* calculate from the byte pointers before and after resync */
|
|
consumed = after_sync - before_sync;
|
|
GST_DEBUG_OBJECT (mad, "resynchronization consumes %d bytes",
|
|
consumed);
|
|
GST_DEBUG_OBJECT (mad, "synced to data: 0x%0x 0x%0x",
|
|
*mad->stream.ptr.byte, *(mad->stream.ptr.byte + 1));
|
|
|
|
mad_stream_sync (&mad->stream);
|
|
/* recoverable errors pass */
|
|
goto flush;
|
|
}
|
|
}
|
|
} else {
|
|
/* decoding ok; found frame */
|
|
ret = GST_FLOW_OK;
|
|
}
|
|
flush:
|
|
if (consumed == 0) {
|
|
consumed = mad->stream.next_frame - (data + offset);
|
|
g_assert (consumed >= 0);
|
|
}
|
|
|
|
if (ret == GST_FLOW_OK)
|
|
goto exit;
|
|
|
|
offset += consumed;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
*_offset = offset;
|
|
*len = consumed;
|
|
|
|
/* ensure that if we added some dummy guard bytes above, we don't claim
|
|
to have used them as they're unknown to the caller. */
|
|
if (mad->eos) {
|
|
g_assert (av >= MAD_BUFFER_GUARD);
|
|
av -= MAD_BUFFER_GUARD;
|
|
if (*_offset > av)
|
|
*_offset = av;
|
|
if (*len > av)
|
|
*len = av;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mad_handle_frame (GstAudioDecoder * dec, GstBuffer * buffer)
|
|
{
|
|
GstMad *mad;
|
|
GstFlowReturn ret = GST_FLOW_UNEXPECTED;
|
|
GstBuffer *outbuffer;
|
|
guint nsamples;
|
|
gint32 *outdata;
|
|
mad_fixed_t const *left_ch, *right_ch;
|
|
|
|
mad = GST_MAD (dec);
|
|
|
|
/* no fancy draining */
|
|
if (G_UNLIKELY (!buffer))
|
|
return GST_FLOW_OK;
|
|
|
|
/* _parse prepared a frame */
|
|
nsamples = MAD_NSBSAMPLES (&mad->frame.header) *
|
|
(mad->stream.options & MAD_OPTION_HALFSAMPLERATE ? 16 : 32);
|
|
GST_LOG_OBJECT (mad, "mad frame with %d samples", nsamples);
|
|
|
|
/* arrange for initial caps before pushing data,
|
|
* and update later on if needed */
|
|
gst_mad_check_caps_reset (mad);
|
|
|
|
ret = gst_pad_alloc_buffer_and_set_caps (GST_AUDIO_DECODER_SRC_PAD (dec),
|
|
0, nsamples * mad->channels * 4,
|
|
GST_PAD_CAPS (GST_AUDIO_DECODER_SRC_PAD (dec)), &outbuffer);
|
|
if (ret != GST_FLOW_OK) {
|
|
/* Head for the exit, dropping samples as we go */
|
|
GST_LOG_OBJECT (dec,
|
|
"Skipping frame synthesis due to pad_alloc return value");
|
|
gst_audio_decoder_finish_frame (dec, NULL, 1);
|
|
goto exit;
|
|
}
|
|
|
|
/* TODO would be nice if core or some helper handled this surprise ... */
|
|
if (GST_BUFFER_SIZE (outbuffer) != nsamples * mad->channels * 4) {
|
|
gst_buffer_unref (outbuffer);
|
|
outbuffer = gst_buffer_new_and_alloc (nsamples * mad->channels * 4);
|
|
}
|
|
|
|
mad_synth_frame (&mad->synth, &mad->frame);
|
|
left_ch = mad->synth.pcm.samples[0];
|
|
right_ch = mad->synth.pcm.samples[1];
|
|
|
|
outdata = (gint32 *) GST_BUFFER_DATA (outbuffer);
|
|
|
|
/* output sample(s) in 16-bit signed native-endian PCM */
|
|
if (mad->channels == 1) {
|
|
gint count = nsamples;
|
|
|
|
while (count--) {
|
|
*outdata++ = scale (*left_ch++) & 0xffffffff;
|
|
}
|
|
} else {
|
|
gint count = nsamples;
|
|
|
|
while (count--) {
|
|
*outdata++ = scale (*left_ch++) & 0xffffffff;
|
|
*outdata++ = scale (*right_ch++) & 0xffffffff;
|
|
}
|
|
}
|
|
|
|
ret = gst_audio_decoder_finish_frame (dec, outbuffer, 1);
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_mad_flush (GstAudioDecoder * dec, gboolean hard)
|
|
{
|
|
GstMad *mad;
|
|
|
|
mad = GST_MAD (dec);
|
|
if (hard) {
|
|
mad_frame_mute (&mad->frame);
|
|
mad_synth_mute (&mad->synth);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mad_event (GstAudioDecoder * dec, GstEvent * event)
|
|
{
|
|
GstMad *mad;
|
|
|
|
mad = GST_MAD (dec);
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
|
|
GST_DEBUG_OBJECT (mad, "We got EOS, will pad next time");
|
|
mad->eos = TRUE;
|
|
}
|
|
|
|
/* Let the base class do its usual thing */
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_mad_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstMad *mad;
|
|
|
|
mad = GST_MAD (object);
|
|
|
|
switch (prop_id) {
|
|
case ARG_HALF:
|
|
mad->half = g_value_get_boolean (value);
|
|
break;
|
|
case ARG_IGNORE_CRC:
|
|
mad->ignore_crc = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mad_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstMad *mad;
|
|
|
|
mad = GST_MAD (object);
|
|
|
|
switch (prop_id) {
|
|
case ARG_HALF:
|
|
g_value_set_boolean (value, mad->half);
|
|
break;
|
|
case ARG_IGNORE_CRC:
|
|
g_value_set_boolean (value, mad->ignore_crc);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* plugin initialisation */
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (mad_debug, "mad", 0, "mad mp3 decoding");
|
|
|
|
return gst_element_register (plugin, "mad", GST_RANK_SECONDARY,
|
|
gst_mad_get_type ());
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
"mad",
|
|
"mp3 decoding based on the mad library",
|
|
plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
|