| /* 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, 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 "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_static_metadata (element_class, "rsndvdbin", |
| "Generic/Bin/Player", |
| "DVD playback element", "Jan Schmidt <thaytan@noraisin.net>"); |
| } |
| |
| static void |
| rsn_dvdbin_init (RsnDvdBin * dvdbin) |
| { |
| g_mutex_init (&dvdbin->dvd_lock); |
| g_mutex_init (&dvdbin->preroll_lock); |
| } |
| |
| static void |
| rsn_dvdbin_finalize (GObject * object) |
| { |
| RsnDvdBin *dvdbin = RESINDVDBIN (object); |
| |
| g_mutex_clear (&dvdbin->dvd_lock); |
| g_mutex_clear (&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, NULL, 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; |
| guint pad_block_id = 0; |
| |
| 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); |
| } |
| pad_block_id = ctx->pad_block_id; |
| ctx->pad_block_id = 0; |
| DVDBIN_PREROLL_UNLOCK (dvdbin); |
| |
| if (pad_block_id) |
| gst_pad_remove_probe (opad, 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); |
| } |
| pad_block_id = ctx->pad_block_id; |
| ctx->pad_block_id = 0; |
| DVDBIN_PREROLL_UNLOCK (dvdbin); |
| |
| if (pad_block_id) |
| gst_pad_remove_probe (opad, 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)); |
| } |
| pad_block_id = ctx->pad_block_id; |
| ctx->pad_block_id = 0; |
| DVDBIN_PREROLL_UNLOCK (dvdbin); |
| |
| if (pad_block_id) |
| gst_pad_remove_probe (opad, 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; |
| } |