gst/realmedia/: Add demuxer for RealAudio files (#349779).
authorTim-Philipp Müller <tim@centricular.net>
Thu, 11 Jan 2007 12:49:23 +0000 (12:49 +0000)
committerTim-Philipp Müller <tim@centricular.net>
Thu, 11 Jan 2007 12:49:23 +0000 (12:49 +0000)
Original commit message from CVS:
* gst/realmedia/Makefile.am:
* gst/realmedia/rademux.c:
* gst/realmedia/rademux.h:
* gst/realmedia/rmdemux.c: (gst_rmdemux_add_stream),
(gst_rmdemux_descramble_dnet_audio), (gst_rmdemux_plugin_init):
* gst/realmedia/rmutils.c: (gst_rm_utils_descramble_dnet_buffer):
* gst/realmedia/rmutils.h:
Add demuxer for RealAudio files (#349779).

ChangeLog
common
gst/realmedia/Makefile.am
gst/realmedia/rademux.c [new file with mode: 0644]
gst/realmedia/rademux.h [new file with mode: 0644]
gst/realmedia/rmdemux.c
gst/realmedia/rmutils.c
gst/realmedia/rmutils.h

index d473589928462c9d741f9a845ee13935c375453d..b6bfd3f259b451f7a9340b816464eda1a637471a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2007-01-11  Tim-Philipp Müller  <tim at centricular dot net>
+
+       * gst/realmedia/Makefile.am:
+       * gst/realmedia/rademux.c:
+       * gst/realmedia/rademux.h:
+       * gst/realmedia/rmdemux.c: (gst_rmdemux_add_stream),
+       (gst_rmdemux_descramble_dnet_audio), (gst_rmdemux_plugin_init):
+       * gst/realmedia/rmutils.c: (gst_rm_utils_descramble_dnet_buffer):
+       * gst/realmedia/rmutils.h:
+         Add demuxer for RealAudio files (#349779).
+
 2007-01-07  Sébastien Moutte  <sebastien@moutte.net>
        
        * Makefile.am:
diff --git a/common b/common
index 64f924f6f2ff6275b06facb4c2adbc7c05f70641..8ba5dffb5ee7e7daea1030f6b34bfef10f9801a3 160000 (submodule)
--- a/common
+++ b/common
@@ -1 +1 @@
-Subproject commit 64f924f6f2ff6275b06facb4c2adbc7c05f70641
+Subproject commit 8ba5dffb5ee7e7daea1030f6b34bfef10f9801a3
index d0c6d587a70420c4e4a4a75593a22deb28121c40..2395c6f0707d190b4a4cecd94d0b6db3387eebd9 100644 (file)
@@ -1,9 +1,9 @@
 plugin_LTLIBRARIES = libgstrmdemux.la
 
-libgstrmdemux_la_SOURCES = rmdemux.c rmutils.c rdtdepay.c 
+libgstrmdemux_la_SOURCES = rademux.c rmdemux.c rmutils.c rdtdepay.c 
 
 libgstrmdemux_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS)
 libgstrmdemux_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS)
 libgstrmdemux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 
-noinst_HEADERS = rmdemux.h rmutils.h rdtdepay.h
+noinst_HEADERS = rademux.h rmdemux.h rmutils.h rdtdepay.h
diff --git a/gst/realmedia/rademux.c b/gst/realmedia/rademux.c
new file mode 100644 (file)
index 0000000..71a0a8c
--- /dev/null
@@ -0,0 +1,973 @@
+/* GStreamer RealAudio demuxer
+ * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
+ *
+ * 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-rademux
+ *
+ * <refsect2>
+ * <para>
+ * Demuxes/parses a RealAudio (.ra) file or stream into compressed audio.
+ * </para>
+ * <title>Example launch line</title>
+ * <para>
+ * <programlisting>
+ * gst-launch filesrc location=interview.ra ! rademux ! ffdec_real_288 ! audioconvert ! audioresample ! alsasink
+ * </programlisting>
+ * Read a RealAudio file and decode it and output it to the soundcard using
+ * the ALSA element. The .ra file is assumed to contain RealAudio version 2.
+ * </para>
+ * <para>
+ * <programlisting>
+ * gst-launch gnomevfssrc location=http://www.example.org/interview.ra ! rademux ! a52dec ! audioconvert ! audioresample ! alsasink
+ * </programlisting>
+ * Stream RealAudio data containing AC3 (dnet) compressed audio and decode it
+ * and output it to the soundcard using the ALSA element.
+ * </para>
+ * </refsect2>
+ *
+ * Last reviewed on 2006-10-24 (0.10.5)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "rademux.h"
+#include "rmdemux.h"
+#include "rmutils.h"
+
+#include <string.h>
+
+static GstElementDetails real_audio_demux_details = {
+  "RealAudio Demuxer",
+  "Codec/Demuxer",
+  "Demultiplex a RealAudio file",
+  "Tim-Philipp Müller <tim centricular net>"
+};
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("application/x-pn-realaudio")
+    );
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (real_audio_demux_debug);
+#define GST_CAT_DEFAULT real_audio_demux_debug
+
+GST_BOILERPLATE (GstRealAudioDemux, gst_real_audio_demux, GstElement,
+    GST_TYPE_ELEMENT);
+
+static GstStateChangeReturn gst_real_audio_demux_change_state (GstElement * e,
+    GstStateChange transition);
+static GstFlowReturn gst_real_audio_demux_chain (GstPad * pad, GstBuffer * buf);
+static gboolean gst_real_audio_demux_sink_event (GstPad * pad, GstEvent * ev);
+static gboolean gst_real_audio_demux_src_event (GstPad * pad, GstEvent * ev);
+static gboolean gst_real_audio_demux_src_query (GstPad * pad, GstQuery * query);
+static void gst_real_audio_demux_loop (GstRealAudioDemux * demux);
+static gboolean gst_real_audio_demux_sink_activate (GstPad * sinkpad);
+static gboolean gst_real_audio_demux_sink_activate_push (GstPad * sinkpad,
+    gboolean active);
+static gboolean gst_real_audio_demux_sink_activate_pull (GstPad * sinkpad,
+    gboolean active);
+
+static void
+gst_real_audio_demux_base_init (gpointer klass)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&sink_template));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&src_template));
+  gst_element_class_set_details (element_class, &real_audio_demux_details);
+
+  GST_DEBUG_CATEGORY_INIT (real_audio_demux_debug, "rademux",
+      0, "Demuxer for RealAudio streams");
+}
+
+static void
+gst_real_audio_demux_finalize (GObject * obj)
+{
+  GstRealAudioDemux *demux = GST_REAL_AUDIO_DEMUX (obj);
+
+  g_object_unref (demux->adapter);
+
+  G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+gst_real_audio_demux_class_init (GstRealAudioDemuxClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  GstElementClass *gstelement_class = (GstElementClass *) klass;
+
+  gobject_class->finalize = gst_real_audio_demux_finalize;
+
+  gstelement_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_real_audio_demux_change_state);
+}
+
+static void
+gst_real_audio_demux_reset (GstRealAudioDemux * demux)
+{
+  gst_adapter_clear (demux->adapter);
+
+  if (demux->srcpad) {
+    GST_DEBUG_OBJECT (demux, "Removing source pad");
+    gst_element_remove_pad (GST_ELEMENT (demux), demux->srcpad);
+    demux->srcpad = NULL;
+  }
+
+  if (demux->pending_tags) {
+    gst_tag_list_free (demux->pending_tags);
+    demux->pending_tags = NULL;
+  }
+
+  demux->state = REAL_AUDIO_DEMUX_STATE_MARKER;
+  demux->ra_version = 0;
+  demux->data_offset = 0;
+  demux->packet_size = 0;
+
+  demux->sample_rate = 0;
+  demux->sample_width = 0;
+  demux->channels = 0;
+  demux->fourcc = 0;
+
+  demux->need_newsegment = TRUE;
+
+  demux->segment_running = FALSE;
+
+  demux->byterate_num = 0;
+  demux->byterate_denom = 0;
+
+  demux->duration = 0;
+  demux->upstream_size = 0;
+
+  demux->offset = 0;
+
+  gst_adapter_clear (demux->adapter);
+}
+
+static void
+gst_real_audio_demux_init (GstRealAudioDemux * demux,
+    GstRealAudioDemuxClass * klass)
+{
+  demux->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
+
+  gst_pad_set_chain_function (demux->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_real_audio_demux_chain));
+  gst_pad_set_event_function (demux->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_real_audio_demux_sink_event));
+  gst_pad_set_activate_function (demux->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_real_audio_demux_sink_activate));
+  gst_pad_set_activatepull_function (demux->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_real_audio_demux_sink_activate_pull));
+  gst_pad_set_activatepush_function (demux->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_real_audio_demux_sink_activate_push));
+
+  gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad);
+
+  demux->adapter = gst_adapter_new ();
+  gst_real_audio_demux_reset (demux);
+}
+
+static gboolean
+gst_real_audio_demux_sink_activate (GstPad * sinkpad)
+{
+  if (gst_pad_check_pull_range (sinkpad)) {
+    return gst_pad_activate_pull (sinkpad, TRUE);
+  } else {
+    return gst_pad_activate_push (sinkpad, TRUE);
+  }
+}
+
+static gboolean
+gst_real_audio_demux_sink_activate_push (GstPad * sinkpad, gboolean active)
+{
+  GstRealAudioDemux *demux;
+
+  demux = GST_REAL_AUDIO_DEMUX (GST_OBJECT_PARENT (sinkpad));
+
+  demux->seekable = FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_real_audio_demux_sink_activate_pull (GstPad * sinkpad, gboolean active)
+{
+  GstRealAudioDemux *demux;
+
+  demux = GST_REAL_AUDIO_DEMUX (GST_OBJECT_PARENT (sinkpad));
+
+  if (active) {
+    demux->seekable = TRUE;
+
+    return gst_pad_start_task (sinkpad,
+        (GstTaskFunction) gst_real_audio_demux_loop, demux);
+  } else {
+    demux->seekable = FALSE;
+    return gst_pad_stop_task (sinkpad);
+  }
+}
+
+static GstFlowReturn
+gst_real_audio_demux_parse_marker (GstRealAudioDemux * demux)
+{
+  const guint8 *data;
+
+  if (gst_adapter_available (demux->adapter) < 6) {
+    GST_LOG_OBJECT (demux, "need at least 6 bytes, waiting for more data");
+    return GST_FLOW_OK;
+  }
+
+  data = gst_adapter_peek (demux->adapter, 6);
+  if (memcmp (data, ".ra\375", 4) != 0)
+    goto wrong_format;
+
+  demux->ra_version = GST_READ_UINT16_BE (data + 4);
+  GST_DEBUG_OBJECT (demux, "ra_version   = %u", demux->ra_version);
+  if (demux->ra_version != 4 && demux->ra_version != 3)
+    goto unsupported_ra_version;
+
+  gst_adapter_flush (demux->adapter, 6);
+  demux->state = REAL_AUDIO_DEMUX_STATE_HEADER;
+  return GST_FLOW_OK;
+
+/* ERRORS */
+wrong_format:
+  {
+    GST_ELEMENT_ERROR (GST_ELEMENT (demux), STREAM, WRONG_TYPE, (NULL), (NULL));
+    return GST_FLOW_ERROR;
+  }
+
+unsupported_ra_version:
+  {
+    GST_ELEMENT_ERROR (GST_ELEMENT (demux), STREAM, DECODE,
+        ("Cannot decode this RealAudio file, please file a bug"),
+        ("ra_version = %u", demux->ra_version));
+    return GST_FLOW_ERROR;
+  }
+}
+
+static GstClockTime
+gst_real_demux_get_timestamp_from_offset (GstRealAudioDemux * demux,
+    guint64 offset)
+{
+  if (offset >= demux->data_offset && demux->byterate_num > 0 &&
+      demux->byterate_denom > 0) {
+    return gst_util_uint64_scale (offset - demux->data_offset,
+        demux->byterate_denom * GST_SECOND, demux->byterate_num);
+  } else if (offset == demux->data_offset) {
+    return (GstClockTime) 0;
+  } else {
+    return GST_CLOCK_TIME_NONE;
+  }
+}
+
+static gboolean
+gst_real_audio_demux_get_data_offset_from_header (GstRealAudioDemux * demux)
+{
+  const guint8 *data;
+
+  data = gst_adapter_peek (demux->adapter, 16);
+  g_assert (data != NULL);
+
+  switch (demux->ra_version) {
+    case 3:
+      demux->data_offset = GST_READ_UINT16_BE (data) + 8;
+      break;
+    case 4:
+      demux->data_offset = GST_READ_UINT32_BE (data + 12) + 16;
+      break;
+    default:
+      demux->data_offset = 0;
+      g_return_val_if_reached (FALSE);
+  }
+
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_real_audio_demux_parse_header (GstRealAudioDemux * demux)
+{
+  const gchar *codec_name = NULL;
+  const guint8 *data;
+  GstCaps *caps = NULL;
+  guint avail;
+
+  g_assert (demux->ra_version == 4 || demux->ra_version == 3);
+
+  avail = gst_adapter_available (demux->adapter);
+  if (avail < 16)
+    return GST_FLOW_OK;
+
+  if (!gst_real_audio_demux_get_data_offset_from_header (demux))
+    return GST_FLOW_ERROR;      /* shouldn't happen */
+
+  GST_DEBUG_OBJECT (demux, "data_offset  = %u", demux->data_offset);
+
+  if (avail + 6 < demux->data_offset) {
+    GST_DEBUG_OBJECT (demux, "Need %u bytes, but only %u available now",
+        demux->data_offset - 6, avail);
+    return GST_FLOW_OK;
+  }
+
+  data = gst_adapter_peek (demux->adapter, demux->data_offset - 6);
+  g_assert (data);
+
+  switch (demux->ra_version) {
+    case 3:
+      demux->fourcc = GST_RM_AUD_14_4;
+      demux->packet_size = 20;
+      demux->sample_rate = 8000;
+      demux->channels = 1;
+      demux->sample_width = 16;
+      demux->flavour = 1;
+      demux->leaf_size = 0;
+      demux->height = 0;
+      break;
+    case 4:
+      demux->flavour = GST_READ_UINT16_BE (data + 16);
+      /* demux->frame_size = GST_READ_UINT32_BE (data + 36); */
+      demux->leaf_size = GST_READ_UINT16_BE (data + 38);
+      demux->height = GST_READ_UINT16_BE (data + 34);
+      demux->packet_size = GST_READ_UINT32_BE (data + 18);
+      demux->sample_rate = GST_READ_UINT16_BE (data + 42);
+      demux->sample_width = GST_READ_UINT16_BE (data + 46);
+      demux->channels = GST_READ_UINT16_BE (data + 48);
+      demux->fourcc = GST_READ_UINT32_LE (data + 56);
+      demux->pending_tags = gst_rm_utils_read_tags (data + 63,
+          demux->data_offset - 63, gst_rm_utils_read_string8);
+      break;
+    default:
+      g_assert_not_reached ();
+#if 0
+    case 5:
+      demux->flavour = GST_READ_UINT16_BE (data + 16);
+      /* demux->frame_size = GST_READ_UINT32_BE (data + 36); */
+      demux->leaf_size = GST_READ_UINT16_BE (data + 38);
+      demux->height = GST_READ_UINT16_BE (data + 34);
+
+      demux->sample_rate = GST_READ_UINT16_BE (data + 48);
+      demux->sample_width = GST_READ_UINT16_BE (data + 52);
+      demux->n_channels = GST_READ_UINT16_BE (data + 54);
+      demux->fourcc = RMDEMUX_FOURCC_GET (data + 60);
+      break;
+#endif
+  }
+
+  GST_INFO_OBJECT (demux, "packet_size  = %u", demux->packet_size);
+  GST_INFO_OBJECT (demux, "sample_rate  = %u", demux->sample_rate);
+  GST_INFO_OBJECT (demux, "sample_width = %u", demux->sample_width);
+  GST_INFO_OBJECT (demux, "channels     = %u", demux->channels);
+  GST_INFO_OBJECT (demux, "fourcc       = '%" GST_FOURCC_FORMAT "' (%08X)",
+      GST_FOURCC_ARGS (demux->fourcc), demux->fourcc);
+
+  switch (demux->fourcc) {
+    case GST_RM_AUD_14_4:
+      caps = gst_caps_new_simple ("audio/x-pn-realaudio", "raversion",
+          G_TYPE_INT, 1, NULL);
+      codec_name = "Real Audio 14.4kbps";
+      demux->byterate_num = 1000;
+      demux->byterate_denom = 1;
+      break;
+
+    case GST_RM_AUD_28_8:
+      /* FIXME: needs descrambling */
+      caps = gst_caps_new_simple ("audio/x-pn-realaudio", "raversion",
+          G_TYPE_INT, 2, NULL);
+      codec_name = "Real Audio 28.8kbps";
+      break;
+
+    case GST_RM_AUD_DNET:
+      codec_name = "AC-3 audio";
+      caps = gst_caps_new_simple ("audio/x-ac3", "rate", G_TYPE_INT,
+          demux->sample_rate, NULL);
+      if (demux->packet_size == 0 || demux->sample_rate == 0)
+        goto broken_file;
+      demux->byterate_num = demux->packet_size * demux->sample_rate;
+      demux->byterate_denom = 1536;
+      break;
+
+      /* Sipro/ACELP.NET Voice Codec (MIME unknown) */
+    case GST_RM_AUD_SIPR:
+      codec_name = "Sipro Voice";
+      caps = gst_caps_new_simple ("audio/x-sipro", NULL);
+      break;
+
+    default:
+      GST_WARNING_OBJECT (demux, "unknown fourcc %08X", demux->fourcc);
+      break;
+  }
+
+  if (caps == NULL)
+    goto unknown_fourcc;
+
+  gst_caps_set_simple (caps,
+      "flavor", G_TYPE_INT, demux->flavour,
+      "rate", G_TYPE_INT, demux->sample_rate,
+      "channels", G_TYPE_INT, demux->channels,
+      "width", G_TYPE_INT, demux->sample_width,
+      "leaf_size", G_TYPE_INT, demux->leaf_size,
+      "packet_size", G_TYPE_INT, demux->packet_size,
+      "height", G_TYPE_INT, demux->height, NULL);
+
+  GST_INFO_OBJECT (demux, "Adding source pad, caps %" GST_PTR_FORMAT, caps);
+  demux->srcpad = gst_pad_new_from_static_template (&src_template, "src");
+  gst_pad_use_fixed_caps (demux->srcpad);
+  gst_pad_set_caps (demux->srcpad, caps);
+  gst_caps_unref (caps);
+  gst_pad_set_event_function (demux->srcpad,
+      GST_DEBUG_FUNCPTR (gst_real_audio_demux_src_event));
+  gst_pad_set_query_function (demux->srcpad,
+      GST_DEBUG_FUNCPTR (gst_real_audio_demux_src_query));
+  gst_pad_set_active (demux->srcpad, TRUE);
+  gst_element_add_pad (GST_ELEMENT (demux), demux->srcpad);
+
+  if (demux->byterate_num > 0 && demux->byterate_denom > 0) {
+    GstFormat bformat = GST_FORMAT_BYTES;
+    gint64 size_bytes = 0;
+
+    GST_INFO_OBJECT (demux, "byte rate = %u/%u = %u bytes/sec",
+        demux->byterate_num, demux->byterate_denom,
+        demux->byterate_num / demux->byterate_denom);
+
+    if (gst_pad_query_peer_duration (demux->sinkpad, &bformat, &size_bytes)) {
+      demux->duration =
+          gst_real_demux_get_timestamp_from_offset (demux, size_bytes);
+      demux->upstream_size = size_bytes;
+      GST_INFO_OBJECT (demux, "upstream_size = %" G_GUINT64_FORMAT,
+          demux->upstream_size);
+      GST_INFO_OBJECT (demux, "duration      = %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (demux->duration));
+    }
+  }
+
+  demux->need_newsegment = TRUE;
+
+  if (codec_name) {
+    if (demux->pending_tags == NULL)
+      demux->pending_tags = gst_tag_list_new ();
+
+    gst_tag_list_add (demux->pending_tags, GST_TAG_MERGE_REPLACE,
+        GST_TAG_AUDIO_CODEC, codec_name, NULL);
+  }
+
+  gst_adapter_flush (demux->adapter, demux->data_offset - 6);
+
+  demux->state = REAL_AUDIO_DEMUX_STATE_DATA;
+  demux->need_newsegment = TRUE;
+
+  return GST_FLOW_OK;
+
+/* ERRORS */
+unknown_fourcc:
+  {
+    GST_ELEMENT_ERROR (GST_ELEMENT (demux), STREAM, DECODE, (NULL),
+        ("Unknown fourcc '%" GST_FOURCC_FORMAT "'",
+            GST_FOURCC_ARGS (demux->fourcc)));
+    return GST_FLOW_ERROR;
+  }
+broken_file:
+  {
+    GST_ELEMENT_ERROR (GST_ELEMENT (demux), STREAM, DECODE, (NULL),
+        ("Broken file - invalid sample_rate or other header value"));
+    return GST_FLOW_ERROR;
+  }
+
+}
+
+static GstFlowReturn
+gst_real_audio_demux_parse_data (GstRealAudioDemux * demux)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  guint avail, unit_size;
+
+  avail = gst_adapter_available (demux->adapter);
+
+  if (demux->packet_size > 0)
+    unit_size = demux->packet_size;
+  else
+    unit_size = avail & 0xfffffff0;     /* round down to next multiple of 16 */
+
+  GST_LOG_OBJECT (demux, "available = %u, unit_size = %u", avail, unit_size);
+
+  while (ret == GST_FLOW_OK && unit_size > 0 && avail >= unit_size) {
+    GstClockTime ts;
+    const guint8 *data;
+    GstBuffer *buf = NULL;
+
+    ret = gst_pad_alloc_buffer_and_set_caps (demux->srcpad,
+        GST_BUFFER_OFFSET_NONE, unit_size, GST_PAD_CAPS (demux->srcpad), &buf);
+
+    if (ret != GST_FLOW_OK) {
+      GST_DEBUG_OBJECT (demux, "pad_alloc flow: %s", gst_flow_get_name (ret));
+      break;
+    }
+
+    data = gst_adapter_peek (demux->adapter, unit_size);
+    memcpy (GST_BUFFER_DATA (buf), data, unit_size);
+    gst_adapter_flush (demux->adapter, unit_size);
+    avail -= unit_size;
+
+    if (demux->need_newsegment) {
+      gst_pad_push_event (demux->srcpad,
+          gst_event_new_new_segment_full (FALSE, demux->segment.rate,
+              demux->segment.applied_rate, GST_FORMAT_TIME,
+              demux->segment.start, demux->segment.stop, demux->segment.time));
+      demux->need_newsegment = FALSE;
+    }
+
+    if (demux->pending_tags) {
+      gst_element_found_tags_for_pad (GST_ELEMENT (demux), demux->srcpad,
+          demux->pending_tags);
+      demux->pending_tags = NULL;
+    }
+
+    if (demux->fourcc == GST_RM_AUD_DNET) {
+      buf = gst_rm_utils_descramble_dnet_buffer (buf);
+    }
+
+    ts = gst_real_demux_get_timestamp_from_offset (demux, demux->offset);
+    GST_BUFFER_TIMESTAMP (buf) = ts;
+
+    gst_segment_set_last_stop (&demux->segment, GST_FORMAT_TIME, ts);
+
+    ret = gst_pad_push (demux->srcpad, buf);
+  }
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_real_audio_demux_handle_buffer (GstRealAudioDemux * demux, GstBuffer * buf)
+{
+  GstFlowReturn ret;
+
+  gst_adapter_push (demux->adapter, buf);
+  buf = NULL;
+
+  switch (demux->state) {
+    case REAL_AUDIO_DEMUX_STATE_MARKER:{
+      ret = gst_real_audio_demux_parse_marker (demux);
+      if (ret != GST_FLOW_OK || demux->state != REAL_AUDIO_DEMUX_STATE_HEADER)
+        break;
+      /* otherwise fall through */
+    }
+    case REAL_AUDIO_DEMUX_STATE_HEADER:{
+      ret = gst_real_audio_demux_parse_header (demux);
+      if (ret != GST_FLOW_OK || demux->state != REAL_AUDIO_DEMUX_STATE_DATA)
+        break;
+      /* otherwise fall through */
+    }
+    case REAL_AUDIO_DEMUX_STATE_DATA:{
+      ret = gst_real_audio_demux_parse_data (demux);
+      break;
+    }
+    default:
+      g_assert_not_reached ();
+  }
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_real_audio_demux_chain (GstPad * pad, GstBuffer * buf)
+{
+  GstRealAudioDemux *demux;
+
+  demux = GST_REAL_AUDIO_DEMUX (GST_PAD_PARENT (pad));
+
+  return gst_real_audio_demux_handle_buffer (demux, buf);
+}
+
+static void
+gst_real_audio_demux_loop (GstRealAudioDemux * demux)
+{
+  GstRealAudioDemuxState old_state;
+  GstFlowReturn ret;
+  GstBuffer *buf;
+  guint bytes_needed;
+
+  old_state = demux->state;
+
+  /* check how much data we need */
+  switch (demux->state) {
+    case REAL_AUDIO_DEMUX_STATE_MARKER:
+      bytes_needed = 6 + 16;    /* 16 are beginning of header */
+      break;
+    case REAL_AUDIO_DEMUX_STATE_HEADER:
+      if (!gst_real_audio_demux_get_data_offset_from_header (demux))
+        goto parse_header_error;
+      bytes_needed = demux->data_offset - (6 + 16);
+      break;
+    case REAL_AUDIO_DEMUX_STATE_DATA:
+      if (demux->packet_size > 0) {
+        /* TODO: should probably take into account width/height as well? */
+        bytes_needed = demux->packet_size;
+      } else {
+        bytes_needed = 1024;
+      }
+      break;
+    default:
+      g_assert_not_reached ();
+  }
+
+  /* now get the data */
+  GST_LOG_OBJECT (demux, "getting data: %5u bytes @ %8" G_GINT64_MODIFIER "u",
+      bytes_needed, demux->offset);
+
+  if (demux->upstream_size > 0 && demux->offset >= demux->upstream_size)
+    goto eos;
+
+  ret = gst_pad_pull_range (demux->sinkpad, demux->offset, bytes_needed, &buf);
+
+  if (ret != GST_FLOW_OK)
+    goto pull_range_error;
+
+  if (GST_BUFFER_SIZE (buf) != bytes_needed)
+    goto pull_range_short_read;
+
+  ret = gst_real_audio_demux_handle_buffer (demux, buf);
+  if (ret != GST_FLOW_OK)
+    goto handle_flow_error;
+
+  /* TODO: increase this in chain function too (for timestamps)? */
+  demux->offset += bytes_needed;
+
+  /* check for the end of the segment */
+  if (demux->segment.stop != -1 && demux->segment.last_stop != -1 &&
+      demux->segment.last_stop > demux->segment.stop) {
+    GST_DEBUG_OBJECT (demux, "reached end of segment");
+    goto eos;
+  }
+
+  return;
+
+/* ERRORS */
+parse_header_error:
+  {
+    GST_ELEMENT_ERROR (demux, STREAM, DECODE, (NULL), (NULL));
+    goto pause_task;
+  }
+handle_flow_error:
+  {
+    GST_WARNING_OBJECT (demux, "handle_buf flow: %s", gst_flow_get_name (ret));
+    goto pause_task;
+  }
+pull_range_error:
+  {
+    GST_WARNING_OBJECT (demux, "pull range flow: %s", gst_flow_get_name (ret));
+    goto pause_task;
+  }
+pull_range_short_read:
+  {
+    GST_WARNING_OBJECT (demux, "pull range short read: wanted %u bytes, but "
+        "got only %u bytes", bytes_needed, GST_BUFFER_SIZE (buf));
+    gst_buffer_unref (buf);
+    goto eos;
+  }
+eos:
+  {
+    if (demux->state != REAL_AUDIO_DEMUX_STATE_DATA) {
+      GST_WARNING_OBJECT (demux, "reached EOS before finished parsing header");
+      goto parse_header_error;
+    }
+    GST_INFO_OBJECT (demux, "EOS");
+    if ((demux->segment.flags & GST_SEEK_FLAG_SEGMENT) != 0) {
+      gint64 stop;
+
+      /* for segment playback we need to post when (in stream time)
+       * we stopped, this is either stop (when set) or the duration. */
+      if ((stop = demux->segment.stop) == -1)
+        stop = demux->segment.duration;
+
+      GST_DEBUG_OBJECT (demux, "sending segment done, at end of segment");
+      gst_element_post_message (GST_ELEMENT (demux),
+          gst_message_new_segment_done (GST_OBJECT (demux), GST_FORMAT_TIME,
+              stop));
+    } else {
+      /* normal playback, send EOS event downstream */
+      GST_DEBUG_OBJECT (demux, "sending EOS event, at end of stream");
+      gst_pad_push_event (demux->srcpad, gst_event_new_eos ());
+    }
+    goto pause_task;
+  }
+pause_task:
+  {
+    demux->segment_running = FALSE;
+    gst_pad_pause_task (demux->sinkpad);
+    GST_DEBUG_OBJECT (demux, "pausing task");
+    return;
+  }
+}
+
+static gboolean
+gst_real_audio_demux_sink_event (GstPad * pad, GstEvent * event)
+{
+  GstRealAudioDemux *demux;
+  gboolean ret;
+
+  demux = GST_REAL_AUDIO_DEMUX (gst_pad_get_parent (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_NEWSEGMENT:{
+      /* FIXME */
+      gst_event_unref (event);
+      demux->need_newsegment = TRUE;
+      ret = TRUE;
+      break;
+    }
+    default:
+      ret = gst_pad_event_default (pad, event);
+      break;
+  }
+
+  gst_object_unref (demux);
+  return ret;
+}
+
+static gboolean
+gst_real_audio_demux_handle_seek (GstRealAudioDemux * demux, GstEvent * event)
+{
+  GstFormat format;
+  GstSeekFlags flags;
+  GstSeekType cur_type, stop_type;
+  gboolean flush, update;
+  gdouble rate;
+  guint64 seek_pos;
+  gint64 cur, stop;
+
+  if (!demux->seekable)
+    goto not_seekable;
+
+  if (demux->byterate_num == 0 || demux->byterate_denom == 0)
+    goto no_bitrate;
+
+  gst_event_parse_seek (event, &rate, &format, &flags,
+      &cur_type, &cur, &stop_type, &stop);
+
+  if (format != GST_FORMAT_TIME)
+    goto only_time_format_supported;
+
+  if (rate <= 0.0)
+    goto cannot_do_backwards_playback;
+
+  flush = ((flags & GST_SEEK_FLAG_FLUSH) != 0);
+
+  GST_DEBUG_OBJECT (demux, "flush=%d, rate=%g", flush, rate);
+
+  /* unlock streaming thread and make streaming stop */
+  if (flush) {
+    gst_pad_push_event (demux->sinkpad, gst_event_new_flush_start ());
+    gst_pad_push_event (demux->srcpad, gst_event_new_flush_start ());
+  } else {
+    gst_pad_pause_task (demux->sinkpad);
+  }
+
+  GST_PAD_STREAM_LOCK (demux->sinkpad);
+
+  if (demux->segment_running && !flush) {
+    GstEvent *newseg;
+
+    newseg = gst_event_new_new_segment_full (TRUE, demux->segment.rate,
+        demux->segment.applied_rate, GST_FORMAT_TIME, demux->segment.start,
+        demux->segment.last_stop, demux->segment.time);
+
+    GST_DEBUG_OBJECT (demux, "sending NEWSEGMENT event to close the current "
+        "segment: %" GST_PTR_FORMAT, newseg);
+
+    gst_pad_push_event (demux->srcpad, newseg);
+  }
+
+  gst_segment_set_seek (&demux->segment, rate, format, flags,
+      cur_type, cur, stop_type, stop, &update);
+
+  GST_DEBUG_OBJECT (demux, "segment: %" GST_SEGMENT_FORMAT, &demux->segment);
+
+  seek_pos = gst_util_uint64_scale (demux->segment.start,
+      demux->byterate_num, demux->byterate_denom * GST_SECOND);
+  if (demux->packet_size > 0) {
+    seek_pos -= seek_pos % demux->packet_size;
+  }
+  seek_pos += demux->data_offset;
+
+  GST_DEBUG_OBJECT (demux, "seek_pos = %" G_GUINT64_FORMAT, seek_pos);
+
+  /* stop flushing */
+  gst_pad_push_event (demux->sinkpad, gst_event_new_flush_stop ());
+  gst_pad_push_event (demux->srcpad, gst_event_new_flush_stop ());
+
+  demux->offset = seek_pos;
+  demux->need_newsegment = TRUE;
+
+  /* notify start of new segment */
+  if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
+    gst_element_post_message (GST_ELEMENT (demux),
+        gst_message_new_segment_start (GST_OBJECT (demux),
+            GST_FORMAT_TIME, demux->segment.last_stop));
+  }
+
+  demux->segment_running = TRUE;
+  /* restart our task since it might have been stopped when we did the flush */
+  gst_pad_start_task (demux->sinkpad,
+      (GstTaskFunction) gst_real_audio_demux_loop, demux);
+
+  /* streaming can continue now */
+  GST_PAD_STREAM_UNLOCK (demux->sinkpad);
+
+  return TRUE;
+
+/* ERRORS */
+not_seekable:
+  {
+    GST_DEBUG_OBJECT (demux, "seek failed: cannot seek in streaming mode");
+    return FALSE;
+  }
+no_bitrate:
+  {
+    GST_DEBUG_OBJECT (demux, "seek failed: bitrate unknown");
+    return FALSE;
+  }
+only_time_format_supported:
+  {
+    GST_DEBUG_OBJECT (demux, "can only seek in TIME format");
+    return FALSE;
+  }
+cannot_do_backwards_playback:
+  {
+    GST_DEBUG_OBJECT (demux, "can only seek with positive rate, not %lf", rate);
+    return FALSE;
+  }
+}
+
+static gboolean
+gst_real_audio_demux_src_event (GstPad * pad, GstEvent * event)
+{
+  GstRealAudioDemux *demux;
+  gboolean ret = FALSE;
+
+  demux = GST_REAL_AUDIO_DEMUX (gst_pad_get_parent (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_QOS:
+      gst_event_unref (event);
+      break;
+    case GST_EVENT_SEEK:
+      ret = gst_real_audio_demux_handle_seek (demux, event);
+      gst_event_unref (event);
+      break;
+    default:
+      ret = gst_pad_event_default (pad, event);
+      break;
+  }
+
+  gst_object_unref (demux);
+  return ret;
+}
+
+static gboolean
+gst_real_audio_demux_src_query (GstPad * pad, GstQuery * query)
+{
+  GstRealAudioDemux *demux;
+  gboolean ret = FALSE;
+
+  demux = GST_REAL_AUDIO_DEMUX (gst_pad_get_parent (pad));
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_DURATION:{
+      GstFormat format;
+
+      gst_query_parse_duration (query, &format, NULL);
+      if (format == GST_FORMAT_TIME && demux->duration > 0) {
+        gst_query_set_duration (query, GST_FORMAT_TIME, demux->duration);
+        ret = TRUE;
+      } else if (format == GST_FORMAT_BYTES && demux->upstream_size > 0) {
+        gst_query_set_duration (query, GST_FORMAT_BYTES,
+            demux->upstream_size - demux->data_offset);
+        ret = TRUE;
+      }
+      break;
+    }
+    case GST_QUERY_SEEKING:{
+      GstFormat format;
+      gboolean seekable;
+
+      gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
+      seekable = (format == GST_FORMAT_TIME && demux->seekable);
+      gst_query_set_seeking (query, format, seekable, 0,
+          (format == GST_FORMAT_TIME) ? demux->duration : -1);
+      ret = TRUE;
+      break;
+    }
+    default:
+      ret = gst_pad_query_default (pad, query);
+      break;
+  }
+
+  gst_object_unref (demux);
+  return ret;
+}
+
+static GstStateChangeReturn
+gst_real_audio_demux_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstRealAudioDemux *demux = GST_REAL_AUDIO_DEMUX (element);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      demux->state = REAL_AUDIO_DEMUX_STATE_MARKER;
+      demux->segment_running = FALSE;
+      gst_segment_init (&demux->segment, GST_FORMAT_TIME);
+      gst_adapter_clear (demux->adapter);
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:{
+      gst_real_audio_demux_reset (demux);
+      gst_segment_init (&demux->segment, GST_FORMAT_UNDEFINED);
+      break;
+    }
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
diff --git a/gst/realmedia/rademux.h b/gst/realmedia/rademux.h
new file mode 100644 (file)
index 0000000..342e0c9
--- /dev/null
@@ -0,0 +1,99 @@
+/* GStreamer RealAudio demuxer
+ * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
+ *
+ * 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_REAL_AUDIO_DEMUX_H__
+#define __GST_REAL_AUDIO_DEMUX_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_REAL_AUDIO_DEMUX \
+  (gst_real_audio_demux_get_type())
+#define GST_REAL_AUDIO_DEMUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_REAL_AUDIO_DEMUX,GstRealAudioDemux))
+#define GST_REAL_AUDIO_DEMUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_REAL_AUDIO_DEMUX,GstRealAudioDemuxClass))
+#define GST_IS_REAL_AUDIO_DEMUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_REAL_AUDIO_DEMUX))
+#define GST_IS_REAL_AUDIO_DEMUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_REAL_AUDIO_DEMUX))
+
+typedef enum
+{
+  REAL_AUDIO_DEMUX_STATE_MARKER,
+  REAL_AUDIO_DEMUX_STATE_HEADER,
+  REAL_AUDIO_DEMUX_STATE_DATA
+} GstRealAudioDemuxState;
+
+typedef struct _GstRealAudioDemux GstRealAudioDemux;
+typedef struct _GstRealAudioDemuxClass GstRealAudioDemuxClass;
+
+struct _GstRealAudioDemux {
+  GstElement               element;
+
+  GstPad                  *sinkpad;
+  GstPad                  *srcpad;
+
+  GstAdapter              *adapter;
+  GstRealAudioDemuxState   state;
+
+  guint                    ra_version;
+  guint                    data_offset;
+
+  guint                    packet_size;
+  guint                    leaf_size;
+  guint                    height;
+  guint                    flavour;
+
+  guint                    sample_rate;
+  guint                    sample_width;
+  guint                    channels;
+  guint32                  fourcc;
+
+  gboolean                 segment_running;
+
+  gboolean                 need_newsegment;
+  GstTagList              *pending_tags;
+
+  guint                    byterate_num;    /* bytes per second */
+  guint                    byterate_denom;
+
+  gint64                   duration;
+  gint64                   upstream_size;
+
+  guint64                  offset;          /* current read byte offset for
+                                             * pull_range-based mode */
+
+  /* playback start/stop positions */
+  GstSegment               segment;
+
+  gboolean                 seekable;
+};
+
+struct _GstRealAudioDemuxClass {
+  GstElementClass  element_class;
+};
+
+GType  gst_real_audio_demux_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_REAL_AUDIO_DEMUX_H__ */
index 21155c2aa249ee13cb9360c9f3e51e4bcabf388d..5a91c09921ec79309155e53178bacb9e29fd0956 100644 (file)
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
+
 #include "rmdemux.h"
 #include "rdtdepay.h"
 #include "rmutils.h"
+#include "rademux.h"
+
 #include <string.h>
 #include <ctype.h>
 #include <zlib.h>
@@ -1405,6 +1408,9 @@ gst_rmdemux_add_stream (GstRMDemux * rmdemux, GstRMDemuxStream * stream)
   rmdemux->n_streams++;
   GST_LOG_OBJECT (rmdemux, "n_streams is now %d", rmdemux->n_streams);
 
+  GST_LOG ("stream->pad = %p, stream_caps = %" GST_PTR_FORMAT, stream->pad,
+      stream_caps);
+
   if (stream->pad && stream_caps) {
 
     GST_LOG_OBJECT (rmdemux, "%d bytes of extra data for stream %s",
@@ -1893,21 +1899,12 @@ gst_rmdemux_descramble_dnet_audio (GstRMDemux * rmdemux,
     GstRMDemuxStream * stream)
 {
   GstBuffer *buf;
-  guint8 *data, *end;
 
   buf = g_ptr_array_index (stream->subpackets, 0);
   g_ptr_array_index (stream->subpackets, 0) = NULL;
   g_ptr_array_set_size (stream->subpackets, 0);
 
-  /* descramble is a bit of a misnomer, it's just byte-order swapped AC3 .. */
-  data = GST_BUFFER_DATA (buf);
-  end = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf);
-  while ((data + 1) < end) {
-    /* byte-swap in an alignment-safe way */
-    GST_WRITE_UINT16_BE (data, GST_READ_UINT16_LE (data));
-    data += sizeof (guint16);
-  }
-
+  buf = gst_rm_utils_descramble_dnet_buffer (buf);
   return gst_pad_push (stream->pad, buf);
 }
 
@@ -2037,11 +2034,13 @@ alloc_failed:
   }
 }
 
-gboolean
+static gboolean
 gst_rmdemux_plugin_init (GstPlugin * plugin)
 {
   return gst_element_register (plugin, "rmdemux",
-      GST_RANK_PRIMARY, GST_TYPE_RMDEMUX);
+      GST_RANK_PRIMARY, GST_TYPE_RMDEMUX) &&
+      gst_element_register (plugin, "rademux",
+      GST_RANK_SECONDARY, GST_TYPE_REAL_AUDIO_DEMUX);
 }
 
 
index a70e206135ecaecd7d6f7690f4bb1b0dc8431217..5d8166987c5974cdc811b61a550eb20e38f88100 100644 (file)
@@ -120,3 +120,21 @@ gst_rm_utils_read_tags (const guint8 * data, guint datalen,
   gst_tag_list_free (tags);
   return NULL;
 }
+
+GstBuffer *
+gst_rm_utils_descramble_dnet_buffer (GstBuffer * buf)
+{
+  guint8 *data, *end;
+
+  buf = gst_buffer_make_writable (buf);
+
+  /* dnet = byte-order swapped AC3 */
+  data = GST_BUFFER_DATA (buf);
+  end = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf);
+  while ((data + 1) < end) {
+    /* byte-swap in an alignment-safe way */
+    GST_WRITE_UINT16_BE (data, GST_READ_UINT16_LE (data));
+    data += sizeof (guint16);
+  }
+  return buf;
+}
index 41b8aa8eda018e4eaa623d7cbee7f7f6544a0026..26040398a66f946ff65a22729e5c0228aabeb2ff 100644 (file)
@@ -38,6 +38,8 @@ GstTagList    *gst_rm_utils_read_tags     (const guint8            * data,
                                            guint                     datalen,
                                            GstRmUtilsStringReadFunc  func);
 
+GstBuffer     *gst_rm_utils_descramble_dnet_buffer (GstBuffer * buf);
+
 G_END_DECLS
 
 #endif /* __GST_RM_UTILS_H__ */