/* GStreamer * Copyright (C) <1999> Erik Walthinsen * * 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-mad * @see_also: lame * * MP3 audio decoder. * * * Example pipelines * |[ * gst-launch filesrc location=music.mp3 ! mpegaudioparse ! mad ! audioconvert ! audioresample ! autoaudiosink * ]| Decode and play the mp3 file * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "gstmad.h" #include 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, " "format = (string) " GST_AUDIO_NE (S32) ", " "layout = (string) interleaved, " "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 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); #define parent_class gst_mad_parent_class G_DEFINE_TYPE (GstMad, gst_mad, GST_TYPE_AUDIO_DECODER); static void gst_mad_class_init (GstMadClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstElementClass *element_class = (GstElementClass *) klass; GstAudioDecoderClass *base_class = (GstAudioDecoderClass *) klass; 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)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&mad_sink_template_factory)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&mad_src_template_factory)); gst_element_class_set_static_metadata (element_class, "mad mp3 decoder", "Codec/Decoder/Audio", "Uses mad code to decode mp3 streams", "Wim Taymans "); 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); } static void gst_mad_init (GstMad * mad) { 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_estimate_rate (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 (!gst_pad_has_current_caps (GST_AUDIO_DECODER_SRC_PAD (mad)) || mad->channels != nchannels || mad->rate != rate) { GstAudioInfo info; static const GstAudioChannelPosition chan_pos[2][2] = { {GST_AUDIO_CHANNEL_POSITION_MONO}, {GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT} }; 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 */ gst_audio_info_init (&info); gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S32, rate, nchannels, chan_pos[nchannels - 1]); gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (mad), &info); 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_EOS; gint av, size, offset; const guint8 *data; gboolean eos, sync; guint8 *guard = NULL; mad = GST_MAD (dec); av = gst_adapter_available (adapter); data = gst_adapter_map (adapter, av); gst_audio_decoder_get_parse_state (dec, &sync, &eos); GST_LOG_OBJECT (mad, "parse state sync %d, eos %d", sync, eos); if (eos) { /* This is one streaming 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 */ guard = g_malloc (av + MAD_BUFFER_GUARD); /* let's be nice and not mess with baseclass state and keep hacks local */ memcpy (guard, data, av); memset (guard + av, 0, MAD_BUFFER_GUARD); GST_DEBUG_OBJECT (mad, "Added %u zero guard bytes in the adapter; " "using fallback buffer of size %u", MAD_BUFFER_GUARD, av + MAD_BUFFER_GUARD); data = guard; av = av + MAD_BUFFER_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 */ offset = 0; size = 0; resume: if (G_UNLIKELY (offset + MAD_BUFFER_GUARD > av)) goto exit; GST_LOG_OBJECT (mad, "setup mad stream at offset %d (of av %d)", offset, av); mad_stream_buffer (&mad->stream, data + offset, av - offset); /* convey sync idea to mad */ mad->stream.sync = sync; /* if we get back here, lost sync anyway */ sync = FALSE; while (TRUE) { GST_LOG_OBJECT (mad, "decoding the header now"); if (mad_header_decode (&mad->frame.header, &mad->stream) == -1) { /* HACK it seems mad reports wrong error when it is trying to determine * free bitrate and scanning for next header */ if (mad->stream.error == MAD_ERROR_LOSTSYNC) { const guint8 *ptr = mad->stream.this_frame; guint32 header; if (ptr >= data && ptr < data + av) { header = GST_READ_UINT32_BE (ptr); /* looks like possible freeform header with not much data */ if (((header & 0xFFE00000) == 0xFFE00000) && (((header >> 12) & 0xF) == 0x0) && (av < 4096)) { GST_DEBUG_OBJECT (mad, "overriding freeform LOST_SYNC to BUFLEN"); mad->stream.error = MAD_ERROR_BUFLEN; } } } if (mad->stream.error == MAD_ERROR_BUFLEN) { GST_LOG_OBJECT (mad, "not enough data, getting more"); offset = mad->stream.next_frame - data; break; } else if (mad->stream.error == MAD_ERROR_LOSTSYNC) { GST_LOG_OBJECT (mad, "lost sync"); continue; } else { /* probably some bogus header, basically also lost sync */ GST_DEBUG_OBJECT (mad, "mad_header_decode had an error: %s", mad_stream_errorstr (&mad->stream)); continue; } } /* could have a frame now, subsequent will confirm */ offset = mad->stream.this_frame - data; size = mad->stream.next_frame - mad->stream.this_frame; g_assert (size); GST_LOG_OBJECT (mad, "parsing and decoding one frame now " "(offset %d, size %d)", offset, size); 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) { /* not really expect this error at this stage anymore * assume bogus frame and bad sync and move along a bit */ GST_WARNING_OBJECT (mad, "not enough data (unexpected), moving along"); offset++; goto resume; } else if (mad->stream.error == MAD_ERROR_BADDATAPTR) { GST_DEBUG_OBJECT (mad, "bad data ptr, skipping presumed frame"); /* flush past presumed frame */ offset += size; goto resume; } 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); } /* move along and try again */ offset++; goto resume; } g_assert_not_reached (); } /* so decoded ok, got a frame now */ ret = GST_FLOW_OK; break; } exit: gst_adapter_unmap (adapter); *_offset = offset; *len = size; /* 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 (eos) { g_assert (av >= MAD_BUFFER_GUARD); av -= MAD_BUFFER_GUARD; if (*_offset > av) *_offset = av; if (*len > av) *len = av; g_assert (guard); g_free (guard); } return ret; } static GstFlowReturn gst_mad_handle_frame (GstAudioDecoder * dec, GstBuffer * buffer) { GstMad *mad; GstFlowReturn ret = GST_FLOW_EOS; GstBuffer *outbuffer; guint nsamples; GstMapInfo outmap; 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); mad_synth_frame (&mad->synth, &mad->frame); left_ch = mad->synth.pcm.samples[0]; right_ch = mad->synth.pcm.samples[1]; outbuffer = gst_buffer_new_and_alloc (nsamples * mad->channels * 4); gst_buffer_map (outbuffer, &outmap, GST_MAP_WRITE); outdata = (gint32 *) outmap.data; /* 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; } } gst_buffer_unmap (outbuffer, &outmap); ret = gst_audio_decoder_finish_frame (dec, outbuffer, 1); 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 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"); /* FIXME 0.11: rename to something better like madmp3dec or madmpegaudiodec * or so? */ 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);