From a638e98a98620d76d28b3970badedbcdf5624497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 11 Jan 2007 12:49:23 +0000 Subject: [PATCH] gst/realmedia/: Add demuxer for RealAudio files (#349779). 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 | 11 + common | 2 +- gst/realmedia/Makefile.am | 4 +- gst/realmedia/rademux.c | 973 ++++++++++++++++++++++++++++++++++++++ gst/realmedia/rademux.h | 99 ++++ gst/realmedia/rmdemux.c | 23 +- gst/realmedia/rmutils.c | 18 + gst/realmedia/rmutils.h | 2 + 8 files changed, 1117 insertions(+), 15 deletions(-) create mode 100644 gst/realmedia/rademux.c create mode 100644 gst/realmedia/rademux.h diff --git a/ChangeLog b/ChangeLog index d473589928..b6bfd3f259 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2007-01-11 Tim-Philipp Müller + + * 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 * Makefile.am: diff --git a/common b/common index 64f924f6f2..8ba5dffb5e 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 64f924f6f2ff6275b06facb4c2adbc7c05f70641 +Subproject commit 8ba5dffb5ee7e7daea1030f6b34bfef10f9801a3 diff --git a/gst/realmedia/Makefile.am b/gst/realmedia/Makefile.am index d0c6d587a7..2395c6f070 100644 --- a/gst/realmedia/Makefile.am +++ b/gst/realmedia/Makefile.am @@ -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 index 0000000000..71a0a8c0cc --- /dev/null +++ b/gst/realmedia/rademux.c @@ -0,0 +1,973 @@ +/* GStreamer RealAudio demuxer + * Copyright (C) 2006 Tim-Philipp Müller + * + * 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 + * + * + * + * Demuxes/parses a RealAudio (.ra) file or stream into compressed audio. + * + * Example launch line + * + * + * gst-launch filesrc location=interview.ra ! rademux ! ffdec_real_288 ! audioconvert ! audioresample ! alsasink + * + * 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. + * + * + * + * gst-launch gnomevfssrc location=http://www.example.org/interview.ra ! rademux ! a52dec ! audioconvert ! audioresample ! alsasink + * + * Stream RealAudio data containing AC3 (dnet) compressed audio and decode it + * and output it to the soundcard using the ALSA element. + * + * + * + * 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 + +static GstElementDetails real_audio_demux_details = { + "RealAudio Demuxer", + "Codec/Demuxer", + "Demultiplex a RealAudio file", + "Tim-Philipp Müller " +}; + +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 index 0000000000..342e0c92bf --- /dev/null +++ b/gst/realmedia/rademux.h @@ -0,0 +1,99 @@ +/* GStreamer RealAudio demuxer + * Copyright (C) 2006 Tim-Philipp Müller + * + * 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 +#include + +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__ */ diff --git a/gst/realmedia/rmdemux.c b/gst/realmedia/rmdemux.c index 21155c2aa2..5a91c09921 100644 --- a/gst/realmedia/rmdemux.c +++ b/gst/realmedia/rmdemux.c @@ -26,9 +26,12 @@ #ifdef HAVE_CONFIG_H # include "config.h" #endif + #include "rmdemux.h" #include "rdtdepay.h" #include "rmutils.h" +#include "rademux.h" + #include #include #include @@ -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); } diff --git a/gst/realmedia/rmutils.c b/gst/realmedia/rmutils.c index a70e206135..5d8166987c 100644 --- a/gst/realmedia/rmutils.c +++ b/gst/realmedia/rmutils.c @@ -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; +} diff --git a/gst/realmedia/rmutils.h b/gst/realmedia/rmutils.h index 41b8aa8eda..26040398a6 100644 --- a/gst/realmedia/rmutils.h +++ b/gst/realmedia/rmutils.h @@ -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__ */