Thiago Santos 91dd173f3c mssdemux: improve state changing
Keep a ref on pad to prevent it being unreffed while the mssdemux
streams are still using it. Also reset the element when going to
ready instead of when going to null.
2013-05-07 21:05:10 -03:00

475 lines
13 KiB
C

/* GStreamer
* Copyright (C) 2012 Smart TV Alliance
* Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd.
*
* gstmssdemux.c:
*
* 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-mssdemux
*
* Demuxes a Microsoft's Smooth Streaming manifest into its audio and/or video streams.
*
* TODO
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gst/gst-i18n-plugin.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gstmssdemux.h"
GST_DEBUG_CATEGORY (mssdemux_debug);
static GstStaticPadTemplate gst_mss_demux_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/vnd.ms-sstr+xml")
);
static GstStaticPadTemplate gst_mss_demux_videosrc_template =
GST_STATIC_PAD_TEMPLATE ("video_%02u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate gst_mss_demux_audiosrc_template =
GST_STATIC_PAD_TEMPLATE ("audio_%02u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
GST_BOILERPLATE (GstMssDemux, gst_mss_demux, GstMssDemux, GST_TYPE_ELEMENT);
static void gst_mss_demux_dispose (GObject * object);
static GstStateChangeReturn
gst_mss_demux_change_state (GstElement * element, GstStateChange transition);
static GstFlowReturn gst_mss_demux_chain (GstPad * pad, GstBuffer * buffer);
static GstFlowReturn gst_mss_demux_event (GstPad * pad, GstEvent * event);
static void gst_mss_demux_stream_loop (GstMssDemuxStream * stream);
static void gst_mss_demux_process_manifest (GstMssDemux * mssdemux);
static void
gst_mss_demux_base_init (gpointer klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
gst_element_class_add_static_pad_template (element_class,
&gst_mss_demux_sink_template);
gst_element_class_add_static_pad_template (element_class,
&gst_mss_demux_videosrc_template);
gst_element_class_add_static_pad_template (element_class,
&gst_mss_demux_audiosrc_template);
gst_element_class_set_details_simple (element_class, "Smooth Streaming "
"demuxer", "Demuxer",
"Parse and demultiplex a Smooth Streaming manifest into audio and video "
"streams", "Thiago Santos <thiago.sousa.santos@collabora.com>");
GST_DEBUG_CATEGORY_INIT (mssdemux_debug, "mssdemux", 0, "mssdemux plugin");
}
static void
gst_mss_demux_class_init (GstMssDemuxClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
parent_class = g_type_class_peek_parent (klass);
gobject_class->dispose = gst_mss_demux_dispose;
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_mss_demux_change_state);
}
static void
gst_mss_demux_init (GstMssDemux * mssdemux, GstMssDemuxClass * klass)
{
mssdemux->sinkpad =
gst_pad_new_from_static_template (&gst_mss_demux_sink_template, "sink");
gst_pad_set_chain_function (mssdemux->sinkpad,
GST_DEBUG_FUNCPTR (gst_mss_demux_chain));
gst_pad_set_event_function (mssdemux->sinkpad,
GST_DEBUG_FUNCPTR (gst_mss_demux_event));
gst_element_add_pad (GST_ELEMENT_CAST (mssdemux), mssdemux->sinkpad);
}
static GstMssDemuxStream *
gst_mss_demux_stream_new (GstMssDemux * mssdemux,
GstMssStream * manifeststream, GstPad * srcpad)
{
GstMssDemuxStream *stream;
stream = g_new0 (GstMssDemuxStream, 1);
stream->downloader = gst_uri_downloader_new ();
/* Streaming task */
g_static_rec_mutex_init (&stream->stream_lock);
stream->stream_task =
gst_task_create ((GstTaskFunction) gst_mss_demux_stream_loop, stream);
gst_task_set_lock (stream->stream_task, &stream->stream_lock);
stream->pad = srcpad;
stream->manifest_stream = manifeststream;
stream->parent = mssdemux;
return stream;
}
static void
gst_mss_demux_stream_free (GstMssDemuxStream * stream)
{
if (stream->stream_task) {
if (GST_TASK_STATE (stream->stream_task) != GST_TASK_STOPPED) {
GST_DEBUG_OBJECT (stream->parent, "Leaving streaming task %s:%s",
GST_DEBUG_PAD_NAME (stream->pad));
gst_task_stop (stream->stream_task);
g_static_rec_mutex_lock (&stream->stream_lock);
g_static_rec_mutex_unlock (&stream->stream_lock);
GST_LOG_OBJECT (stream->parent, "Waiting for task to finish");
gst_task_join (stream->stream_task);
GST_LOG_OBJECT (stream->parent, "Finished");
}
gst_object_unref (stream->stream_task);
g_static_rec_mutex_free (&stream->stream_lock);
stream->stream_task = NULL;
}
if (stream->downloader != NULL) {
g_object_unref (stream->downloader);
stream->downloader = NULL;
}
if (stream->pad) {
gst_object_unref (stream->pad);
stream->pad = NULL;
}
g_free (stream);
}
static void
gst_mss_demux_reset (GstMssDemux * mssdemux)
{
GSList *iter;
if (mssdemux->manifest_buffer) {
gst_buffer_unref (mssdemux->manifest_buffer);
mssdemux->manifest_buffer = NULL;
}
for (iter = mssdemux->streams; iter; iter = g_slist_next (iter)) {
GstMssDemuxStream *stream = iter->data;
gst_element_remove_pad (GST_ELEMENT_CAST (mssdemux), stream->pad);
gst_mss_demux_stream_free (stream);
}
g_slist_free (mssdemux->streams);
mssdemux->streams = NULL;
if (mssdemux->manifest) {
gst_mss_manifest_free (mssdemux->manifest);
mssdemux->manifest = NULL;
}
mssdemux->n_videos = mssdemux->n_audios = 0;
g_free (mssdemux->base_url);
mssdemux->base_url = NULL;
}
static void
gst_mss_demux_dispose (GObject * object)
{
/* GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (object); */
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static GstStateChangeReturn
gst_mss_demux_change_state (GstElement * element, GstStateChange transition)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (element);
GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_mss_demux_reset (mssdemux);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
break;
default:
break;
}
result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:{
break;
}
default:
break;
}
return result;
}
static GstFlowReturn
gst_mss_demux_chain (GstPad * pad, GstBuffer * buffer)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (GST_PAD_PARENT (pad));
if (mssdemux->manifest_buffer == NULL)
mssdemux->manifest_buffer = buffer;
else
mssdemux->manifest_buffer =
gst_buffer_join (mssdemux->manifest_buffer, buffer);
return GST_FLOW_OK;
}
static void
gst_mss_demux_start (GstMssDemux * mssdemux)
{
GSList *iter;
GST_INFO_OBJECT (mssdemux, "Starting streams' tasks");
for (iter = mssdemux->streams; iter; iter = g_slist_next (iter)) {
GstMssDemuxStream *stream = iter->data;
gst_task_start (stream->stream_task);
}
}
static gboolean
gst_mss_demux_event (GstPad * pad, GstEvent * event)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (GST_PAD_PARENT (pad));
gboolean forward = TRUE;
gboolean ret = TRUE;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
if (mssdemux->manifest_buffer == NULL) {
GST_WARNING_OBJECT (mssdemux, "Received EOS without a manifest.");
break;
}
gst_mss_demux_process_manifest (mssdemux);
gst_mss_demux_start (mssdemux);
forward = FALSE;
break;
default:
break;
}
if (forward) {
ret = gst_pad_event_default (pad, event);
} else {
gst_event_unref (event);
}
return ret;
}
static void
gst_mss_demux_create_streams (GstMssDemux * mssdemux)
{
GSList *streams = gst_mss_manifest_get_streams (mssdemux->manifest);
GSList *iter;
if (streams == NULL) {
GST_INFO_OBJECT (mssdemux, "No streams found in the manifest");
/* TODO post eos? */
}
for (iter = streams; iter; iter = g_slist_next (iter)) {
gchar *name;
GstPad *srcpad = NULL;
GstMssDemuxStream *stream = NULL;
GstMssStream *manifeststream = iter->data;
GstMssStreamType streamtype;
streamtype = gst_mss_stream_get_type (manifeststream);
GST_DEBUG_OBJECT (mssdemux, "Found stream of type: %s",
gst_mss_stream_type_name (streamtype));
/* TODO use stream's name as the pad name? */
if (streamtype == MSS_STREAM_TYPE_VIDEO) {
name = g_strdup_printf ("video_%02u", mssdemux->n_videos++);
srcpad =
gst_pad_new_from_static_template (&gst_mss_demux_videosrc_template,
name);
g_free (name);
} else if (streamtype == MSS_STREAM_TYPE_AUDIO) {
name = g_strdup_printf ("audio_%02u", mssdemux->n_audios++);
srcpad =
gst_pad_new_from_static_template (&gst_mss_demux_audiosrc_template,
name);
g_free (name);
}
if (!srcpad) {
GST_WARNING_OBJECT (mssdemux, "Ignoring unknown type stream");
continue;
}
stream = gst_mss_demux_stream_new (mssdemux, manifeststream, srcpad);
mssdemux->streams = g_slist_append (mssdemux->streams, stream);
}
}
static void
gst_mss_demux_expose_stream (GstMssDemux * mssdemux, GstMssDemuxStream * stream)
{
GstCaps *caps;
GstPad *pad = stream->pad;
caps = gst_mss_stream_get_caps (stream->manifest_stream);
if (caps) {
gst_pad_set_caps (pad, caps);
gst_caps_unref (caps);
gst_pad_set_active (pad, TRUE);
GST_INFO_OBJECT (mssdemux, "Adding srcpad %s:%s with caps %" GST_PTR_FORMAT,
GST_DEBUG_PAD_NAME (pad), caps);
gst_object_ref (pad);
gst_element_add_pad (GST_ELEMENT_CAST (mssdemux), pad);
} else {
GST_WARNING_OBJECT (mssdemux, "Not exposing stream of unrecognized format");
}
}
static void
gst_mss_demux_process_manifest (GstMssDemux * mssdemux)
{
GstQuery *query;
gchar *uri = NULL;
gboolean ret;
GSList *iter;
g_return_if_fail (mssdemux->manifest_buffer != NULL);
g_return_if_fail (mssdemux->manifest == NULL);
query = gst_query_new_uri ();
ret = gst_pad_peer_query (mssdemux->sinkpad, query);
if (ret) {
gchar *baseurl_end;
gst_query_parse_uri (query, &uri);
GST_INFO_OBJECT (mssdemux, "Upstream is using URI: %s", uri);
baseurl_end = g_strrstr (uri, "/Manifest");
if (baseurl_end) {
/* set the new end of the string */
baseurl_end[0] = '\0';
} else {
GST_WARNING_OBJECT (mssdemux, "Stream's URI didn't end with /manifest");
}
mssdemux->base_url = uri;
}
gst_query_unref (query);
mssdemux->manifest = gst_mss_manifest_new (mssdemux->manifest_buffer);
if (!mssdemux->manifest) {
GST_ELEMENT_ERROR (mssdemux, STREAM, FORMAT, ("Bad manifest file"),
("Xml manifest file couldn't be parsed"));
return;
}
gst_mss_demux_create_streams (mssdemux);
for (iter = mssdemux->streams; iter; iter = g_slist_next (iter)) {
gst_mss_demux_expose_stream (mssdemux, iter->data);
}
}
static void
gst_mss_demux_stream_loop (GstMssDemuxStream * stream)
{
GstMssDemux *mssdemux = stream->parent;
gchar *path;
gchar *url;
GstFragment *fragment;
GstBufferList *buflist;
GstFlowReturn ret;
GST_DEBUG_OBJECT (mssdemux, "Getting url for stream %p", stream);
ret = gst_mss_stream_get_fragment_url (stream->manifest_stream, &path);
switch (ret) {
case GST_FLOW_OK:
break; /* all is good, let's go */
case GST_FLOW_UNEXPECTED: /* EOS */
goto eos;
case GST_FLOW_ERROR:
goto error;
default:
break;
}
if (!path) {
/* TODO */
}
GST_DEBUG_OBJECT (mssdemux, "Got url path '%s' for stream %p", path, stream);
url = g_strdup_printf ("%s/%s", mssdemux->base_url, path);
fragment = gst_uri_downloader_fetch_uri (stream->downloader, url);
g_free (path);
g_free (url);
buflist = gst_fragment_get_buffer_list (fragment);
ret = gst_pad_push_list (stream->pad, buflist); /* TODO check return */
switch (ret) {
case GST_FLOW_UNEXPECTED:
goto eos; /* EOS ? */
case GST_FLOW_ERROR:
goto error;
case GST_FLOW_NOT_LINKED:
break; /* TODO what to do here? pause the task or just keep pushing? */
case GST_FLOW_OK:
default:
break;
}
gst_mss_stream_advance_fragment (stream->manifest_stream);
return;
eos:
{
GstEvent *eos = gst_event_new_eos ();
GST_DEBUG_OBJECT (mssdemux, "Pushing EOS on pad %s:%s",
GST_DEBUG_PAD_NAME (stream->pad));
gst_pad_push_event (stream->pad, eos);
gst_task_stop (stream->stream_task);
return;
}
error:
{
GST_WARNING_OBJECT (mssdemux, "Error while pushing fragment");
gst_task_stop (stream->stream_task);
return;
}
}