mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-17 03:35:21 +00:00
playback: New elements
With contributions from Jan Schmidt <jan@centricular.com> * decodebin3 and playbin3 have the same purpose as the decodebin and playbin elements, except make usage of more 1.x features and the new GstStream API. This allows them to be more memory/cpu efficient. * parsebin is a new element that demuxers/depayloads/parses an incoming stream and exposes elementary streams. It is used by decodebin3. It also automatically creates GstStream and GstStreamCollection for elements that don't natively create them and sends the corresponding events and messages * Any application using playbin can use playbin3 by setting the env variable USE_PLAYBIN3=1 without reconfiguration/recompilation.
This commit is contained in:
parent
5de9d5809b
commit
d514e79bee
15 changed files with 19833 additions and 3 deletions
|
@ -918,6 +918,7 @@ tests/check/Makefile
|
|||
tests/examples/Makefile
|
||||
tests/examples/app/Makefile
|
||||
tests/examples/audio/Makefile
|
||||
tests/examples/decodebin_next/Makefile
|
||||
tests/examples/dynamic/Makefile
|
||||
tests/examples/encoding/Makefile
|
||||
tests/examples/fft/Makefile
|
||||
|
|
|
@ -4,9 +4,14 @@ csp_cflags = -DCOLORSPACE=\"videoconvert\"
|
|||
|
||||
libgstplayback_la_SOURCES = \
|
||||
gstdecodebin2.c \
|
||||
gstdecodebin3.c \
|
||||
gsturidecodebin.c \
|
||||
gsturidecodebin3.c \
|
||||
gsturisourcebin.c \
|
||||
gstparsebin.c \
|
||||
gstplayback.c \
|
||||
gstplaybin2.c \
|
||||
gstplaybin3.c \
|
||||
gstplaysink.c \
|
||||
gstplay-enum.c \
|
||||
gstsubtitleoverlay.c \
|
||||
|
@ -26,7 +31,10 @@ libgstplayback_la_LIBADD = \
|
|||
$(GST_LIBS)
|
||||
libgstplayback_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
|
||||
|
||||
# FIXME: gstdecodebin3-parse.c isn't really a header,
|
||||
# but for now it's included into gstdecodebin3.c directly
|
||||
noinst_HEADERS = \
|
||||
gstdecodebin3-parse.c \
|
||||
gstplayback.h \
|
||||
gstplaysink.h \
|
||||
gstplay-enum.h \
|
||||
|
|
551
gst/playback/gstdecodebin3-parse.c
Normal file
551
gst/playback/gstdecodebin3-parse.c
Normal file
|
@ -0,0 +1,551 @@
|
|||
/* GStreamer
|
||||
*
|
||||
* Copyright (C) <2015> Centricular Ltd
|
||||
* @author: Edward Hervey <edward@centricular.com>
|
||||
* @author: Jan Schmidt <jan@centricular.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if 0
|
||||
/* Not needed for now - we're including gstdecodebin3-parse.c into gstdecodebin3.c */
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <glib/gprintf.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include "gstplayback.h"
|
||||
#endif
|
||||
|
||||
/* Streams that come from demuxers (input/upstream) */
|
||||
/* FIXME : All this is hardcoded. Switch to tree of chains */
|
||||
struct _DecodebinInputStream
|
||||
{
|
||||
GstDecodebin3 *dbin;
|
||||
GstStream *pending_stream; /* Extra ref */
|
||||
GstStream *active_stream;
|
||||
|
||||
DecodebinInput *input;
|
||||
|
||||
GstPad *srcpad; /* From demuxer */
|
||||
|
||||
/* id of the pad event probe */
|
||||
gulong output_event_probe_id;
|
||||
|
||||
/* id of the buffer blocking probe on the input (demuxer src) pad */
|
||||
gulong input_buffer_probe_id;
|
||||
|
||||
/* Whether we saw an EOS on input. This should be treated accordingly
|
||||
* when the stream is no longer used */
|
||||
gboolean saw_eos;
|
||||
/* TRUE if the EOS being pushed is only for draining and does not represent
|
||||
* the full media EOS */
|
||||
gboolean drain_eos;
|
||||
};
|
||||
|
||||
static void parsebin_pad_added_cb (GstElement * demux, GstPad * pad,
|
||||
DecodebinInput * input);
|
||||
static void parsebin_pad_removed_cb (GstElement * demux, GstPad * pad,
|
||||
DecodebinInput * input);
|
||||
|
||||
static gboolean
|
||||
pending_pads_are_eos (DecodebinInput * input)
|
||||
{
|
||||
GList *tmp;
|
||||
|
||||
for (tmp = input->pending_pads; tmp; tmp = tmp->next) {
|
||||
PendingPad *ppad = (PendingPad *) tmp->data;
|
||||
if (ppad->saw_eos == FALSE)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
all_inputs_are_eos (GstDecodebin3 * dbin)
|
||||
{
|
||||
GList *tmp;
|
||||
/* First check input streams */
|
||||
for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
|
||||
DecodebinInputStream *input = (DecodebinInputStream *) tmp->data;
|
||||
if (input->saw_eos == FALSE)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Check pending pads */
|
||||
if (!pending_pads_are_eos (dbin->main_input))
|
||||
return FALSE;
|
||||
for (tmp = dbin->other_inputs; tmp; tmp = tmp->next)
|
||||
if (!pending_pads_are_eos ((DecodebinInput *) tmp->data))
|
||||
return FALSE;
|
||||
|
||||
GST_DEBUG_OBJECT (dbin, "All streams are EOS");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
check_all_streams_for_eos (GstDecodebin3 * dbin)
|
||||
{
|
||||
GList *tmp;
|
||||
|
||||
if (!all_inputs_are_eos (dbin))
|
||||
return;
|
||||
|
||||
/* We know all streams are EOS, properly clean up everything */
|
||||
for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
|
||||
DecodebinInputStream *input = (DecodebinInputStream *) tmp->data;
|
||||
GstPad *peer = gst_pad_get_peer (input->srcpad);
|
||||
|
||||
/* Send EOS and then remove elements */
|
||||
if (peer) {
|
||||
gst_pad_send_event (peer, gst_event_new_eos ());
|
||||
gst_object_unref (peer);
|
||||
}
|
||||
GST_FIXME_OBJECT (input->srcpad, "Remove input stream");
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the intersection of parser caps and available (sorted) decoders */
|
||||
static GstCaps *
|
||||
get_parser_caps_filter (GstDecodebin3 * dbin, GstCaps * caps)
|
||||
{
|
||||
GList *tmp;
|
||||
GstCaps *filter_caps = gst_caps_new_empty ();
|
||||
|
||||
g_mutex_lock (&dbin->factories_lock);
|
||||
gst_decode_bin_update_factories_list (dbin);
|
||||
for (tmp = dbin->decoder_factories; tmp; tmp = tmp->next) {
|
||||
GstElementFactory *factory = (GstElementFactory *) tmp->data;
|
||||
GstCaps *tcaps, *intersection;
|
||||
const GList *tmps;
|
||||
|
||||
GST_LOG ("Trying factory %s",
|
||||
gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
|
||||
for (tmps = gst_element_factory_get_static_pad_templates (factory); tmps;
|
||||
tmps = tmps->next) {
|
||||
GstStaticPadTemplate *st = (GstStaticPadTemplate *) tmps->data;
|
||||
if (st->direction != GST_PAD_SINK || st->presence != GST_PAD_ALWAYS)
|
||||
continue;
|
||||
tcaps = gst_static_pad_template_get_caps (st);
|
||||
intersection =
|
||||
gst_caps_intersect_full (tcaps, caps, GST_CAPS_INTERSECT_FIRST);
|
||||
filter_caps = gst_caps_merge (filter_caps, intersection);
|
||||
gst_caps_unref (tcaps);
|
||||
}
|
||||
}
|
||||
g_mutex_unlock (&dbin->factories_lock);
|
||||
GST_DEBUG_OBJECT (dbin, "Got filter caps %" GST_PTR_FORMAT, filter_caps);
|
||||
return filter_caps;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
check_parser_caps_filter (GstDecodebin3 * dbin, GstCaps * caps)
|
||||
{
|
||||
GList *tmp;
|
||||
gboolean res = FALSE;
|
||||
|
||||
g_mutex_lock (&dbin->factories_lock);
|
||||
gst_decode_bin_update_factories_list (dbin);
|
||||
for (tmp = dbin->decoder_factories; tmp; tmp = tmp->next) {
|
||||
GstElementFactory *factory = (GstElementFactory *) tmp->data;
|
||||
GstCaps *tcaps;
|
||||
const GList *tmps;
|
||||
|
||||
GST_LOG ("Trying factory %s",
|
||||
gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
|
||||
for (tmps = gst_element_factory_get_static_pad_templates (factory); tmps;
|
||||
tmps = tmps->next) {
|
||||
GstStaticPadTemplate *st = (GstStaticPadTemplate *) tmps->data;
|
||||
if (st->direction != GST_PAD_SINK || st->presence != GST_PAD_ALWAYS)
|
||||
continue;
|
||||
tcaps = gst_static_pad_template_get_caps (st);
|
||||
if (gst_caps_can_intersect (tcaps, caps)) {
|
||||
res = TRUE;
|
||||
gst_caps_unref (tcaps);
|
||||
goto beach;
|
||||
}
|
||||
gst_caps_unref (tcaps);
|
||||
}
|
||||
}
|
||||
beach:
|
||||
g_mutex_unlock (&dbin->factories_lock);
|
||||
GST_DEBUG_OBJECT (dbin, "Can intersect : %d", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Probe on the output of a parser chain (the last
|
||||
* src pad) */
|
||||
static GstPadProbeReturn
|
||||
parse_chain_output_probe (GstPad * pad, GstPadProbeInfo * info,
|
||||
DecodebinInputStream * input)
|
||||
{
|
||||
GstPadProbeReturn ret = GST_PAD_PROBE_OK;
|
||||
|
||||
if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) {
|
||||
GstEvent *ev = GST_PAD_PROBE_INFO_EVENT (info);
|
||||
|
||||
GST_DEBUG_OBJECT (pad, "Got event %s", GST_EVENT_TYPE_NAME (ev));
|
||||
switch (GST_EVENT_TYPE (ev)) {
|
||||
case GST_EVENT_STREAM_START:
|
||||
{
|
||||
GstStream *stream = NULL;
|
||||
guint group_id = G_MAXUINT32;
|
||||
gst_event_parse_group_id (ev, &group_id);
|
||||
GST_DEBUG_OBJECT (pad, "Got stream-start, group_id:%d, input %p",
|
||||
group_id, input->input);
|
||||
if (set_input_group_id (input->input, &group_id)) {
|
||||
ev = gst_event_make_writable (ev);
|
||||
gst_event_set_group_id (ev, group_id);
|
||||
GST_PAD_PROBE_INFO_DATA (info) = ev;
|
||||
}
|
||||
input->saw_eos = FALSE;
|
||||
|
||||
gst_event_parse_stream (ev, &stream);
|
||||
/* FIXME : Would we ever end up with a stream already set on the input ?? */
|
||||
if (stream) {
|
||||
if (input->active_stream != stream) {
|
||||
MultiQueueSlot *slot;
|
||||
if (input->active_stream)
|
||||
gst_object_unref (input->active_stream);
|
||||
input->active_stream = stream;
|
||||
/* We have the beginning of a stream, get a multiqueue slot and link to it */
|
||||
slot = get_slot_for_input (input->dbin, input);
|
||||
link_input_to_slot (input, slot);
|
||||
} else
|
||||
gst_object_unref (stream);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GST_EVENT_CAPS:
|
||||
{
|
||||
GstCaps *caps = NULL;
|
||||
gst_event_parse_caps (ev, &caps);
|
||||
GST_DEBUG_OBJECT (pad, "caps %" GST_PTR_FORMAT, caps);
|
||||
if (caps && input->active_stream)
|
||||
gst_stream_set_caps (input->active_stream, caps);
|
||||
}
|
||||
break;
|
||||
case GST_EVENT_EOS:
|
||||
/* FIXME : Make sure this makes sense ... */
|
||||
if (TRUE) {
|
||||
GST_DEBUG_OBJECT (pad, "real input pad, marking as EOS");
|
||||
input->saw_eos = TRUE;
|
||||
check_all_streams_for_eos (input->dbin);
|
||||
ret = GST_PAD_PROBE_DROP;
|
||||
} else {
|
||||
MultiQueueSlot *slot = get_slot_for_input (input->dbin, input);
|
||||
|
||||
slot->drain_eos = input->drain_eos;
|
||||
|
||||
if (input->drain_eos) {
|
||||
GST_DEBUG_OBJECT (pad,
|
||||
"Got EOS at end of input stream (drain_eos:%d) Dropping.",
|
||||
input->drain_eos);
|
||||
ret = GST_PAD_PROBE_DROP;
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (pad,
|
||||
"Got EOS at end of input stream (drain_eos:%d) Passing.",
|
||||
input->drain_eos);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (GST_IS_QUERY (GST_PAD_PROBE_INFO_DATA (info))) {
|
||||
GstQuery *q = GST_PAD_PROBE_INFO_QUERY (info);
|
||||
GST_DEBUG_OBJECT (pad, "Seeing query %s", GST_QUERY_TYPE_NAME (q));
|
||||
/* If we have a parser, we want to reply to the caps query */
|
||||
/* FIXME: Set a flag when the input stream is created for
|
||||
* streams where we shouldn't reply to these queries */
|
||||
if (GST_QUERY_TYPE (q) == GST_QUERY_CAPS
|
||||
&& (info->type & GST_PAD_PROBE_TYPE_PULL)) {
|
||||
GstCaps *filter = NULL;
|
||||
GstCaps *allowed;
|
||||
gst_query_parse_caps (q, &filter);
|
||||
allowed = get_parser_caps_filter (input->dbin, filter);
|
||||
GST_DEBUG_OBJECT (pad,
|
||||
"Intercepting caps query, setting %" GST_PTR_FORMAT, allowed);
|
||||
gst_query_set_caps_result (q, allowed);
|
||||
gst_caps_unref (allowed);
|
||||
ret = GST_PAD_PROBE_HANDLED;
|
||||
} else if (GST_QUERY_TYPE (q) == GST_QUERY_ACCEPT_CAPS) {
|
||||
GstCaps *prop = NULL;
|
||||
gst_query_parse_accept_caps (q, &prop);
|
||||
/* Fast check against target caps */
|
||||
if (gst_caps_can_intersect (prop, input->dbin->caps))
|
||||
gst_query_set_accept_caps_result (q, TRUE);
|
||||
else {
|
||||
gboolean accepted = check_parser_caps_filter (input->dbin, prop);
|
||||
/* check against caps filter */
|
||||
gst_query_set_accept_caps_result (q, accepted);
|
||||
GST_DEBUG_OBJECT (pad, "ACCEPT_CAPS query, returning %d", accepted);
|
||||
}
|
||||
ret = GST_PAD_PROBE_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DecodebinInputStream *
|
||||
create_input_stream (GstDecodebin3 * dbin, GstStream * stream, GstPad * pad,
|
||||
DecodebinInput * input)
|
||||
{
|
||||
DecodebinInputStream *res = g_new0 (DecodebinInputStream, 1);
|
||||
|
||||
GST_DEBUG_OBJECT (pad, "Creating input stream for stream %p %s (input:%p)",
|
||||
stream, gst_stream_get_stream_id (stream), input);
|
||||
|
||||
res->dbin = dbin;
|
||||
res->input = input;
|
||||
res->pending_stream = gst_object_ref (stream);
|
||||
res->srcpad = pad;
|
||||
|
||||
/* Put probe on output source pad (for detecting EOS/STREAM_START) */
|
||||
res->output_event_probe_id =
|
||||
gst_pad_add_probe (pad,
|
||||
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
|
||||
(GstPadProbeCallback) parse_chain_output_probe, res, NULL);
|
||||
|
||||
/* Add to list of current input streams */
|
||||
dbin->input_streams = g_list_append (dbin->input_streams, res);
|
||||
GST_DEBUG_OBJECT (pad, "Done creating input stream");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
remove_input_stream (GstDecodebin3 * dbin, DecodebinInputStream * stream)
|
||||
{
|
||||
MultiQueueSlot *slot;
|
||||
|
||||
GST_DEBUG_OBJECT (dbin, "Removing input stream %p (%s)", stream,
|
||||
stream->active_stream ? gst_stream_get_stream_id (stream->active_stream) :
|
||||
"<NONE>");
|
||||
|
||||
/* Unlink from slot */
|
||||
if (stream->srcpad) {
|
||||
GstPad *peer;
|
||||
peer = gst_pad_get_peer (stream->srcpad);
|
||||
if (peer) {
|
||||
gst_pad_unlink (stream->srcpad, peer);
|
||||
gst_object_unref (peer);
|
||||
}
|
||||
}
|
||||
|
||||
slot = get_slot_for_input (dbin, stream);
|
||||
if (slot) {
|
||||
slot->pending_stream = NULL;
|
||||
slot->input = NULL;
|
||||
GST_DEBUG_OBJECT (dbin, "slot %p cleared", slot);
|
||||
}
|
||||
|
||||
dbin->input_streams = g_list_remove (dbin->input_streams, stream);
|
||||
|
||||
g_free (stream);
|
||||
}
|
||||
|
||||
|
||||
/* FIXME : HACK, REMOVE, USE INPUT CHAINS */
|
||||
static GstPadProbeReturn
|
||||
parsebin_buffer_probe (GstPad * pad, GstPadProbeInfo * info,
|
||||
DecodebinInput * input)
|
||||
{
|
||||
GstDecodebin3 *dbin = input->dbin;
|
||||
GList *tmp;
|
||||
|
||||
GST_FIXME_OBJECT (dbin, "Need a lock !");
|
||||
|
||||
GST_DEBUG_OBJECT (pad, "Got a buffer ! UNBLOCK !");
|
||||
|
||||
/* Any data out the demuxer means it's not creating pads
|
||||
* any more right now */
|
||||
|
||||
/* 1. Re-use existing streams if/when possible */
|
||||
GST_FIXME_OBJECT (dbin, "Re-use existing input streams if/when possible");
|
||||
|
||||
/* 2. Remove unused streams (push EOS) */
|
||||
GST_DEBUG_OBJECT (dbin, "Removing unused streams");
|
||||
tmp = dbin->input_streams;
|
||||
while (tmp != NULL) {
|
||||
DecodebinInputStream *input_stream = (DecodebinInputStream *) tmp->data;
|
||||
GList *next = tmp->next;
|
||||
|
||||
GST_DEBUG_OBJECT (dbin, "Checking input stream %p", input_stream);
|
||||
if (input_stream->input_buffer_probe_id) {
|
||||
GST_DEBUG_OBJECT (dbin,
|
||||
"Removing pad block on input %p pad %" GST_PTR_FORMAT, input_stream,
|
||||
input_stream->srcpad);
|
||||
gst_pad_remove_probe (input_stream->srcpad,
|
||||
input_stream->input_buffer_probe_id);
|
||||
}
|
||||
input_stream->input_buffer_probe_id = 0;
|
||||
|
||||
if (input_stream->saw_eos) {
|
||||
remove_input_stream (dbin, input_stream);
|
||||
tmp = dbin->input_streams;
|
||||
} else
|
||||
tmp = next;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (dbin, "Creating new streams (if needed)");
|
||||
/* 3. Create new streams */
|
||||
for (tmp = input->pending_pads; tmp; tmp = tmp->next) {
|
||||
GstStream *stream;
|
||||
PendingPad *ppad = (PendingPad *) tmp->data;
|
||||
|
||||
stream = gst_pad_get_stream (ppad->pad);
|
||||
if (stream == NULL) {
|
||||
GST_ERROR_OBJECT (dbin, "No stream for pad ????");
|
||||
} else {
|
||||
MultiQueueSlot *slot;
|
||||
DecodebinInputStream *input_stream;
|
||||
/* The remaining pads in pending_pads are the ones that require a new
|
||||
* input stream */
|
||||
input_stream = create_input_stream (dbin, stream, ppad->pad, ppad->input);
|
||||
/* See if we can link it straight away */
|
||||
input_stream->active_stream = stream;
|
||||
slot = get_slot_for_input (dbin, input_stream);
|
||||
link_input_to_slot (input_stream, slot);
|
||||
/* Remove the buffer and event probe */
|
||||
gst_pad_remove_probe (ppad->pad, ppad->buffer_probe);
|
||||
gst_pad_remove_probe (ppad->pad, ppad->event_probe);
|
||||
g_free (ppad);
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free (input->pending_pads);
|
||||
input->pending_pads = NULL;
|
||||
|
||||
/* Weed out unused multiqueue slots */
|
||||
for (tmp = dbin->slots; tmp; tmp = tmp->next) {
|
||||
MultiQueueSlot *slot = (MultiQueueSlot *) tmp->data;
|
||||
GST_LOG_OBJECT (dbin, "Slot %d input:%p drain_eos:%d",
|
||||
slot->id, slot->input, slot->drain_eos);
|
||||
if (slot->input == NULL) {
|
||||
GST_DEBUG_OBJECT (slot->sink_pad, "Sending EOS to unused slot");
|
||||
gst_pad_send_event (slot->sink_pad, gst_event_new_eos ());
|
||||
}
|
||||
}
|
||||
|
||||
return GST_PAD_PROBE_OK;
|
||||
}
|
||||
|
||||
static GstPadProbeReturn
|
||||
parsebin_pending_event_probe (GstPad * pad, GstPadProbeInfo * info,
|
||||
PendingPad * ppad)
|
||||
{
|
||||
GstDecodebin3 *dbin = ppad->dbin;
|
||||
/* We drop all events by default */
|
||||
GstPadProbeReturn ret = GST_PAD_PROBE_DROP;
|
||||
GstEvent *ev = GST_PAD_PROBE_INFO_EVENT (info);
|
||||
|
||||
GST_DEBUG_OBJECT (pad, "Got event %p %s", ev, GST_EVENT_TYPE_NAME (ev));
|
||||
switch (GST_EVENT_TYPE (ev)) {
|
||||
case GST_EVENT_EOS:
|
||||
{
|
||||
GST_DEBUG_OBJECT (pad, "Pending pad marked as EOS, removing");
|
||||
ppad->input->pending_pads =
|
||||
g_list_remove (ppad->input->pending_pads, ppad);
|
||||
gst_pad_remove_probe (ppad->pad, ppad->buffer_probe);
|
||||
gst_pad_remove_probe (ppad->pad, ppad->event_probe);
|
||||
g_free (ppad);
|
||||
|
||||
check_all_streams_for_eos (dbin);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
parsebin_pad_added_cb (GstElement * demux, GstPad * pad, DecodebinInput * input)
|
||||
{
|
||||
GstDecodebin3 *dbin = input->dbin;
|
||||
PendingPad *ppad;
|
||||
GList *tmp;
|
||||
|
||||
GST_DEBUG_OBJECT (dbin, "New pad %s:%s (input:%p)", GST_DEBUG_PAD_NAME (pad),
|
||||
input);
|
||||
|
||||
ppad = g_new0 (PendingPad, 1);
|
||||
ppad->dbin = dbin;
|
||||
ppad->input = input;
|
||||
ppad->pad = pad;
|
||||
|
||||
ppad->event_probe =
|
||||
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
|
||||
(GstPadProbeCallback) parsebin_pending_event_probe, ppad, NULL);
|
||||
ppad->buffer_probe =
|
||||
gst_pad_add_probe (pad,
|
||||
GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
|
||||
(GstPadProbeCallback) parsebin_buffer_probe, input, NULL);
|
||||
|
||||
input->pending_pads = g_list_append (input->pending_pads, ppad);
|
||||
|
||||
/* FIXME : ONLY DO FOR THIS PARSEBIN/INPUT ! */
|
||||
/* Check if all existing input streams have a buffer probe set */
|
||||
for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
|
||||
DecodebinInputStream *input_stream = (DecodebinInputStream *) tmp->data;
|
||||
if (input_stream->input_buffer_probe_id == 0) {
|
||||
GST_DEBUG_OBJECT (input_stream->srcpad, "Adding blocking buffer probe");
|
||||
input_stream->input_buffer_probe_id =
|
||||
gst_pad_add_probe (input_stream->srcpad,
|
||||
GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
|
||||
(GstPadProbeCallback) parsebin_buffer_probe, input_stream->input,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
parsebin_pad_removed_cb (GstElement * demux, GstPad * pad, DecodebinInput * inp)
|
||||
{
|
||||
GstDecodebin3 *dbin = inp->dbin;
|
||||
DecodebinInputStream *input = NULL;
|
||||
GList *tmp;
|
||||
GST_DEBUG_OBJECT (pad, "removed");
|
||||
|
||||
for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
|
||||
DecodebinInputStream *cand = (DecodebinInputStream *) tmp->data;
|
||||
if (cand->srcpad == pad)
|
||||
input = cand;
|
||||
}
|
||||
/* If there are no pending pads, this means we will definitely not need this
|
||||
* stream anymore */
|
||||
if (input) {
|
||||
GST_DEBUG_OBJECT (pad, "stream %p", input);
|
||||
if (inp->pending_pads == NULL) {
|
||||
GST_DEBUG_OBJECT (pad, "Remove input stream %p", input);
|
||||
remove_input_stream (dbin, input);
|
||||
} else {
|
||||
input->srcpad = NULL;
|
||||
if (input->input_buffer_probe_id)
|
||||
gst_pad_remove_probe (pad, input->input_buffer_probe_id);
|
||||
input->input_buffer_probe_id = 0;
|
||||
}
|
||||
}
|
||||
}
|
2438
gst/playback/gstdecodebin3.c
Normal file
2438
gst/playback/gstdecodebin3.c
Normal file
File diff suppressed because it is too large
Load diff
4393
gst/playback/gstparsebin.c
Normal file
4393
gst/playback/gstparsebin.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -46,13 +46,26 @@ plugin_init (GstPlugin * plugin)
|
|||
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
|
||||
#endif /* ENABLE_NLS */
|
||||
|
||||
res = gst_play_bin2_plugin_init (plugin);
|
||||
/* Swap in playbin3 as 'playbin' if USE_PLAYBIN3=1 */
|
||||
{
|
||||
const gchar *env = g_getenv ("USE_PLAYBIN3");
|
||||
if (env && g_str_has_prefix (env, "1"))
|
||||
res = gst_play_bin3_plugin_init (plugin, TRUE);
|
||||
else
|
||||
res = gst_play_bin2_plugin_init (plugin);
|
||||
}
|
||||
|
||||
res &= gst_play_bin3_plugin_init (plugin, FALSE);
|
||||
res &= gst_play_sink_plugin_init (plugin);
|
||||
res &= gst_subtitle_overlay_plugin_init (plugin);
|
||||
res &= gst_stream_synchronizer_plugin_init (plugin);
|
||||
|
||||
res &= gst_decode_bin_plugin_init (plugin);
|
||||
res &= gst_decodebin3_plugin_init (plugin);
|
||||
res &= gst_uri_decode_bin_plugin_init (plugin);
|
||||
res &= gst_uri_decode_bin3_plugin_init (plugin);
|
||||
res &= gst_uri_source_bin_plugin_init (plugin);
|
||||
res &= gst_parse_bin_plugin_init (plugin);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -24,10 +24,15 @@
|
|||
#include <gst/gst.h>
|
||||
|
||||
gboolean gst_decode_bin_plugin_init (GstPlugin * plugin);
|
||||
gboolean gst_decodebin3_plugin_init (GstPlugin * plugin);
|
||||
gboolean gst_uri_decode_bin_plugin_init (GstPlugin * plugin);
|
||||
gboolean gst_uri_decode_bin3_plugin_init (GstPlugin * plugin);
|
||||
gboolean gst_uri_source_bin_plugin_init (GstPlugin * plugin);
|
||||
gboolean gst_parse_bin_plugin_init (GstPlugin * plugin);
|
||||
|
||||
gboolean gst_play_bin_plugin_init (GstPlugin * plugin);
|
||||
gboolean gst_play_bin2_plugin_init (GstPlugin * plugin);
|
||||
gboolean gst_play_bin3_plugin_init (GstPlugin * plugin, gboolean as_playbin);
|
||||
|
||||
|
||||
#endif /* __GST_PLAY_BACK_H__ */
|
||||
|
|
6079
gst/playback/gstplaybin3.c
Normal file
6079
gst/playback/gstplaybin3.c
Normal file
File diff suppressed because it is too large
Load diff
2833
gst/playback/gsturidecodebin3.c
Normal file
2833
gst/playback/gsturidecodebin3.c
Normal file
File diff suppressed because it is too large
Load diff
2823
gst/playback/gsturisourcebin.c
Normal file
2823
gst/playback/gsturisourcebin.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,8 +2,8 @@ if HAVE_GTK
|
|||
GTK_SUBDIRS = playback seek snapshot
|
||||
endif
|
||||
|
||||
SUBDIRS = app audio dynamic fft $(GTK_SUBDIRS) gio overlay playrec encoding
|
||||
DIST_SUBDIRS = app audio dynamic fft gio playback overlay seek snapshot playrec encoding
|
||||
SUBDIRS = app audio decodebin_next dynamic fft $(GTK_SUBDIRS) gio overlay playrec encoding
|
||||
DIST_SUBDIRS = app audio dynamic decodebin_next fft gio playback overlay seek snapshot playrec encoding
|
||||
|
||||
include $(top_srcdir)/common/parallel-subdirs.mak
|
||||
|
||||
|
|
2
tests/examples/decodebin_next/.gitignore
vendored
Normal file
2
tests/examples/decodebin_next/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
decodebin3
|
||||
playbin-test
|
5
tests/examples/decodebin_next/Makefile.am
Normal file
5
tests/examples/decodebin_next/Makefile.am
Normal file
|
@ -0,0 +1,5 @@
|
|||
noinst_PROGRAMS = decodebin3 playbin-test
|
||||
|
||||
LDADD = $(GST_LIBS)
|
||||
|
||||
AM_CFLAGS = -I$(top_builddir)/gst-libs $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS)
|
353
tests/examples/decodebin_next/decodebin3.c
Normal file
353
tests/examples/decodebin_next/decodebin3.c
Normal file
|
@ -0,0 +1,353 @@
|
|||
/* sample application for testing decodebin3
|
||||
*
|
||||
* Copyright (C) 2015 Centricular Ltd
|
||||
* @author: Edward Hervey <edward@centricular.com>
|
||||
*
|
||||
* 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 <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <glib/gprintf.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
/* Global structure */
|
||||
|
||||
typedef struct _MyDataStruct
|
||||
{
|
||||
GMainLoop *mainloop;
|
||||
GstElement *pipeline;
|
||||
GstBus *demux_bus;
|
||||
|
||||
GstElement *decodebin;
|
||||
|
||||
GstElement *src;
|
||||
GList *other_src;
|
||||
GstElement *playsink;
|
||||
|
||||
/* Current collection */
|
||||
GstStreamCollection *collection;
|
||||
guint notify_id;
|
||||
|
||||
guint current_audio;
|
||||
guint current_video;
|
||||
guint current_text;
|
||||
|
||||
glong timeout_id;
|
||||
} MyDataStruct;
|
||||
|
||||
static void
|
||||
print_tag_foreach (const GstTagList * tags, const gchar * tag,
|
||||
gpointer user_data)
|
||||
{
|
||||
GValue val = { 0, };
|
||||
gchar *str;
|
||||
gint depth = GPOINTER_TO_INT (user_data);
|
||||
|
||||
if (!gst_tag_list_copy_value (&val, tags, tag))
|
||||
return;
|
||||
|
||||
if (G_VALUE_HOLDS_STRING (&val))
|
||||
str = g_value_dup_string (&val);
|
||||
else
|
||||
str = gst_value_serialize (&val);
|
||||
|
||||
g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
|
||||
g_free (str);
|
||||
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
static void
|
||||
dump_collection (GstStreamCollection * collection)
|
||||
{
|
||||
guint i;
|
||||
const GstTagList *tags;
|
||||
const GstCaps *caps;
|
||||
|
||||
for (i = 0; i < gst_stream_collection_get_size (collection); i++) {
|
||||
GstStream *stream = gst_stream_collection_get_stream (collection, i);
|
||||
g_print (" Stream %u type %s flags 0x%x\n", i,
|
||||
gst_stream_type_get_name (gst_stream_get_stream_type (stream)),
|
||||
gst_stream_get_stream_flags (stream));
|
||||
g_print (" ID: %s\n", gst_stream_get_stream_id (stream));
|
||||
|
||||
caps = gst_stream_get_caps (stream);
|
||||
if (caps) {
|
||||
gchar *caps_str = gst_caps_to_string (caps);
|
||||
g_print (" caps: %s\n", caps_str);
|
||||
g_free (caps_str);
|
||||
}
|
||||
|
||||
tags = gst_stream_get_tags (stream);
|
||||
if (tags) {
|
||||
g_print (" tags:\n");
|
||||
gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
switch_streams (MyDataStruct * data)
|
||||
{
|
||||
guint i, nb_streams;
|
||||
gint nb_video = 0, nb_audio = 0, nb_text = 0;
|
||||
GstStream *videos[256], *audios[256], *texts[256];
|
||||
GList *streams = NULL;
|
||||
GstEvent *ev;
|
||||
|
||||
g_print ("Switching Streams...\n");
|
||||
|
||||
/* Calculate the number of streams of each type */
|
||||
nb_streams = gst_stream_collection_get_size (data->collection);
|
||||
for (i = 0; i < nb_streams; i++) {
|
||||
GstStream *stream = gst_stream_collection_get_stream (data->collection, i);
|
||||
GstStreamType stype = gst_stream_get_stream_type (stream);
|
||||
if (stype == GST_STREAM_TYPE_VIDEO) {
|
||||
videos[nb_video] = stream;
|
||||
nb_video += 1;
|
||||
} else if (stype == GST_STREAM_TYPE_AUDIO) {
|
||||
audios[nb_audio] = stream;
|
||||
nb_audio += 1;
|
||||
} else if (stype == GST_STREAM_TYPE_TEXT) {
|
||||
texts[nb_text] = stream;
|
||||
nb_text += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (nb_video) {
|
||||
data->current_video = (data->current_video + 1) % nb_video;
|
||||
streams =
|
||||
g_list_append (streams,
|
||||
(gchar *) gst_stream_get_stream_id (videos[data->current_video]));
|
||||
g_print (" Selecting video channel #%d : %s\n", data->current_video,
|
||||
gst_stream_get_stream_id (videos[data->current_video]));
|
||||
}
|
||||
if (nb_audio) {
|
||||
data->current_audio = (data->current_audio + 1) % nb_audio;
|
||||
streams =
|
||||
g_list_append (streams,
|
||||
(gchar *) gst_stream_get_stream_id (audios[data->current_audio]));
|
||||
g_print (" Selecting audio channel #%d : %s\n", data->current_audio,
|
||||
gst_stream_get_stream_id (audios[data->current_audio]));
|
||||
}
|
||||
if (nb_text) {
|
||||
data->current_text = (data->current_text + 1) % nb_text;
|
||||
streams =
|
||||
g_list_append (streams,
|
||||
(gchar *) gst_stream_get_stream_id (texts[data->current_text]));
|
||||
g_print (" Selecting text channel #%d : %s\n", data->current_text,
|
||||
gst_stream_get_stream_id (texts[data->current_text]));
|
||||
}
|
||||
|
||||
ev = gst_event_new_select_streams (streams);
|
||||
gst_element_send_event (data->pipeline, ev);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
|
||||
GParamSpec * pspec, guint * val)
|
||||
{
|
||||
g_print ("Got stream-notify from stream %s for %s (collection %p)\n",
|
||||
stream->stream_id, pspec->name, collection);
|
||||
if (g_str_equal (pspec->name, "caps")) {
|
||||
GstCaps *caps = gst_stream_get_caps (stream);
|
||||
gchar *caps_str = gst_caps_to_string (caps);
|
||||
g_print (" New caps: %s\n", caps_str);
|
||||
g_free (caps_str);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
}
|
||||
|
||||
static GstBusSyncReply
|
||||
_on_bus_message (GstBus * bus, GstMessage * message, MyDataStruct * data)
|
||||
{
|
||||
GstObject *src = GST_MESSAGE_SRC (message);
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:{
|
||||
GError *err = NULL;
|
||||
gchar *name = gst_object_get_path_string (GST_MESSAGE_SRC (message));
|
||||
gst_message_parse_error (message, &err, NULL);
|
||||
|
||||
g_printerr ("ERROR: from element %s: %s\n", name, err->message);
|
||||
g_error_free (err);
|
||||
g_free (name);
|
||||
|
||||
g_printf ("Stopping\n");
|
||||
g_main_loop_quit (data->mainloop);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_EOS:
|
||||
g_printf ("EOS ! Stopping \n");
|
||||
g_main_loop_quit (data->mainloop);
|
||||
break;
|
||||
case GST_MESSAGE_STREAM_COLLECTION:
|
||||
{
|
||||
GstStreamCollection *collection = NULL;
|
||||
gst_message_parse_stream_collection (message, &collection);
|
||||
if (collection) {
|
||||
g_printf ("Got a collection from %s:\n",
|
||||
src ? GST_OBJECT_NAME (src) : "Unknown");
|
||||
dump_collection (collection);
|
||||
if (data->collection && data->notify_id) {
|
||||
g_signal_handler_disconnect (data->collection, data->notify_id);
|
||||
data->notify_id = 0;
|
||||
}
|
||||
gst_object_replace ((GstObject **) & data->collection,
|
||||
(GstObject *) collection);
|
||||
if (data->collection) {
|
||||
data->notify_id =
|
||||
g_signal_connect (data->collection, "stream-notify",
|
||||
(GCallback) stream_notify_cb, data);
|
||||
}
|
||||
if (data->timeout_id == 0)
|
||||
/* In 5s try to change streams */
|
||||
data->timeout_id =
|
||||
g_timeout_add_seconds (5, (GSourceFunc) switch_streams, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return GST_BUS_PASS;
|
||||
}
|
||||
|
||||
static void
|
||||
decodebin_pad_added_cb (GstElement * dbin, GstPad * pad, MyDataStruct * data)
|
||||
{
|
||||
gchar *pad_name = gst_pad_get_name (pad);
|
||||
const gchar *sink_pad = NULL;
|
||||
|
||||
GST_DEBUG_OBJECT (pad, "New pad ! Link to playsink !");
|
||||
if (!g_ascii_strncasecmp (pad_name, "video_", 6))
|
||||
sink_pad = "video_sink";
|
||||
else if (!g_ascii_strncasecmp (pad_name, "audio_", 6))
|
||||
sink_pad = "audio_sink";
|
||||
else if (!g_ascii_strncasecmp (pad_name, "text_", 5))
|
||||
sink_pad = "text_sink";
|
||||
else
|
||||
GST_WARNING_OBJECT (pad, "non audio/video/text pad");
|
||||
|
||||
g_free (pad_name);
|
||||
|
||||
if (sink_pad) {
|
||||
GstPad *playsink_pad;
|
||||
|
||||
playsink_pad = gst_element_get_request_pad (data->playsink, sink_pad);
|
||||
if (playsink_pad)
|
||||
gst_pad_link (pad, playsink_pad);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, gchar ** argv)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GstBus *bus;
|
||||
MyDataStruct *data;
|
||||
int i;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
data = g_new0 (MyDataStruct, 1);
|
||||
|
||||
if (argc < 2) {
|
||||
g_print ("Usage: decodebin3 URI\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
data->pipeline = gst_pipeline_new ("pipeline");
|
||||
data->decodebin = gst_element_factory_make ("decodebin3", NULL);
|
||||
|
||||
data->src =
|
||||
gst_element_make_from_uri (GST_URI_SRC, argv[1], "source", &error);
|
||||
if (error) {
|
||||
g_printf ("pipeline could not be constructed: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return 1;
|
||||
}
|
||||
data->playsink = gst_element_factory_make ("playsink", NULL);
|
||||
|
||||
#if 0
|
||||
{
|
||||
GstElement *sink = gst_element_factory_make ("fakesink", NULL);
|
||||
g_object_set (sink, "sync", FALSE, NULL);
|
||||
g_object_set (data->playsink, "video-sink", sink, NULL);
|
||||
|
||||
sink = gst_element_factory_make ("fakesink", NULL);
|
||||
g_object_set (sink, "sync", FALSE, NULL);
|
||||
g_object_set (data->playsink, "audio-sink", sink, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
gst_bin_add_many ((GstBin *) data->pipeline, data->src, data->decodebin,
|
||||
data->playsink, NULL);
|
||||
if (!gst_element_link (data->src, (GstElement *) data->decodebin)) {
|
||||
g_printf ("Could not link source to demuxer\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Handle other inputs if specified */
|
||||
if (argc > 2) {
|
||||
for (i = 2; i < argc; i++) {
|
||||
GstElement *new_src =
|
||||
gst_element_make_from_uri (GST_URI_SRC, argv[i], NULL, &error);
|
||||
GstPad *src_pad, *sink_pad;
|
||||
if (error) {
|
||||
g_printf ("pipeline could not be constructed: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return 1;
|
||||
}
|
||||
data->other_src = g_list_append (data->other_src, new_src);
|
||||
gst_bin_add ((GstBin *) data->pipeline, new_src);
|
||||
src_pad = gst_element_get_static_pad (new_src, "src");
|
||||
sink_pad = gst_element_get_request_pad (data->decodebin, "sink_%u");
|
||||
if (gst_pad_link (src_pad, sink_pad) != GST_PAD_LINK_OK) {
|
||||
g_printf ("Could not link new source to decodebin : %s\n", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
g_signal_connect (data->decodebin, "pad-added",
|
||||
(GCallback) decodebin_pad_added_cb, data);
|
||||
data->mainloop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* Put a bus handler */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
|
||||
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) _on_bus_message, data,
|
||||
NULL);
|
||||
|
||||
/* Start pipeline */
|
||||
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
|
||||
g_main_loop_run (data->mainloop);
|
||||
|
||||
gst_element_set_state (data->pipeline, GST_STATE_NULL);
|
||||
|
||||
gst_object_unref (data->pipeline);
|
||||
gst_object_unref (bus);
|
||||
|
||||
return 0;
|
||||
}
|
326
tests/examples/decodebin_next/playbin-test.c
Normal file
326
tests/examples/decodebin_next/playbin-test.c
Normal file
|
@ -0,0 +1,326 @@
|
|||
/* sample application for testing decodebin3 w/ playbin
|
||||
*
|
||||
* Copyright (C) 2015 Centricular Ltd
|
||||
* @author: Edward Hervey <edward@centricular.com>
|
||||
*
|
||||
* 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 <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <glib/gprintf.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
/* Global structure */
|
||||
|
||||
typedef struct _MyDataStruct
|
||||
{
|
||||
GMainLoop *mainloop;
|
||||
GstElement *pipeline;
|
||||
GstBus *bus;
|
||||
|
||||
/* Current collection */
|
||||
GstStreamCollection *collection;
|
||||
guint notify_id;
|
||||
|
||||
guint current_audio;
|
||||
guint current_video;
|
||||
guint current_text;
|
||||
|
||||
glong timeout_id;
|
||||
} MyDataStruct;
|
||||
|
||||
static void
|
||||
print_tag_foreach (const GstTagList * tags, const gchar * tag,
|
||||
gpointer user_data)
|
||||
{
|
||||
GValue val = { 0, };
|
||||
gchar *str;
|
||||
gint depth = GPOINTER_TO_INT (user_data);
|
||||
|
||||
if (!gst_tag_list_copy_value (&val, tags, tag))
|
||||
return;
|
||||
|
||||
if (G_VALUE_HOLDS_STRING (&val))
|
||||
str = g_value_dup_string (&val);
|
||||
else
|
||||
str = gst_value_serialize (&val);
|
||||
|
||||
g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
|
||||
g_free (str);
|
||||
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
static void
|
||||
dump_collection (GstStreamCollection * collection)
|
||||
{
|
||||
guint i;
|
||||
const GstTagList *tags;
|
||||
const GstCaps *caps;
|
||||
|
||||
for (i = 0; i < gst_stream_collection_get_size (collection); i++) {
|
||||
GstStream *stream = gst_stream_collection_get_stream (collection, i);
|
||||
g_print (" Stream %u type %s flags 0x%x\n", i,
|
||||
gst_stream_type_get_name (gst_stream_get_stream_type (stream)),
|
||||
gst_stream_get_stream_flags (stream));
|
||||
g_print (" ID: %s\n", gst_stream_get_stream_id (stream));
|
||||
|
||||
caps = gst_stream_get_caps (stream);
|
||||
if (caps) {
|
||||
gchar *caps_str = gst_caps_to_string (caps);
|
||||
g_print (" caps: %s\n", caps_str);
|
||||
g_free (caps_str);
|
||||
}
|
||||
|
||||
tags = gst_stream_get_tags (stream);
|
||||
if (tags) {
|
||||
g_print (" tags:\n");
|
||||
gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
switch_streams (MyDataStruct * data)
|
||||
{
|
||||
guint i, nb_streams;
|
||||
gint nb_video = 0, nb_audio = 0, nb_text = 0;
|
||||
GstStream *videos[256], *audios[256], *texts[256];
|
||||
GList *streams = NULL;
|
||||
GstEvent *ev;
|
||||
|
||||
g_print ("Switching Streams...\n");
|
||||
|
||||
/* Calculate the number of streams of each type */
|
||||
nb_streams = gst_stream_collection_get_size (data->collection);
|
||||
for (i = 0; i < nb_streams; i++) {
|
||||
GstStream *stream = gst_stream_collection_get_stream (data->collection, i);
|
||||
GstStreamType stype = gst_stream_get_stream_type (stream);
|
||||
if (stype == GST_STREAM_TYPE_VIDEO) {
|
||||
videos[nb_video] = stream;
|
||||
nb_video += 1;
|
||||
} else if (stype == GST_STREAM_TYPE_AUDIO) {
|
||||
audios[nb_audio] = stream;
|
||||
nb_audio += 1;
|
||||
} else if (stype == GST_STREAM_TYPE_TEXT) {
|
||||
texts[nb_text] = stream;
|
||||
nb_text += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (nb_video) {
|
||||
data->current_video = (data->current_video + 1) % nb_video;
|
||||
streams =
|
||||
g_list_append (streams,
|
||||
(gchar *) gst_stream_get_stream_id (videos[data->current_video]));
|
||||
g_print (" Selecting video channel #%d : %s\n", data->current_video,
|
||||
gst_stream_get_stream_id (videos[data->current_video]));
|
||||
}
|
||||
if (nb_audio) {
|
||||
data->current_audio = (data->current_audio + 1) % nb_audio;
|
||||
streams =
|
||||
g_list_append (streams,
|
||||
(gchar *) gst_stream_get_stream_id (audios[data->current_audio]));
|
||||
g_print (" Selecting audio channel #%d : %s\n", data->current_audio,
|
||||
gst_stream_get_stream_id (audios[data->current_audio]));
|
||||
}
|
||||
if (nb_text) {
|
||||
data->current_text = (data->current_text + 1) % nb_text;
|
||||
streams =
|
||||
g_list_append (streams,
|
||||
(gchar *) gst_stream_get_stream_id (texts[data->current_text]));
|
||||
g_print (" Selecting text channel #%d : %s\n", data->current_text,
|
||||
gst_stream_get_stream_id (texts[data->current_text]));
|
||||
}
|
||||
|
||||
ev = gst_event_new_select_streams (streams);
|
||||
gst_element_send_event (data->pipeline, ev);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
|
||||
GParamSpec * pspec, guint * val)
|
||||
{
|
||||
g_print ("Got stream-notify from stream %s for %s (collection %p)\n",
|
||||
stream->stream_id, pspec->name, collection);
|
||||
if (g_str_equal (pspec->name, "caps")) {
|
||||
GstCaps *caps = gst_stream_get_caps (stream);
|
||||
gchar *caps_str = gst_caps_to_string (caps);
|
||||
g_print (" New caps: %s\n", caps_str);
|
||||
g_free (caps_str);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
}
|
||||
|
||||
static GstBusSyncReply
|
||||
_on_bus_message (GstBus * bus, GstMessage * message, MyDataStruct * data)
|
||||
{
|
||||
GstObject *src = GST_MESSAGE_SRC (message);
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:{
|
||||
GError *err = NULL;
|
||||
gchar *name = gst_object_get_path_string (GST_MESSAGE_SRC (message));
|
||||
gst_message_parse_error (message, &err, NULL);
|
||||
|
||||
g_printerr ("ERROR: from element %s: %s\n", name, err->message);
|
||||
g_error_free (err);
|
||||
g_free (name);
|
||||
|
||||
g_printf ("Stopping\n");
|
||||
g_main_loop_quit (data->mainloop);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_EOS:
|
||||
g_printf ("EOS ! Stopping \n");
|
||||
g_main_loop_quit (data->mainloop);
|
||||
break;
|
||||
case GST_MESSAGE_STREAM_COLLECTION:
|
||||
{
|
||||
GstStreamCollection *collection = NULL;
|
||||
gst_message_parse_stream_collection (message, &collection);
|
||||
if (collection) {
|
||||
g_printf ("Got a collection from %s:\n",
|
||||
src ? GST_OBJECT_NAME (src) : "Unknown");
|
||||
dump_collection (collection);
|
||||
if (data->collection && data->notify_id) {
|
||||
g_signal_handler_disconnect (data->collection, data->notify_id);
|
||||
data->notify_id = 0;
|
||||
}
|
||||
gst_object_replace ((GstObject **) & data->collection,
|
||||
(GstObject *) collection);
|
||||
if (data->collection) {
|
||||
data->notify_id =
|
||||
g_signal_connect (data->collection, "stream-notify",
|
||||
(GCallback) stream_notify_cb, data);
|
||||
}
|
||||
if (data->timeout_id == 0)
|
||||
/* In 5s try to change streams */
|
||||
data->timeout_id =
|
||||
g_timeout_add_seconds (5, (GSourceFunc) switch_streams, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_STREAMS_SELECTED:
|
||||
{
|
||||
GstStreamCollection *collection = NULL;
|
||||
gst_message_parse_streams_selected (message, &collection);
|
||||
if (collection) {
|
||||
guint i, len;
|
||||
g_printf ("Got a STREAMS_SELECTED message from %s (seqnum:%"
|
||||
G_GUINT32_FORMAT "):\n", src ? GST_OBJECT_NAME (src) : "unknown",
|
||||
GST_MESSAGE_SEQNUM (message));
|
||||
len = gst_message_streams_selected_get_size (message);
|
||||
for (i = 0; i < len; i++) {
|
||||
GstStream *stream =
|
||||
gst_message_streams_selected_get_stream (message, i);
|
||||
g_printf (" Stream #%d : %s\n", i,
|
||||
gst_stream_get_stream_id (stream));
|
||||
gst_object_unref (stream);
|
||||
}
|
||||
gst_object_unref (collection);
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return GST_BUS_PASS;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
cmdline_to_uri (const gchar * arg)
|
||||
{
|
||||
if (gst_uri_is_valid (arg))
|
||||
return g_strdup (arg);
|
||||
|
||||
return gst_filename_to_uri (arg, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, gchar ** argv)
|
||||
{
|
||||
GstBus *bus;
|
||||
MyDataStruct *data;
|
||||
gchar *uri;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
data = g_new0 (MyDataStruct, 1);
|
||||
|
||||
uri = cmdline_to_uri (argv[1]);
|
||||
|
||||
if (argc < 2 || uri == NULL) {
|
||||
g_print ("Usage: %s URI\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
data->pipeline = gst_element_factory_make ("playbin3", NULL);
|
||||
if (data->pipeline == NULL) {
|
||||
g_printerr ("Failed to create playbin element. Aborting");
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_object_set (data->pipeline, "uri", uri, "auto-select-streams", FALSE, NULL);
|
||||
g_free (uri);
|
||||
|
||||
#if 0
|
||||
{
|
||||
GstElement *sink = gst_element_factory_make ("fakesink", NULL);
|
||||
g_object_set (sink, "sync", FALSE, NULL);
|
||||
g_object_set (data->pipeline, "video-sink", sink, NULL);
|
||||
|
||||
sink = gst_element_factory_make ("fakesink", NULL);
|
||||
g_object_set (sink, "sync", FALSE, NULL);
|
||||
g_object_set (data->pipeline, "audio-sink", sink, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Handle other input if specified */
|
||||
if (argc > 2) {
|
||||
uri = cmdline_to_uri (argv[2]);
|
||||
if (uri != NULL) {
|
||||
g_object_set (data->pipeline, "suburi", uri, NULL);
|
||||
g_free (uri);
|
||||
} else {
|
||||
g_warning ("Could not parse auxilliary file argument. Ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
data->mainloop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* Put a bus handler */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
|
||||
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) _on_bus_message, data,
|
||||
NULL);
|
||||
|
||||
/* Start pipeline */
|
||||
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
|
||||
g_main_loop_run (data->mainloop);
|
||||
|
||||
gst_element_set_state (data->pipeline, GST_STATE_NULL);
|
||||
|
||||
gst_object_unref (data->pipeline);
|
||||
gst_object_unref (bus);
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue