gst/mpegaudioparse/: Bring mp3parse into the 21st century.
authorMichael Smith <msmith@xiph.org>
Mon, 13 Nov 2006 16:23:22 +0000 (16:23 +0000)
committerMichael Smith <msmith@xiph.org>
Mon, 13 Nov 2006 16:23:22 +0000 (16:23 +0000)
Original commit message from CVS:
* gst/mpegaudioparse/Makefile.am:
* gst/mpegaudioparse/gstmpegaudioparse.c:
(mp3_type_frame_length_from_header), (gst_mp3parse_class_init),
(gst_mp3parse_reset), (gst_mp3parse_init), (gst_mp3parse_dispose),
(gst_mp3parse_sink_event), (gst_mp3parse_chain), (head_check),
(gst_mp3parse_change_state), (plugin_init):
* gst/mpegaudioparse/gstmpegaudioparse.h:
Bring mp3parse into the 21st century.
Use its own debug category, use gstadapter, format nicely to 80
columns, and fix incorrect handling of 32 kHz and less files.

ChangeLog
gst/mpegaudioparse/Makefile.am
gst/mpegaudioparse/gstmpegaudioparse.c
gst/mpegaudioparse/gstmpegaudioparse.h

index 6195f5d..b776ef9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2006-11-13  Michael Smith  <msmith@fluendo.com>
+
+       * gst/mpegaudioparse/Makefile.am:
+       * gst/mpegaudioparse/gstmpegaudioparse.c:
+       (mp3_type_frame_length_from_header), (gst_mp3parse_class_init),
+       (gst_mp3parse_reset), (gst_mp3parse_init), (gst_mp3parse_dispose),
+       (gst_mp3parse_sink_event), (gst_mp3parse_chain), (head_check),
+       (gst_mp3parse_change_state), (plugin_init):
+       * gst/mpegaudioparse/gstmpegaudioparse.h:
+         Bring mp3parse into the 21st century.
+         Use its own debug category, use gstadapter, format nicely to 80
+         columns, and fix incorrect handling of 32 kHz and less files.
+
 2006-11-03  Wim Taymans  <wim@fluendo.com>
 
        Patch by: Sebastian Droege <slomo at ubuntu dot com>
index 9a35bdd..4011ff9 100644 (file)
@@ -2,7 +2,7 @@ plugin_LTLIBRARIES = libgstmpegaudioparse.la
 
 libgstmpegaudioparse_la_SOURCES = gstmpegaudioparse.c
 libgstmpegaudioparse_la_CFLAGS = $(GST_CFLAGS)
-libgstmpegaudioparse_la_LIBADD = $(GST_LIBS)
+libgstmpegaudioparse_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS)
 libgstmpegaudioparse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 
 noinst_HEADERS = gstmpegaudioparse.h
index dfc189c..5616bf1 100644 (file)
  * Boston, MA 02111-1307, USA.
  */
 
-/*#define GST_DEBUG_ENABLED */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 #include "gstmpegaudioparse.h"
 
+GST_DEBUG_CATEGORY_STATIC (mp3parse_debug);
+#define GST_CAT_DEFAULT mp3parse_debug
 
 /* elementfactory information */
 static GstElementDetails mp3parse_details = {
@@ -70,8 +71,9 @@ static void gst_mp3parse_init (GstMPEGAudioParse * mp3parse);
 static gboolean gst_mp3parse_sink_event (GstPad * pad, GstEvent * event);
 static GstFlowReturn gst_mp3parse_chain (GstPad * pad, GstBuffer * buffer);
 
-static int head_check (unsigned long head);
+static int head_check (GstMPEGAudioParse * mp3parse, unsigned long head);
 
+static void gst_mp3parse_dispose (GObject * object);
 static void gst_mp3parse_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec);
 static void gst_mp3parse_get_property (GObject * object, guint prop_id,
@@ -122,8 +124,9 @@ static guint mp3types_freqs[3][3] = { {44100, 48000, 32000},
 };
 
 static inline guint
-mp3_type_frame_length_from_header (guint32 header, guint * put_layer,
-    guint * put_channels, guint * put_bitrate, guint * put_samplerate)
+mp3_type_frame_length_from_header (GstMPEGAudioParse * mp3parse, guint32 header,
+    guint * put_layer, guint * put_channels, guint * put_bitrate,
+    guint * put_samplerate)
 {
   guint length;
   gulong mode, samplerate, bitrate, layer, channels, padding;
@@ -160,9 +163,10 @@ mp3_type_frame_length_from_header (guint32 header, guint * put_layer,
       break;
   }
 
-  GST_DEBUG ("Calculated mp3 frame length of %u bytes", length);
-  GST_DEBUG ("samplerate = %lu, bitrate = %lu, layer = %lu, channels = %lu",
-      samplerate, bitrate, layer, channels);
+  GST_DEBUG_OBJECT (mp3parse, "Calculated mp3 frame length of %u bytes",
+      length);
+  GST_DEBUG_OBJECT (mp3parse, "samplerate = %lu, bitrate = %lu, layer = %lu, "
+      "channels = %lu", samplerate, bitrate, layer, channels);
 
   if (put_layer)
     *put_layer = layer;
@@ -176,32 +180,6 @@ mp3_type_frame_length_from_header (guint32 header, guint * put_layer,
   return length;
 }
 
-/*
- * The chance that random data is identified as a valid mp3 header is 63 / 2^18
- * (0.024%) per try. This makes the function for calculating false positives
- *   1 - (1 - ((63 / 2 ^18) ^ GST_MP3_TYPEFIND_MIN_HEADERS)) ^ buffersize)
- * This has the following probabilities of false positives:
- * bufsize                MIN_HEADERS
- * (bytes)      1       2       3       4
- * 4096         62.6%    0.02%   0%      0%
- * 16384        98%      0.09%   0%      0%
- * 1 MiB       100%      5.88%   0%      0%
- * 1 GiB       100%    100%      1.44%   0%
- * 1 TiB       100%    100%    100%      0.35%
- * This means that the current choice (3 headers by most of the time 4096 byte
- * buffers is pretty safe for now.
- *
- * The max. size of each frame is 1440 bytes, which means that for N frames
- * to be detected, we need 1440 * GST_MP3_TYPEFIND_MIN_HEADERS + 3 of data.
- * Assuming we step into the stream right after the frame header, this
- * means we need 1440 * (GST_MP3_TYPEFIND_MIN_HEADERS + 1) - 1 + 3 bytes
- * of data (5762) to always detect any mp3.
- */
-
-/* increase this value when this function finds too many false positives */
-#define GST_MP3_TYPEFIND_MIN_HEADERS 3
-#define GST_MP3_TYPEFIND_MIN_DATA (1440 * (GST_MP3_TYPEFIND_MIN_HEADERS + 1) - 1 + 3)
-
 static GstCaps *
 mp3_caps_create (guint layer, guint channels, guint bitrate, guint samplerate)
 {
@@ -245,6 +223,7 @@ gst_mp3parse_class_init (GstMPEGAudioParseClass * klass)
 
   gobject_class->set_property = gst_mp3parse_set_property;
   gobject_class->get_property = gst_mp3parse_get_property;
+  gobject_class->dispose = gst_mp3parse_dispose;
 
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SKIP,
       g_param_spec_int ("skip", "skip", "skip",
@@ -257,6 +236,17 @@ gst_mp3parse_class_init (GstMPEGAudioParseClass * klass)
 }
 
 static void
+gst_mp3parse_reset (GstMPEGAudioParse * mp3parse)
+{
+  mp3parse->skip = 0;
+  mp3parse->resyncing = TRUE;
+
+  gst_adapter_clear (mp3parse->adapter);
+
+  mp3parse->rate = mp3parse->channels = mp3parse->layer = -1;
+}
+
+static void
 gst_mp3parse_init (GstMPEGAudioParse * mp3parse)
 {
   mp3parse->sinkpad =
@@ -271,13 +261,23 @@ gst_mp3parse_init (GstMPEGAudioParse * mp3parse)
       (&mp3_src_template), "src");
   gst_pad_use_fixed_caps (mp3parse->srcpad);
   gst_element_add_pad (GST_ELEMENT (mp3parse), mp3parse->srcpad);
-  /*gst_pad_set_type_id(mp3parse->srcpad, mp3frametype); */
 
-  mp3parse->partialbuf = NULL;
-  mp3parse->skip = 0;
-  mp3parse->in_flush = FALSE;
+  mp3parse->adapter = gst_adapter_new ();
 
-  mp3parse->rate = mp3parse->channels = mp3parse->layer = -1;
+  gst_mp3parse_reset (mp3parse);
+}
+
+static void
+gst_mp3parse_dispose (GObject * object)
+{
+  GstMPEGAudioParse *mp3parse = GST_MP3PARSE (object);
+
+  if (mp3parse->adapter) {
+    g_object_unref (mp3parse->adapter);
+    mp3parse->adapter = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
 }
 
 static gboolean
@@ -297,10 +297,10 @@ gst_mp3parse_sink_event (GstPad * pad, GstEvent * event)
           NULL);
 
       if (format != GST_FORMAT_TIME)
-        mp3parse->last_ts = 0;
+        mp3parse->next_ts = 0;
       else
         /* we will be receiving timestamps */
-        mp3parse->last_ts = -1;
+        mp3parse->next_ts = -1;
       break;
     }
     default:
@@ -313,109 +313,107 @@ gst_mp3parse_sink_event (GstPad * pad, GstEvent * event)
   return res;
 }
 
-/* FIXME, use adapter */
 static GstFlowReturn
 gst_mp3parse_chain (GstPad * pad, GstBuffer * buf)
 {
   GstMPEGAudioParse *mp3parse;
-  guchar *data;
-  glong size, offset = 0;
+  const guchar *data;
   guint32 header;
   int bpf;
   GstBuffer *outbuf;
   GstClockTime timestamp;
+  guint available;
 
   mp3parse = GST_MP3PARSE (gst_pad_get_parent (pad));
 
-  GST_DEBUG ("mp3parse: received buffer of %d bytes", GST_BUFFER_SIZE (buf));
+  GST_DEBUG_OBJECT (mp3parse, "received buffer of %d bytes",
+      GST_BUFFER_SIZE (buf));
 
   timestamp = GST_BUFFER_TIMESTAMP (buf);
-  if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
-    mp3parse->last_ts = timestamp;
-  }
-
-  /* if we have something left from the previous frame */
-  if (mp3parse->partialbuf) {
-    GstBuffer *newbuf;
 
-    newbuf = gst_buffer_merge (mp3parse->partialbuf, buf);
-    /* and the one we received.. */
-    gst_buffer_unref (buf);
-    gst_buffer_unref (mp3parse->partialbuf);
-    mp3parse->partialbuf = newbuf;
-  } else {
-    mp3parse->partialbuf = buf;
+  if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
+    GST_DEBUG_OBJECT (mp3parse, "Using incoming timestamp of %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (timestamp));
+    mp3parse->next_ts = timestamp;
   }
 
-  size = GST_BUFFER_SIZE (mp3parse->partialbuf);
-  data = GST_BUFFER_DATA (mp3parse->partialbuf);
-
-  /* while we still have bytes left -4 for the header */
-  while (offset < size - 4) {
-    int skipped = 0;
+  gst_adapter_push (mp3parse->adapter, buf);
 
-    GST_DEBUG ("mp3parse: offset %ld, size %ld ", offset, size);
+  /* while we still have at least 4 bytes (for the header) available */
+  while (gst_adapter_available (mp3parse->adapter) >= 4) {
 
     /* search for a possible start byte */
-    for (; ((offset < size - 4) && (data[offset] != 0xff)); offset++)
-      skipped++;
-    if (skipped) {
-      GST_DEBUG ("mp3parse: **** now at %ld skipped %d bytes", offset, skipped);
+    data = gst_adapter_peek (mp3parse->adapter, 4);
+    if (*data != 0xff) {
+      /* It'd be nice to make this efficient, but it's ok for now; this is only
+       * when resyncing
+       */
+      mp3parse->resyncing = TRUE;
+      gst_adapter_flush (mp3parse->adapter, 1);
+      continue;
     }
+
+    available = gst_adapter_available (mp3parse->adapter);
+
     /* construct the header word */
-    header = GST_READ_UINT32_BE (data + offset);
+    header = GST_READ_UINT32_BE (data);
     /* if it's a valid header, go ahead and send off the frame */
-    if (head_check (header)) {
+    if (head_check (mp3parse, header)) {
       guint bitrate = 0, layer = 0, rate = 0, channels = 0;
 
-      if (!(bpf = mp3_type_frame_length_from_header (header, &layer,
+      if (!(bpf = mp3_type_frame_length_from_header (mp3parse, header, &layer,
                   &channels, &bitrate, &rate))) {
         g_error ("Header failed internal error");
       }
 
-      /********************************************************************************
+      /*************************************************************************
       * robust seek support
-      * - This performs additional frame validation if the in_flush flag is set
+      * - This performs additional frame validation if the resyncing flag is set
       *   (indicating a discontinuous stream).
-      * - The current frame header is not accepted as valid unless the NEXT frame
-      *   header has the same values for most fields.  This significantly increases
-      *   the probability that we aren't processing random data.
+      * - The current frame header is not accepted as valid unless the NEXT 
+      *   frame header has the same values for most fields.  This significantly
+      *   increases the probability that we aren't processing random data.
       * - It is not clear if this is sufficient for robust seeking of Layer III
-      *   streams which utilize the concept of a "bit reservoir" by borrow bitrate
-      *   from previous frames.  In this case, seeking may be more complicated because
-      *   the frames are not independently coded.
-      ********************************************************************************/
-      if (mp3parse->in_flush) {
+      *   streams which utilize the concept of a "bit reservoir" by borrowing
+      *   bitrate from previous frames.  In this case, seeking may be more 
+      *   complicated because the frames are not independently coded.
+      *************************************************************************/
+      if (mp3parse->resyncing) {
         guint32 header2;
+        const guint8 *data2;
 
-        if ((size - offset) < (bpf + 4)) {
-          if (mp3parse->in_flush)
-            break;
-        }
-        /* wait until we have the the entire current frame as well as the next frame header */
-        header2 = GST_READ_UINT32_BE (data + offset + bpf);
-        GST_DEBUG ("mp3parse: header=%08X, header2=%08X, bpf=%d",
+        /* wait until we have the the entire current frame as well as the next 
+         * frame header */
+        if (available < bpf + 4)
+          break;
+
+        data2 = gst_adapter_peek (mp3parse->adapter, bpf + 4);
+        header2 = GST_READ_UINT32_BE (data2 + bpf);
+        GST_DEBUG_OBJECT (mp3parse, "header=%08X, header2=%08X, bpf=%d",
             (unsigned int) header, (unsigned int) header2, bpf);
 
 /* mask the bits which are allowed to differ between frames */
 #define HDRMASK ~((0xF << 12)  /* bitrate */ | \
                   (0x1 <<  9)  /* padding */ | \
-                  (0x3 <<  4))  /*mode extension */
+                  (0x3 <<  4))  /* mode extension */
 
-        if ((header2 & HDRMASK) != (header & HDRMASK)) {        /* require 2 matching headers in a row */
-          GST_DEBUG
-              ("mp3parse: next header doesn't match (header=%08X, header2=%08X, bpf=%d)",
+        /* require 2 matching headers in a row */
+        if ((header2 & HDRMASK) != (header & HDRMASK)) {
+          GST_DEBUG_OBJECT (mp3parse, "next header doesn't match "
+              "(header=%08X, header2=%08X, bpf=%d)",
               (unsigned int) header, (unsigned int) header2, bpf);
-          offset++;             /* This frame is invalid.  Start looking for a valid frame at the next position in the stream */
+          /* This frame is invalid.  Start looking for a valid frame at the 
+           * next position in the stream */
+          mp3parse->resyncing = TRUE;
+          gst_adapter_flush (mp3parse->adapter, 1);
           continue;
         }
-
       }
 
       /* if we don't have the whole frame... */
-      if ((size - offset) < bpf) {
-        GST_DEBUG ("mp3parse: partial buffer needed %ld < %d ", (size - offset),
-            bpf);
+      if (available < bpf) {
+        GST_DEBUG_OBJECT (mp3parse, "insufficient data available, need "
+            "%d bytes, have %d", bpf, available);
         break;
       } else {
         if (channels != mp3parse->channels ||
@@ -433,16 +431,17 @@ gst_mp3parse_chain (GstPad * pad, GstBuffer * buf)
           mp3parse->bit_rate = bitrate;
         }
 
-        outbuf = gst_buffer_create_sub (mp3parse->partialbuf, offset, bpf);
+        outbuf = gst_adapter_take_buffer (mp3parse->adapter, bpf);
+
+        if (!mp3parse->skip) {
+          gint spf;             /* samples per frame */
 
-        offset += bpf;
-        if (mp3parse->skip == 0) {
-          gint spf;             /* samples fer frame */
+          mp3parse->resyncing = FALSE;
 
-          GST_DEBUG ("mp3parse: pushing buffer of %d bytes",
+          GST_DEBUG_OBJECT (mp3parse, "pushing buffer of %d bytes",
               GST_BUFFER_SIZE (outbuf));
 
-          GST_BUFFER_TIMESTAMP (outbuf) = mp3parse->last_ts;
+          GST_BUFFER_TIMESTAMP (outbuf) = mp3parse->next_ts;
 
           /* see http://www.codeproject.com/audio/MPEGAudioInfo.asp */
           if (mp3parse->layer == 1)
@@ -450,46 +449,32 @@ gst_mp3parse_chain (GstPad * pad, GstBuffer * buf)
           else if (mp3parse->layer == 2)
             spf = 1152;
           else {
-            if (mp3parse->rate < 32100)
+            if (mp3parse->rate < 16000)
               spf = 576;
             else
               spf = 1152;
           }
           GST_BUFFER_DURATION (outbuf) = spf * GST_SECOND / mp3parse->rate;
 
-          mp3parse->last_ts += GST_BUFFER_DURATION (outbuf);
+          mp3parse->next_ts += GST_BUFFER_DURATION (outbuf);
 
           gst_buffer_set_caps (outbuf, GST_PAD_CAPS (mp3parse->srcpad));
 
           gst_pad_push (mp3parse->srcpad, outbuf);
 
         } else {
-          GST_DEBUG ("mp3parse: skipping buffer of %d bytes",
+          GST_DEBUG_OBJECT (mp3parse, "skipping buffer of %d bytes",
               GST_BUFFER_SIZE (outbuf));
           gst_buffer_unref (outbuf);
           mp3parse->skip--;
         }
       }
     } else {
-      offset++;
-      GST_DEBUG ("mp3parse: *** wrong header, skipping byte (FIXME?)");
+      mp3parse->resyncing = TRUE;
+      gst_adapter_flush (mp3parse->adapter, 1);
+      GST_DEBUG_OBJECT (mp3parse, "wrong header, skipping byte");
     }
   }
-  /* if we have processed this block and there are still */
-  /* bytes left not in a partial block, copy them over. */
-  if (size - offset > 0) {
-    glong remainder = (size - offset);
-
-    GST_DEBUG ("mp3parse: partial buffer needed %ld for trailing bytes",
-        remainder);
-
-    outbuf = gst_buffer_create_sub (mp3parse->partialbuf, offset, remainder);
-    gst_buffer_unref (mp3parse->partialbuf);
-    mp3parse->partialbuf = outbuf;
-  } else {
-    gst_buffer_unref (mp3parse->partialbuf);
-    mp3parse->partialbuf = NULL;
-  }
 
   gst_object_unref (mp3parse);
 
@@ -497,44 +482,44 @@ gst_mp3parse_chain (GstPad * pad, GstBuffer * buf)
 }
 
 static gboolean
-head_check (unsigned long head)
+head_check (GstMPEGAudioParse * mp3parse, unsigned long head)
 {
-  GST_DEBUG ("checking mp3 header 0x%08lx", head);
+  GST_DEBUG_OBJECT (mp3parse, "checking mp3 header 0x%08lx", head);
   /* if it's not a valid sync */
   if ((head & 0xffe00000) != 0xffe00000) {
-    GST_DEBUG ("invalid sync");
+    GST_DEBUG_OBJECT (mp3parse, "invalid sync");
     return FALSE;
   }
   /* if it's an invalid MPEG version */
   if (((head >> 19) & 3) == 0x1) {
-    GST_DEBUG ("invalid MPEG version");
+    GST_DEBUG_OBJECT (mp3parse, "invalid MPEG version");
     return FALSE;
   }
   /* if it's an invalid layer */
   if (!((head >> 17) & 3)) {
-    GST_DEBUG ("invalid layer");
+    GST_DEBUG_OBJECT (mp3parse, "invalid layer");
     return FALSE;
   }
   /* if it's an invalid bitrate */
   if (((head >> 12) & 0xf) == 0x0) {
-    GST_DEBUG ("invalid bitrate");
+    GST_DEBUG_OBJECT (mp3parse, "invalid bitrate");
     return FALSE;
   }
   if (((head >> 12) & 0xf) == 0xf) {
-    GST_DEBUG ("invalid bitrate");
+    GST_DEBUG_OBJECT (mp3parse, "invalid bitrate");
     return FALSE;
   }
   /* if it's an invalid samplerate */
   if (((head >> 10) & 0x3) == 0x3) {
-    GST_DEBUG ("invalid samplerate");
+    GST_DEBUG_OBJECT (mp3parse, "invalid samplerate");
     return FALSE;
   }
   if ((head & 0xffff0000) == 0xfffe0000) {
-    GST_DEBUG ("invalid sync");
+    GST_DEBUG_OBJECT (mp3parse, "invalid sync");
     return FALSE;
   }
   if (head & 0x00000002) {
-    GST_DEBUG ("invalid emphasis");
+    GST_DEBUG_OBJECT (mp3parse, "invalid emphasis");
     return FALSE;
   }
 
@@ -584,22 +569,21 @@ gst_mp3parse_get_property (GObject * object, guint prop_id, GValue * value,
 static GstStateChangeReturn
 gst_mp3parse_change_state (GstElement * element, GstStateChange transition)
 {
-  GstMPEGAudioParse *src;
+  GstMPEGAudioParse *mp3parse;
   GstStateChangeReturn result;
 
-  src = GST_MP3PARSE (element);
+  mp3parse = GST_MP3PARSE (element);
+
+  result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
 
   switch (transition) {
     case GST_STATE_CHANGE_PAUSED_TO_READY:
-      src->channels = -1;
-      src->rate = -1;
-      src->layer = -1;
+      gst_mp3parse_reset (mp3parse);
       break;
     default:
       break;
   }
 
-  result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
 
   return result;
 }
@@ -607,6 +591,8 @@ gst_mp3parse_change_state (GstElement * element, GstStateChange transition)
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
+  GST_DEBUG_CATEGORY_INIT (mp3parse_debug, "mp3parse", 0, "MP3 Parser");
+
   return gst_element_register (plugin, "mp3parse",
       GST_RANK_NONE, GST_TYPE_MP3PARSE);
 }
index d2ca67e..fae6e63 100644 (file)
@@ -23,6 +23,7 @@
 
 
 #include <gst/gst.h>
+#include <gst/base/gstadapter.h>
 
 G_BEGIN_DECLS
 
@@ -45,13 +46,16 @@ struct _GstMPEGAudioParse {
 
   GstPad *sinkpad,*srcpad;
 
-  guint64       last_ts;
+  GstClockTime next_ts;
+
+  GstAdapter *adapter;
 
-  GstBuffer *partialbuf;        /* previous buffer (if carryover) */
   guint skip; /* number of frames to skip */
-  guint bit_rate;
+  guint bit_rate; /* in kbps */
   gint channels, rate, layer;
-  gboolean in_flush;
+
+  gboolean resyncing; /* True when attempting to resync (stricter checks are
+                         performed) */
 };
 
 struct _GstMPEGAudioParseClass {