/* 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., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "rademux.h" #include "rmdemux.h" #include "rmutils.h" #include 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 #define gst_real_audio_demux_parent_class parent_class G_DEFINE_TYPE (GstRealAudioDemux, gst_real_audio_demux, GST_TYPE_ELEMENT); static GstStateChangeReturn gst_real_audio_demux_change_state (GstElement * e, GstStateChange transition); static GstFlowReturn gst_real_audio_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf); static gboolean gst_real_audio_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * ev); static gboolean gst_real_audio_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * ev); static gboolean gst_real_audio_demux_src_query (GstPad * pad, GstObject * parent, GstQuery * query); static void gst_real_audio_demux_loop (GstRealAudioDemux * demux); static gboolean gst_real_audio_demux_sink_activate (GstPad * sinkpad, GstObject * parent); static gboolean gst_real_audio_demux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, GstPadMode mode, gboolean active); 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; gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&sink_template)); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&src_template)); gst_element_class_set_static_metadata (gstelement_class, "RealAudio Demuxer", "Codec/Demuxer", "Demultiplex a RealAudio file", "Tim-Philipp Müller "); gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_real_audio_demux_change_state); GST_DEBUG_CATEGORY_INIT (real_audio_demux_debug, "rademux", 0, "Demuxer for RealAudio streams"); } 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_unref (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; demux->have_group_id = FALSE; demux->group_id = G_MAXUINT; gst_adapter_clear (demux->adapter); } static void gst_real_audio_demux_init (GstRealAudioDemux * demux) { 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_activatemode_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_real_audio_demux_sink_activate_mode)); 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, GstObject * parent) { GstQuery *query; gboolean pull_mode; query = gst_query_new_scheduling (); if (!gst_pad_peer_query (sinkpad, query)) { gst_query_unref (query); goto activate_push; } pull_mode = gst_query_has_scheduling_mode_with_flags (query, GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE); gst_query_unref (query); if (!pull_mode) goto activate_push; GST_DEBUG_OBJECT (sinkpad, "activating pull"); return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE); activate_push: { GST_DEBUG_OBJECT (sinkpad, "activating push"); return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); } } static gboolean gst_real_audio_demux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, GstPadMode mode, gboolean active) { gboolean res; GstRealAudioDemux *demux; demux = GST_REAL_AUDIO_DEMUX (parent); switch (mode) { case GST_PAD_MODE_PUSH: demux->seekable = FALSE; res = TRUE; break; case GST_PAD_MODE_PULL: if (active) { demux->seekable = TRUE; res = gst_pad_start_task (sinkpad, (GstTaskFunction) gst_real_audio_demux_loop, demux, NULL); } else { demux->seekable = FALSE; res = gst_pad_stop_task (sinkpad); } break; default: res = FALSE; break; } return res; } static GstFlowReturn gst_real_audio_demux_parse_marker (GstRealAudioDemux * demux) { guint8 data[6]; if (gst_adapter_available (demux->adapter) < 6) { GST_LOG_OBJECT (demux, "need at least 6 bytes, waiting for more data"); return GST_FLOW_OK; } gst_adapter_copy (demux->adapter, data, 0, 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) { guint8 data[16]; gst_adapter_copy (demux->adapter, data, 0, 16); 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 guint8 *data; gchar *codec_name = NULL; GstCaps *caps = NULL; GstEvent *event; gchar *stream_id; 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_map (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); if (demux->pending_tags) gst_tag_list_set_scope (demux->pending_tags, GST_TAG_SCOPE_GLOBAL); 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); 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); break; case GST_RM_AUD_DNET: 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: caps = gst_caps_new_empty_simple ("audio/x-sipro"); 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_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_pad_use_fixed_caps (demux->srcpad); stream_id = gst_pad_create_stream_id (demux->srcpad, GST_ELEMENT_CAST (demux), NULL); event = gst_pad_get_sticky_event (demux->sinkpad, GST_EVENT_STREAM_START, 0); if (event) { if (gst_event_parse_group_id (event, &demux->group_id)) demux->have_group_id = TRUE; else demux->have_group_id = FALSE; gst_event_unref (event); } else if (!demux->have_group_id) { demux->have_group_id = TRUE; demux->group_id = gst_util_group_id_next (); } event = gst_event_new_stream_start (stream_id); if (demux->have_group_id) gst_event_set_group_id (event, demux->group_id); gst_pad_push_event (demux->srcpad, event); g_free (stream_id); gst_pad_set_caps (demux->srcpad, caps); codec_name = gst_pb_utils_get_codec_description (caps); gst_caps_unref (caps); 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_peer_query_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_empty (); gst_tag_list_set_scope (demux->pending_tags, GST_TAG_SCOPE_GLOBAL); } gst_tag_list_add (demux->pending_tags, GST_TAG_MERGE_REPLACE, GST_TAG_AUDIO_CODEC, codec_name, NULL); g_free (codec_name); } gst_adapter_unmap (demux->adapter); 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 '0x%" G_GINT32_MODIFIER "x'", 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; GstBuffer *buf; buf = gst_adapter_take_buffer (demux->adapter, unit_size); avail -= unit_size; if (demux->need_newsegment) { gst_pad_push_event (demux->srcpad, gst_event_new_segment (&demux->segment)); demux->need_newsegment = FALSE; } if (demux->pending_tags) { gst_pad_push_event (demux->srcpad, gst_event_new_tag (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; demux->segment.position = 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_return_val_if_reached (GST_FLOW_ERROR); } return ret; } static GstFlowReturn gst_real_audio_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) { GstRealAudioDemux *demux; demux = GST_REAL_AUDIO_DEMUX (parent); return gst_real_audio_demux_handle_buffer (demux, buf); } static void gst_real_audio_demux_loop (GstRealAudioDemux * demux) { GstFlowReturn ret; GstBuffer *buf; guint bytes_needed; /* 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_return_if_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; buf = NULL; ret = gst_pad_pull_range (demux->sinkpad, demux->offset, bytes_needed, &buf); if (ret != GST_FLOW_OK) goto pull_range_error; if (gst_buffer_get_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.position != -1 && demux->segment.position > 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 %" G_GSIZE_FORMAT " bytes", bytes_needed, gst_buffer_get_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)); gst_pad_push_event (demux->srcpad, gst_event_new_segment_done (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, GstObject * parent, GstEvent * event) { GstRealAudioDemux *demux; gboolean ret; demux = GST_REAL_AUDIO_DEMUX (parent); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEGMENT:{ /* FIXME */ gst_event_unref (event); demux->need_newsegment = TRUE; ret = TRUE; break; } default: ret = gst_pad_event_default (pad, parent, event); break; } 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); gst_segment_do_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 (TRUE)); gst_pad_push_event (demux->srcpad, gst_event_new_flush_stop (TRUE)); 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.position)); } 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, NULL); /* 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, GstObject * parent, GstEvent * event) { GstRealAudioDemux *demux; gboolean ret = FALSE; demux = GST_REAL_AUDIO_DEMUX (parent); 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, parent, event); break; } return ret; } static gboolean gst_real_audio_demux_src_query (GstPad * pad, GstObject * parent, GstQuery * query) { GstRealAudioDemux *demux; gboolean ret = FALSE; demux = GST_REAL_AUDIO_DEMUX (parent); 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; } case GST_QUERY_SEGMENT: { GstFormat format; gint64 start, stop; format = demux->segment.format; start = gst_segment_to_stream_time (&demux->segment, format, demux->segment.start); if ((stop = demux->segment.stop) == -1) stop = demux->segment.duration; else stop = gst_segment_to_stream_time (&demux->segment, format, stop); gst_query_set_segment (query, demux->segment.rate, format, start, stop); ret = TRUE; break; } default: ret = gst_pad_query_default (pad, parent, query); break; } 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; } gboolean gst_rademux_plugin_init (GstPlugin * plugin) { return gst_element_register (plugin, "rademux", GST_RANK_SECONDARY, GST_TYPE_REAL_AUDIO_DEMUX); }