mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-18 15:51:11 +00:00
1001 lines
28 KiB
C
1001 lines
28 KiB
C
/* GStreamer
|
|
*
|
|
* example program for the ipcpipelinesrc/ipcpipelinesink elements
|
|
*
|
|
* Copyright (C) 2013-2014 Tim-Philipp Müller <tim centricular net>
|
|
* Copyright (C) 2013 Collabora Ltd.
|
|
* Copyright (C) 2015 Centricular Ltd
|
|
* Copyright (C) 2015-2017 YouView TV Ltd
|
|
* Author: George Kiagiadakis <george.kiagiadakis@collabora.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.
|
|
*/
|
|
|
|
/*
|
|
* Based on gst-play and ipcpipeline1. This program will play any URI
|
|
* while splitting the pipeline in two processes, running the source & demuxer
|
|
* on the master process and the decoders & sinks on the slave.
|
|
* See keyboard_cb() for the various keyboard shortcuts you can use to
|
|
* interact with it while the video window is focused.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <gst/gst.h>
|
|
#include <gst/video/navigation.h>
|
|
|
|
static GMainLoop *loop;
|
|
static int pipes[2] = { -1, -1 };
|
|
|
|
static const char *arg_video_sink = "autovideosink";
|
|
static const char *arg_audio_sink = "autoaudiosink";
|
|
|
|
/******* MASTER *******/
|
|
|
|
#define GST_PLAY_KB_ARROW_UP "\033[A"
|
|
#define GST_PLAY_KB_ARROW_DOWN "\033[B"
|
|
#define GST_PLAY_KB_ARROW_RIGHT "\033[C"
|
|
#define GST_PLAY_KB_ARROW_LEFT "\033[D"
|
|
|
|
typedef enum
|
|
{
|
|
GST_PLAY_TRICK_MODE_NONE = 0,
|
|
GST_PLAY_TRICK_MODE_DEFAULT,
|
|
GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO,
|
|
GST_PLAY_TRICK_MODE_KEY_UNITS,
|
|
GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO,
|
|
GST_PLAY_TRICK_MODE_LAST
|
|
} GstPlayTrickMode;
|
|
|
|
static GstPlayTrickMode trick_mode = GST_PLAY_TRICK_MODE_NONE;
|
|
static gdouble cur_rate = 1.0;
|
|
static gboolean buffering = FALSE;
|
|
static GstState desired_state = GST_STATE_PLAYING;
|
|
|
|
static gboolean play_do_seek (GstElement * pipeline, gint64 pos, gdouble rate,
|
|
GstPlayTrickMode mode);
|
|
|
|
static void
|
|
toggle_paused (GstElement * pipeline)
|
|
{
|
|
if (desired_state == GST_STATE_PLAYING)
|
|
desired_state = GST_STATE_PAUSED;
|
|
else
|
|
desired_state = GST_STATE_PLAYING;
|
|
|
|
if (!buffering) {
|
|
gst_element_set_state (pipeline, desired_state);
|
|
} else if (desired_state == GST_STATE_PLAYING) {
|
|
g_print ("\nWill play as soon as buffering finishes)\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
relative_seek (GstElement * pipeline, gdouble percent)
|
|
{
|
|
GstQuery *query;
|
|
gboolean seekable = FALSE;
|
|
gint64 dur = -1, pos = -1, step;
|
|
|
|
g_return_if_fail (percent >= -1.0 && percent <= 1.0);
|
|
|
|
if (!gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos))
|
|
goto seek_failed;
|
|
|
|
query = gst_query_new_seeking (GST_FORMAT_TIME);
|
|
if (!gst_element_query (pipeline, query)) {
|
|
gst_query_unref (query);
|
|
goto seek_failed;
|
|
}
|
|
|
|
gst_query_parse_seeking (query, NULL, &seekable, NULL, &dur);
|
|
gst_query_unref (query);
|
|
|
|
if (!seekable || dur <= 0)
|
|
goto seek_failed;
|
|
|
|
step = dur * percent;
|
|
if (ABS (step) < GST_SECOND)
|
|
step = (percent < 0) ? -GST_SECOND : GST_SECOND;
|
|
|
|
pos = pos + step;
|
|
if (pos > dur) {
|
|
g_print ("\nReached end of play list.\n");
|
|
g_main_loop_quit (loop);
|
|
} else {
|
|
if (pos < 0)
|
|
pos = 0;
|
|
|
|
play_do_seek (pipeline, pos, cur_rate, trick_mode);
|
|
}
|
|
|
|
return;
|
|
|
|
seek_failed:
|
|
{
|
|
g_print ("\nCould not seek.\n");
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
play_set_rate_and_trick_mode (GstElement * pipeline, gdouble rate,
|
|
GstPlayTrickMode mode)
|
|
{
|
|
gint64 pos = -1;
|
|
|
|
g_return_val_if_fail (rate != 0, FALSE);
|
|
|
|
if (!gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos))
|
|
return FALSE;
|
|
|
|
return play_do_seek (pipeline, pos, rate, mode);
|
|
}
|
|
|
|
static gboolean
|
|
play_do_seek (GstElement * pipeline, gint64 pos, gdouble rate,
|
|
GstPlayTrickMode mode)
|
|
{
|
|
GstSeekFlags seek_flags;
|
|
GstQuery *query;
|
|
GstEvent *seek;
|
|
gboolean seekable = FALSE;
|
|
|
|
query = gst_query_new_seeking (GST_FORMAT_TIME);
|
|
if (!gst_element_query (pipeline, query)) {
|
|
gst_query_unref (query);
|
|
return FALSE;
|
|
}
|
|
|
|
gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
|
|
gst_query_unref (query);
|
|
|
|
if (!seekable)
|
|
return FALSE;
|
|
|
|
seek_flags = GST_SEEK_FLAG_FLUSH;
|
|
|
|
switch (mode) {
|
|
case GST_PLAY_TRICK_MODE_DEFAULT:
|
|
seek_flags |= GST_SEEK_FLAG_TRICKMODE;
|
|
break;
|
|
case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
|
|
seek_flags |= GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
|
|
break;
|
|
case GST_PLAY_TRICK_MODE_KEY_UNITS:
|
|
seek_flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
|
|
break;
|
|
case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
|
|
seek_flags |=
|
|
GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
|
|
break;
|
|
case GST_PLAY_TRICK_MODE_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (rate >= 0)
|
|
seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
|
|
seek_flags | GST_SEEK_FLAG_ACCURATE,
|
|
/* start */ GST_SEEK_TYPE_SET, pos,
|
|
/* stop */ GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
|
|
else
|
|
seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
|
|
seek_flags | GST_SEEK_FLAG_ACCURATE,
|
|
/* start */ GST_SEEK_TYPE_SET, 0,
|
|
/* stop */ GST_SEEK_TYPE_SET, pos);
|
|
|
|
if (!gst_element_send_event (pipeline, seek))
|
|
return FALSE;
|
|
|
|
cur_rate = rate;
|
|
trick_mode = mode;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
play_set_playback_rate (GstElement * pipeline, gdouble rate)
|
|
{
|
|
if (play_set_rate_and_trick_mode (pipeline, rate, trick_mode)) {
|
|
g_print ("Playback rate: %.2f", rate);
|
|
g_print (" \n");
|
|
} else {
|
|
g_print ("\n");
|
|
g_print ("Could not change playback rate to %.2f", rate);
|
|
g_print (".\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
play_set_relative_playback_rate (GstElement * pipeline, gdouble rate_step,
|
|
gboolean reverse_direction)
|
|
{
|
|
gdouble new_rate = cur_rate + rate_step;
|
|
|
|
if (reverse_direction)
|
|
new_rate *= -1.0;
|
|
|
|
play_set_playback_rate (pipeline, new_rate);
|
|
}
|
|
|
|
static const gchar *
|
|
trick_mode_get_description (GstPlayTrickMode mode)
|
|
{
|
|
switch (mode) {
|
|
case GST_PLAY_TRICK_MODE_NONE:
|
|
return "normal playback, trick modes disabled";
|
|
case GST_PLAY_TRICK_MODE_DEFAULT:
|
|
return "trick mode: default";
|
|
case GST_PLAY_TRICK_MODE_DEFAULT_NO_AUDIO:
|
|
return "trick mode: default, no audio";
|
|
case GST_PLAY_TRICK_MODE_KEY_UNITS:
|
|
return "trick mode: key frames only";
|
|
case GST_PLAY_TRICK_MODE_KEY_UNITS_NO_AUDIO:
|
|
return "trick mode: key frames only, no audio";
|
|
default:
|
|
break;
|
|
}
|
|
return "unknown trick mode";
|
|
}
|
|
|
|
static void
|
|
play_switch_trick_mode (GstElement * pipeline)
|
|
{
|
|
GstPlayTrickMode new_mode = ++trick_mode;
|
|
const gchar *mode_desc;
|
|
|
|
if (new_mode == GST_PLAY_TRICK_MODE_LAST)
|
|
new_mode = GST_PLAY_TRICK_MODE_NONE;
|
|
|
|
mode_desc = trick_mode_get_description (new_mode);
|
|
|
|
if (play_set_rate_and_trick_mode (pipeline, cur_rate, new_mode)) {
|
|
g_print ("Rate: %.2f (%s) \n", cur_rate, mode_desc);
|
|
} else {
|
|
g_print ("\nCould not change trick mode to %s.\n", mode_desc);
|
|
}
|
|
}
|
|
|
|
static void
|
|
keyboard_cb (const gchar * key_input, GstElement * pipeline)
|
|
{
|
|
gchar key = '\0';
|
|
|
|
/* only want to switch/case on single char, not first char of string */
|
|
if (key_input[0] != '\0' && key_input[1] == '\0')
|
|
key = g_ascii_tolower (key_input[0]);
|
|
|
|
switch (key) {
|
|
case ' ':
|
|
toggle_paused (pipeline);
|
|
break;
|
|
case 'q':
|
|
case 'Q':
|
|
g_main_loop_quit (loop);
|
|
break;
|
|
case 'p':
|
|
if (cur_rate > -0.2 && cur_rate < 0.0)
|
|
play_set_relative_playback_rate (pipeline, 0.0, TRUE);
|
|
else if (ABS (cur_rate) < 2.0)
|
|
play_set_relative_playback_rate (pipeline, 0.1, FALSE);
|
|
else if (ABS (cur_rate) < 4.0)
|
|
play_set_relative_playback_rate (pipeline, 0.5, FALSE);
|
|
else
|
|
play_set_relative_playback_rate (pipeline, 1.0, FALSE);
|
|
break;
|
|
case 'o':
|
|
if (cur_rate > 0.0 && cur_rate < 0.20)
|
|
play_set_relative_playback_rate (pipeline, 0.0, TRUE);
|
|
else if (ABS (cur_rate) <= 2.0)
|
|
play_set_relative_playback_rate (pipeline, -0.1, FALSE);
|
|
else if (ABS (cur_rate) <= 4.0)
|
|
play_set_relative_playback_rate (pipeline, -0.5, FALSE);
|
|
else
|
|
play_set_relative_playback_rate (pipeline, -1.0, FALSE);
|
|
break;
|
|
case 'd':
|
|
play_set_relative_playback_rate (pipeline, 0.0, TRUE);
|
|
break;
|
|
case 't':
|
|
play_switch_trick_mode (pipeline);
|
|
break;
|
|
case 27: /* ESC */
|
|
if (key_input[1] == '\0') {
|
|
g_main_loop_quit (loop);
|
|
break;
|
|
}
|
|
case '0':
|
|
play_do_seek (pipeline, 0, cur_rate, trick_mode);
|
|
break;
|
|
case 'r':
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.master.requested");
|
|
break;
|
|
default:
|
|
if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
|
|
relative_seek (pipeline, +0.08);
|
|
} else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
|
|
relative_seek (pipeline, -0.01);
|
|
} else {
|
|
GST_INFO ("keyboard input:");
|
|
for (; *key_input != '\0'; ++key_input)
|
|
GST_INFO (" code %3d", *key_input);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
master_bus_msg (GstBus * bus, GstMessage * msg, gpointer data)
|
|
{
|
|
GstPipeline *pipeline = data;
|
|
|
|
switch (GST_MESSAGE_TYPE (msg)) {
|
|
case GST_MESSAGE_ERROR:{
|
|
GError *err;
|
|
gchar *dbg;
|
|
|
|
gst_message_parse_error (msg, &err, &dbg);
|
|
g_printerr ("MASTER: ERROR: %s\n", err->message);
|
|
if (dbg != NULL)
|
|
g_printerr ("MASTER: ERROR debug information: %s\n", dbg);
|
|
g_error_free (err);
|
|
g_free (dbg);
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.master.error");
|
|
|
|
g_main_loop_quit (loop);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_WARNING:{
|
|
GError *err;
|
|
gchar *dbg;
|
|
|
|
gst_message_parse_warning (msg, &err, &dbg);
|
|
g_printerr ("MASTER: WARNING: %s\n", err->message);
|
|
if (dbg != NULL)
|
|
g_printerr ("MASTER: WARNING debug information: %s\n", dbg);
|
|
g_error_free (err);
|
|
g_free (dbg);
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.master.warning");
|
|
break;
|
|
}
|
|
case GST_MESSAGE_ASYNC_DONE:
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.master.async-done");
|
|
break;
|
|
case GST_MESSAGE_EOS:
|
|
g_print ("EOS on master\n");
|
|
gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
|
|
g_main_loop_quit (loop);
|
|
break;
|
|
case GST_MESSAGE_BUFFERING:{
|
|
gint percent;
|
|
GstBufferingMode bufmode;
|
|
|
|
if (!buffering)
|
|
g_print ("\n");
|
|
|
|
gst_message_parse_buffering (msg, &percent);
|
|
g_print ("%s %d%% \r", "Buffering...", percent);
|
|
|
|
gst_message_parse_buffering_stats (msg, &bufmode, NULL, NULL, NULL);
|
|
|
|
/* no state management needed for live pipelines */
|
|
if (bufmode != GST_BUFFERING_LIVE) {
|
|
if (percent == 100) {
|
|
/* a 100% message means buffering is done */
|
|
if (buffering) {
|
|
buffering = FALSE;
|
|
gst_element_set_state (GST_ELEMENT (pipeline), desired_state);
|
|
g_print ("\n%s\n", gst_element_state_get_name (desired_state));
|
|
}
|
|
} else {
|
|
/* buffering... */
|
|
if (!buffering) {
|
|
gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED);
|
|
buffering = TRUE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case GST_MESSAGE_CLOCK_LOST:{
|
|
g_print ("Clock lost, selecting a new one\n");
|
|
gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED);
|
|
gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_LATENCY:
|
|
{
|
|
gst_bin_recalculate_latency (GST_BIN (pipeline));
|
|
break;
|
|
}
|
|
case GST_MESSAGE_REQUEST_STATE:{
|
|
GstState state;
|
|
gchar *name;
|
|
|
|
name = gst_object_get_path_string (GST_MESSAGE_SRC (msg));
|
|
|
|
gst_message_parse_request_state (msg, &state);
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_VERBOSE, "ipc.slave.reqstate");
|
|
|
|
g_print ("Setting state to %s as requested by %s...\n",
|
|
gst_element_state_get_name (state), name);
|
|
|
|
gst_element_set_state (GST_ELEMENT (pipeline), state);
|
|
g_free (name);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_ELEMENT:
|
|
{
|
|
GstNavigationMessageType mtype = gst_navigation_message_get_type (msg);
|
|
if (mtype == GST_NAVIGATION_MESSAGE_EVENT) {
|
|
GstEvent *ev = NULL;
|
|
|
|
if (gst_navigation_message_parse_event (msg, &ev)) {
|
|
GstNavigationEventType e_type = gst_navigation_event_get_type (ev);
|
|
switch (e_type) {
|
|
case GST_NAVIGATION_EVENT_KEY_PRESS:
|
|
{
|
|
const gchar *key;
|
|
|
|
if (gst_navigation_event_parse_key_event (ev, &key)) {
|
|
GST_INFO ("Key press: %s", key);
|
|
|
|
if (strcmp (key, "Left") == 0)
|
|
key = GST_PLAY_KB_ARROW_LEFT;
|
|
else if (strcmp (key, "Right") == 0)
|
|
key = GST_PLAY_KB_ARROW_RIGHT;
|
|
else if (strcmp (key, "Up") == 0)
|
|
key = GST_PLAY_KB_ARROW_UP;
|
|
else if (strcmp (key, "Down") == 0)
|
|
key = GST_PLAY_KB_ARROW_DOWN;
|
|
else if (strcmp (key, "space") == 0)
|
|
key = " ";
|
|
else if (strlen (key) > 1)
|
|
break;
|
|
|
|
keyboard_cb (key, GST_ELEMENT (pipeline));
|
|
}
|
|
break;
|
|
}
|
|
case GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS:
|
|
{
|
|
gint button;
|
|
if (gst_navigation_event_parse_mouse_button_event (ev, &button,
|
|
NULL, NULL)) {
|
|
if (button == 4) {
|
|
/* wheel up */
|
|
relative_seek (GST_ELEMENT (pipeline), +0.08);
|
|
} else if (button == 5) {
|
|
/* wheel down */
|
|
relative_seek (GST_ELEMENT (pipeline), -0.01);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (ev)
|
|
gst_event_unref (ev);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static int
|
|
sendfd (int s, int fd)
|
|
{
|
|
char buf[1];
|
|
struct iovec iov;
|
|
struct msghdr msg;
|
|
struct cmsghdr *cmsg;
|
|
int n;
|
|
char cms[CMSG_SPACE (sizeof (int))];
|
|
|
|
buf[0] = 0;
|
|
iov.iov_base = buf;
|
|
iov.iov_len = 1;
|
|
|
|
memset (&msg, 0, sizeof msg);
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = (caddr_t) cms;
|
|
msg.msg_controllen = CMSG_LEN (sizeof (int));
|
|
|
|
cmsg = CMSG_FIRSTHDR (&msg);
|
|
cmsg->cmsg_len = CMSG_LEN (sizeof (int));
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
memmove (CMSG_DATA (cmsg), &fd, sizeof (int));
|
|
|
|
if ((n = sendmsg (s, &msg, 0)) != iov.iov_len)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static gint
|
|
find_ipcpipelinesink (gconstpointer e, gconstpointer c)
|
|
{
|
|
const GValue *elem = e;
|
|
const gchar *caps_name = c;
|
|
const gchar *n = g_object_get_data (g_value_get_object (elem),
|
|
"ipcpipelinesink-caps-name");
|
|
return g_strcmp0 (caps_name, n);
|
|
}
|
|
|
|
/* in HLS the decodebin pads are destroyed and re-created every time
|
|
* the stream changes bitrate. This trick here ensures that the new
|
|
* pads that will appear will go and link to the same ipcpipelinesinks,
|
|
* avoiding the creation of new pipelines in the slave. */
|
|
static void
|
|
on_pad_unlinked (GstPad * pad, GstPad * peer, GstElement * pipeline)
|
|
{
|
|
GstCaps *caps;
|
|
const GstStructure *structure;
|
|
|
|
caps = gst_pad_get_current_caps (pad);
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
|
|
g_object_set_data_full (G_OBJECT (GST_OBJECT_PARENT (peer)),
|
|
"ipcpipelinesink-caps-name",
|
|
g_strdup (gst_structure_get_name (structure)), g_free);
|
|
}
|
|
|
|
static void
|
|
on_pad_added (GstElement * element, GstPad * pad, GstElement * pipeline)
|
|
{
|
|
GstElement *ipcpipelinesink;
|
|
GstPad *sinkpad;
|
|
GstCaps *caps;
|
|
const GstStructure *structure;
|
|
GstIterator *it;
|
|
GValue elem = G_VALUE_INIT;
|
|
int sockets[2];
|
|
gboolean create_sockets;
|
|
|
|
caps = gst_pad_get_current_caps (pad);
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (pipeline));
|
|
if (gst_iterator_find_custom (it, find_ipcpipelinesink, &elem,
|
|
(gpointer) gst_structure_get_name (structure))) {
|
|
ipcpipelinesink = g_value_get_object (&elem);
|
|
create_sockets = FALSE;
|
|
g_value_reset (&elem);
|
|
} else {
|
|
ipcpipelinesink = gst_element_factory_make ("ipcpipelinesink", NULL);
|
|
gst_bin_add (GST_BIN (pipeline), ipcpipelinesink);
|
|
create_sockets = TRUE;
|
|
}
|
|
|
|
sinkpad = gst_element_get_static_pad (ipcpipelinesink, "sink");
|
|
if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK) {
|
|
fprintf (stderr, "Failed to link ipcpipelinesink\n");
|
|
exit (1);
|
|
}
|
|
gst_object_unref (sinkpad);
|
|
|
|
g_signal_connect (pad, "unlinked", (GCallback) on_pad_unlinked, pipeline);
|
|
|
|
if (create_sockets) {
|
|
if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockets)) {
|
|
fprintf (stderr, "Error creating sockets: %s\n", strerror (errno));
|
|
exit (1);
|
|
}
|
|
if (fcntl (sockets[0], F_SETFL, O_NONBLOCK) < 0 ||
|
|
fcntl (sockets[1], F_SETFL, O_NONBLOCK) < 0) {
|
|
fprintf (stderr, "Error setting O_NONBLOCK on sockets: %s\n",
|
|
strerror (errno));
|
|
exit (1);
|
|
}
|
|
g_object_set (ipcpipelinesink, "fdin", sockets[0], "fdout", sockets[0],
|
|
NULL);
|
|
|
|
printf ("new socket %d\n", sockets[1]);
|
|
sendfd (pipes[1], sockets[1]);
|
|
}
|
|
|
|
gst_element_set_state (ipcpipelinesink, GST_STATE_PLAYING);
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "pad.added");
|
|
}
|
|
|
|
typedef enum
|
|
{
|
|
GST_AUTOPLUG_SELECT_TRY,
|
|
GST_AUTOPLUG_SELECT_EXPOSE,
|
|
GST_AUTOPLUG_SELECT_SKIP
|
|
} GstAutoplugSelectResult;
|
|
|
|
static GstAutoplugSelectResult
|
|
on_autoplug_select (GstElement * uridecodebin, GstPad * pad, GstCaps * caps,
|
|
GstElementFactory * factory, GstElement * pipeline)
|
|
{
|
|
/* if decodebin is about to plug a decoder,
|
|
* stop it right there and expose the pad;
|
|
* the slave's decodebin will take it from there... */
|
|
if (gst_element_factory_list_is_type (factory,
|
|
GST_ELEMENT_FACTORY_TYPE_DECODER)) {
|
|
gchar *capsstr = gst_caps_to_string (caps);
|
|
g_print (" exposing to slave: %s\n", capsstr);
|
|
g_free (capsstr);
|
|
return GST_AUTOPLUG_SELECT_EXPOSE;
|
|
}
|
|
return GST_AUTOPLUG_SELECT_TRY;
|
|
}
|
|
|
|
static void
|
|
start_source (const gchar * uri)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *uridecodebin;
|
|
GstBus *bus;
|
|
|
|
pipeline = gst_pipeline_new (NULL);
|
|
|
|
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
|
gst_bus_add_watch (bus, master_bus_msg, pipeline);
|
|
gst_object_unref (bus);
|
|
|
|
uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
|
|
g_object_set (uridecodebin, "uri", uri, NULL);
|
|
g_signal_connect (uridecodebin, "pad-added", G_CALLBACK (on_pad_added),
|
|
pipeline);
|
|
g_signal_connect (uridecodebin, "autoplug-select",
|
|
G_CALLBACK (on_autoplug_select), pipeline);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), uridecodebin);
|
|
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
|
}
|
|
|
|
/*********** SLAVE ***********/
|
|
|
|
static gboolean
|
|
slave_bus_msg (GstBus * bus, GstMessage * msg, gpointer data)
|
|
{
|
|
GstPipeline *pipeline = data;
|
|
|
|
switch (GST_MESSAGE_TYPE (msg)) {
|
|
case GST_MESSAGE_ERROR:{
|
|
GError *err;
|
|
gchar *dbg;
|
|
|
|
gst_message_parse_error (msg, &err, &dbg);
|
|
g_printerr ("SLAVE: ERROR: %s\n", err->message);
|
|
if (dbg != NULL)
|
|
g_printerr ("SLAVE: ERROR debug information: %s\n", dbg);
|
|
g_error_free (err);
|
|
g_free (dbg);
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.slave.error");
|
|
break;
|
|
}
|
|
case GST_MESSAGE_WARNING:{
|
|
GError *err;
|
|
gchar *dbg;
|
|
|
|
gst_message_parse_warning (msg, &err, &dbg);
|
|
g_printerr ("SLAVE: WARNING: %s\n", err->message);
|
|
if (dbg != NULL)
|
|
g_printerr ("SLAVE: WARNING debug information: %s\n", dbg);
|
|
g_error_free (err);
|
|
g_free (dbg);
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.slave.warning");
|
|
break;
|
|
}
|
|
case GST_MESSAGE_ASYNC_START:
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_VERBOSE, "ipc.slave.async-start");
|
|
break;
|
|
case GST_MESSAGE_ASYNC_DONE:
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.slave.async-done");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_decoded_pad_added (GstElement * element, GstPad * pad, gpointer data)
|
|
{
|
|
GstBin *pipeline = data;
|
|
GstCaps *caps;
|
|
GstPad *cpad;
|
|
const gchar *type;
|
|
gchar *capsstr;
|
|
|
|
caps = gst_pad_get_current_caps (pad);
|
|
capsstr = gst_caps_to_string (caps);
|
|
printf (" caps: %s\n", capsstr);
|
|
g_free (capsstr);
|
|
|
|
type = gst_structure_get_name (gst_caps_get_structure (caps, 0));
|
|
if (!strcmp (type, "video/x-raw")) {
|
|
GstElement *c, *s;
|
|
c = gst_element_factory_make ("videoconvert", NULL);
|
|
s = gst_element_factory_make (arg_video_sink, NULL);
|
|
gst_bin_add_many (GST_BIN (pipeline), c, s, NULL);
|
|
gst_element_link_many (c, s, NULL);
|
|
cpad = gst_element_get_static_pad (c, "sink");
|
|
gst_pad_link (pad, cpad);
|
|
gst_object_unref (cpad);
|
|
gst_element_set_state (s, GST_STATE_PLAYING);
|
|
gst_element_set_state (c, GST_STATE_PLAYING);
|
|
} else if (!strcmp (type, "audio/x-raw")) {
|
|
GstElement *c, *s;
|
|
c = gst_element_factory_make ("audioconvert", NULL);
|
|
s = gst_element_factory_make (arg_audio_sink, NULL);
|
|
gst_bin_add_many (GST_BIN (pipeline), c, s, NULL);
|
|
gst_element_link_many (c, s, NULL);
|
|
cpad = gst_element_get_static_pad (c, "sink");
|
|
gst_pad_link (pad, cpad);
|
|
gst_object_unref (cpad);
|
|
gst_element_set_state (s, GST_STATE_PLAYING);
|
|
gst_element_set_state (c, GST_STATE_PLAYING);
|
|
} else {
|
|
GstElement *s;
|
|
s = gst_element_factory_make ("fakesink", NULL);
|
|
g_object_set (s, "sync", TRUE, "async", TRUE, NULL);
|
|
gst_bin_add_many (GST_BIN (pipeline), s, NULL);
|
|
cpad = gst_element_get_static_pad (s, "sink");
|
|
gst_pad_link (pad, cpad);
|
|
gst_object_unref (cpad);
|
|
gst_element_set_state (s, GST_STATE_PLAYING);
|
|
}
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "decoded.pad.added");
|
|
}
|
|
|
|
static int
|
|
recvfd (int s)
|
|
{
|
|
int n;
|
|
int fd;
|
|
char buf[1];
|
|
struct iovec iov;
|
|
struct msghdr msg;
|
|
struct cmsghdr *cmsg;
|
|
char cms[CMSG_SPACE (sizeof (int))];
|
|
|
|
iov.iov_base = buf;
|
|
iov.iov_len = 1;
|
|
|
|
memset (&msg, 0, sizeof msg);
|
|
msg.msg_name = 0;
|
|
msg.msg_namelen = 0;
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
|
|
msg.msg_control = (caddr_t) cms;
|
|
msg.msg_controllen = sizeof cms;
|
|
|
|
if ((n = recvmsg (s, &msg, 0)) < 0)
|
|
return -1;
|
|
if (n == 0) {
|
|
perror ("unexpected EOF");
|
|
return -1;
|
|
}
|
|
cmsg = CMSG_FIRSTHDR (&msg);
|
|
memmove (&fd, CMSG_DATA (cmsg), sizeof (int));
|
|
return fd;
|
|
}
|
|
|
|
static gboolean
|
|
pipe_reader (gpointer data)
|
|
{
|
|
GstElement *pipeline = data;
|
|
GstElement *ipcpipelinesrc, *mq, *decodebin;
|
|
GstPad *rpad, *sink_pad;
|
|
int fd;
|
|
fd_set set;
|
|
struct timeval tv;
|
|
int ret;
|
|
static int idx = 0;
|
|
char name[32];
|
|
|
|
FD_ZERO (&set);
|
|
FD_SET (pipes[0], &set);
|
|
tv.tv_sec = tv.tv_usec = 0;
|
|
ret = select (pipes[0] + 1, &set, NULL, NULL, &tv);
|
|
if (ret < 0) {
|
|
fprintf (stderr, "Failed to select: %s\n", strerror (errno));
|
|
return TRUE;
|
|
}
|
|
if (!FD_ISSET (pipes[0], &set))
|
|
return TRUE;
|
|
|
|
fd = recvfd (pipes[0]);
|
|
ipcpipelinesrc = gst_element_factory_make ("ipcpipelinesrc", NULL);
|
|
gst_bin_add (GST_BIN (pipeline), ipcpipelinesrc);
|
|
g_object_set (ipcpipelinesrc, "fdin", fd, "fdout", fd, NULL);
|
|
|
|
|
|
mq = gst_bin_get_by_name (GST_BIN (pipeline), "mq");
|
|
if (!mq) {
|
|
fprintf (stderr, "Failed to get mq\n");
|
|
return TRUE;
|
|
}
|
|
if (!gst_element_link (ipcpipelinesrc, mq)) {
|
|
fprintf (stderr, "Failed to link ipcpipelinesrc and mq\n");
|
|
return TRUE;
|
|
}
|
|
|
|
snprintf (name, sizeof (name), "src_%u", idx++);
|
|
rpad = gst_element_get_static_pad (mq, name);
|
|
if (!rpad) {
|
|
fprintf (stderr, "Failed to get mq request pad\n");
|
|
return TRUE;
|
|
}
|
|
|
|
decodebin = gst_element_factory_make ("decodebin", NULL);
|
|
gst_bin_add (GST_BIN (pipeline), decodebin);
|
|
sink_pad = gst_element_get_static_pad (decodebin, "sink");
|
|
gst_pad_link (rpad, sink_pad);
|
|
gst_object_unref (sink_pad);
|
|
|
|
g_signal_connect (decodebin, "pad-added", G_CALLBACK (on_decoded_pad_added),
|
|
pipeline);
|
|
|
|
/* dynamically added elements should be synced manually
|
|
* to the state of the slave pipeline */
|
|
gst_element_sync_state_with_parent (ipcpipelinesrc);
|
|
gst_element_sync_state_with_parent (decodebin);
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.slave.added");
|
|
gst_object_unref (mq);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
start_sink (void)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *multiqueue;
|
|
|
|
pipeline = gst_element_factory_make ("ipcslavepipeline", NULL);
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), slave_bus_msg, pipeline);
|
|
|
|
multiqueue = gst_element_factory_make ("multiqueue", "mq");
|
|
gst_bin_add (GST_BIN (pipeline), multiqueue);
|
|
|
|
g_timeout_add (10, &pipe_reader, gst_object_ref (pipeline));
|
|
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "ipc.sink");
|
|
/* The state of the slave pipeline will change together with the state
|
|
* of the master, there is no need to call gst_element_set_state() here */
|
|
}
|
|
|
|
|
|
/********** COMMON ***********/
|
|
|
|
static void
|
|
init (int *argc, char ***argv)
|
|
{
|
|
GOptionEntry options[] = {
|
|
{"audio-sink", 0, 0, G_OPTION_ARG_STRING, &arg_audio_sink,
|
|
"Audio sink element to use (default autoaudiosink)", NULL},
|
|
{"video-sink", 0, 0, G_OPTION_ARG_STRING, &arg_video_sink,
|
|
"Video sink element to use (default autovideosink)", NULL},
|
|
{NULL}
|
|
};
|
|
GOptionContext *ctx;
|
|
GError *err = NULL;
|
|
|
|
ctx = g_option_context_new ("");
|
|
g_option_context_add_main_entries (ctx, options, "");
|
|
if (!g_option_context_parse (ctx, argc, argv, &err)) {
|
|
fprintf (stderr, "Error initializing: %s\n", err->message);
|
|
exit (1);
|
|
}
|
|
g_option_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
run (pid_t pid)
|
|
{
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
g_main_loop_run (loop);
|
|
if (pid > 0)
|
|
kill (pid, SIGTERM);
|
|
}
|
|
|
|
gint
|
|
main (gint argc, gchar ** argv)
|
|
{
|
|
GError *error = NULL;
|
|
gchar *uri = NULL;
|
|
pid_t pid;
|
|
|
|
init (&argc, &argv);
|
|
|
|
if (argc < 2) {
|
|
fprintf (stderr, "usage: %s [av-filename-or-url]\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (!g_strstr_len (argv[1], -1, "://")) {
|
|
uri = gst_filename_to_uri (argv[1], &error);
|
|
} else {
|
|
uri = g_strdup (argv[1]);
|
|
}
|
|
|
|
if (error) {
|
|
fprintf (stderr, "usage: %s [av-filename-or-url]\n", argv[0]);
|
|
g_clear_error (&error);
|
|
return 1;
|
|
}
|
|
|
|
if (socketpair (AF_UNIX, SOCK_STREAM, 0, pipes)) {
|
|
fprintf (stderr, "Error creating pipes: %s\n", strerror (errno));
|
|
return 2;
|
|
}
|
|
if (fcntl (pipes[0], F_SETFL, O_NONBLOCK) < 0 ||
|
|
fcntl (pipes[1], F_SETFL, O_NONBLOCK) < 0) {
|
|
fprintf (stderr, "Error setting O_NONBLOCK on pipes: %s\n",
|
|
strerror (errno));
|
|
return 2;
|
|
}
|
|
|
|
pid = fork ();
|
|
if (pid < 0) {
|
|
fprintf (stderr, "Error forking: %s\n", strerror (errno));
|
|
return 1;
|
|
} else if (pid > 0) {
|
|
setenv ("GST_DEBUG_FILE", "gstsrc.log", 1);
|
|
gst_init (&argc, &argv);
|
|
start_source (uri);
|
|
} else {
|
|
setenv ("GST_DEBUG_FILE", "gstsink.log", 1);
|
|
gst_init (&argc, &argv);
|
|
start_sink ();
|
|
}
|
|
|
|
g_free (uri);
|
|
run (pid);
|
|
|
|
return 0;
|
|
}
|