rtp: add mpa-robust depayloader
authorMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Thu, 10 Jun 2010 14:14:43 +0000 (16:14 +0200)
committerMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Fri, 11 Jun 2010 09:45:48 +0000 (11:45 +0200)
Fixes #589997.

gst/rtp/Makefile.am
gst/rtp/gstrtp.c
gst/rtp/gstrtpmparobustdepay.c [new file with mode: 0644]
gst/rtp/gstrtpmparobustdepay.h [new file with mode: 0644]

index 5117a7b..b6aefe6 100644 (file)
@@ -16,6 +16,7 @@ libgstrtp_la_SOURCES = \
        gstrtpilbcpay.c \
        gstrtpmpadepay.c \
        gstrtpmpapay.c \
+       gstrtpmparobustdepay.c \
        gstrtpmpvdepay.c \
        gstrtpmpvpay.c \
        gstrtppcmadepay.c \
@@ -114,6 +115,7 @@ noinst_HEADERS =                    \
                 gstrtpgsmdepay.h       \
                 gstrtpgsmpay.h         \
                 gstrtpmpadepay.h       \
+                gstrtpmparobustdepay.h         \
                 gstrtpmpapay.h         \
                 gstrtpmpvdepay.h \
                 gstrtpmpvpay.h \
index b35e854..852906e 100644 (file)
@@ -47,6 +47,7 @@
 #include "gstrtpamrdepay.h"
 #include "gstrtpmpapay.h"
 #include "gstrtpmpadepay.h"
+#include "gstrtpmparobustdepay.h"
 #include "gstrtpmpvdepay.h"
 #include "gstrtpmpvpay.h"
 #include "gstrtph263pdepay.h"
@@ -166,6 +167,9 @@ plugin_init (GstPlugin * plugin)
   if (!gst_rtp_mpa_pay_plugin_init (plugin))
     return FALSE;
 
+  if (!gst_rtp_mpa_robust_depay_plugin_init (plugin))
+    return FALSE;
+
   if (!gst_rtp_mpv_depay_plugin_init (plugin))
     return FALSE;
 
diff --git a/gst/rtp/gstrtpmparobustdepay.c b/gst/rtp/gstrtpmparobustdepay.c
new file mode 100644 (file)
index 0000000..e50b5cf
--- /dev/null
@@ -0,0 +1,782 @@
+/* GStreamer
+ * Copyright (C) <2010> Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
+ * Copyright (C) <2010> Nokia Corporation
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <gst/rtp/gstrtpbuffer.h>
+
+#include <string.h>
+#include "gstrtpmparobustdepay.h"
+
+GST_DEBUG_CATEGORY_STATIC (rtpmparobustdepay_debug);
+#define GST_CAT_DEFAULT (rtpmparobustdepay_debug)
+
+static GstStaticPadTemplate gst_rtp_mpa_robust_depay_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("audio/mpeg, " "mpegversion = (int) 1")
+    );
+
+static GstStaticPadTemplate gst_rtp_mpa_robust_depay_sink_template =
+    GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("application/x-rtp, "
+        "media = (string) \"audio\", "
+        "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+        "clock-rate = (int) 90000, "
+        "encoding-name = (string) \"MPA-ROBUST\" " "; "
+        /* draft versions appear still in use out there */
+        "application/x-rtp, "
+        "media = (string) \"audio\", "
+        "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+        "clock-rate = (int) [1, MAX], "
+        "encoding-name = (string) { \"X-MP3-DRAFT-00\", \"X-MP3-DRAFT-01\", "
+        " \"X-MP3-DRAFT-02\", \"X-MP3-DRAFT-03\", \"X-MP3-DRAFT-04\", "
+        " \"X-MP3-DRAFT-05\", \"X-MP3-DRAFT-06\" }")
+    );
+
+typedef struct _GstADUFrame
+{
+  guint32 header;
+  gint size;
+  gint side_info;
+  gint data_size;
+  gint layer;
+  gint backpointer;
+
+  GstBuffer *buffer;
+} GstADUFrame;
+
+GST_BOILERPLATE (GstRtpMPARobustDepay, gst_rtp_mpa_robust_depay,
+    GstBaseRTPDepayload, GST_TYPE_BASE_RTP_DEPAYLOAD);
+
+static GstStateChangeReturn gst_rtp_mpa_robust_change_state (GstElement *
+    element, GstStateChange transition);
+
+static gboolean gst_rtp_mpa_robust_depay_setcaps (GstBaseRTPDepayload *
+    depayload, GstCaps * caps);
+static GstBuffer *gst_rtp_mpa_robust_depay_process (GstBaseRTPDepayload *
+    depayload, GstBuffer * buf);
+
+static void
+gst_rtp_mpa_robust_depay_base_init (gpointer klass)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_rtp_mpa_robust_depay_src_template));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_rtp_mpa_robust_depay_sink_template));
+
+  gst_element_class_set_details_simple (element_class,
+      "RTP MPEG audio depayloader", "Codec/Depayloader/Network",
+      "Extracts MPEG audio from RTP packets (RFC 5219)",
+      "Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>");
+}
+
+static void
+gst_rtp_mpa_robust_depay_finalize (GObject * object)
+{
+  GstRtpMPARobustDepay *rtpmpadepay;
+
+  rtpmpadepay = (GstRtpMPARobustDepay *) object;
+
+  g_object_unref (rtpmpadepay->adapter);
+  g_queue_free (rtpmpadepay->adu_frames);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gst_rtp_mpa_robust_depay_class_init (GstRtpMPARobustDepayClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GstBaseRTPDepayloadClass *gstbasertpdepayload_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+  gstbasertpdepayload_class = (GstBaseRTPDepayloadClass *) klass;
+
+  gobject_class->finalize = gst_rtp_mpa_robust_depay_finalize;
+
+  gstelement_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_rtp_mpa_robust_change_state);
+
+  gstbasertpdepayload_class->set_caps = gst_rtp_mpa_robust_depay_setcaps;
+  gstbasertpdepayload_class->process = gst_rtp_mpa_robust_depay_process;
+
+  GST_DEBUG_CATEGORY_INIT (rtpmparobustdepay_debug, "rtpmparobustdepay", 0,
+      "Robust MPEG Audio RTP Depayloader");
+}
+
+static void
+gst_rtp_mpa_robust_depay_init (GstRtpMPARobustDepay * rtpmpadepay,
+    GstRtpMPARobustDepayClass * klass)
+{
+  rtpmpadepay->adapter = gst_adapter_new ();
+  rtpmpadepay->adu_frames = g_queue_new ();
+}
+
+static gboolean
+gst_rtp_mpa_robust_depay_setcaps (GstBaseRTPDepayload * depayload,
+    GstCaps * caps)
+{
+  GstRtpMPARobustDepay *rtpmpadepay;
+  GstStructure *structure;
+  GstCaps *outcaps;
+  gint clock_rate, draft;
+  gboolean res;
+  const gchar *encoding;
+
+  rtpmpadepay = GST_RTP_MPA_ROBUST_DEPAY (depayload);
+
+  structure = gst_caps_get_structure (caps, 0);
+
+  if (!gst_structure_get_int (structure, "clock-rate", &clock_rate))
+    clock_rate = 90000;
+  depayload->clock_rate = clock_rate;
+
+  rtpmpadepay->has_descriptor = TRUE;
+  if ((encoding = gst_structure_get_string (structure, "encoding-name"))) {
+    if (sscanf (encoding, "X-MP3-DRAFT-%d", &draft) && (draft == 0))
+      rtpmpadepay->has_descriptor = FALSE;
+  }
+
+  outcaps =
+      gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 1, NULL);
+  res = gst_pad_set_caps (depayload->srcpad, outcaps);
+  gst_caps_unref (outcaps);
+
+  return res;
+}
+
+/* thanks again go to mp3parse ... */
+
+static const guint mp3types_bitrates[2][3][16] = {
+  {
+        {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448,},
+        {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,},
+        {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,}
+      },
+  {
+        {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,},
+        {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,},
+        {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,}
+      },
+};
+
+static const guint mp3types_freqs[3][3] = { {44100, 48000, 32000},
+{22050, 24000, 16000},
+{11025, 12000, 8000}
+};
+
+static inline guint
+mp3_type_frame_length_from_header (GstElement * mp3parse, guint32 header,
+    guint * put_version, guint * put_layer, guint * put_channels,
+    guint * put_bitrate, guint * put_samplerate, guint * put_mode,
+    guint * put_crc)
+{
+  guint length;
+  gulong mode, samplerate, bitrate, layer, channels, padding, crc;
+  gulong version;
+  gint lsf, mpg25;
+
+  if (header & (1 << 20)) {
+    lsf = (header & (1 << 19)) ? 0 : 1;
+    mpg25 = 0;
+  } else {
+    lsf = 1;
+    mpg25 = 1;
+  }
+
+  version = 1 + lsf + mpg25;
+
+  layer = 4 - ((header >> 17) & 0x3);
+
+  crc = (header >> 16) & 0x1;
+
+  bitrate = (header >> 12) & 0xF;
+  bitrate = mp3types_bitrates[lsf][layer - 1][bitrate] * 1000;
+  /* The caller has ensured we have a valid header, so bitrate can't be
+     zero here. */
+  if (bitrate == 0)
+    return 0;
+
+  samplerate = (header >> 10) & 0x3;
+  samplerate = mp3types_freqs[lsf + mpg25][samplerate];
+
+  padding = (header >> 9) & 0x1;
+
+  mode = (header >> 6) & 0x3;
+  channels = (mode == 3) ? 1 : 2;
+
+  switch (layer) {
+    case 1:
+      length = 4 * ((bitrate * 12) / samplerate + padding);
+      break;
+    case 2:
+      length = (bitrate * 144) / samplerate + padding;
+      break;
+    default:
+    case 3:
+      length = (bitrate * 144) / (samplerate << lsf) + padding;
+      break;
+  }
+
+  GST_LOG_OBJECT (mp3parse, "Calculated mp3 frame length of %u bytes", length);
+  GST_LOG_OBJECT (mp3parse, "samplerate = %lu, bitrate = %lu, version = %lu, "
+      "layer = %lu, channels = %lu, mode = %lu", samplerate, bitrate, version,
+      layer, channels, mode);
+
+  if (put_version)
+    *put_version = version;
+  if (put_layer)
+    *put_layer = layer;
+  if (put_channels)
+    *put_channels = channels;
+  if (put_bitrate)
+    *put_bitrate = bitrate;
+  if (put_samplerate)
+    *put_samplerate = samplerate;
+  if (put_mode)
+    *put_mode = mode;
+  if (put_crc)
+    *put_crc = crc;
+
+  return length;
+}
+
+/* generate empty/silent/dummy frame that mimics @frame,
+ * except for rate, where maximum possible is selected */
+static GstADUFrame *
+gst_rtp_mpa_robust_depay_generate_dummy_frame (GstRtpMPARobustDepay *
+    rtpmpadepay, GstADUFrame * frame)
+{
+  GstADUFrame *dummy;
+
+  dummy = g_slice_dup (GstADUFrame, frame);
+
+  /* go for maximum bitrate */
+  dummy->header = frame->header | (0xf << 12);
+  dummy->size =
+      mp3_type_frame_length_from_header (GST_ELEMENT_CAST (rtpmpadepay),
+      dummy->header, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+  dummy->data_size = dummy->size - dummy->side_info;
+  dummy->backpointer = 0;
+
+  dummy->buffer = gst_buffer_new_and_alloc (dummy->size);
+  memset (GST_BUFFER_DATA (dummy->buffer), 0, dummy->size);
+  GST_WRITE_UINT32_BE (GST_BUFFER_DATA (dummy->buffer), dummy->header);
+  GST_BUFFER_TIMESTAMP (dummy->buffer) = GST_BUFFER_TIMESTAMP (frame->buffer);
+
+  return dummy;
+}
+
+/* validates and parses @buf, and queues for further transformation if valid,
+ * otherwise discards @buf
+ * Takes ownership of @buf. */
+static gboolean
+gst_rtp_mpa_robust_depay_queue_frame (GstRtpMPARobustDepay * rtpmpadepay,
+    GstBuffer * buf)
+{
+  GstADUFrame *frame = NULL;
+  guint version, layer, channels, size;
+  guint crc;
+
+  g_return_val_if_fail (buf != NULL, FALSE);
+
+  if (GST_BUFFER_SIZE (buf) < 6) {
+    goto corrupt_frame;
+  }
+
+  frame = g_slice_new0 (GstADUFrame);
+  frame->header = GST_READ_UINT32_BE (GST_BUFFER_DATA (buf));
+
+  size = mp3_type_frame_length_from_header (GST_ELEMENT_CAST (rtpmpadepay),
+      frame->header, &version, &layer, &channels, NULL, NULL, NULL, &crc);
+  if (!size)
+    goto corrupt_frame;
+
+  frame->size = size;
+  frame->layer = layer;
+  if (version == 1 && channels == 2)
+    frame->side_info = 32;
+  else if ((version == 1 && channels == 1) || (version >= 2 && channels == 2))
+    frame->side_info = 17;
+  else if (version >= 2 && channels == 1)
+    frame->side_info = 9;
+  else {
+    g_assert_not_reached ();
+    goto corrupt_frame;
+  }
+
+  /* backpointer */
+  if (layer == 3) {
+    frame->backpointer = GST_READ_UINT16_BE (GST_BUFFER_DATA (buf) + 4);
+    frame->backpointer >>= 7;
+    GST_LOG_OBJECT (rtpmpadepay, "backpointer: %d", frame->backpointer);
+  }
+
+  if (crc)
+    frame->side_info += 2;
+
+  GST_LOG_OBJECT (rtpmpadepay, "side info: %d", frame->side_info);
+  frame->data_size = frame->size - 4 - frame->side_info;
+
+  /* some size validation checks */
+  if (4 + frame->side_info > GST_BUFFER_SIZE (buf))
+    goto corrupt_frame;
+
+  /* ADU data would then extend past MP3 frame,
+   * even using past byte reservoir */
+  if (-frame->backpointer + GST_BUFFER_SIZE (buf) > frame->size)
+    goto corrupt_frame;
+
+  /* ok, take buffer and queue */
+  frame->buffer = buf;
+  g_queue_push_tail (rtpmpadepay->adu_frames, frame);
+
+  return TRUE;
+
+  /* ERRORS */
+corrupt_frame:
+  {
+    GST_DEBUG_OBJECT (rtpmpadepay, "frame is corrupt");
+    gst_buffer_unref (buf);
+    if (frame)
+      g_slice_free (GstADUFrame, frame);
+    return FALSE;
+  }
+}
+
+static inline void
+gst_rtp_mpa_robust_depay_free_frame (GstADUFrame * frame)
+{
+  if (frame->buffer)
+    gst_buffer_unref (frame->buffer);
+  g_slice_free (GstADUFrame, frame);
+}
+
+static inline void
+gst_rtp_mpa_robust_depay_dequeue_frame (GstRtpMPARobustDepay * rtpmpadepay)
+{
+  GstADUFrame *head;
+
+  GST_LOG_OBJECT (rtpmpadepay, "dequeueing ADU frame");
+
+  if (rtpmpadepay->adu_frames->head == rtpmpadepay->cur_adu_frame)
+    rtpmpadepay->cur_adu_frame = NULL;
+
+  head = g_queue_pop_head (rtpmpadepay->adu_frames);
+  g_assert (head->buffer);
+  gst_rtp_mpa_robust_depay_free_frame (head);
+
+  return;
+}
+
+/* returns TRUE if at least one new ADU frame was enqueued for MP3 conversion.
+ * Takes ownership of @buf. */
+static gboolean
+gst_rtp_mpa_robust_depay_deinterleave (GstRtpMPARobustDepay * rtpmpadepay,
+    GstBuffer * buf)
+{
+  gboolean ret = FALSE;
+  guint8 *data;
+  guint val, iindex, icc;
+
+  data = GST_BUFFER_DATA (buf);
+  val = GST_READ_UINT16_BE (data) >> 5;
+  iindex = val >> 3;
+  icc = val & 0x7;
+
+  GST_LOG_OBJECT (rtpmpadepay, "sync: 0x%x, index: %u, cycle count: %u",
+      val, iindex, icc);
+
+  /* basic case; no interleaving ever seen */
+  if (val == 0x7ff && rtpmpadepay->last_icc < 0) {
+    ret = gst_rtp_mpa_robust_depay_queue_frame (rtpmpadepay, buf);
+  } else {
+    if (G_UNLIKELY (rtpmpadepay->last_icc < 0)) {
+      rtpmpadepay->last_icc = icc;
+      rtpmpadepay->last_ii = iindex;
+    }
+    if (icc != rtpmpadepay->last_icc || iindex == rtpmpadepay->last_ii) {
+      gint i;
+
+      for (i = 0; i < 256; ++i) {
+        if (rtpmpadepay->deinter[i] != NULL) {
+          ret |= gst_rtp_mpa_robust_depay_queue_frame (rtpmpadepay,
+              rtpmpadepay->deinter[i]);
+          rtpmpadepay->deinter[i] = NULL;
+        }
+      }
+    }
+    /* rewrite buffer sync header */
+    val = GST_READ_UINT16_BE (buf);
+    val = (0x7ff << 5) | val;
+    GST_WRITE_UINT16_BE (buf, val);
+    /* store and keep track of last indices */
+    rtpmpadepay->last_icc = icc;
+    rtpmpadepay->last_ii = iindex;
+    rtpmpadepay->deinter[iindex] = buf;
+  }
+
+  return ret;
+}
+
+/* Head ADU frame corresponds to mp3_frame (i.e. in header in side-info) that
+ * is currently being written
+ * cur_adu_frame refers to ADU frame whose data should be bytewritten next
+ * (possibly starting from offset rather than start 0) (and is typicall tail
+ * at time of last push round).
+ * If at start, position where it should start writing depends on (data) sizes
+ * of previous mp3 frames (corresponding to foregoing ADU frames) kept in size,
+ * and its backpointer */
+static GstFlowReturn
+gst_rtp_mpa_robust_depay_push_mp3_frames (GstRtpMPARobustDepay * rtpmpadepay)
+{
+  GstBuffer *buf;
+  GstADUFrame *frame, *head;
+  gint av;
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  while (1) {
+
+    if (G_UNLIKELY (!rtpmpadepay->cur_adu_frame)) {
+      rtpmpadepay->cur_adu_frame = rtpmpadepay->adu_frames->head;
+      rtpmpadepay->offset = 0;
+      rtpmpadepay->size = 0;
+    }
+
+    if (G_UNLIKELY (!rtpmpadepay->cur_adu_frame))
+      break;
+
+    frame = (GstADUFrame *) rtpmpadepay->cur_adu_frame->data;
+    head = (GstADUFrame *) rtpmpadepay->adu_frames->head->data;
+
+    /* special case: non-layer III are sent straight through */
+    if (G_UNLIKELY (frame->layer != 3)) {
+      GST_DEBUG_OBJECT (rtpmpadepay, "layer %d frame, sending as-is",
+          frame->layer);
+      gst_base_rtp_depayload_push (GST_BASE_RTP_DEPAYLOAD (rtpmpadepay),
+          frame->buffer);
+      frame->buffer = NULL;
+      /* and remove it from any further consideration */
+      g_slice_free (GstADUFrame, frame);
+      g_queue_delete_link (rtpmpadepay->adu_frames, rtpmpadepay->cur_adu_frame);
+      rtpmpadepay->cur_adu_frame = NULL;
+      continue;
+    }
+
+    if (rtpmpadepay->offset == GST_BUFFER_SIZE (frame->buffer)) {
+      if (g_list_next (rtpmpadepay->cur_adu_frame)) {
+        GST_LOG_OBJECT (rtpmpadepay,
+            "moving to next ADU frame, size %d, side_info %d",
+            frame->size, frame->side_info);
+        rtpmpadepay->size += frame->data_size;
+        rtpmpadepay->cur_adu_frame = g_list_next (rtpmpadepay->cur_adu_frame);
+        frame = (GstADUFrame *) rtpmpadepay->cur_adu_frame->data;
+        rtpmpadepay->offset = 0;
+        /* layer I and II packets have no bitreservoir and must be sent as-is;
+         * so flush any pending frame */
+        if (G_UNLIKELY (frame->layer != 3 && rtpmpadepay->mp3_frame))
+          goto flush;
+      } else {
+        break;
+      }
+    }
+
+    if (G_UNLIKELY (!rtpmpadepay->mp3_frame)) {
+      GST_LOG_OBJECT (rtpmpadepay,
+          "setting up new MP3 frame of size %d, side_info %d",
+          head->size, head->side_info);
+      rtpmpadepay->mp3_frame = gst_byte_writer_new_with_size (head->size, TRUE);
+      /* 0-fill possible gaps */
+      gst_byte_writer_fill (rtpmpadepay->mp3_frame, 0, head->size);
+      gst_byte_writer_set_pos (rtpmpadepay->mp3_frame, 0);
+      /* bytewriter corresponds to head frame,
+       * i.e. the header and the side info must match */
+      gst_byte_writer_put_data (rtpmpadepay->mp3_frame,
+          GST_BUFFER_DATA (head->buffer), 4 + head->side_info);
+    }
+
+    buf = frame->buffer;
+    av = gst_byte_writer_get_remaining (rtpmpadepay->mp3_frame);
+    GST_LOG_OBJECT (rtpmpadepay, "current mp3 frame remaining: %d", av);
+    GST_LOG_OBJECT (rtpmpadepay, "accumulated ADU frame data_size: %d",
+        rtpmpadepay->size);
+
+    if (rtpmpadepay->offset) {
+      /* no need to position, simply append */
+      g_assert (GST_BUFFER_SIZE (buf) > rtpmpadepay->offset);
+      av = MIN (av, GST_BUFFER_SIZE (buf) - rtpmpadepay->offset);
+      GST_LOG_OBJECT (rtpmpadepay,
+          "appending %d bytes from ADU frame at offset %d", av,
+          rtpmpadepay->offset);
+      gst_byte_writer_put_data (rtpmpadepay->mp3_frame,
+          GST_BUFFER_DATA (buf) + rtpmpadepay->offset, av);
+      rtpmpadepay->offset += av;
+    } else {
+      gint pos, tpos;
+
+      /* position writing according to ADU frame backpointer */
+      pos = gst_byte_writer_get_pos (rtpmpadepay->mp3_frame);
+      tpos = rtpmpadepay->size - frame->backpointer + 4 + head->side_info;
+      GST_LOG_OBJECT (rtpmpadepay, "current MP3 frame at position %d, "
+          "starting new ADU frame data at offset %d", pos, tpos);
+      if (tpos < pos) {
+        GstADUFrame *dummy;
+
+        /* try to insert as few frames as possible,
+         * so go for a reasonably large dummy frame size */
+        GST_LOG_OBJECT (rtpmpadepay,
+            "overlapping previous data; inserting dummy frame");
+        dummy =
+            gst_rtp_mpa_robust_depay_generate_dummy_frame (rtpmpadepay, frame);
+        g_queue_insert_before (rtpmpadepay->adu_frames,
+            rtpmpadepay->cur_adu_frame, dummy);
+        /* offset is known to be zero, so we can shift current one */
+        rtpmpadepay->cur_adu_frame = rtpmpadepay->cur_adu_frame->prev;
+        /* ... and continue adding that empty one immediately,
+         * and then see if that provided enough extra space */
+        continue;
+      } else if (tpos >= pos + av) {
+        /* ADU frame no longer needs current MP3 frame; move to its end */
+        GST_LOG_OBJECT (rtpmpadepay, "passed current MP3 frame");
+        gst_byte_writer_set_pos (rtpmpadepay->mp3_frame, pos + av);
+      } else {
+        /* position and append */
+        GST_LOG_OBJECT (rtpmpadepay, "adding to current MP3 frame");
+        gst_byte_writer_set_pos (rtpmpadepay->mp3_frame, tpos);
+        av = MIN (av, GST_BUFFER_SIZE (buf) - 4 - frame->side_info);
+        gst_byte_writer_put_data (rtpmpadepay->mp3_frame,
+            GST_BUFFER_DATA (buf) + 4 + frame->side_info, av);
+        rtpmpadepay->offset += av + 4 + frame->side_info;
+      }
+    }
+
+    /* if mp3 frame filled, send on its way */
+    if (gst_byte_writer_get_remaining (rtpmpadepay->mp3_frame) == 0) {
+    flush:
+      buf = gst_byte_writer_free_and_get_buffer (rtpmpadepay->mp3_frame);
+      rtpmpadepay->mp3_frame = NULL;
+      GST_BUFFER_TIMESTAMP (buf) = GST_BUFFER_TIMESTAMP (head->buffer);
+      /* no longer need head ADU frame header and side info */
+      /* NOTE maybe head == current, then size and offset go off a bit,
+       * but current gets reset to NULL, and then also offset and size */
+      rtpmpadepay->size -= head->data_size;
+      gst_rtp_mpa_robust_depay_dequeue_frame (rtpmpadepay);
+      /* send */
+      ret = gst_base_rtp_depayload_push (GST_BASE_RTP_DEPAYLOAD (rtpmpadepay),
+          buf);
+    }
+  }
+
+  return ret;
+}
+
+/* process ADU frame @buf through:
+ * - deinterleaving
+ * - converting to MP3 frames
+ * Takes ownership of @buf.
+ */
+static GstFlowReturn
+gst_rtp_mpa_robust_depay_submit_adu (GstRtpMPARobustDepay * rtpmpadepay,
+    GstBuffer * buf)
+{
+  if (gst_rtp_mpa_robust_depay_deinterleave (rtpmpadepay, buf))
+    return gst_rtp_mpa_robust_depay_push_mp3_frames (rtpmpadepay);
+
+  return GST_FLOW_OK;
+}
+
+static GstBuffer *
+gst_rtp_mpa_robust_depay_process (GstBaseRTPDepayload * depayload,
+    GstBuffer * buf)
+{
+  GstRtpMPARobustDepay *rtpmpadepay;
+  gint payload_len, offset;
+  guint8 *payload;
+  gboolean cont, dtype;
+  guint av, size;
+  GstClockTime timestamp;
+
+  rtpmpadepay = GST_RTP_MPA_ROBUST_DEPAY (depayload);
+
+  payload_len = gst_rtp_buffer_get_payload_len (buf);
+  timestamp = GST_BUFFER_TIMESTAMP (buf);
+
+  if (payload_len <= 1)
+    goto short_read;
+
+  payload = gst_rtp_buffer_get_payload (buf);
+  offset = 0;
+  GST_LOG_OBJECT (rtpmpadepay, "payload_len: %d", payload_len);
+
+  /* strip off descriptor
+   *
+   *  0                   1
+   *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
+   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   * |C|T|            ADU size         |
+   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   *
+   * C: if 1, data is continuation
+   * T: if 1, size is 14 bits, otherwise 6 bits
+   * ADU size: size of following packet (not including descriptor)
+   */
+  while (payload_len) {
+    if (G_LIKELY (rtpmpadepay->has_descriptor)) {
+      cont = !!(payload[offset] & 0x80);
+      dtype = !!(payload[offset] & 0x40);
+      if (dtype) {
+        size = (payload[offset] & 0x3f) << 8 | payload[offset + 1];
+        payload_len--;
+        offset++;
+      } else if (payload_len >= 2) {
+        size = (payload[offset] & 0x3f);
+        payload_len -= 2;
+        offset += 2;
+      } else {
+        goto short_read;
+      }
+    } else {
+      cont = FALSE;
+      dtype = -1;
+      size = payload_len;
+    }
+
+    GST_LOG_OBJECT (rtpmpadepay, "offset %d has cont: %d, dtype: %d, size: %d",
+        offset, cont, dtype, size);
+
+    buf = gst_rtp_buffer_get_payload_subbuffer (buf, offset,
+        MIN (size, payload_len));
+
+    if (cont) {
+      av = gst_adapter_available (rtpmpadepay->adapter);
+      if (G_UNLIKELY (!av)) {
+        GST_DEBUG_OBJECT (rtpmpadepay,
+            "discarding continuation fragment without prior fragment");
+        gst_buffer_unref (buf);
+      } else {
+        av += GST_BUFFER_SIZE (buf);
+        gst_adapter_push (rtpmpadepay->adapter, buf);
+        if (av == size) {
+          timestamp = gst_adapter_prev_timestamp (rtpmpadepay->adapter, NULL);
+          buf = gst_adapter_take_buffer (rtpmpadepay->adapter, size);
+          GST_BUFFER_TIMESTAMP (buf) = timestamp;
+          gst_rtp_mpa_robust_depay_submit_adu (rtpmpadepay, buf);
+        } else if (av > size) {
+          GST_DEBUG_OBJECT (rtpmpadepay,
+              "assembled ADU size %d larger than expected %d; discarding",
+              av, size);
+          gst_adapter_clear (rtpmpadepay->adapter);
+        }
+      }
+      size = payload_len;
+    } else {
+      /* not continuation, first fragment or whole ADU */
+      if (payload_len == size) {
+        /* whole ADU */
+        GST_BUFFER_TIMESTAMP (buf) = timestamp;
+        gst_rtp_mpa_robust_depay_submit_adu (rtpmpadepay, buf);
+      } else if (payload_len < size) {
+        /* first fragment */
+        gst_adapter_push (rtpmpadepay->adapter, buf);
+        size = payload_len;
+      }
+    }
+
+    offset += size;
+    payload_len -= size;
+
+    /* timestamp applies to first payload, no idea for subsequent ones */
+    timestamp = GST_CLOCK_TIME_NONE;
+  }
+
+  return NULL;
+
+  /* ERRORS */
+short_read:
+  {
+    GST_ELEMENT_WARNING (rtpmpadepay, STREAM, DECODE,
+        (NULL), ("Packet contains invalid data"));
+    return NULL;
+  }
+}
+
+static GstStateChangeReturn
+gst_rtp_mpa_robust_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstStateChangeReturn ret;
+  GstRtpMPARobustDepay *rtpmpadepay;
+
+  rtpmpadepay = GST_RTP_MPA_ROBUST_DEPAY (element);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      rtpmpadepay->last_ii = -1;
+      rtpmpadepay->last_icc = -1;
+      rtpmpadepay->size = 0;
+      rtpmpadepay->offset = 0;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+  if (ret != GST_STATE_CHANGE_SUCCESS)
+    return ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+    {
+      gint i;
+
+      gst_adapter_clear (rtpmpadepay->adapter);
+      for (i = 0; i < G_N_ELEMENTS (rtpmpadepay->deinter); i++) {
+        gst_buffer_unref (rtpmpadepay->deinter[i]);
+        rtpmpadepay->deinter[i] = NULL;
+      }
+      rtpmpadepay->cur_adu_frame = NULL;
+      g_queue_foreach (rtpmpadepay->adu_frames,
+          (GFunc) gst_rtp_mpa_robust_depay_free_frame, NULL);
+      g_queue_clear (rtpmpadepay->adu_frames);
+      break;
+    }
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+gboolean
+gst_rtp_mpa_robust_depay_plugin_init (GstPlugin * plugin)
+{
+  return gst_element_register (plugin, "rtpmparobustdepay",
+      GST_RANK_MARGINAL, GST_TYPE_RTP_MPA_ROBUST_DEPAY);
+}
diff --git a/gst/rtp/gstrtpmparobustdepay.h b/gst/rtp/gstrtpmparobustdepay.h
new file mode 100644 (file)
index 0000000..8ef5af1
--- /dev/null
@@ -0,0 +1,78 @@
+/* GStreamer
+ * Copyright (C) <2010> Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
+ * Copyright (C) <2010> Nokia Corporation
+ *
+ * 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.
+ */
+
+#ifndef __GST_RTP_MPA_ROBUST_DEPAY_H__
+#define __GST_RTP_MPA_ROBUST_DEPAY_H__
+
+#include <gst/gst.h>
+#include <gst/rtp/gstbasertpdepayload.h>
+#include <gst/base/gstadapter.h>
+#include <gst/base/gstbytewriter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_RTP_MPA_ROBUST_DEPAY \
+  (gst_rtp_mpa_robust_depay_get_type())
+#define GST_RTP_MPA_ROBUST_DEPAY(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_MPA_ROBUST_DEPAY,GstRtpMPARobustDepay))
+#define GST_RTP_MPA_ROBUST_DEPAY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_MPA_ROBUST_DEPAY,GstRtpMPARobustDepayClass))
+#define GST_IS_RTP_MPA_ROBUST_DEPAY(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_MPA_ROBUST_DEPAY))
+#define GST_IS_RTP_MPA_ROBUST_DEPAY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_MPA_ROBUST_DEPAY))
+
+typedef struct _GstRtpMPARobustDepay GstRtpMPARobustDepay;
+typedef struct _GstRtpMPARobustDepayClass GstRtpMPARobustDepayClass;
+
+struct _GstRtpMPARobustDepay
+{
+  GstBaseRTPDepayload depayload;
+
+  GstAdapter *adapter;
+  gboolean    has_descriptor;
+
+  /* last interleave index */
+  gint        last_ii;
+  /* last interleave cycle count */
+  gint        last_icc;
+  /* buffers pending deinterleaving */
+  GstBuffer   *deinter[256];
+
+  /* ADU buffers pending MP3 transformation */
+  GQueue      *adu_frames;
+  GList       *cur_adu_frame;
+  gint         offset;
+  gint         size;
+  GstByteWriter *mp3_frame;
+};
+
+struct _GstRtpMPARobustDepayClass
+{
+  GstBaseRTPDepayloadClass parent_class;
+};
+
+GType gst_rtp_mpa_robust_depay_get_type (void);
+
+gboolean gst_rtp_mpa_robust_depay_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+
+#endif /* __GST_RTP_MPA_ROBUST_DEPAY_H__ */