Index: gstxingmux.c
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-bad/gst/xingheader/gstxingmux.c,v
retrieving revision 1.5
diff -u -r1.5 gstxingmux.c
--- gstxingmux.c	5 Oct 2007 08:51:44 -0000	1.5
+++ gstxingmux.c	12 Jan 2008 17:01:51 -0000
@@ -30,13 +30,81 @@
 
 GST_BOILERPLATE (GstXingMux, gst_xing_mux, GstElement, GST_TYPE_ELEMENT);
 
+/* Wikipedia Table */
+static const guint16 SAMPLING_FREQ[4][4] = {
+  { 32000, 16000,  8000, 0 }, // MPEG 2.5
+  {     0,     0,     0, 0 }, // RESERVED
+  { 48000, 24000, 12000, 0 }, // MPEG 2
+  { 44100, 22050, 11025, 0 }  // MPEG 1
+};
+
+/* mpgedit.org */
+/*static const guint16 SAMPLING_FREQ[4][4] = {
+  { 11025, 12000,  8000, 0 },  // MPEG 2.5
+  {     0,     0,     0, 0 },  // RESERVED
+  { 22050, 24000, 16000, 0 },  // MPEG 2
+  { 44100, 48000, 32000, 0 }   // MPEG 1
+};*/
+
+static const guint32 BITRATE_MPEG1[4][16] = {
+  { 0,     0,     0,     0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0, 0 },  // reserved
+  { 0, 32000, 40000, 48000,  56000,  64000,  80000,  96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0 },  // Layer III
+  { 0, 32000, 48000, 56000,  64000,  80000,  96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0 },  // Layer II
+  { 0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, 0 }   // Layer I
+};
+
+static const guint32 BITRATE_MPEG2[4][16] = {
+  { 0,     0,     0,     0,     0,     0,     0,      0,      0,      0,      0,      0,      0,      0,      0, 0 },  // reserved
+  { 0,  8000, 16000, 24000, 32000, 40000, 48000,  56000,  64000,  80000,  96000, 112000, 128000, 144000, 160000, 0 },  // Layer III
+  { 0,  8000, 16000, 24000, 32000, 40000, 48000,  56000,  64000,  80000,  96000, 112000, 128000, 144000, 160000, 0 },  // Layer II
+  { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0 },  // Layer I
+};
+
+/* */
+#define LAYER_III   1U
+#define LAYER_II    2U
+#define LAYER_I     3U
+
+/* */
+#define MPEG_IIi       0U
+#define MPEG_RESERVED  1U
+#define MPEG_II        2U
+#define MPEG_I         3U
+
+/* */
+#define MODE_STEREO        0U
+#define MODE_JOINT_STEREO  1U
+#define MODE_DUAL          2U
+#define MODE_MONO          3U
+
+/* taken from: README for Xing's Variable Bitrate MP3 Playback SDK */
+struct _XingHeader
+{
+  gchar    xing[4];
+  guint32  flags;
+  guint32  frame_count;
+  guint32  byte_count;
+  guchar   toc[100];
+  guint32  vbr_scale;
+};
+
+struct _MPEGInfo
+{
+  guchar        mp3_header[4];
+  guint8        layer;
+  guint8        mpgId;
+  guint16       sampleRate;
+  guint16       nextHeader;
+};
+
 /* Xing Header stuff */
 struct _GstXingMuxPriv
 {
-  GstClockTime duration;
-  guint64 byte_count;
-  GList *seek_table;
-  gboolean flush;
+  guint32             byte_count;
+  guint32             frame_count;
+  GList              *seek_table;
+  gboolean            flush;
+  struct _MPEGInfo    mpgInfo;
 };
 
 #define GST_XING_FRAME_FIELD   (1 << 0)
@@ -68,7 +136,7 @@
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
     GST_STATIC_CAPS ("audio/mpeg, "
-        "mpegversion = (int) 1, " "layer = (int) 3"));
+        "mpegversion = (int) { 1, 2 }, " "layer = (int) { 1, 2, 3 }"));
 
 
 static GstStaticPadTemplate gst_xing_mux_src_template =
@@ -76,7 +144,7 @@
     GST_PAD_SRC,
     GST_PAD_ALWAYS,
     GST_STATIC_CAPS ("audio/mpeg, "
-        "mpegversion = (int) 1, " "layer = (int) 3"));
+        "mpegversion = (int) { 1, 2 }, " "layer = (int) { 1, 2, 3 }"));
 
 
 static void
@@ -146,86 +214,129 @@
 
   xing->priv = g_malloc0 (sizeof (GstXingMuxPriv));
   xing_set_flush (xing, TRUE);
-  xing->priv->duration = GST_CLOCK_TIME_NONE;
-
 }
 
-G_GNUC_UNUSED static void
-xing_update_data (GstXingMux * xing, gint bytes, guint64 duration)
+static GstFlowReturn
+xing_update_data (GstXingMux *xing, GstBuffer *buffer)
 {
-  if (xing->priv == NULL) {
-    return;
-  }
-  xing->priv->byte_count += bytes;
+  guchar   currHeader[4];
+  guint64  size;
+  guint8   padding;
+
+  if (xing->priv == NULL)
+    return GST_FLOW_WRONG_STATE;
+
+  size = GST_BUFFER_SIZE(buffer);
+
+  xing->priv->byte_count += size;
+
+  // is next header in this frame ?
+  if (size < (xing->priv->mpgInfo.nextHeader + sizeof(currHeader)))
+  {
+    xing->priv->mpgInfo.nextHeader -= size;
+//    g_print("current buffer has no frame-header.\n");
+//    gst_util_dump_mem((guchar*)GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
+  }
+  else
+  {
+    guint32 i;
+    // count all frames in the current buffer
+    for (i = 0; ; i++)
+    {
+      guint8   bitRateIndex;
+      guint32  bitRate;
+      guint32  nextOffset;
 
-  if (duration == GST_CLOCK_TIME_NONE) {
-    return;
-  }
-  if (xing->priv->duration == GST_CLOCK_TIME_NONE) {
-    xing->priv->duration = duration;
-  } else {
-    xing->priv->duration += duration;
+      xing->priv->frame_count++;
+
+      memcpy(&currHeader, &GST_BUFFER_DATA(buffer)[xing->priv->mpgInfo.nextHeader], sizeof(currHeader));
+      bitRateIndex = (currHeader[2] >> 4) & 0xF;
+
+      // does a simple sanity check of sync and a vaild bitRate
+      if ((currHeader[0] != 0xFF) || (((currHeader[1] >> 5) & 3) != 3) || (bitRateIndex == 15))
+      {
+        gst_util_dump_mem((guchar*)&currHeader, sizeof(currHeader));
+        gst_util_dump_mem((guchar*)GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
+
+        GST_ELEMENT_ERROR (xing, STREAM, FAILED, ("Search of next MP3-header failed!"), ("Search of next MP3-header failed!"));
+
+        return GST_FLOW_ERROR;
+      }
+
+      // get Bitrate for current Frame
+      if (xing->priv->mpgInfo.mpgId == MPEG_I)
+        bitRate = BITRATE_MPEG1[xing->priv->mpgInfo.layer][bitRateIndex];
+      else
+        bitRate = BITRATE_MPEG2[xing->priv->mpgInfo.layer][bitRateIndex];
+
+      // check for padding
+      if ((currHeader[2] >> 1) & 1)
+        padding = xing->priv->mpgInfo.layer == LAYER_I ? 4 : 1;
+      else
+        padding = 0;
+
+      if (xing->priv->mpgInfo.layer == LAYER_I)
+        nextOffset = (12 * bitRate / xing->priv->mpgInfo.sampleRate + padding) * 4;
+      else
+        nextOffset = 144 * bitRate / xing->priv->mpgInfo.sampleRate + padding;
+
+//      g_print("size : %lld, nextOffset  : %d, bitRate: %d\n", size, nextOffset, bitRate);
+//      g_print("layer: %d,   bitRateIndex: %d, samplerate: %d, MPEG-ID: %d\n", xing->priv->mpgInfo.layer, bitRateIndex, xing->priv->mpgInfo.sampleRate, xing->priv->mpgInfo.mpgId);
+
+      if ((nextOffset + xing->priv->mpgInfo.nextHeader) >= size)
+      {
+        xing->priv->mpgInfo.nextHeader = (nextOffset + xing->priv->mpgInfo.nextHeader) - size;
+        break;
+      }
+      xing->priv->mpgInfo.nextHeader = nextOffset + xing->priv->mpgInfo.nextHeader;
+
+//      g_print("nextHeader in same buffer: %d\n", xing->priv->mpgInfo.nextHeader);
+
+      // assume there are never more then 20 mp3 frames per buffer
+      if (i > 20)
+      {
+        gst_util_dump_mem((guchar*)GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
+
+        GST_ELEMENT_ERROR (xing, STREAM, FAILED, ("Next MP3 header in current buffer not found!"), ("Next MP3 header in current buffer not found!"));
+
+        return GST_FLOW_ERROR;
+      }
+    }
   }
+
+  return GST_FLOW_OK;
 }
 
 static GstBuffer *
 xing_generate_header (GstXingMux * xing)
 {
-  guint32 xing_flags;
-  GstBuffer *header;
-  guint32 *data;
-
-  /* Dummy header that we will stick at the beginning of our frame
-   *
-   * 0xffe => synchronization bits
-   * 0x1b  => 11010b (11b == MPEG1 | 01b == Layer III | 0b == no CRC)
-   * 0x9   => 128kbps
-   * 0x00  => 00b == 44100 Hz | 0b == no padding | 0b == private bit
-   * 0x44  => 0010b 0010b (00b == stereo | 10b == (unused) mode extension)
-   *                      (0b == no copyright bit | 0b == original bit)
-   *                      (00b == no emphasis)
-   *
-   * Such a frame (MPEG1 Layer III) contains 1152 samples, its size is thus:
-   * (1152*(128000/8))/44100 = 417.96 rounded to the next smaller integer, i.e.
-   * 417.
-   * 
-   * There are also 32 bytes (ie 8 32 bits values) to skip after the header 
-   * for such frames
-   */
-  const guint8 mp3_header[4] = { 0xff, 0xfb, 0x90, 0x44 };
-  const int SIDE_INFO_SIZE = 32 / sizeof (guint32);
+  guint32             xing_flags;
+  GstBuffer          *header;
+  guchar             *data;
+  guint32             xingOffset;
+  struct _XingHeader  xingHeader;
+
+  guint8 mode    = (xing->priv->mpgInfo.mp3_header[3] >> 6) & 3;
+
+  // be sure there's no padding-bit set
+  xing->priv->mpgInfo.mp3_header[2] &= 0xFD;
+  // be sure there's no CRC-bit set
+  xing->priv->mpgInfo.mp3_header[1] &= 0xFE;
 
   header = gst_buffer_new_and_alloc (XING_FRAME_SIZE);
 
-  data = (guint32 *) GST_BUFFER_DATA (header);
+  data = (guchar*) GST_BUFFER_DATA (header);
   memset (data, 0, XING_FRAME_SIZE);
-  memcpy (data, mp3_header, 4);
-  memcpy (&data[8 + 1], "Xing", 4);
+  memcpy (data, xing->priv->mpgInfo.mp3_header, sizeof(xing->priv->mpgInfo.mp3_header));
 
   xing_flags = 0;
-  if (xing->priv->duration != GST_CLOCK_TIME_NONE) {
-    guint32 number_of_frames;
-
-    /* The Xing Header contains a NumberOfFrames field, which verifies to:
-     * Duration = NumberOfFrames *SamplesPerFrame/SamplingRate
-     * SamplesPerFrame and SamplingRate are values for the current frame, 
-     * ie 1152 and 44100 in our case.
-     */
-
-    /* FIXME: Better count the actual number of frames as the calculation
-     * below introduces rounding errors
-     */
-    number_of_frames = gst_util_uint64_scale (xing->priv->duration, 44100,
-        GST_SECOND) / 1152;
-    data[SIDE_INFO_SIZE + 3] = GUINT32_TO_BE (number_of_frames);
-
+  if (xing->priv->frame_count != 0)
     xing_flags |= GST_XING_FRAME_FIELD;
-  }
 
-  if (xing->priv->byte_count != 0) {
+  if (xing->priv->byte_count != 0)
     xing_flags |= GST_XING_BYTES_FIELD;
-    data[SIDE_INFO_SIZE + 4] = GUINT32_TO_BE (xing->priv->byte_count);
-  }
+
+//  g_print("xing: frames: %d, bytes: %d\n", xing->priv->frame_count, xing->priv->byte_count);
 
   /* TODO: Un-#ifdef when it's implemented :) xing code in VbrTag.c looks like
    * it could be stolen
@@ -241,7 +352,19 @@
   }
 #endif
 
-  data[SIDE_INFO_SIZE + 2] = GUINT32_TO_BE (xing_flags);
+  memset(&xingHeader, 0, sizeof(xingHeader));
+  memcpy(&xingHeader.xing[0], "Xing", sizeof(xingHeader.xing));
+  xingHeader.flags       = GUINT32_TO_BE(xing_flags);
+  xingHeader.frame_count = GUINT32_TO_BE(xing->priv->frame_count);
+  xingHeader.byte_count  = GUINT32_TO_BE(xing->priv->byte_count);
+
+  if (xing->priv->mpgInfo.mpgId == MPEG_I)
+    xingOffset = (mode == MODE_MONO ? (17 + 4) : (32 + 4));
+  else
+    xingOffset = (mode == MODE_MONO ? (9 + 4) : (17 + 4));
+
+  memcpy(&data[xingOffset], &xingHeader, sizeof(xingHeader));
+
   gst_buffer_set_caps (header, GST_PAD_CAPS (xing->srcpad));
   //  gst_util_dump_mem ((guchar *)data, XING_FRAME_SIZE);
   return header;
@@ -282,17 +405,36 @@
 static GstFlowReturn
 gst_xing_mux_chain (GstPad * pad, GstBuffer * buffer)
 {
-  GstXingMux *xing = GST_XING_MUX (GST_OBJECT_PARENT (pad));
+  GstFlowReturn   ret;
+  GstXingMux     *xing = GST_XING_MUX (GST_OBJECT_PARENT (pad));
 
-  xing_update_data (xing, GST_BUFFER_SIZE (buffer),
-      GST_BUFFER_DURATION (buffer));
+  g_return_val_if_fail(xing != NULL, GST_FLOW_ERROR);
 
   if (xing_ready_to_flush (xing)) {
+
+    memcpy(&xing->priv->mpgInfo.mp3_header, GST_BUFFER_DATA(buffer), sizeof(xing->priv->mpgInfo.mp3_header));
+
+    guint8 srIndex = (xing->priv->mpgInfo.mp3_header[2] >> 2) & 3;
+
+    xing->priv->mpgInfo.mpgId      = (xing->priv->mpgInfo.mp3_header[1] >> 3) & 3;
+    xing->priv->mpgInfo.layer      = (xing->priv->mpgInfo.mp3_header[1] >> 1) & 3;
+    xing->priv->mpgInfo.sampleRate = SAMPLING_FREQ[xing->priv->mpgInfo.mpgId][srIndex];
+
+#if 0
+    g_print("id         : %d\n", xing->priv->mpgInfo.mpgId);
+    g_print("sampling   : %d\n", srIndex);
+    g_print("layer      : %d\n", xing->priv->mpgInfo.layer);
+    g_print("frequenz   : %d\n", xing->priv->mpgInfo.sampleRate);
+#endif
+
     GST_INFO ("Writing empty Xing header to stream");
     gst_pad_push (xing->srcpad, xing_generate_header (xing));
     xing_set_flush (xing, FALSE);
   }
 
+  if ((ret = xing_update_data (xing, buffer)) != GST_FLOW_OK)
+    return ret;
+
   return gst_pad_push (xing->srcpad, buffer);
 }
 
