gstreamer/ext/resindvd/resindvdbin.c
Jan Schmidt c90cdadbe4 resindvd: Fix segment handling in the demuxer.
Transfer the incoming segment base time to the src
segment the demuxer sends downstream. Add some
(disabled) debug to the bin for tracking outgoing
timestamps.
2012-09-18 00:27:22 +10:00

1025 lines
30 KiB
C

/* GStreamer
* Copyright (C) 2008 Jan Schmidt <thaytan@noraisin.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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <gst/gst.h>
#include <gst/glib-compat-private.h>
#include <gst/pbutils/missing-plugins.h>
#include <gst/video/video.h>
#include <gst/audio/audio.h>
#include "resindvdbin.h"
#include "resindvdsrc.h"
#include "rsninputselector.h"
// #include "rsnaudiomunge.h"
#include "rsndec.h"
// #include "rsnparsetter.h"
#include "gstmpegdemux.h"
#define RSN_TYPE_INPUT_SELECTOR GST_TYPE_INPUT_SELECTOR
GST_DEBUG_CATEGORY_EXTERN (resindvd_debug);
#define GST_CAT_DEFAULT resindvd_debug
#define DVDBIN_LOCK(d) g_mutex_lock((d)->dvd_lock)
#define DVDBIN_UNLOCK(d) g_mutex_unlock((d)->dvd_lock)
#define DVDBIN_PREROLL_LOCK(d) g_mutex_lock((d)->preroll_lock)
#define DVDBIN_PREROLL_UNLOCK(d) g_mutex_unlock((d)->preroll_lock)
#define DEFAULT_DEVICE "/dev/dvd"
enum
{
/* FILL ME */
LAST_SIGNAL
};
enum
{
ARG_0,
ARG_DEVICE
};
static GstStaticPadTemplate video_src_template =
GST_STATIC_PAD_TEMPLATE ("video",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL))
);
static GstStaticPadTemplate audio_src_template =
GST_STATIC_PAD_TEMPLATE ("audio",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL))
);
static GstStaticPadTemplate subpicture_src_template =
GST_STATIC_PAD_TEMPLATE ("subpicture",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("subpicture/x-dvd")
);
static void rsn_dvdbin_finalize (GObject * object);
static void rsn_dvdbin_uri_handler_init (gpointer g_iface, gpointer iface_data);
#define rsn_dvdbin_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (RsnDvdBin, rsn_dvdbin, GST_TYPE_BIN,
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, rsn_dvdbin_uri_handler_init));
static void demux_pad_added (GstElement * element, GstPad * pad,
RsnDvdBin * dvdbin);
static void demux_no_more_pads (GstElement * element, RsnDvdBin * dvdbin);
static void rsn_dvdbin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void rsn_dvdbin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstStateChangeReturn rsn_dvdbin_change_state (GstElement * element,
GstStateChange transition);
static void rsn_dvdbin_no_more_pads (RsnDvdBin * dvdbin);
static void
rsn_dvdbin_class_init (RsnDvdBinClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *element_class;
gobject_class = (GObjectClass *) klass;
element_class = (GstElementClass *) klass;
gobject_class->finalize = rsn_dvdbin_finalize;
gobject_class->set_property = rsn_dvdbin_set_property;
gobject_class->get_property = rsn_dvdbin_get_property;
g_object_class_install_property (gobject_class, ARG_DEVICE,
g_param_spec_string ("device", "Device", "DVD device location",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&video_src_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&audio_src_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&subpicture_src_template));
element_class->change_state = GST_DEBUG_FUNCPTR (rsn_dvdbin_change_state);
gst_element_class_set_metadata (element_class, "rsndvdbin",
"Generic/Bin/Player",
"DVD playback element", "Jan Schmidt <thaytan@noraisin.net>");
}
static void
rsn_dvdbin_init (RsnDvdBin * dvdbin)
{
dvdbin->dvd_lock = g_mutex_new ();
dvdbin->preroll_lock = g_mutex_new ();
}
static void
rsn_dvdbin_finalize (GObject * object)
{
RsnDvdBin *dvdbin = RESINDVDBIN (object);
g_mutex_free (dvdbin->dvd_lock);
g_mutex_free (dvdbin->preroll_lock);
g_free (dvdbin->last_uri);
g_free (dvdbin->device);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/* URI interface */
static GstURIType
rsn_dvdbin_uri_get_type (GType type)
{
return GST_URI_SRC;
}
static const gchar *const *
rsn_dvdbin_uri_get_protocols (GType type)
{
static const gchar *protocols[] = { "dvd", NULL };
return protocols;
}
static gchar *
rsn_dvdbin_uri_get_uri (GstURIHandler * handler)
{
RsnDvdBin *dvdbin = RESINDVDBIN (handler);
DVDBIN_LOCK (dvdbin);
g_free (dvdbin->last_uri);
if (dvdbin->device)
dvdbin->last_uri = g_strdup_printf ("dvd://%s", dvdbin->device);
else
dvdbin->last_uri = g_strdup ("dvd://");
DVDBIN_UNLOCK (dvdbin);
return g_strdup (dvdbin->last_uri);
}
static gboolean
rsn_dvdbin_uri_set_uri (GstURIHandler * handler, const gchar * uri,
GError ** error)
{
RsnDvdBin *dvdbin = RESINDVDBIN (handler);
gboolean ret;
gchar *protocol, *location;
protocol = gst_uri_get_protocol (uri);
ret = (protocol && !strcmp (protocol, "dvd")) ? TRUE : FALSE;
g_free (protocol);
protocol = NULL;
if (!ret)
return ret;
location = gst_uri_get_location (uri);
if (!location)
return ret;
/*
* URI structure: dvd:///path/to/device
*/
if (g_str_has_prefix (uri, "dvd://")) {
g_free (dvdbin->device);
if (strlen (uri) > 6)
dvdbin->device = g_strdup (uri + 6);
else
dvdbin->device = g_strdup (DEFAULT_DEVICE);
}
#if 0
/*
* Parse out the new t/c/a and seek to them
*/
{
gchar **strs;
gchar **strcur;
gint pos = 0;
strcur = strs = g_strsplit (location, ",", 0);
while (strcur && *strcur) {
gint val;
if (!sscanf (*strcur, "%d", &val))
break;
switch (pos) {
case 0:
if (val != dvdbin->uri_title) {
dvdbin->uri_title = val;
dvdbin->new_seek = TRUE;
}
break;
case 1:
if (val != dvdbin->uri_chapter) {
dvdbin->uri_chapter = val;
dvdbin->new_seek = TRUE;
}
break;
case 2:
dvdbin->uri_angle = val;
break;
}
strcur++;
pos++;
}
g_strfreev (strs);
}
#endif
g_free (location);
return ret;
}
static void
rsn_dvdbin_uri_handler_init (gpointer g_iface, gpointer iface_data)
{
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
iface->get_type = rsn_dvdbin_uri_get_type;
iface->get_protocols = rsn_dvdbin_uri_get_protocols;
iface->get_uri = rsn_dvdbin_uri_get_uri;
iface->set_uri = rsn_dvdbin_uri_set_uri;
}
static void
rsn_dvdbin_no_more_pads (RsnDvdBin * dvdbin)
{
if (dvdbin->did_no_more_pads)
return;
dvdbin->did_no_more_pads = TRUE;
GST_DEBUG_OBJECT (dvdbin, "Firing no more pads");
/* Shrink subpicture queue to smaller size */
g_object_set (dvdbin->pieces[DVD_ELEM_SPUQ],
"max-size-time", G_GUINT64_CONSTANT (0), "max-size-bytes", 0,
"max-size-buffers", 1, NULL);
gst_element_no_more_pads (GST_ELEMENT (dvdbin));
}
static gboolean
try_create_piece (RsnDvdBin * dvdbin, gint index,
const gchar * factory, GType type, const gchar * name, const gchar * descr)
{
GstElement *e;
DVDBIN_LOCK (dvdbin);
if (dvdbin->pieces[index] != NULL) {
DVDBIN_UNLOCK (dvdbin);
return TRUE; /* Already exists */
}
DVDBIN_UNLOCK (dvdbin);
if (factory != NULL) {
e = gst_element_factory_make (factory, name);
} else {
if (name)
e = g_object_new (type, "name", name, NULL);
else
e = g_object_new (type, NULL);
}
if (e == NULL)
goto create_failed;
if (!gst_bin_add (GST_BIN (dvdbin), e))
goto add_failed;
GST_DEBUG_OBJECT (dvdbin, "Added %s element: %" GST_PTR_FORMAT, descr, e);
DVDBIN_LOCK (dvdbin);
dvdbin->pieces[index] = e;
DVDBIN_UNLOCK (dvdbin);
return TRUE;
create_failed:
gst_element_post_message (GST_ELEMENT_CAST (dvdbin),
gst_missing_element_message_new (GST_ELEMENT_CAST (dvdbin), factory));
GST_ELEMENT_ERROR (dvdbin, CORE, MISSING_PLUGIN, (NULL),
("Could not create %s element '%s'", descr, factory));
return FALSE;
add_failed:
gst_object_unref (e);
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not add %s element to bin", descr));
return FALSE;
}
typedef struct
{
RsnDvdBin *dvdbin;
GstPad *pad;
gulong pad_block_id;
} RsnDvdBinPadBlockCtx;
static GstPadProbeReturn dvdbin_pad_blocked_cb (GstPad * pad,
GstPadProbeInfo * info, RsnDvdBinPadBlockCtx * ctx);
static void
_pad_block_destroy_notify (RsnDvdBinPadBlockCtx * ctx)
{
gst_object_unref (ctx->dvdbin);
gst_object_unref (ctx->pad);
g_slice_free (RsnDvdBinPadBlockCtx, ctx);
}
#if DEBUG_TIMING
static GstPadProbeReturn
dvdbin_dump_timing_info (GstPad * opad,
GstPadProbeInfo * info, gpointer userdata)
{
if (GST_PAD_PROBE_INFO_TYPE (info) & (GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM |
GST_PAD_PROBE_TYPE_EVENT_FLUSH)) {
GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
const GstSegment *seg;
gst_event_parse_segment (event, &seg);
g_print ("%s:%s segment: rate %g format %d, start: %"
GST_TIME_FORMAT ", stop: %" GST_TIME_FORMAT ", time: %"
GST_TIME_FORMAT " base: %" GST_TIME_FORMAT "\n",
GST_DEBUG_PAD_NAME (opad),
seg->rate, seg->format, GST_TIME_ARGS (seg->start),
GST_TIME_ARGS (seg->stop), GST_TIME_ARGS (seg->time),
GST_TIME_ARGS (seg->base));
} else if (GST_EVENT_TYPE (event) == GST_EVENT_GAP) {
GstClockTime ts, dur, end;
gst_event_parse_gap (event, &ts, &dur);
end = ts;
if (ts != GST_CLOCK_TIME_NONE && dur != GST_CLOCK_TIME_NONE)
end += dur;
g_print ("%s:%s Gap TS: %" GST_TIME_FORMAT " dur %" GST_TIME_FORMAT
" (to %" GST_TIME_FORMAT ")\n", GST_DEBUG_PAD_NAME (opad),
GST_TIME_ARGS (ts), GST_TIME_ARGS (dur), GST_TIME_ARGS (end));
} else if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) {
g_print ("%s:%s FLUSHED\n", GST_DEBUG_PAD_NAME (opad));
}
}
if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER) {
GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER (info);
g_print ("%s:%s Buffer PTS %" GST_TIME_FORMAT " duration %" GST_TIME_FORMAT
"\n", GST_DEBUG_PAD_NAME (opad), GST_TIME_ARGS (GST_BUFFER_PTS (buf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
}
return GST_PAD_PROBE_OK;
}
#endif
static gboolean
try_link_pieces (GstElement * e1, const gchar * pad1, GstElement * e2,
const gchar * pad2)
{
GstPad *src = gst_element_get_static_pad (e1, pad1);
GstPad *sink = gst_element_get_static_pad (e2, pad2);
gboolean ret = FALSE;
if (src == NULL || sink == NULL)
goto done;
if (GST_PAD_LINK_FAILED (gst_pad_link (src, sink)))
goto done;
ret = TRUE;
done:
if (src)
gst_object_unref (src);
if (sink)
gst_object_unref (sink);
return ret;
}
static gboolean
create_elements (RsnDvdBin * dvdbin)
{
GstPadTemplate *src_templ = NULL;
GstPad *src = NULL;
GstPad *sink = NULL;
RsnDvdBinPadBlockCtx *bctx = NULL;
if (!try_create_piece (dvdbin, DVD_ELEM_SOURCE, NULL,
RESIN_TYPE_DVDSRC, "dvdsrc", "DVD source")) {
return FALSE;
}
/* FIXME: Locking */
if (dvdbin->device) {
g_object_set (G_OBJECT (dvdbin->pieces[DVD_ELEM_SOURCE]),
"device", dvdbin->device, NULL);
}
/* FIXME: Import and use local copy of mpeg PS demuxer */
if (!try_create_piece (dvdbin, DVD_ELEM_DEMUX,
NULL, GST_TYPE_FLUPS_DEMUX, "dvddemux", "DVD demuxer"))
return FALSE;
if (gst_element_link (dvdbin->pieces[DVD_ELEM_SOURCE],
dvdbin->pieces[DVD_ELEM_DEMUX]) == FALSE)
goto failed_connect;
/* Listen for new pads from the demuxer */
g_signal_connect (G_OBJECT (dvdbin->pieces[DVD_ELEM_DEMUX]), "pad-added",
G_CALLBACK (demux_pad_added), dvdbin);
g_signal_connect (G_OBJECT (dvdbin->pieces[DVD_ELEM_DEMUX]), "no-more-pads",
G_CALLBACK (demux_no_more_pads), dvdbin);
if (!try_create_piece (dvdbin, DVD_ELEM_MQUEUE, "multiqueue", 0, "rsnmq",
"multiqueue"))
return FALSE;
g_object_set (dvdbin->pieces[DVD_ELEM_MQUEUE],
"max-size-time", (7 * GST_SECOND / 10), "max-size-bytes", 0,
"max-size-buffers", 0, NULL);
if (!try_create_piece (dvdbin, DVD_ELEM_VIDPARSE, "mpegvideoparse", 0,
"rsnvidparse", "video parser"))
return FALSE;
/* Decodebin will throw a missing element message to find an MPEG decoder */
if (!try_create_piece (dvdbin, DVD_ELEM_VIDDEC, NULL, RSN_TYPE_VIDEODEC,
"rsnviddec", "video decoder"))
return FALSE;
/* FIXME: Replace identity */
if (!try_create_piece (dvdbin, DVD_ELEM_PARSET, "identity", 0, //RSN_TYPE_RSNPARSETTER,
"rsnparsetter", "Aspect ratio adjustment"))
return FALSE;
if (!try_link_pieces (dvdbin->pieces[DVD_ELEM_VIDPARSE], "src",
dvdbin->pieces[DVD_ELEM_VIDDEC], "sink"))
goto failed_vidparse_connect;
src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_VIDDEC], "src");
sink = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_PARSET], "sink");
if (src == NULL || sink == NULL)
goto failed_viddec_connect;
if (GST_PAD_LINK_FAILED (gst_pad_link (src, sink)))
goto failed_viddec_connect;
gst_object_unref (src);
gst_object_unref (sink);
src = sink = NULL;
src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_PARSET], "src");
if (src == NULL)
goto failed_video_ghost;
src_templ = gst_static_pad_template_get (&video_src_template);
dvdbin->video_pad = gst_ghost_pad_new_from_template ("video", src, src_templ);
gst_object_unref (src_templ);
if (dvdbin->video_pad == NULL)
goto failed_video_ghost;
gst_pad_set_active (dvdbin->video_pad, TRUE);
bctx = g_slice_new (RsnDvdBinPadBlockCtx);
bctx->dvdbin = gst_object_ref (dvdbin);
bctx->pad = gst_object_ref (dvdbin->video_pad);
bctx->pad_block_id =
gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
(GstPadProbeCallback) dvdbin_pad_blocked_cb, bctx, (GDestroyNotify)
_pad_block_destroy_notify);
gst_object_unref (src);
src = NULL;
#if DEBUG_TIMING
gst_pad_add_probe (dvdbin->video_pad,
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_BUFFER |
GST_PAD_PROBE_TYPE_EVENT_FLUSH,
(GstPadProbeCallback) dvdbin_dump_timing_info, NULL, NULL);
#endif
/* FIXME: Merge stream-selection logic to core and switch back */
if (!try_create_piece (dvdbin, DVD_ELEM_SPU_SELECT, NULL,
RSN_TYPE_INPUT_SELECTOR, "subpselect", "Subpicture stream selector"))
return FALSE;
g_object_set (G_OBJECT (dvdbin->pieces[DVD_ELEM_SPU_SELECT]),
"sync-streams", FALSE, NULL);
/* Add a single standalone queue to hold a single buffer of SPU data */
if (!try_create_piece (dvdbin, DVD_ELEM_SPUQ, "queue", 0, "spu_q",
"subpicture decoder buffer"))
return FALSE;
/* Allow a lot more while pre-rolling */
g_object_set (dvdbin->pieces[DVD_ELEM_SPUQ],
"max-size-time", G_GUINT64_CONSTANT (0), "max-size-bytes", 0,
"max-size-buffers", 100, NULL);
src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_SPU_SELECT], "src");
sink = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_SPUQ], "sink");
if (src == NULL || sink == NULL)
goto failed_spuq_connect;
if (GST_PAD_LINK_FAILED (gst_pad_link (src, sink)))
goto failed_spuq_connect;
gst_object_unref (src);
gst_object_unref (sink);
src = sink = NULL;
src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_SPUQ], "src");
if (src == NULL)
goto failed_spu_ghost;
src_templ = gst_static_pad_template_get (&subpicture_src_template);
dvdbin->subpicture_pad =
gst_ghost_pad_new_from_template ("subpicture", src, src_templ);
gst_object_unref (src_templ);
if (dvdbin->subpicture_pad == NULL)
goto failed_spu_ghost;
gst_pad_set_active (dvdbin->subpicture_pad, TRUE);
bctx = g_slice_new (RsnDvdBinPadBlockCtx);
bctx->dvdbin = gst_object_ref (dvdbin);
bctx->pad = gst_object_ref (dvdbin->subpicture_pad);
bctx->pad_block_id =
gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
(GstPadProbeCallback) dvdbin_pad_blocked_cb, bctx, (GDestroyNotify)
_pad_block_destroy_notify);
gst_object_unref (src);
src = NULL;
if (!try_create_piece (dvdbin, DVD_ELEM_AUD_SELECT, NULL,
RSN_TYPE_INPUT_SELECTOR, "audioselect", "Audio stream selector"))
return FALSE;
g_object_set (G_OBJECT (dvdbin->pieces[DVD_ELEM_AUD_SELECT]),
"sync-streams", FALSE, NULL);
if (!try_create_piece (dvdbin, DVD_ELEM_AUDDEC, NULL,
RSN_TYPE_AUDIODEC, "auddec", "audio decoder"))
return FALSE;
src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUD_SELECT], "src");
sink = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUDDEC], "sink");
if (src == NULL || sink == NULL)
goto failed_aud_connect;
if (GST_PAD_LINK_FAILED (gst_pad_link (src, sink)))
goto failed_aud_connect;
gst_object_unref (sink);
gst_object_unref (src);
src = sink = NULL;
/* ghost audio munge output pad onto bin */
src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUDDEC], "src");
if (src == NULL)
goto failed_aud_ghost;
src_templ = gst_static_pad_template_get (&audio_src_template);
dvdbin->audio_pad = gst_ghost_pad_new_from_template ("audio", src, src_templ);
gst_object_unref (src_templ);
if (dvdbin->audio_pad == NULL)
goto failed_aud_ghost;
gst_pad_set_active (dvdbin->audio_pad, TRUE);
bctx = g_slice_new (RsnDvdBinPadBlockCtx);
bctx->dvdbin = gst_object_ref (dvdbin);
bctx->pad = gst_object_ref (dvdbin->audio_pad);
bctx->pad_block_id =
gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
(GstPadProbeCallback) dvdbin_pad_blocked_cb, bctx, (GDestroyNotify)
_pad_block_destroy_notify);
gst_object_unref (src);
src = NULL;
if (dvdbin->video_added && (dvdbin->audio_added || dvdbin->audio_broken)
&& dvdbin->subpicture_added) {
rsn_dvdbin_no_more_pads (dvdbin);
}
return TRUE;
failed_connect:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not connect DVD source and demuxer elements"));
goto error_out;
failed_vidparse_connect:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not connect DVD video parser and video decoder"));
goto error_out;
failed_viddec_connect:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not connect DVD video decoder and aspect ratio adjuster"));
goto error_out;
failed_video_ghost:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not ghost video output pad"));
goto error_out;
failed_spuq_connect:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not connect DVD subpicture selector and buffer elements"));
goto error_out;
failed_spu_ghost:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not ghost SPU output pad"));
goto error_out;
failed_aud_connect:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not connect DVD audio decoder"));
goto error_out;
failed_aud_ghost:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Could not ghost audio output pad"));
goto error_out;
error_out:
if (src != NULL)
gst_object_unref (src);
if (sink != NULL)
gst_object_unref (sink);
return FALSE;
}
static void
remove_elements (RsnDvdBin * dvdbin)
{
gint i;
GList *tmp;
if (dvdbin->pieces[DVD_ELEM_MQUEUE] != NULL) {
for (tmp = dvdbin->mq_req_pads; tmp; tmp = g_list_next (tmp)) {
gst_element_release_request_pad (dvdbin->pieces[DVD_ELEM_MQUEUE],
GST_PAD (tmp->data));
}
}
g_list_free (dvdbin->mq_req_pads);
dvdbin->mq_req_pads = NULL;
for (i = 0; i < DVD_ELEM_LAST; i++) {
DVDBIN_LOCK (dvdbin);
if (dvdbin->pieces[i] != NULL) {
GstElement *piece = dvdbin->pieces[i];
dvdbin->pieces[i] = NULL;
DVDBIN_UNLOCK (dvdbin);
gst_element_set_state (piece, GST_STATE_NULL);
gst_bin_remove (GST_BIN (dvdbin), piece);
} else
DVDBIN_UNLOCK (dvdbin);
}
if (dvdbin->video_pad) {
if (dvdbin->video_added)
gst_element_remove_pad (GST_ELEMENT (dvdbin), dvdbin->video_pad);
else
gst_object_unref (dvdbin->video_pad);
}
if (dvdbin->audio_pad) {
if (dvdbin->audio_added)
gst_element_remove_pad (GST_ELEMENT (dvdbin), dvdbin->audio_pad);
else
gst_object_unref (dvdbin->audio_pad);
}
if (dvdbin->subpicture_pad) {
if (dvdbin->subpicture_added)
gst_element_remove_pad (GST_ELEMENT (dvdbin), dvdbin->subpicture_pad);
else
gst_object_unref (dvdbin->subpicture_pad);
}
dvdbin->video_added = dvdbin->audio_added = dvdbin->subpicture_added = FALSE;
dvdbin->audio_broken = FALSE;
dvdbin->video_pad = dvdbin->audio_pad = dvdbin->subpicture_pad = NULL;
dvdbin->did_no_more_pads = FALSE;
}
static GstPad *
connect_thru_mq (RsnDvdBin * dvdbin, GstPad * pad)
{
GstPad *mq_sink;
GstPad *mq_src;
gchar *tmp, *sinkname, *srcname;
/* Request a pad from multiqueue, then connect this one, then
* discover the corresponding output pad and return it */
mq_sink = gst_element_get_request_pad (dvdbin->pieces[DVD_ELEM_MQUEUE],
"sink_%u");
if (mq_sink == NULL)
return FALSE;
dvdbin->mq_req_pads = g_list_prepend (dvdbin->mq_req_pads, mq_sink);
if (gst_pad_link (pad, mq_sink) != GST_PAD_LINK_OK)
return FALSE;
sinkname = gst_pad_get_name (mq_sink);
tmp = sinkname + 5;
srcname = g_strdup_printf ("src_%s", tmp);
mq_src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_MQUEUE],
srcname);
g_free (sinkname);
g_free (srcname);
return mq_src;
}
static gboolean
can_sink_caps (GstElement * e, GstCaps * caps)
{
gboolean res = FALSE;
GstPad *sink = gst_element_get_static_pad (e, "sink");
if (sink) {
GstCaps *sink_caps = gst_pad_query_caps (sink, caps);
if (sink_caps) {
res = !gst_caps_is_empty (sink_caps);
gst_caps_unref (sink_caps);
}
gst_object_unref (sink);
}
return res;
}
static void
demux_pad_added (GstElement * element, GstPad * pad, RsnDvdBin * dvdbin)
{
gboolean skip_mq = FALSE;
GstPad *mq_pad = NULL;
GstPad *dest_pad = NULL;
GstCaps *caps;
GstStructure *s;
GST_DEBUG_OBJECT (dvdbin, "New pad: %" GST_PTR_FORMAT, pad);
caps = gst_pad_query_caps (pad, NULL);
if (caps == NULL) {
GST_WARNING_OBJECT (dvdbin, "NULL caps from pad %" GST_PTR_FORMAT, pad);
return;
}
if (!gst_caps_is_fixed (caps)) {
GST_WARNING_OBJECT (dvdbin, "Unfixed caps %" GST_PTR_FORMAT
" on pad %" GST_PTR_FORMAT, caps, pad);
gst_caps_unref (caps);
return;
}
GST_DEBUG_OBJECT (dvdbin,
"Pad %" GST_PTR_FORMAT " has caps: %" GST_PTR_FORMAT, pad, caps);
s = gst_caps_get_structure (caps, 0);
g_return_if_fail (s != NULL);
if (can_sink_caps (dvdbin->pieces[DVD_ELEM_VIDPARSE], caps)) {
GST_LOG_OBJECT (dvdbin, "Found video pad w/ caps %" GST_PTR_FORMAT, caps);
dest_pad =
gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_VIDPARSE], "sink");
} else if (g_str_equal (gst_structure_get_name (s), "subpicture/x-dvd")) {
GST_LOG_OBJECT (dvdbin, "Found subpicture pad w/ caps %" GST_PTR_FORMAT,
caps);
dest_pad =
gst_element_get_request_pad (dvdbin->pieces[DVD_ELEM_SPU_SELECT],
"sink_%u");
skip_mq = TRUE;
} else if (can_sink_caps (dvdbin->pieces[DVD_ELEM_AUDDEC], caps)) {
GST_LOG_OBJECT (dvdbin, "Found audio pad w/ caps %" GST_PTR_FORMAT, caps);
dest_pad =
gst_element_get_request_pad (dvdbin->pieces[DVD_ELEM_AUD_SELECT],
"sink_%u");
} else {
GstStructure *s;
GST_DEBUG_OBJECT (dvdbin, "Ignoring unusable pad w/ caps %" GST_PTR_FORMAT,
caps);
gst_element_post_message (GST_ELEMENT_CAST (dvdbin),
gst_missing_decoder_message_new (GST_ELEMENT_CAST (dvdbin), caps));
s = gst_caps_get_structure (caps, 0);
if (g_str_has_prefix ("video/", gst_structure_get_name (s))) {
GST_ELEMENT_ERROR (dvdbin, STREAM, CODEC_NOT_FOUND, (NULL),
("No MPEG video decoder found"));
} else {
GST_ELEMENT_WARNING (dvdbin, STREAM, CODEC_NOT_FOUND, (NULL),
("No audio decoder found"));
}
}
gst_caps_unref (caps);
if (dest_pad == NULL) {
GST_DEBUG_OBJECT (dvdbin, "Don't know how to handle pad. Ignoring");
return;
}
if (skip_mq) {
mq_pad = gst_object_ref (pad);
} else {
mq_pad = connect_thru_mq (dvdbin, pad);
if (mq_pad == NULL)
goto failed;
GST_DEBUG_OBJECT (dvdbin, "Linking new pad %" GST_PTR_FORMAT
" through multiqueue to %" GST_PTR_FORMAT, pad, dest_pad);
}
gst_pad_link (mq_pad, dest_pad);
gst_object_unref (mq_pad);
gst_object_unref (dest_pad);
return;
failed:
GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL),
("Failed to handle new demuxer pad %s", GST_PAD_NAME (pad)));
if (mq_pad)
gst_object_unref (mq_pad);
if (dest_pad)
gst_object_unref (dest_pad);
return;
}
static void
demux_no_more_pads (GstElement * element, RsnDvdBin * dvdbin)
{
gboolean no_more_pads = FALSE;
guint n_audio_pads = 0;
GST_DEBUG_OBJECT (dvdbin, "Received no more pads from demuxer");
DVDBIN_PREROLL_LOCK (dvdbin);
g_object_get (dvdbin->pieces[DVD_ELEM_AUD_SELECT], "n-pads", &n_audio_pads,
NULL);
if (n_audio_pads == 0) {
no_more_pads = dvdbin->video_added && dvdbin->subpicture_added;
dvdbin->audio_broken = TRUE;
}
DVDBIN_PREROLL_UNLOCK (dvdbin);
if (no_more_pads) {
GST_DEBUG_OBJECT (dvdbin,
"Firing no more pads from demuxer no-more-pads cb");
rsn_dvdbin_no_more_pads (dvdbin);
}
}
static GstPadProbeReturn
dvdbin_pad_blocked_cb (GstPad * opad,
GstPadProbeInfo * info, RsnDvdBinPadBlockCtx * ctx)
{
RsnDvdBin *dvdbin;
GstPad *pad;
gboolean added_last_pad = FALSE;
gboolean added = FALSE;
dvdbin = ctx->dvdbin;
pad = ctx->pad;
if (pad == dvdbin->subpicture_pad) {
GST_DEBUG_OBJECT (opad, "Pad block -> subpicture pad");
DVDBIN_PREROLL_LOCK (dvdbin);
added = dvdbin->subpicture_added;
dvdbin->subpicture_added = TRUE;
if (!added) {
gst_element_add_pad (GST_ELEMENT (dvdbin), dvdbin->subpicture_pad);
added_last_pad = ((dvdbin->audio_broken || dvdbin->audio_added)
&& dvdbin->video_added);
}
DVDBIN_PREROLL_UNLOCK (dvdbin);
if (ctx->pad_block_id)
gst_pad_remove_probe (opad, ctx->pad_block_id);
} else if (pad == dvdbin->audio_pad) {
GST_DEBUG_OBJECT (opad, "Pad block -> audio pad");
DVDBIN_PREROLL_LOCK (dvdbin);
added = dvdbin->audio_added;
dvdbin->audio_added = TRUE;
if (!added) {
gst_element_add_pad (GST_ELEMENT (dvdbin), dvdbin->audio_pad);
added_last_pad = (dvdbin->subpicture_added && dvdbin->video_added);
}
DVDBIN_PREROLL_UNLOCK (dvdbin);
if (ctx->pad_block_id)
gst_pad_remove_probe (opad, ctx->pad_block_id);
} else if (pad == dvdbin->video_pad) {
GST_DEBUG_OBJECT (opad, "Pad block -> video pad");
DVDBIN_PREROLL_LOCK (dvdbin);
added = dvdbin->video_added;
dvdbin->video_added = TRUE;
if (!added) {
gst_element_add_pad (GST_ELEMENT (dvdbin), dvdbin->video_pad);
added_last_pad = (dvdbin->subpicture_added && (dvdbin->audio_added
|| dvdbin->audio_broken));
}
DVDBIN_PREROLL_UNLOCK (dvdbin);
if (ctx->pad_block_id)
gst_pad_remove_probe (opad, ctx->pad_block_id);
}
if (added_last_pad) {
GST_DEBUG_OBJECT (dvdbin, "Firing no more pads from pad-blocked cb");
rsn_dvdbin_no_more_pads (dvdbin);
}
return GST_PAD_PROBE_OK;
}
static void
rsn_dvdbin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
RsnDvdBin *dvdbin = RESINDVDBIN (object);
switch (prop_id) {
case ARG_DEVICE:
DVDBIN_LOCK (dvdbin);
g_free (dvdbin->device);
if (g_value_get_string (value) == NULL)
dvdbin->device = g_strdup (DEFAULT_DEVICE);
else
dvdbin->device = g_value_dup_string (value);
if (dvdbin->pieces[DVD_ELEM_SOURCE]) {
g_object_set_property (G_OBJECT (dvdbin->pieces[DVD_ELEM_SOURCE]),
"device", value);
}
DVDBIN_UNLOCK (dvdbin);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
rsn_dvdbin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
RsnDvdBin *dvdbin = RESINDVDBIN (object);
switch (prop_id) {
case ARG_DEVICE:
DVDBIN_LOCK (dvdbin);
if (dvdbin->device)
g_value_set_string (value, dvdbin->device);
else if (dvdbin->pieces[DVD_ELEM_SOURCE])
g_object_get_property (G_OBJECT (dvdbin->pieces[DVD_ELEM_SOURCE]),
"device", value);
else
g_value_set_string (value, DEFAULT_DEVICE);
DVDBIN_UNLOCK (dvdbin);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstStateChangeReturn
rsn_dvdbin_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret;
RsnDvdBin *dvdbin = RESINDVDBIN (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
if (!create_elements (dvdbin)) {
remove_elements (dvdbin);
return GST_STATE_CHANGE_FAILURE;
}
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (ret == GST_STATE_CHANGE_FAILURE)
return ret;
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
case GST_STATE_CHANGE_READY_TO_NULL:
remove_elements (dvdbin);
break;
default:
break;
}
return ret;
}