| /* 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 ! mpegaudioparse ! mad ! audioconvert ! audioresample ! autoaudiosink |
| * ]| Decode and play the mp3 file |
| * </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, " |
| "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 <wim.taymans@gmail.com>"); |
| |
| 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); |