Update
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* appsink-src.c: example for using appsink and appsrc.
|
||||
*
|
||||
* Copyright (C) 2008 Wim Taymans <wim.taymans@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <gst/app/gstappsrc.h>
|
||||
#include <gst/app/gstappsink.h>
|
||||
|
||||
/* these are the caps we are going to pass through the appsink and appsrc */
|
||||
const gchar *audio_caps =
|
||||
"audio/x-raw,format=S16LE,channels=1,rate=8000, layout=interleaved";
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GMainLoop *loop;
|
||||
GstElement *source;
|
||||
GstElement *sink;
|
||||
} ProgramData;
|
||||
|
||||
/* called when the appsink notifies us that there is a new buffer ready for
|
||||
* processing */
|
||||
static GstFlowReturn
|
||||
on_new_sample_from_sink (GstElement * elt, ProgramData * data)
|
||||
{
|
||||
GstSample *sample;
|
||||
GstBuffer *app_buffer, *buffer;
|
||||
GstElement *source;
|
||||
GstFlowReturn ret;
|
||||
|
||||
/* get the sample from appsink */
|
||||
sample = gst_app_sink_pull_sample (GST_APP_SINK (elt));
|
||||
buffer = gst_sample_get_buffer (sample);
|
||||
|
||||
/* make a copy */
|
||||
app_buffer = gst_buffer_copy (buffer);
|
||||
|
||||
/* we don't need the appsink sample anymore */
|
||||
gst_sample_unref (sample);
|
||||
|
||||
/* get source an push new buffer */
|
||||
source = gst_bin_get_by_name (GST_BIN (data->sink), "testsource");
|
||||
ret = gst_app_src_push_buffer (GST_APP_SRC (source), app_buffer);
|
||||
gst_object_unref (source);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* called when we get a GstMessage from the source pipeline when we get EOS, we
|
||||
* notify the appsrc of it. */
|
||||
static gboolean
|
||||
on_source_message (GstBus * bus, GstMessage * message, ProgramData * data)
|
||||
{
|
||||
GstElement *source;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("The source got dry\n");
|
||||
source = gst_bin_get_by_name (GST_BIN (data->sink), "testsource");
|
||||
gst_app_src_end_of_stream (GST_APP_SRC (source));
|
||||
gst_object_unref (source);
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_print ("Received error\n");
|
||||
g_main_loop_quit (data->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* called when we get a GstMessage from the sink pipeline when we get EOS, we
|
||||
* exit the mainloop and this testapp. */
|
||||
static gboolean
|
||||
on_sink_message (GstBus * bus, GstMessage * message, ProgramData * data)
|
||||
{
|
||||
/* nil */
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("Finished playback\n");
|
||||
g_main_loop_quit (data->loop);
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_print ("Received error\n");
|
||||
g_main_loop_quit (data->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
gchar *filename = NULL;
|
||||
ProgramData *data = NULL;
|
||||
gchar *string = NULL;
|
||||
GstBus *bus = NULL;
|
||||
GstElement *testsink = NULL;
|
||||
GstElement *testsource = NULL;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
if (argc == 2)
|
||||
filename = g_strdup (argv[1]);
|
||||
else
|
||||
filename = g_strdup ("/usr/share/sounds/ekiga/ring.wav");
|
||||
|
||||
if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
|
||||
g_print ("File %s does not exist\n", filename);
|
||||
g_free (filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
data = g_new0 (ProgramData, 1);
|
||||
|
||||
data->loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* setting up source pipeline, we read from a file and convert to our desired
|
||||
* caps. */
|
||||
string =
|
||||
g_strdup_printf
|
||||
("filesrc location=\"%s\" ! wavparse ! audioconvert ! audioresample ! appsink caps=\"%s\" name=testsink",
|
||||
filename, audio_caps);
|
||||
g_free (filename);
|
||||
data->source = gst_parse_launch (string, NULL);
|
||||
g_free (string);
|
||||
|
||||
if (data->source == NULL) {
|
||||
g_print ("Bad source\n");
|
||||
g_main_loop_unref (data->loop);
|
||||
g_free (data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* to be notified of messages from this pipeline, mostly EOS */
|
||||
bus = gst_element_get_bus (data->source);
|
||||
gst_bus_add_watch (bus, (GstBusFunc) on_source_message, data);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* we use appsink in push mode, it sends us a signal when data is available
|
||||
* and we pull out the data in the signal callback. We want the appsink to
|
||||
* push as fast as it can, hence the sync=false */
|
||||
testsink = gst_bin_get_by_name (GST_BIN (data->source), "testsink");
|
||||
g_object_set (G_OBJECT (testsink), "emit-signals", TRUE, "sync", FALSE, NULL);
|
||||
g_signal_connect (testsink, "new-sample",
|
||||
G_CALLBACK (on_new_sample_from_sink), data);
|
||||
gst_object_unref (testsink);
|
||||
|
||||
/* setting up sink pipeline, we push audio data into this pipeline that will
|
||||
* then play it back using the default audio sink. We have no blocking
|
||||
* behaviour on the src which means that we will push the entire file into
|
||||
* memory. */
|
||||
string =
|
||||
g_strdup_printf ("appsrc name=testsource caps=\"%s\" ! autoaudiosink",
|
||||
audio_caps);
|
||||
data->sink = gst_parse_launch (string, NULL);
|
||||
g_free (string);
|
||||
|
||||
if (data->sink == NULL) {
|
||||
g_print ("Bad sink\n");
|
||||
gst_object_unref (data->source);
|
||||
g_main_loop_unref (data->loop);
|
||||
g_free (data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
testsource = gst_bin_get_by_name (GST_BIN (data->sink), "testsource");
|
||||
/* configure for time-based format */
|
||||
g_object_set (testsource, "format", GST_FORMAT_TIME, NULL);
|
||||
/* uncomment the next line to block when appsrc has buffered enough */
|
||||
/* g_object_set (testsource, "block", TRUE, NULL); */
|
||||
gst_object_unref (testsource);
|
||||
|
||||
bus = gst_element_get_bus (data->sink);
|
||||
gst_bus_add_watch (bus, (GstBusFunc) on_sink_message, data);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* launching things */
|
||||
gst_element_set_state (data->sink, GST_STATE_PLAYING);
|
||||
gst_element_set_state (data->source, GST_STATE_PLAYING);
|
||||
|
||||
/* let's run !, this loop will quit when the sink pipeline goes EOS or when an
|
||||
* error occurs in the source or sink pipelines. */
|
||||
g_print ("Let's run!\n");
|
||||
g_main_loop_run (data->loop);
|
||||
g_print ("Going out\n");
|
||||
|
||||
gst_element_set_state (data->source, GST_STATE_NULL);
|
||||
gst_element_set_state (data->sink, GST_STATE_NULL);
|
||||
|
||||
gst_object_unref (data->source);
|
||||
gst_object_unref (data->sink);
|
||||
g_main_loop_unref (data->loop);
|
||||
g_free (data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* appsrc-src2.c: example for using gst_app_src_push_sample().
|
||||
*
|
||||
* Copyright (C) 2014 Nicola Murino <nicola.murino@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <gst/app/gstappsrc.h>
|
||||
#include <gst/app/gstappsink.h>
|
||||
|
||||
/*
|
||||
* In this sample we show the usage of gst_app_src_push_sample in push
|
||||
* mode, this method set the appsrc caps based on the caps from the sample
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GMainLoop *loop;
|
||||
GstElement *source;
|
||||
GstElement *sink;
|
||||
} ProgramData;
|
||||
|
||||
/* called when the appsink notifies us that there is a new buffer ready for
|
||||
* processing */
|
||||
|
||||
static GstFlowReturn
|
||||
on_new_sample_from_sink (GstElement * elt, ProgramData * data)
|
||||
{
|
||||
GstSample *sample;
|
||||
GstElement *source;
|
||||
GstFlowReturn ret;
|
||||
|
||||
/* get the sample from appsink */
|
||||
sample = gst_app_sink_pull_sample (GST_APP_SINK (elt));
|
||||
|
||||
/* get source an push new sample */
|
||||
source = gst_bin_get_by_name (GST_BIN (data->sink), "testsource");
|
||||
ret = gst_app_src_push_sample (GST_APP_SRC (source), sample);
|
||||
gst_object_unref (source);
|
||||
|
||||
/* we don't need the appsink sample anymore */
|
||||
gst_sample_unref (sample);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* called when we get a GstMessage from the source pipeline when we get EOS, we
|
||||
* notify the appsrc of it. */
|
||||
static gboolean
|
||||
on_source_message (GstBus * bus, GstMessage * message, ProgramData * data)
|
||||
{
|
||||
GstElement *source;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("The source got dry\n");
|
||||
source = gst_bin_get_by_name (GST_BIN (data->sink), "testsource");
|
||||
gst_app_src_end_of_stream (GST_APP_SRC (source));
|
||||
gst_object_unref (source);
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_print ("Received error\n");
|
||||
g_main_loop_quit (data->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* called when we get a GstMessage from the sink pipeline when we get EOS, we
|
||||
* exit the mainloop and this testapp. */
|
||||
static gboolean
|
||||
on_sink_message (GstBus * bus, GstMessage * message, ProgramData * data)
|
||||
{
|
||||
/* nil */
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("Finished playback\n");
|
||||
g_main_loop_quit (data->loop);
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_print ("Received error\n");
|
||||
g_main_loop_quit (data->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
ProgramData *data = NULL;
|
||||
gchar *string = NULL;
|
||||
GstBus *bus = NULL;
|
||||
GstElement *testsink = NULL;
|
||||
GstElement *testsource = NULL;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
data = g_new0 (ProgramData, 1);
|
||||
|
||||
data->loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* setting up source pipeline, we read from a file and convert to our desired
|
||||
* caps. */
|
||||
string =
|
||||
g_strdup_printf
|
||||
("audiotestsrc num-buffers=200 ! wavenc ! wavparse ! appsink name=testsink");
|
||||
data->source = gst_parse_launch (string, NULL);
|
||||
g_free (string);
|
||||
|
||||
if (data->source == NULL) {
|
||||
g_print ("Bad source\n");
|
||||
g_main_loop_unref (data->loop);
|
||||
g_free (data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* to be notified of messages from this pipeline, mostly EOS */
|
||||
bus = gst_element_get_bus (data->source);
|
||||
gst_bus_add_watch (bus, (GstBusFunc) on_source_message, data);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* we use appsink in push mode, it sends us a signal when data is available
|
||||
* and we pull out the data in the signal callback. We want the appsink to
|
||||
* push as fast as it can, hence the sync=false */
|
||||
testsink = gst_bin_get_by_name (GST_BIN (data->source), "testsink");
|
||||
g_object_set (G_OBJECT (testsink), "emit-signals", TRUE, "sync", FALSE, NULL);
|
||||
g_signal_connect (testsink, "new-sample",
|
||||
G_CALLBACK (on_new_sample_from_sink), data);
|
||||
gst_object_unref (testsink);
|
||||
|
||||
/* setting up sink pipeline, we push audio data into this pipeline that will
|
||||
* then play it back using the default audio sink. */
|
||||
string =
|
||||
g_strdup_printf
|
||||
("appsrc name=testsource ! audioconvert ! audioresample ! autoaudiosink");
|
||||
data->sink = gst_parse_launch (string, NULL);
|
||||
g_free (string);
|
||||
|
||||
if (data->sink == NULL) {
|
||||
g_print ("Bad sink\n");
|
||||
g_main_loop_unref (data->loop);
|
||||
g_free (data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
testsource = gst_bin_get_by_name (GST_BIN (data->sink), "testsource");
|
||||
/* configure for time-based format */
|
||||
g_object_set (testsource, "format", GST_FORMAT_TIME, NULL);
|
||||
/* uncomment the next line to block when appsrc has buffered enough */
|
||||
/* g_object_set (testsource, "block", TRUE, NULL); */
|
||||
gst_object_unref (testsource);
|
||||
|
||||
bus = gst_element_get_bus (data->sink);
|
||||
gst_bus_add_watch (bus, (GstBusFunc) on_sink_message, data);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* launching things */
|
||||
gst_element_set_state (data->sink, GST_STATE_PLAYING);
|
||||
gst_element_set_state (data->source, GST_STATE_PLAYING);
|
||||
|
||||
/* let's run !, this loop will quit when the sink pipeline goes EOS or when an
|
||||
* error occurs in the source or sink pipelines. */
|
||||
g_print ("Let's run!\n");
|
||||
g_main_loop_run (data->loop);
|
||||
g_print ("Going out\n");
|
||||
|
||||
gst_element_set_state (data->source, GST_STATE_NULL);
|
||||
gst_element_set_state (data->sink, GST_STATE_NULL);
|
||||
|
||||
gst_object_unref (data->source);
|
||||
gst_object_unref (data->sink);
|
||||
g_main_loop_unref (data->loop);
|
||||
g_free (data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* appsrc-ra.c: example for using appsrc in random-access mode.
|
||||
*
|
||||
* Copyright (C) 2008 Wim Taymans <wim.taymans@gmail.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 <gst/gst.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
GST_DEBUG_CATEGORY (appsrc_playbin_debug);
|
||||
#define GST_CAT_DEFAULT appsrc_playbin_debug
|
||||
|
||||
/*
|
||||
* an example application of using appsrc in random-access mode. When the
|
||||
* appsrc requests data with the need-data signal, we retrieve a buffer of the
|
||||
* requested size and push it to appsrc.
|
||||
*
|
||||
* This is a good example how one would deal with a local file resource.
|
||||
*
|
||||
* Appsrc in random-access mode needs seeking support and we must thus connect
|
||||
* to the seek signal to perform any seeks when requested.
|
||||
*
|
||||
* In random-access mode we must set the size of the source material.
|
||||
*/
|
||||
typedef struct _App App;
|
||||
|
||||
struct _App
|
||||
{
|
||||
GstElement *playbin;
|
||||
GstElement *appsrc;
|
||||
|
||||
GMainLoop *loop;
|
||||
|
||||
GMappedFile *file;
|
||||
guint8 *data;
|
||||
gsize length;
|
||||
guint64 offset;
|
||||
};
|
||||
|
||||
App s_app;
|
||||
|
||||
/* This method is called by the need-data signal callback, we feed data into the
|
||||
* appsrc with the requested size.
|
||||
*/
|
||||
static void
|
||||
feed_data (GstElement * appsrc, guint size, App * app)
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
GstFlowReturn ret;
|
||||
|
||||
if (app->offset >= app->length) {
|
||||
/* we are EOS, send end-of-stream */
|
||||
g_signal_emit_by_name (app->appsrc, "end-of-stream", &ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* read the amount of data, we are allowed to return less if we are EOS */
|
||||
buffer = gst_buffer_new ();
|
||||
|
||||
if (app->offset + size > app->length)
|
||||
size = app->length - app->offset;
|
||||
|
||||
gst_buffer_append_memory (buffer,
|
||||
gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY,
|
||||
app->data, app->length, app->offset, size, NULL, NULL));
|
||||
|
||||
/* we need to set an offset for random access */
|
||||
GST_BUFFER_OFFSET (buffer) = app->offset;
|
||||
GST_BUFFER_OFFSET_END (buffer) = app->offset + size;
|
||||
|
||||
GST_DEBUG ("feed buffer %p, offset %" G_GUINT64_FORMAT "-%u", buffer,
|
||||
app->offset, size);
|
||||
g_signal_emit_by_name (app->appsrc, "push-buffer", buffer, &ret);
|
||||
gst_buffer_unref (buffer);
|
||||
|
||||
app->offset += size;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* called when appsrc wants us to return data from a new position with the next
|
||||
* call to push-buffer. */
|
||||
static gboolean
|
||||
seek_data (GstElement * appsrc, guint64 position, App * app)
|
||||
{
|
||||
GST_DEBUG ("seek to offset %" G_GUINT64_FORMAT, position);
|
||||
app->offset = position;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* this callback is called when playbin has constructed a source object to read
|
||||
* from. Since we provided the appsrc:// uri to playbin, this will be the
|
||||
* appsrc that we must handle. We set up some signals to push data into appsrc
|
||||
* and one to perform a seek. */
|
||||
static void
|
||||
found_source (GObject * object, GObject * orig, GParamSpec * pspec, App * app)
|
||||
{
|
||||
/* get a handle to the appsrc */
|
||||
g_object_get (orig, pspec->name, &app->appsrc, NULL);
|
||||
|
||||
GST_DEBUG ("got appsrc %p", app->appsrc);
|
||||
|
||||
/* we can set the length in appsrc. This allows some elements to estimate the
|
||||
* total duration of the stream. It's a good idea to set the property when you
|
||||
* can but it's not required. */
|
||||
g_object_set (app->appsrc, "size", (gint64) app->length, NULL);
|
||||
gst_util_set_object_arg (G_OBJECT (app->appsrc), "stream-type",
|
||||
"random-access");
|
||||
|
||||
/* configure the appsrc, we will push a buffer to appsrc when it needs more
|
||||
* data */
|
||||
g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
|
||||
g_signal_connect (app->appsrc, "seek-data", G_CALLBACK (seek_data), app);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
bus_message (GstBus * bus, GstMessage * message, App * app)
|
||||
{
|
||||
GST_DEBUG ("got message %s",
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_error ("received error");
|
||||
g_main_loop_quit (app->loop);
|
||||
break;
|
||||
case GST_MESSAGE_EOS:
|
||||
g_main_loop_quit (app->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
App *app = &s_app;
|
||||
GError *error = NULL;
|
||||
GstBus *bus;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (appsrc_playbin_debug, "appsrc-playbin", 0,
|
||||
"appsrc playbin example");
|
||||
|
||||
if (argc < 2) {
|
||||
g_print ("usage: %s <filename>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* try to open the file as an mmapped file */
|
||||
app->file = g_mapped_file_new (argv[1], FALSE, &error);
|
||||
if (error) {
|
||||
g_print ("failed to open file: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return -2;
|
||||
}
|
||||
/* get some vitals, this will be used to read data from the mmapped file and
|
||||
* feed it to appsrc. */
|
||||
app->length = g_mapped_file_get_length (app->file);
|
||||
app->data = (guint8 *) g_mapped_file_get_contents (app->file);
|
||||
app->offset = 0;
|
||||
|
||||
/* create a mainloop to get messages */
|
||||
app->loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
app->playbin = gst_element_factory_make ("playbin", NULL);
|
||||
g_assert (app->playbin);
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (app->playbin));
|
||||
|
||||
/* add watch for messages */
|
||||
gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
|
||||
|
||||
/* set to read from appsrc */
|
||||
g_object_set (app->playbin, "uri", "appsrc://", NULL);
|
||||
|
||||
/* get notification when the source is created so that we get a handle to it
|
||||
* and can configure it */
|
||||
g_signal_connect (app->playbin, "deep-notify::source",
|
||||
(GCallback) found_source, app);
|
||||
|
||||
/* go to playing and wait in a mainloop. */
|
||||
gst_element_set_state (app->playbin, GST_STATE_PLAYING);
|
||||
|
||||
/* this mainloop is stopped when we receive an error or EOS */
|
||||
g_main_loop_run (app->loop);
|
||||
|
||||
GST_DEBUG ("stopping");
|
||||
|
||||
gst_element_set_state (app->playbin, GST_STATE_NULL);
|
||||
|
||||
/* free the file */
|
||||
g_mapped_file_unref (app->file);
|
||||
|
||||
gst_object_unref (bus);
|
||||
g_main_loop_unref (app->loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* appsrc-seekable.c: example for using appsrc in seekable mode.
|
||||
*
|
||||
* Copyright (C) 2008 Wim Taymans <wim.taymans@gmail.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 <gst/gst.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
GST_DEBUG_CATEGORY (appsrc_playbin_debug);
|
||||
#define GST_CAT_DEFAULT appsrc_playbin_debug
|
||||
|
||||
/*
|
||||
* an example application of using appsrc in seekable mode. When the
|
||||
* appsrc requests data with the need-data signal, we retrieve a buffer and
|
||||
* push it to appsrc. We can also use the method as demonstrated in
|
||||
* appsrc-stream.c, ie. pushing buffers when we can.
|
||||
*
|
||||
* This is a good example how one would deal with a remote http server that
|
||||
* supports range requests.
|
||||
*
|
||||
* Appsrc in seekable mode needs seeking support and we must thus connect
|
||||
* to the seek signal to perform any seeks when requested.
|
||||
*
|
||||
* In seekable mode we should set the size of the source material.
|
||||
*/
|
||||
typedef struct _App App;
|
||||
|
||||
struct _App
|
||||
{
|
||||
GstElement *playbin;
|
||||
GstElement *appsrc;
|
||||
|
||||
GMainLoop *loop;
|
||||
|
||||
GMappedFile *file;
|
||||
guint8 *data;
|
||||
gsize length;
|
||||
guint64 offset;
|
||||
};
|
||||
|
||||
App s_app;
|
||||
|
||||
#define CHUNK_SIZE 4096
|
||||
|
||||
/* This method is called by the need-data signal callback, we feed data into the
|
||||
* appsrc with an arbitrary size.
|
||||
*/
|
||||
static void
|
||||
feed_data (GstElement * appsrc, guint size, App * app)
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
guint len;
|
||||
GstFlowReturn ret;
|
||||
|
||||
if (app->offset >= app->length) {
|
||||
/* we are EOS, send end-of-stream */
|
||||
g_signal_emit_by_name (app->appsrc, "end-of-stream", &ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* read any amount of data, we are allowed to return less if we are EOS */
|
||||
buffer = gst_buffer_new ();
|
||||
|
||||
len = CHUNK_SIZE;
|
||||
if (app->offset + len > app->length)
|
||||
len = app->length - app->offset;
|
||||
|
||||
gst_buffer_append_memory (buffer,
|
||||
gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY,
|
||||
app->data, app->length, app->offset, len, NULL, NULL));
|
||||
|
||||
GST_DEBUG ("feed buffer %p, offset %" G_GUINT64_FORMAT "-%u", buffer,
|
||||
app->offset, len);
|
||||
g_signal_emit_by_name (app->appsrc, "push-buffer", buffer, &ret);
|
||||
gst_buffer_unref (buffer);
|
||||
|
||||
app->offset += len;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* called when appsrc wants us to return data from a new position with the next
|
||||
* call to push-buffer. */
|
||||
static gboolean
|
||||
seek_data (GstElement * appsrc, guint64 position, App * app)
|
||||
{
|
||||
GST_DEBUG ("seek to offset %" G_GUINT64_FORMAT, position);
|
||||
app->offset = position;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* this callback is called when playbin has constructed a source object to read
|
||||
* from. Since we provided the appsrc:// uri to playbin, this will be the
|
||||
* appsrc that we must handle. We set up some signals to push data into appsrc
|
||||
* and one to perform a seek. */
|
||||
static void
|
||||
found_source (GObject * object, GObject * orig, GParamSpec * pspec, App * app)
|
||||
{
|
||||
/* get a handle to the appsrc */
|
||||
g_object_get (orig, pspec->name, &app->appsrc, NULL);
|
||||
|
||||
GST_DEBUG ("got appsrc %p", app->appsrc);
|
||||
|
||||
/* we can set the length in appsrc. This allows some elements to estimate the
|
||||
* total duration of the stream. It's a good idea to set the property when you
|
||||
* can but it's not required. */
|
||||
g_object_set (app->appsrc, "size", (gint64) app->length, NULL);
|
||||
/* we are seekable in push mode, this means that the element usually pushes
|
||||
* out buffers of an undefined size and that seeks happen only occasionally
|
||||
* and only by request of the user. */
|
||||
gst_util_set_object_arg (G_OBJECT (app->appsrc), "stream-type", "seekable");
|
||||
|
||||
/* configure the appsrc, we will push a buffer to appsrc when it needs more
|
||||
* data */
|
||||
g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
|
||||
g_signal_connect (app->appsrc, "seek-data", G_CALLBACK (seek_data), app);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
bus_message (GstBus * bus, GstMessage * message, App * app)
|
||||
{
|
||||
GST_DEBUG ("got message %s",
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_error ("received error");
|
||||
g_main_loop_quit (app->loop);
|
||||
break;
|
||||
case GST_MESSAGE_EOS:
|
||||
g_main_loop_quit (app->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
App *app = &s_app;
|
||||
GError *error = NULL;
|
||||
GstBus *bus;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (appsrc_playbin_debug, "appsrc-playbin", 0,
|
||||
"appsrc playbin example");
|
||||
|
||||
if (argc < 2) {
|
||||
g_print ("usage: %s <filename>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* try to open the file as an mmapped file */
|
||||
app->file = g_mapped_file_new (argv[1], FALSE, &error);
|
||||
if (error) {
|
||||
g_print ("failed to open file: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return -2;
|
||||
}
|
||||
/* get some vitals, this will be used to read data from the mmapped file and
|
||||
* feed it to appsrc. */
|
||||
app->length = g_mapped_file_get_length (app->file);
|
||||
app->data = (guint8 *) g_mapped_file_get_contents (app->file);
|
||||
app->offset = 0;
|
||||
|
||||
/* create a mainloop to get messages */
|
||||
app->loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
app->playbin = gst_element_factory_make ("playbin", NULL);
|
||||
g_assert (app->playbin);
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (app->playbin));
|
||||
|
||||
/* add watch for messages */
|
||||
gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
|
||||
|
||||
/* set to read from appsrc */
|
||||
g_object_set (app->playbin, "uri", "appsrc://", NULL);
|
||||
|
||||
/* get notification when the source is created so that we get a handle to it
|
||||
* and can configure it */
|
||||
g_signal_connect (app->playbin, "deep-notify::source",
|
||||
(GCallback) found_source, app);
|
||||
|
||||
/* go to playing and wait in a mainloop. */
|
||||
gst_element_set_state (app->playbin, GST_STATE_PLAYING);
|
||||
|
||||
/* this mainloop is stopped when we receive an error or EOS */
|
||||
g_main_loop_run (app->loop);
|
||||
|
||||
GST_DEBUG ("stopping");
|
||||
|
||||
gst_element_set_state (app->playbin, GST_STATE_NULL);
|
||||
|
||||
/* free the file */
|
||||
g_mapped_file_unref (app->file);
|
||||
|
||||
gst_object_unref (bus);
|
||||
g_main_loop_unref (app->loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* appsrc-stream.c: example for using appsrc in streaming mode.
|
||||
*
|
||||
* Copyright (C) 2008 Wim Taymans <wim.taymans@gmail.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 <gst/gst.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
GST_DEBUG_CATEGORY (appsrc_playbin_debug);
|
||||
#define GST_CAT_DEFAULT appsrc_playbin_debug
|
||||
|
||||
/*
|
||||
* an example application of using appsrc in streaming push mode. We simply push
|
||||
* buffers into appsrc. The size of the buffers we push can be any size we
|
||||
* choose.
|
||||
*
|
||||
* This example is very close to how one would deal with a streaming webserver
|
||||
* that does not support range requests or does not report the total file size.
|
||||
*
|
||||
* Some optimisations are done so that we don't push too much data. We connect
|
||||
* to the need-data and enough-data signals to start/stop sending buffers.
|
||||
*
|
||||
* Appsrc in streaming mode (the default) does not support seeking so we don't
|
||||
* have to handle any seek callbacks.
|
||||
*
|
||||
* Some formats are able to estimate the duration of the media file based on the
|
||||
* file length (mp3, mpeg,..), others report an unknown length (ogg,..).
|
||||
*/
|
||||
typedef struct _App App;
|
||||
|
||||
struct _App
|
||||
{
|
||||
GstElement *playbin;
|
||||
GstElement *appsrc;
|
||||
|
||||
GMainLoop *loop;
|
||||
guint sourceid;
|
||||
|
||||
GMappedFile *file;
|
||||
guint8 *data;
|
||||
gsize length;
|
||||
guint64 offset;
|
||||
};
|
||||
|
||||
App s_app;
|
||||
|
||||
#define CHUNK_SIZE 4096
|
||||
|
||||
/* This method is called by the idle GSource in the mainloop. We feed CHUNK_SIZE
|
||||
* bytes into appsrc.
|
||||
* The ide handler is added to the mainloop when appsrc requests us to start
|
||||
* sending data (need-data signal) and is removed when appsrc has enough data
|
||||
* (enough-data signal).
|
||||
*/
|
||||
static gboolean
|
||||
read_data (App * app)
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
guint len;
|
||||
GstFlowReturn ret;
|
||||
|
||||
if (app->offset >= app->length) {
|
||||
/* we are EOS, send end-of-stream and remove the source */
|
||||
g_signal_emit_by_name (app->appsrc, "end-of-stream", &ret);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* read the next chunk */
|
||||
buffer = gst_buffer_new ();
|
||||
|
||||
len = CHUNK_SIZE;
|
||||
if (app->offset + len > app->length)
|
||||
len = app->length - app->offset;
|
||||
|
||||
gst_buffer_append_memory (buffer,
|
||||
gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY,
|
||||
app->data, app->length, app->offset, len, NULL, NULL));
|
||||
|
||||
GST_DEBUG ("feed buffer %p, offset %" G_GUINT64_FORMAT "-%u", buffer,
|
||||
app->offset, len);
|
||||
g_signal_emit_by_name (app->appsrc, "push-buffer", buffer, &ret);
|
||||
gst_buffer_unref (buffer);
|
||||
if (ret != GST_FLOW_OK) {
|
||||
/* some error, stop sending data */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
app->offset += len;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* This signal callback is called when appsrc needs data, we add an idle handler
|
||||
* to the mainloop to start pushing data into the appsrc */
|
||||
static void
|
||||
start_feed (GstElement * playbin, guint size, App * app)
|
||||
{
|
||||
if (app->sourceid == 0) {
|
||||
GST_DEBUG ("start feeding");
|
||||
app->sourceid = g_idle_add ((GSourceFunc) read_data, app);
|
||||
}
|
||||
}
|
||||
|
||||
/* This callback is called when appsrc has enough data and we can stop sending.
|
||||
* We remove the idle handler from the mainloop */
|
||||
static void
|
||||
stop_feed (GstElement * playbin, App * app)
|
||||
{
|
||||
if (app->sourceid != 0) {
|
||||
GST_DEBUG ("stop feeding");
|
||||
g_source_remove (app->sourceid);
|
||||
app->sourceid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* this callback is called when playbin has constructed a source object to read
|
||||
* from. Since we provided the appsrc:// uri to playbin, this will be the
|
||||
* appsrc that we must handle. We set up some signals to start and stop pushing
|
||||
* data into appsrc */
|
||||
static void
|
||||
found_source (GObject * object, GObject * orig, GParamSpec * pspec, App * app)
|
||||
{
|
||||
/* get a handle to the appsrc */
|
||||
g_object_get (orig, pspec->name, &app->appsrc, NULL);
|
||||
|
||||
GST_DEBUG ("got appsrc %p", app->appsrc);
|
||||
|
||||
/* we can set the length in appsrc. This allows some elements to estimate the
|
||||
* total duration of the stream. It's a good idea to set the property when you
|
||||
* can but it's not required. */
|
||||
g_object_set (app->appsrc, "size", (gint64) app->length, NULL);
|
||||
|
||||
/* configure the appsrc, we will push data into the appsrc from the
|
||||
* mainloop. */
|
||||
g_signal_connect (app->appsrc, "need-data", G_CALLBACK (start_feed), app);
|
||||
g_signal_connect (app->appsrc, "enough-data", G_CALLBACK (stop_feed), app);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
bus_message (GstBus * bus, GstMessage * message, App * app)
|
||||
{
|
||||
GST_DEBUG ("got message %s",
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_error ("received error");
|
||||
g_main_loop_quit (app->loop);
|
||||
break;
|
||||
case GST_MESSAGE_EOS:
|
||||
g_main_loop_quit (app->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
App *app = &s_app;
|
||||
GError *error = NULL;
|
||||
GstBus *bus;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (appsrc_playbin_debug, "appsrc-playbin", 0,
|
||||
"appsrc playbin example");
|
||||
|
||||
if (argc < 2) {
|
||||
g_print ("usage: %s <filename>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* try to open the file as an mmapped file */
|
||||
app->file = g_mapped_file_new (argv[1], FALSE, &error);
|
||||
if (error) {
|
||||
g_print ("failed to open file: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return -2;
|
||||
}
|
||||
/* get some vitals, this will be used to read data from the mmapped file and
|
||||
* feed it to appsrc. */
|
||||
app->length = g_mapped_file_get_length (app->file);
|
||||
app->data = (guint8 *) g_mapped_file_get_contents (app->file);
|
||||
app->offset = 0;
|
||||
|
||||
/* create a mainloop to get messages and to handle the idle handler that will
|
||||
* feed data to appsrc. */
|
||||
app->loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
app->playbin = gst_element_factory_make ("playbin", NULL);
|
||||
g_assert (app->playbin);
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (app->playbin));
|
||||
|
||||
/* add watch for messages */
|
||||
gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
|
||||
|
||||
/* set to read from appsrc */
|
||||
g_object_set (app->playbin, "uri", "appsrc://", NULL);
|
||||
|
||||
/* get notification when the source is created so that we get a handle to it
|
||||
* and can configure it */
|
||||
g_signal_connect (app->playbin, "deep-notify::source",
|
||||
(GCallback) found_source, app);
|
||||
|
||||
/* go to playing and wait in a mainloop. */
|
||||
gst_element_set_state (app->playbin, GST_STATE_PLAYING);
|
||||
|
||||
/* this mainloop is stopped when we receive an error or EOS */
|
||||
g_main_loop_run (app->loop);
|
||||
|
||||
GST_DEBUG ("stopping");
|
||||
|
||||
gst_element_set_state (app->playbin, GST_STATE_NULL);
|
||||
|
||||
/* free the file */
|
||||
g_mapped_file_unref (app->file);
|
||||
|
||||
gst_object_unref (bus);
|
||||
g_main_loop_unref (app->loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* appsrc-stream2.c: example for using appsrc in streaming mode.
|
||||
*
|
||||
* Copyright (C) 2008 Wim Taymans <wim.taymans@gmail.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 <gst/gst.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
GST_DEBUG_CATEGORY (appsrc_playbin_debug);
|
||||
#define GST_CAT_DEFAULT appsrc_playbin_debug
|
||||
|
||||
/*
|
||||
* an example application of using appsrc in streaming pull mode. When the
|
||||
* appsrc request data with the need-data signal, we retrieve a buffer of an
|
||||
* arbitrary size and push it to appsrc.
|
||||
*
|
||||
* This example keeps the internal buffer queue of appsrc to a minimal size,
|
||||
* only feeding data to appsrc when needed.
|
||||
*
|
||||
* This is a good example how one would deal with a live resource, such as a udp
|
||||
* socket where one would feed the most recently acquired buffer to appsrc.
|
||||
*
|
||||
* Usually one would timestamp the buffers with the running_time of the
|
||||
* pipeline or configure the appsrc to do timestamping by setting the
|
||||
* do-timestamp property to TRUE.
|
||||
*
|
||||
* Appsrc in streaming mode (the default) does not support seeking so we don't
|
||||
* have to handle any seek callbacks.
|
||||
*
|
||||
* Some formats are able to estimate the duration of the media file based on the
|
||||
* file length (mp3, mpeg,..), others report an unknown length (ogg,..).
|
||||
*/
|
||||
typedef struct _App App;
|
||||
|
||||
struct _App
|
||||
{
|
||||
GstElement *playbin;
|
||||
GstElement *appsrc;
|
||||
|
||||
GMainLoop *loop;
|
||||
|
||||
GMappedFile *file;
|
||||
guint8 *data;
|
||||
gsize length;
|
||||
guint64 offset;
|
||||
};
|
||||
|
||||
App s_app;
|
||||
|
||||
#define CHUNK_SIZE 4096
|
||||
|
||||
/* This method is called by the need-data signal callback, we feed data into the
|
||||
* appsrc.
|
||||
*/
|
||||
static void
|
||||
feed_data (GstElement * appsrc, guint size, App * app)
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
guint len;
|
||||
GstFlowReturn ret;
|
||||
|
||||
if (app->offset >= app->length) {
|
||||
/* we are EOS, send end-of-stream */
|
||||
g_signal_emit_by_name (app->appsrc, "end-of-stream", &ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* read the next chunk */
|
||||
buffer = gst_buffer_new ();
|
||||
|
||||
len = CHUNK_SIZE;
|
||||
if (app->offset + len > app->length)
|
||||
len = app->length - app->offset;
|
||||
|
||||
gst_buffer_append_memory (buffer,
|
||||
gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY,
|
||||
app->data, app->length, app->offset, len, NULL, NULL));
|
||||
|
||||
GST_DEBUG ("feed buffer %p, offset %" G_GUINT64_FORMAT "-%u", buffer,
|
||||
app->offset, len);
|
||||
g_signal_emit_by_name (app->appsrc, "push-buffer", buffer, &ret);
|
||||
gst_buffer_unref (buffer);
|
||||
|
||||
app->offset += len;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* this callback is called when playbin has constructed a source object to read
|
||||
* from. Since we provided the appsrc:// uri to playbin, this will be the
|
||||
* appsrc that we must handle. We set up a signals to push data into appsrc. */
|
||||
static void
|
||||
found_source (GObject * object, GObject * orig, GParamSpec * pspec, App * app)
|
||||
{
|
||||
/* get a handle to the appsrc */
|
||||
g_object_get (orig, pspec->name, &app->appsrc, NULL);
|
||||
|
||||
GST_DEBUG ("got appsrc %p", app->appsrc);
|
||||
|
||||
/* we can set the length in appsrc. This allows some elements to estimate the
|
||||
* total duration of the stream. It's a good idea to set the property when you
|
||||
* can but it's not required. */
|
||||
g_object_set (app->appsrc, "size", (gint64) app->length, NULL);
|
||||
|
||||
/* configure the appsrc, we will push a buffer to appsrc when it needs more
|
||||
* data */
|
||||
g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
bus_message (GstBus * bus, GstMessage * message, App * app)
|
||||
{
|
||||
GST_DEBUG ("got message %s",
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_error ("received error");
|
||||
g_main_loop_quit (app->loop);
|
||||
break;
|
||||
case GST_MESSAGE_EOS:
|
||||
g_main_loop_quit (app->loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
App *app = &s_app;
|
||||
GError *error = NULL;
|
||||
GstBus *bus;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (appsrc_playbin_debug, "appsrc-playbin", 0,
|
||||
"appsrc playbin example");
|
||||
|
||||
if (argc < 2) {
|
||||
g_print ("usage: %s <filename>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* try to open the file as an mmapped file */
|
||||
app->file = g_mapped_file_new (argv[1], FALSE, &error);
|
||||
if (error) {
|
||||
g_print ("failed to open file: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return -2;
|
||||
}
|
||||
/* get some vitals, this will be used to read data from the mmapped file and
|
||||
* feed it to appsrc. */
|
||||
app->length = g_mapped_file_get_length (app->file);
|
||||
app->data = (guint8 *) g_mapped_file_get_contents (app->file);
|
||||
app->offset = 0;
|
||||
|
||||
/* create a mainloop to get messages */
|
||||
app->loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
app->playbin = gst_element_factory_make ("playbin", NULL);
|
||||
g_assert (app->playbin);
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (app->playbin));
|
||||
|
||||
/* add watch for messages */
|
||||
gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
|
||||
|
||||
/* set to read from appsrc */
|
||||
g_object_set (app->playbin, "uri", "appsrc://", NULL);
|
||||
|
||||
/* get notification when the source is created so that we get a handle to it
|
||||
* and can configure it */
|
||||
g_signal_connect (app->playbin, "deep-notify::source",
|
||||
(GCallback) found_source, app);
|
||||
|
||||
/* go to playing and wait in a mainloop. */
|
||||
gst_element_set_state (app->playbin, GST_STATE_PLAYING);
|
||||
|
||||
/* this mainloop is stopped when we receive an error or EOS */
|
||||
g_main_loop_run (app->loop);
|
||||
|
||||
GST_DEBUG ("stopping");
|
||||
|
||||
gst_element_set_state (app->playbin, GST_STATE_NULL);
|
||||
|
||||
/* free the file */
|
||||
g_mapped_file_unref (app->file);
|
||||
|
||||
gst_object_unref (bus);
|
||||
g_main_loop_unref (app->loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* appsrc_ex.c: example for using appsrc and appsink linked.
|
||||
*
|
||||
* Copyright (C) 2007 David Schleef <ds@schleef.org>
|
||||
*
|
||||
* 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 <gst/gst.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
#include <gst/app/gstappsink.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
typedef struct _App App;
|
||||
struct _App
|
||||
{
|
||||
GstElement *pipe;
|
||||
GstElement *src;
|
||||
GstElement *id;
|
||||
GstElement *sink;
|
||||
};
|
||||
|
||||
App s_app;
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
App *app = &s_app;
|
||||
int i;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
app->pipe = gst_pipeline_new (NULL);
|
||||
g_assert (app->pipe);
|
||||
|
||||
app->src = gst_element_factory_make ("appsrc", NULL);
|
||||
g_assert (app->src);
|
||||
gst_bin_add (GST_BIN (app->pipe), app->src);
|
||||
|
||||
app->id = gst_element_factory_make ("identity", NULL);
|
||||
g_assert (app->id);
|
||||
gst_bin_add (GST_BIN (app->pipe), app->id);
|
||||
|
||||
app->sink = gst_element_factory_make ("appsink", NULL);
|
||||
g_assert (app->sink);
|
||||
gst_bin_add (GST_BIN (app->pipe), app->sink);
|
||||
|
||||
gst_element_link (app->src, app->id);
|
||||
gst_element_link (app->id, app->sink);
|
||||
|
||||
gst_element_set_state (app->pipe, GST_STATE_PLAYING);
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
GstBuffer *buf;
|
||||
GstMapInfo map;
|
||||
|
||||
buf = gst_buffer_new_and_alloc (100);
|
||||
gst_buffer_map (buf, &map, GST_MAP_WRITE);
|
||||
memset (map.data, i, 100);
|
||||
gst_buffer_unmap (buf, &map);
|
||||
|
||||
printf ("%d: pushing buffer for pointer %p, %p\n", i, map.data, buf);
|
||||
gst_app_src_push_buffer (GST_APP_SRC (app->src), buf);
|
||||
}
|
||||
|
||||
/* push EOS */
|
||||
gst_app_src_end_of_stream (GST_APP_SRC (app->src));
|
||||
|
||||
/* _is_eos() does not block and returns TRUE if there is not currently an EOS
|
||||
* to be retrieved */
|
||||
while (!gst_app_sink_is_eos (GST_APP_SINK (app->sink))) {
|
||||
GstSample *sample;
|
||||
|
||||
/* pull the next item, this can return NULL when there is no more data and
|
||||
* EOS has been received */
|
||||
sample = gst_app_sink_pull_sample (GST_APP_SINK (app->sink));
|
||||
printf ("retrieved sample %p\n", sample);
|
||||
if (sample)
|
||||
gst_sample_unref (sample);
|
||||
}
|
||||
gst_element_set_state (app->pipe, GST_STATE_NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
app_examples = [
|
||||
'appsrc_ex',
|
||||
'appsrc-stream',
|
||||
'appsrc-stream2',
|
||||
'appsrc-ra',
|
||||
'appsrc-seekable',
|
||||
'appsink-src',
|
||||
'appsink-src2',
|
||||
]
|
||||
|
||||
foreach app : app_examples
|
||||
executable(app, '@0@.c'.format(app),
|
||||
c_args : gst_plugins_base_args,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [glib_deps, gst_dep, app_dep],
|
||||
install: false)
|
||||
endforeach
|
||||
@@ -0,0 +1,209 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* audiomix.c: sample audio mixing application
|
||||
*
|
||||
* Copyright (C) 2011 Stefan Sauer <ensonic@users.sf.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 <gtk/gtk.h>
|
||||
|
||||
/* global items for the interaction */
|
||||
static GtkWidget *scale;
|
||||
static GObject *volumes[2];
|
||||
static gint num_vol = 0;
|
||||
|
||||
|
||||
static void
|
||||
value_changed_callback (GtkWidget * widget, gpointer * user_data)
|
||||
{
|
||||
gdouble value = gtk_range_get_value (GTK_RANGE (widget));
|
||||
g_object_set (volumes[0], "volume", 1.0 - value, NULL);
|
||||
g_object_set (volumes[1], "volume", value, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_gui (GstElement * volume, gchar * file_name1, gchar * file_name2)
|
||||
{
|
||||
GtkWidget *window, *layout, *label;
|
||||
gchar *name, *ext;
|
||||
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "audiomix");
|
||||
g_signal_connect (window, "destroy", gtk_main_quit, NULL);
|
||||
|
||||
layout = gtk_grid_new ();
|
||||
g_object_set (G_OBJECT (layout), "column-spacing", 6, NULL);
|
||||
gtk_container_add (GTK_CONTAINER (window), layout);
|
||||
|
||||
/* channel labels */
|
||||
name = g_path_get_basename (file_name1);
|
||||
if ((ext = strrchr (name, '.')))
|
||||
*ext = '\0';
|
||||
label = gtk_label_new (name);
|
||||
g_free (name);
|
||||
gtk_grid_attach (GTK_GRID (layout), label, 0, 0, 1, 1);
|
||||
|
||||
gtk_grid_attach (GTK_GRID (layout), gtk_label_new ("|"), 1, 0, 1, 1);
|
||||
|
||||
name = g_path_get_basename (file_name2);
|
||||
if ((ext = strrchr (name, '.')))
|
||||
*ext = '\0';
|
||||
label = gtk_label_new (name);
|
||||
g_free (name);
|
||||
gtk_grid_attach (GTK_GRID (layout), label, 2, 0, 1, 1);
|
||||
|
||||
/* mix slider */
|
||||
scale =
|
||||
gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0.0, 1.0,
|
||||
1.0 / 200.0);
|
||||
gtk_range_set_value (GTK_RANGE (scale), 0.0);
|
||||
gtk_widget_set_size_request (scale, 200, -1);
|
||||
gtk_widget_set_hexpand (scale, TRUE);
|
||||
gtk_grid_attach (GTK_GRID (layout), scale, 0, 1, 3, 1);
|
||||
g_signal_connect (scale, "value-changed",
|
||||
G_CALLBACK (value_changed_callback), volume);
|
||||
|
||||
gtk_widget_show_all (GTK_WIDGET (window));
|
||||
}
|
||||
|
||||
static void
|
||||
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
||||
{
|
||||
const GstStructure *s;
|
||||
|
||||
s = gst_message_get_structure (message);
|
||||
g_print ("message from \"%s\" (%s): ",
|
||||
GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
if (s) {
|
||||
gchar *sstr;
|
||||
|
||||
sstr = gst_structure_to_string (s);
|
||||
g_print ("%s\n", sstr);
|
||||
g_free (sstr);
|
||||
} else {
|
||||
g_print ("no message details\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
eos_message_received (GstBus * bus, GstMessage * message,
|
||||
GstPipeline * pipeline)
|
||||
{
|
||||
message_received (bus, message, pipeline);
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
static void
|
||||
dynamic_link (GstPadTemplate * templ, GstPad * newpad, gpointer user_data)
|
||||
{
|
||||
GstPad *target = GST_PAD (user_data);
|
||||
|
||||
gst_pad_link (newpad, target);
|
||||
gst_object_unref (target);
|
||||
}
|
||||
|
||||
static void
|
||||
make_mixer_channel (GstElement * pipeline, GstElement * mix, gchar * file_name)
|
||||
{
|
||||
GstElement *filesrc, *decodebin, *volume, *convert, *format;
|
||||
GstCaps *caps;
|
||||
|
||||
/* prepare mixer channel */
|
||||
filesrc = gst_element_factory_make ("filesrc", NULL);
|
||||
decodebin = gst_element_factory_make ("decodebin", NULL);
|
||||
volume = gst_element_factory_make ("volume", NULL);
|
||||
convert = gst_element_factory_make ("audioconvert", NULL);
|
||||
format = gst_element_factory_make ("capsfilter", NULL);
|
||||
gst_bin_add_many (GST_BIN (pipeline), filesrc, decodebin, volume, convert,
|
||||
format, NULL);
|
||||
gst_element_link (filesrc, decodebin);
|
||||
gst_element_link_many (volume, convert, format, mix, NULL);
|
||||
|
||||
/* configure elements */
|
||||
g_object_set (filesrc, "location", file_name, NULL);
|
||||
g_object_set (volume, "volume", (num_vol == 0) ? 1.0 : 0.0, NULL);
|
||||
|
||||
caps = gst_caps_from_string ("audio/x-raw, "
|
||||
"format = (string) S16LE, " "channels = (int) 2");
|
||||
g_object_set (format, "caps", caps, NULL);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
/* remember volume element */
|
||||
volumes[num_vol++] = (GObject *) volume;
|
||||
|
||||
/* handle dynamic pads */
|
||||
g_signal_connect (G_OBJECT (decodebin), "pad-added",
|
||||
G_CALLBACK (dynamic_link), gst_element_get_static_pad (volume, "sink"));
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
GstElement *pipeline = NULL;
|
||||
GstElement *mix, *convert, *sink;
|
||||
GstBus *bus;
|
||||
|
||||
if (argc < 3) {
|
||||
g_print ("Usage: audiomix <file1> <file2>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
gtk_init (&argc, &argv);
|
||||
|
||||
/* prepare tail of pipeline */
|
||||
pipeline = gst_pipeline_new ("audiomix");
|
||||
mix = gst_element_factory_make ("adder", NULL);
|
||||
convert = gst_element_factory_make ("audioconvert", NULL);
|
||||
sink = gst_element_factory_make ("autoaudiosink", NULL);
|
||||
gst_bin_add_many (GST_BIN (pipeline), mix, convert, sink, NULL);
|
||||
gst_element_link_many (mix, convert, sink, NULL);
|
||||
|
||||
/* prepare mixer channel strips */
|
||||
make_mixer_channel (pipeline, mix, argv[1]);
|
||||
make_mixer_channel (pipeline, mix, argv[2]);
|
||||
|
||||
/* setup message handling */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
|
||||
g_signal_connect (bus, "message::error", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::warning", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::eos", (GCallback) eos_message_received,
|
||||
pipeline);
|
||||
|
||||
/* setup GUI */
|
||||
setup_gui (pipeline, argv[1], argv[2]);
|
||||
|
||||
/* go to main loop */
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
gtk_main ();
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
gst_object_unref (bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# autotools build also passes -D_GNU_SOURCE but not sure why it's needed
|
||||
if gtk_dep.found()
|
||||
executable('audiomix', 'audiomix.c',
|
||||
c_args : gst_plugins_base_args,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [glib_deps, gst_dep, audio_dep, gtk_dep],
|
||||
install: false)
|
||||
executable('volume', 'volume.c',
|
||||
c_args : gst_plugins_base_args,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [glib_deps, gst_dep, libm, audio_dep, gtk_dep],
|
||||
install: false)
|
||||
endif
|
||||
@@ -0,0 +1,194 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* volume.c: sample application to change the volume of a pipeline
|
||||
*
|
||||
* Copyright (C) <2004> Thomas Vander Stichele <thomas at apestaart dot org>
|
||||
*
|
||||
* 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 <math.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
/* global pointer for the scale widget */
|
||||
static GtkWidget *elapsed;
|
||||
static GtkWidget *scale;
|
||||
|
||||
#ifndef M_LN10
|
||||
#define M_LN10 (log(10.0))
|
||||
#endif
|
||||
|
||||
static void
|
||||
value_changed_callback (GtkWidget * widget, GstElement * volume)
|
||||
{
|
||||
gdouble value;
|
||||
gdouble level;
|
||||
|
||||
value = gtk_range_get_value (GTK_RANGE (widget));
|
||||
level = exp (value / 20.0 * M_LN10);
|
||||
g_print ("Value: %f dB, level: %f\n", value, level);
|
||||
g_object_set (volume, "volume", level, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_gui (GstElement * volume)
|
||||
{
|
||||
GtkWidget *window;
|
||||
GtkWidget *vbox;
|
||||
GtkWidget *label, *hbox;
|
||||
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
g_signal_connect (window, "destroy", gtk_main_quit, NULL);
|
||||
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_container_add (GTK_CONTAINER (window), vbox);
|
||||
|
||||
/* elapsed widget */
|
||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
label = gtk_label_new ("Elapsed: ");
|
||||
elapsed = gtk_label_new ("0.0");
|
||||
gtk_container_add (GTK_CONTAINER (hbox), label);
|
||||
gtk_container_add (GTK_CONTAINER (hbox), elapsed);
|
||||
gtk_container_add (GTK_CONTAINER (vbox), hbox);
|
||||
|
||||
/* volume */
|
||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
label = gtk_label_new ("volume");
|
||||
gtk_container_add (GTK_CONTAINER (hbox), label);
|
||||
scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, -90.0, 10.0,
|
||||
0.2);
|
||||
gtk_range_set_value (GTK_RANGE (scale), 0.0);
|
||||
gtk_widget_set_size_request (scale, 100, -1);
|
||||
gtk_container_add (GTK_CONTAINER (hbox), scale);
|
||||
gtk_container_add (GTK_CONTAINER (vbox), hbox);
|
||||
g_signal_connect (scale, "value-changed",
|
||||
G_CALLBACK (value_changed_callback), volume);
|
||||
|
||||
gtk_widget_show_all (GTK_WIDGET (window));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
progress_update (gpointer data)
|
||||
{
|
||||
GstElement *pipeline = (GstElement *) data;
|
||||
gint64 position;
|
||||
gchar *position_str;
|
||||
|
||||
if (gst_element_query_position (pipeline, GST_FORMAT_TIME, &position))
|
||||
position_str = g_strdup_printf ("%.1f", (gfloat) position / GST_SECOND);
|
||||
else
|
||||
position_str = g_strdup_printf ("n/a");
|
||||
gtk_label_set_text (GTK_LABEL (elapsed), position_str);
|
||||
|
||||
g_free (position_str);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
||||
{
|
||||
const GstStructure *s;
|
||||
|
||||
s = gst_message_get_structure (message);
|
||||
g_print ("message from \"%s\" (%s): ",
|
||||
GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
if (s) {
|
||||
gchar *sstr;
|
||||
|
||||
sstr = gst_structure_to_string (s);
|
||||
g_print ("%s\n", sstr);
|
||||
g_free (sstr);
|
||||
} else {
|
||||
g_print ("no message details\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
eos_message_received (GstBus * bus, GstMessage * message,
|
||||
GstPipeline * pipeline)
|
||||
{
|
||||
message_received (bus, message, pipeline);
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
|
||||
GstElement *pipeline = NULL;
|
||||
|
||||
#ifndef GST_DISABLE_PARSE
|
||||
GError *error = NULL;
|
||||
#endif
|
||||
GstElement *volume;
|
||||
GstBus *bus;
|
||||
|
||||
#ifdef GST_DISABLE_PARSE
|
||||
g_print ("GStreamer was built without pipeline parsing capabilities.\n");
|
||||
g_print
|
||||
("Please rebuild GStreamer with pipeline parsing capabilities activated to use this example.\n");
|
||||
return 1;
|
||||
#else
|
||||
gst_init (&argc, &argv);
|
||||
gtk_init (&argc, &argv);
|
||||
|
||||
pipeline = gst_parse_launchv ((const gchar **) &argv[1], &error);
|
||||
if (error) {
|
||||
g_print ("pipeline could not be constructed: %s\n", error->message);
|
||||
g_print ("Please give a complete pipeline with a 'volume' element.\n");
|
||||
g_print ("Example: audiotestsrc ! volume ! %s\n", DEFAULT_AUDIOSINK);
|
||||
g_error_free (error);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
volume = gst_bin_get_by_name (GST_BIN (pipeline), "volume0");
|
||||
if (volume == NULL) {
|
||||
g_print ("Please give a pipeline with a 'volume' element in it\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* setup message handling */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
|
||||
g_signal_connect (bus, "message::error", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::warning", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::eos", (GCallback) eos_message_received,
|
||||
pipeline);
|
||||
|
||||
/* setup GUI */
|
||||
setup_gui (volume);
|
||||
|
||||
/* go to main loop */
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
g_timeout_add (100, progress_update, pipeline);
|
||||
gtk_main ();
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (volume);
|
||||
gst_object_unref (pipeline);
|
||||
gst_object_unref (bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2017 Thibault Saunier <thibault.saunier@osg-samsung.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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simple crossfade example using the compositor element.
|
||||
*
|
||||
* Takes two video files and crossfades them for 10 seconds and returns.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/controller/gstdirectcontrolbinding.h>
|
||||
#include <gst/controller/gstinterpolationcontrolsource.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstElement *compositor;
|
||||
guint z_order;
|
||||
} VideoInfo;
|
||||
|
||||
static gchar *
|
||||
ensure_uri (const gchar * location)
|
||||
{
|
||||
if (gst_uri_is_valid (location))
|
||||
return g_strdup (location);
|
||||
else
|
||||
return gst_filename_to_uri (location, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
_pad_added_cb (GstElement * decodebin, GstPad * pad, VideoInfo * info)
|
||||
{
|
||||
GstPad *sinkpad =
|
||||
gst_element_request_pad_simple (GST_ELEMENT (info->compositor),
|
||||
"sink_%u");
|
||||
GstControlSource *control_source;
|
||||
gboolean is_last = info->z_order == 1;
|
||||
|
||||
control_source = gst_interpolation_control_source_new ();
|
||||
|
||||
gst_util_set_object_arg (G_OBJECT (sinkpad), "operator",
|
||||
info->z_order == 0 ? "source" : "add");
|
||||
gst_object_add_control_binding (GST_OBJECT (sinkpad),
|
||||
gst_direct_control_binding_new_absolute (GST_OBJECT (sinkpad), "alpha",
|
||||
control_source));
|
||||
|
||||
g_object_set (control_source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL);
|
||||
|
||||
gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
|
||||
(control_source), 0, is_last ? 0.0 : 1.0);
|
||||
gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
|
||||
(control_source), 10 * GST_SECOND, is_last ? 1.0 : 0.0);
|
||||
g_object_set (sinkpad, "zorder", info->z_order, NULL);
|
||||
|
||||
gst_pad_link (pad, sinkpad);
|
||||
|
||||
g_free (info);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
gint i;
|
||||
GstMessage *message;
|
||||
GstElement *compositor, *sink, *pipeline;
|
||||
GstBus *bus;
|
||||
|
||||
if (argc != 3) {
|
||||
g_error ("Need to provide 2 input videos");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
pipeline = gst_element_factory_make ("pipeline", NULL);
|
||||
compositor = gst_element_factory_make ("compositor", NULL);
|
||||
sink =
|
||||
gst_parse_bin_from_description ("videoconvert ! autovideosink", TRUE,
|
||||
NULL);
|
||||
|
||||
gst_util_set_object_arg (G_OBJECT (compositor), "background", "black");
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), compositor, sink, NULL);
|
||||
g_assert (gst_element_link (compositor, sink));
|
||||
|
||||
for (i = 1; i < 3; i++) {
|
||||
gchar *uri = ensure_uri (argv[i]);
|
||||
VideoInfo *info = g_malloc0 (sizeof (VideoInfo));
|
||||
GstElement *uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
|
||||
|
||||
g_object_set (uridecodebin, "uri", uri, "expose-all-streams", FALSE,
|
||||
"caps", gst_caps_from_string ("video/x-raw(ANY)"), NULL);
|
||||
|
||||
info->compositor = compositor;
|
||||
info->z_order = i - 1;
|
||||
g_signal_connect (uridecodebin, "pad-added", (GCallback) _pad_added_cb,
|
||||
info);
|
||||
|
||||
gst_bin_add (GST_BIN (pipeline), uridecodebin);
|
||||
}
|
||||
|
||||
bus = gst_element_get_bus (pipeline);
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
message =
|
||||
gst_bus_timed_pop_filtered (bus, 11 * GST_SECOND,
|
||||
GST_MESSAGE_EOS | GST_MESSAGE_ERROR);
|
||||
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
||||
GST_DEBUG_GRAPH_SHOW_ALL, "go");
|
||||
if (message)
|
||||
gst_print ("%" GST_PTR_FORMAT "\n", message);
|
||||
else
|
||||
gst_print ("Timeout\n");
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
executable('crossfade', 'crossfade.c',
|
||||
include_directories: [configinc],
|
||||
c_args: ['-DHAVE_CONFIG_H'],
|
||||
dependencies: [gst_controller_dep, gst_dep],
|
||||
install: false)
|
||||
|
||||
executable('mosaic', 'mosaic.c',
|
||||
include_directories: [configinc],
|
||||
c_args: ['-DHAVE_CONFIG_H'],
|
||||
dependencies: [video_dep, gst_dep],
|
||||
install: false)
|
||||
|
||||
executable('signals', 'signals.c',
|
||||
include_directories: [configinc],
|
||||
c_args: ['-DHAVE_CONFIG_H'],
|
||||
dependencies: [gst_dep, gst_base_dep],
|
||||
install: false)
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2017 Thibault Saunier <thibault.saunier@osg-samsung.com>
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simple example using the compositor element.
|
||||
*
|
||||
* Takes two video files and display them side-by-side as a mosaic
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstElement *compositor;
|
||||
gint x, y, w, h;
|
||||
gint zorder;
|
||||
} VideoInfo;
|
||||
|
||||
static gchar *
|
||||
ensure_uri (const gchar * location)
|
||||
{
|
||||
if (gst_uri_is_valid (location))
|
||||
return g_strdup (location);
|
||||
else
|
||||
return gst_filename_to_uri (location, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
_pad_added_cb (GstElement * decodebin, GstPad * pad, VideoInfo * info)
|
||||
{
|
||||
GstStructure *converter_config;
|
||||
GstPad *sinkpad =
|
||||
gst_element_request_pad_simple (GST_ELEMENT (info->compositor),
|
||||
"sink_%u");
|
||||
|
||||
converter_config = gst_structure_new ("GstVideoConverter",
|
||||
GST_VIDEO_CONVERTER_OPT_THREADS, G_TYPE_UINT, 0,
|
||||
GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD, GST_TYPE_VIDEO_RESAMPLER_METHOD,
|
||||
GST_VIDEO_RESAMPLER_METHOD_NEAREST, GST_VIDEO_CONVERTER_OPT_DEST_X,
|
||||
G_TYPE_INT, 0, GST_VIDEO_CONVERTER_OPT_DEST_Y, G_TYPE_INT, 0,
|
||||
GST_VIDEO_CONVERTER_OPT_DEST_WIDTH, G_TYPE_INT, info->w,
|
||||
GST_VIDEO_CONVERTER_OPT_DEST_HEIGHT, G_TYPE_INT, info->h, NULL);
|
||||
|
||||
g_object_set (sinkpad, "xpos", info->x, "ypos", info->y, "width", info->w,
|
||||
"height", info->h, "converter-config", converter_config, NULL);
|
||||
|
||||
gst_structure_free (converter_config);
|
||||
|
||||
gst_pad_link (pad, sinkpad);
|
||||
|
||||
g_free (info);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
gint i;
|
||||
GstMessage *message;
|
||||
GstElement *compositor, *pipeline;
|
||||
GstBus *bus;
|
||||
|
||||
if (argc != 3) {
|
||||
g_error ("Need to provide 2 input videos");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
pipeline =
|
||||
gst_parse_launch
|
||||
("videotestsrc pattern=black is-live=true ! video/x-raw,width=1,height=1,format=AYUV ! compositor name=comp start-time-selection=first ! video/x-raw,format=AYUV,width=1275,height=833,framerate=25/1 ! videoconvert ! autovideosink",
|
||||
NULL);
|
||||
|
||||
g_assert (pipeline != NULL);
|
||||
compositor = gst_bin_get_by_name (GST_BIN (pipeline), "comp");
|
||||
|
||||
gst_util_set_object_arg (G_OBJECT (compositor), "background", "black");
|
||||
|
||||
for (i = 1; i < 3; i++) {
|
||||
gchar *uri = ensure_uri (argv[i]);
|
||||
VideoInfo *info = g_malloc0 (sizeof (VideoInfo));
|
||||
GstElement *uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
|
||||
|
||||
g_object_set (uridecodebin, "uri", uri, "expose-all-streams", FALSE,
|
||||
"caps", gst_caps_from_string ("video/x-raw(ANY)"), NULL);
|
||||
|
||||
info->compositor = compositor;
|
||||
if (i == 1) {
|
||||
info->x = 326;
|
||||
info->y = 155;
|
||||
info->w = 930;
|
||||
info->h = 523;
|
||||
info->zorder = 2;
|
||||
} else {
|
||||
info->x = 19;
|
||||
info->y = 155;
|
||||
info->w = 288;
|
||||
info->h = 162;
|
||||
info->zorder = 3;
|
||||
}
|
||||
g_signal_connect (uridecodebin, "pad-added", (GCallback) _pad_added_cb,
|
||||
info);
|
||||
|
||||
gst_bin_add (GST_BIN (pipeline), uridecodebin);
|
||||
}
|
||||
|
||||
bus = gst_element_get_bus (pipeline);
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
message =
|
||||
gst_bus_timed_pop_filtered (bus, 60 * GST_SECOND,
|
||||
GST_MESSAGE_EOS | GST_MESSAGE_ERROR);
|
||||
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
||||
GST_DEBUG_GRAPH_SHOW_ALL | GST_DEBUG_GRAPH_SHOW_VERBOSE, "go");
|
||||
if (message)
|
||||
gst_print ("%" GST_PTR_FORMAT "\n", message);
|
||||
else
|
||||
gst_print ("Timeout\n");
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/gstaggregator.h>
|
||||
|
||||
#define MAKE_AND_ADD(var, pipe, name, label) \
|
||||
G_STMT_START \
|
||||
{ \
|
||||
GError* make_and_add_err = NULL; \
|
||||
if (G_UNLIKELY(!(var = (gst_parse_bin_from_description_full( \
|
||||
name, \
|
||||
TRUE, \
|
||||
NULL, \
|
||||
GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS, \
|
||||
&make_and_add_err))))) { \
|
||||
GST_ERROR( \
|
||||
"Could not create element %s (%s)", name, make_and_add_err->message); \
|
||||
g_clear_error(&make_and_add_err); \
|
||||
goto label; \
|
||||
} \
|
||||
if (G_UNLIKELY(!gst_bin_add(GST_BIN_CAST(pipe), var))) { \
|
||||
GST_ERROR("Could not add element %s", name); \
|
||||
goto label; \
|
||||
} \
|
||||
} \
|
||||
G_STMT_END
|
||||
|
||||
static gboolean
|
||||
check_aggregated_buffer (GstElement * agg, GstPad * pad,
|
||||
GHashTable * consumed_buffers)
|
||||
{
|
||||
GstSample *sample;
|
||||
GList *pad_consumed_buffers;
|
||||
GList *tmp;
|
||||
|
||||
sample =
|
||||
gst_aggregator_peek_next_sample (GST_AGGREGATOR (agg),
|
||||
GST_AGGREGATOR_PAD (pad));
|
||||
|
||||
pad_consumed_buffers = g_hash_table_lookup (consumed_buffers, pad);
|
||||
|
||||
for (tmp = pad_consumed_buffers; tmp; tmp = tmp->next) {
|
||||
GstBuffer *consumed_buffer = (GstBuffer *) tmp->data;
|
||||
gboolean aggregated = FALSE;
|
||||
|
||||
if (sample) {
|
||||
aggregated =
|
||||
consumed_buffer == gst_sample_get_buffer (sample) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
gst_printerr ("One consumed buffer: %" GST_PTR_FORMAT
|
||||
", it was%s aggregated\n", consumed_buffer, aggregated ? "" : " not");
|
||||
}
|
||||
|
||||
if (sample) {
|
||||
gst_sample_unref (sample);
|
||||
}
|
||||
|
||||
g_list_free_full (pad_consumed_buffers, (GDestroyNotify) gst_buffer_unref);
|
||||
g_hash_table_steal (consumed_buffers, pad);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
samples_selected_cb (GstElement * agg, GstSegment * segment, GstClockTime pts,
|
||||
GstClockTime dts, GstClockTime duration, GstStructure * info,
|
||||
GHashTable * consumed_buffers)
|
||||
{
|
||||
gst_printerr
|
||||
("Compositor has selected the samples it will aggregate for output buffer with PTS %"
|
||||
GST_TIME_FORMAT " and duration %" GST_TIME_FORMAT "\n",
|
||||
GST_TIME_ARGS (pts), GST_TIME_ARGS (duration));
|
||||
gst_element_foreach_sink_pad (agg,
|
||||
(GstElementForeachPadFunc) check_aggregated_buffer, consumed_buffers);
|
||||
}
|
||||
|
||||
static void
|
||||
pad_buffer_consumed_cb (GstAggregatorPad * pad, GstBuffer * buffer,
|
||||
GHashTable * consumed_buffers)
|
||||
{
|
||||
GList *pad_consumed_buffers;
|
||||
gboolean was_empty;
|
||||
|
||||
pad_consumed_buffers = g_hash_table_lookup (consumed_buffers, pad);
|
||||
|
||||
was_empty = (pad_consumed_buffers == NULL);
|
||||
|
||||
pad_consumed_buffers =
|
||||
g_list_append (pad_consumed_buffers, gst_buffer_ref (buffer));
|
||||
|
||||
/* we know the list's head pointer doesn't change when items get appended */
|
||||
if (was_empty)
|
||||
g_hash_table_insert (consumed_buffers, pad, pad_consumed_buffers);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
unref_consumed_buffers (gpointer key, GList * pad_consumed_buffers)
|
||||
{
|
||||
g_list_free_full (pad_consumed_buffers, (GDestroyNotify) gst_buffer_unref);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int ac, char **av)
|
||||
{
|
||||
int ret = 0;
|
||||
GstElement *pipe;
|
||||
GstBus *bus;
|
||||
GstElement *vsrc, *vcfltr1, *compositor, *vcfltr2, *vsink;
|
||||
GstCaps *caps;
|
||||
GstPad *pad;
|
||||
GHashTable *consumed_buffers =
|
||||
g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
|
||||
gst_init (NULL, NULL);
|
||||
|
||||
pipe = gst_pipeline_new (NULL);
|
||||
|
||||
MAKE_AND_ADD (vsrc, pipe, "videotestsrc", err);
|
||||
MAKE_AND_ADD (vcfltr1, pipe, "capsfilter", err);
|
||||
MAKE_AND_ADD (compositor, pipe, "compositor", err);
|
||||
MAKE_AND_ADD (vcfltr2, pipe, "capsfilter", err);
|
||||
MAKE_AND_ADD (vsink, pipe, "autovideosink", err);
|
||||
|
||||
if (!gst_element_link_many (vsrc, vcfltr1, compositor, vcfltr2, vsink, NULL)) {
|
||||
GST_ERROR ("Failed to link pipeline");
|
||||
goto err;
|
||||
}
|
||||
|
||||
caps =
|
||||
gst_caps_new_simple ("video/x-raw", "framerate", GST_TYPE_FRACTION, 30, 1,
|
||||
NULL);
|
||||
g_object_set (vcfltr1, "caps", caps, NULL);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
caps =
|
||||
gst_caps_new_simple ("video/x-raw", "framerate", GST_TYPE_FRACTION, 6, 1,
|
||||
NULL);
|
||||
g_object_set (vcfltr2, "caps", caps, NULL);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
g_object_set (vsrc, "num-buffers", 300, NULL);
|
||||
|
||||
g_object_set (compositor, "emit-signals", TRUE, NULL);
|
||||
g_signal_connect (compositor, "samples-selected",
|
||||
G_CALLBACK (samples_selected_cb), consumed_buffers);
|
||||
|
||||
pad = gst_element_get_static_pad (compositor, "sink_0");
|
||||
g_object_set (pad, "emit-signals", TRUE, NULL);
|
||||
g_signal_connect (pad, "buffer-consumed", G_CALLBACK (pad_buffer_consumed_cb),
|
||||
consumed_buffers);
|
||||
gst_object_unref (pad);
|
||||
|
||||
gst_element_set_state (pipe, GST_STATE_PLAYING);
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipe));
|
||||
|
||||
gst_message_unref (gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
|
||||
GST_MESSAGE_EOS));
|
||||
|
||||
gst_object_unref (bus);
|
||||
|
||||
done:
|
||||
g_hash_table_foreach_remove (consumed_buffers,
|
||||
(GHRFunc) unref_consumed_buffers, NULL);
|
||||
g_hash_table_unref (consumed_buffers);
|
||||
gst_element_set_state (pipe, GST_STATE_NULL);
|
||||
gst_object_unref (pipe);
|
||||
return ret;
|
||||
|
||||
err:
|
||||
ret = 1;
|
||||
goto done;
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/* 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;
|
||||
GstTagList *tags;
|
||||
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);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
tags = gst_stream_get_tags (stream);
|
||||
if (tags) {
|
||||
g_print (" tags:\n");
|
||||
gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (3));
|
||||
gst_tag_list_unref (tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
gst_object_unref (collection);
|
||||
}
|
||||
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_request_pad_simple (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_request_pad_simple (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;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
foreach example : ['decodebin3', 'playbin-test', 'uridecodebin3-select-all']
|
||||
executable(example, '@0@.c'.format(example),
|
||||
c_args : gst_plugins_base_args,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [glib_deps, gst_dep],
|
||||
install: false)
|
||||
endforeach
|
||||
@@ -0,0 +1,330 @@
|
||||
/* 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;
|
||||
GstTagList *tags;
|
||||
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);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
tags = gst_stream_get_tags (stream);
|
||||
if (tags) {
|
||||
g_print (" tags:\n");
|
||||
gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (3));
|
||||
gst_tag_list_unref (tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
g_list_free (streams);
|
||||
|
||||
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);
|
||||
gst_object_unref (collection);
|
||||
}
|
||||
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, 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 auxiliary 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;
|
||||
}
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
/* sample application for testing decodebin3
|
||||
*
|
||||
* Copyright (C) 2015 Centricular Ltd
|
||||
* @author: Edward Hervey <edward@centricular.com>
|
||||
* Copyright (C) 2020 Seungha Yang <seungha@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 _AppData
|
||||
{
|
||||
GMainLoop *mainloop;
|
||||
GstElement *pipeline;
|
||||
|
||||
GstElement *decodebin;
|
||||
|
||||
/* Current collection */
|
||||
GstStreamCollection *collection;
|
||||
guint notify_id;
|
||||
} AppData;
|
||||
|
||||
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);
|
||||
|
||||
gst_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;
|
||||
GstTagList *tags;
|
||||
GstCaps *caps;
|
||||
|
||||
for (i = 0; i < gst_stream_collection_get_size (collection); i++) {
|
||||
GstStream *stream = gst_stream_collection_get_stream (collection, i);
|
||||
gst_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));
|
||||
gst_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);
|
||||
gst_print (" caps: %s\n", caps_str);
|
||||
g_free (caps_str);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
tags = gst_stream_get_tags (stream);
|
||||
if (tags) {
|
||||
gst_print (" tags:\n");
|
||||
gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (3));
|
||||
gst_tag_list_unref (tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
activate_all_av_streams (AppData * data)
|
||||
{
|
||||
guint i, num_streams;
|
||||
gint num_videos = 0, num_audios = 0, num_texts = 0, num_unknowns = 0;
|
||||
GList *streams = NULL;
|
||||
GstEvent *event;
|
||||
gboolean ret;
|
||||
|
||||
num_streams = gst_stream_collection_get_size (data->collection);
|
||||
for (i = 0; i < num_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) {
|
||||
streams = g_list_append (streams,
|
||||
(gchar *) gst_stream_get_stream_id (stream));
|
||||
num_videos++;
|
||||
} else if (stype == GST_STREAM_TYPE_AUDIO) {
|
||||
streams = g_list_append (streams,
|
||||
(gchar *) gst_stream_get_stream_id (stream));
|
||||
num_audios++;
|
||||
} else if (stype == GST_STREAM_TYPE_TEXT) {
|
||||
num_texts++;
|
||||
} else {
|
||||
/* Unknown, container or complex type */
|
||||
num_unknowns++;
|
||||
}
|
||||
}
|
||||
|
||||
gst_println ("Have %d streams (video: %d, audio: %d, text: %d, unknown %d)",
|
||||
num_streams, num_videos, num_audios, num_texts, num_unknowns);
|
||||
|
||||
if (!num_videos && !num_audios) {
|
||||
gst_println ("No AV stream to expose");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
event = gst_event_new_select_streams (streams);
|
||||
ret = gst_element_send_event (data->decodebin, event);
|
||||
|
||||
gst_println ("Sent select-streams event ret %d", ret);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
|
||||
GParamSpec * pspec, guint * val)
|
||||
{
|
||||
gst_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);
|
||||
gst_print (" New caps: %s\n", caps_str);
|
||||
g_free (caps_str);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
}
|
||||
|
||||
static GstBusSyncReply
|
||||
_on_bus_message (GstBus * bus, GstMessage * message, AppData * 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);
|
||||
|
||||
gst_printerr ("ERROR: from element %s: %s\n", name, err->message);
|
||||
g_error_free (err);
|
||||
g_free (name);
|
||||
|
||||
gst_println ("Stopping");
|
||||
g_main_loop_quit (data->mainloop);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_EOS:
|
||||
gst_println ("EOS ! Stopping");
|
||||
g_main_loop_quit (data->mainloop);
|
||||
break;
|
||||
case GST_MESSAGE_STREAM_COLLECTION:
|
||||
{
|
||||
GstStreamCollection *collection = NULL;
|
||||
gst_message_parse_stream_collection (message, &collection);
|
||||
if (collection) {
|
||||
/* Replace stream collection with new one */
|
||||
gst_println ("Got a collection from %s",
|
||||
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);
|
||||
}
|
||||
|
||||
/* Try to expose all audio/video streams */
|
||||
if (!activate_all_av_streams (data))
|
||||
g_main_loop_quit (data->mainloop);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return GST_BUS_PASS;
|
||||
}
|
||||
|
||||
static void
|
||||
decodebin_pad_added_cb (GstElement * dbin, GstPad * pad, AppData * data)
|
||||
{
|
||||
gchar *pad_name = gst_pad_get_name (pad);
|
||||
GstStream *stream;
|
||||
GstStreamType type;
|
||||
|
||||
gst_println ("New pad %s added, try linking with sink", pad_name);
|
||||
g_free (pad_name);
|
||||
|
||||
stream = gst_pad_get_stream (pad);
|
||||
if (!stream) {
|
||||
g_error ("New pad was exposed without GstStream object");
|
||||
g_main_loop_quit (data->mainloop);
|
||||
return;
|
||||
}
|
||||
|
||||
type = gst_stream_get_stream_type (stream);
|
||||
|
||||
switch (type) {
|
||||
case GST_STREAM_TYPE_VIDEO:{
|
||||
GstElement *queue;
|
||||
GstElement *convert;
|
||||
GstElement *sink;
|
||||
GstPad *sinkpad;
|
||||
|
||||
queue = gst_element_factory_make ("queue", NULL);
|
||||
if (!queue) {
|
||||
gst_println ("queue element is unavailable");
|
||||
g_main_loop_quit (data->mainloop);
|
||||
return;
|
||||
}
|
||||
gst_bin_add (GST_BIN_CAST (data->pipeline), queue);
|
||||
|
||||
convert = gst_element_factory_make ("videoconvert", NULL);
|
||||
if (!convert) {
|
||||
gst_println ("videoconvert element is unavailable");
|
||||
goto error;
|
||||
}
|
||||
gst_bin_add (GST_BIN_CAST (data->pipeline), convert);
|
||||
|
||||
sink = gst_element_factory_make ("autovideosink", NULL);
|
||||
if (!sink) {
|
||||
gst_println ("autovideosink element is unavailable");
|
||||
goto error;
|
||||
}
|
||||
gst_bin_add (GST_BIN_CAST (data->pipeline), sink);
|
||||
|
||||
sinkpad = gst_element_get_static_pad (queue, "sink");
|
||||
gst_pad_set_active (sinkpad, TRUE);
|
||||
|
||||
gst_element_link_many (queue, convert, sink, NULL);
|
||||
gst_pad_link (pad, sinkpad);
|
||||
gst_object_unref (sinkpad);
|
||||
gst_element_sync_state_with_parent (queue);
|
||||
gst_element_sync_state_with_parent (convert);
|
||||
gst_element_sync_state_with_parent (sink);
|
||||
|
||||
break;
|
||||
}
|
||||
case GST_STREAM_TYPE_AUDIO:{
|
||||
GstElement *queue;
|
||||
GstElement *convert;
|
||||
GstElement *resample;
|
||||
GstElement *sink;
|
||||
GstPad *sinkpad;
|
||||
|
||||
queue = gst_element_factory_make ("queue", NULL);
|
||||
if (!queue) {
|
||||
gst_println ("queue element is unavailable");
|
||||
goto error;
|
||||
}
|
||||
gst_bin_add (GST_BIN_CAST (data->pipeline), queue);
|
||||
|
||||
convert = gst_element_factory_make ("audioconvert", NULL);
|
||||
if (!convert) {
|
||||
gst_println ("audioconvert element is unavailable");
|
||||
goto error;
|
||||
}
|
||||
gst_bin_add (GST_BIN_CAST (data->pipeline), convert);
|
||||
|
||||
resample = gst_element_factory_make ("audioresample", NULL);
|
||||
if (!resample) {
|
||||
gst_println ("audioresample element is unavailable");
|
||||
goto error;
|
||||
}
|
||||
gst_bin_add (GST_BIN_CAST (data->pipeline), resample);
|
||||
|
||||
sink = gst_element_factory_make ("autoaudiosink", NULL);
|
||||
if (!sink) {
|
||||
gst_println ("autoaudiosink element is unavailable");
|
||||
goto error;
|
||||
}
|
||||
|
||||
gst_bin_add (GST_BIN_CAST (data->pipeline), sink);
|
||||
|
||||
sinkpad = gst_element_get_static_pad (queue, "sink");
|
||||
gst_pad_set_active (sinkpad, TRUE);
|
||||
|
||||
gst_element_link_many (queue, convert, resample, sink, NULL);
|
||||
gst_pad_link (pad, sinkpad);
|
||||
gst_object_unref (sinkpad);
|
||||
gst_element_sync_state_with_parent (queue);
|
||||
gst_element_sync_state_with_parent (convert);
|
||||
gst_element_sync_state_with_parent (resample);
|
||||
gst_element_sync_state_with_parent (sink);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
gst_println ("Ignore non video/audio stream %s (0x%x)",
|
||||
gst_stream_type_get_name (type), type);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
gst_object_unref (stream);
|
||||
return;
|
||||
|
||||
error:
|
||||
gst_object_unref (stream);
|
||||
g_main_loop_quit (data->mainloop);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, gchar ** argv)
|
||||
{
|
||||
GstBus *bus;
|
||||
AppData *data;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
if (argc < 2) {
|
||||
gst_print ("Usage: uridecodebin3 URI\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
data = g_new0 (AppData, 1);
|
||||
|
||||
data->pipeline = gst_pipeline_new ("pipeline");
|
||||
data->decodebin = gst_element_factory_make ("uridecodebin3", NULL);
|
||||
|
||||
g_object_set (data->decodebin, "uri", argv[1], NULL);
|
||||
|
||||
gst_bin_add (GST_BIN_CAST (data->pipeline), data->decodebin);
|
||||
|
||||
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_clear_object (&data->collection);
|
||||
gst_object_unref (bus);
|
||||
|
||||
g_free (data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Mathieu Duponchelle <mathieu@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.
|
||||
*/
|
||||
|
||||
/* Simple device provider example.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* GST_PLUGIN_PATH=$GST_PLUGIN_PATH:/path/to/libexample_device_provider.so/folder gst-device-monitor-1.0 -f
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#define NEW_DEVICE_INTERVAL 1 /* seconds */
|
||||
|
||||
#define EXAMPLE_TYPE_DEVICE_PROVIDER example_device_provider_get_type()
|
||||
#define EXAMPLE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),EXAMPLE_TYPE_DEVICE_PROVIDER,ExampleDeviceProvider))
|
||||
|
||||
typedef struct _ExampleDeviceProvider ExampleDeviceProvider;
|
||||
typedef struct _ExampleDeviceProviderClass ExampleDeviceProviderClass;
|
||||
|
||||
struct _ExampleDeviceProviderClass
|
||||
{
|
||||
GstDeviceProviderClass parent_class;
|
||||
};
|
||||
|
||||
/**
|
||||
* Our device provider instance.
|
||||
*
|
||||
* @factory: the videotestsrc factory
|
||||
* @patterns: When started, the list of videotestsrc pattern
|
||||
* (as strings) to iterate through when adding new devices,
|
||||
* eg "smpte", "snow", ...
|
||||
* @timeout_id: When started, we will add a new device every
|
||||
* %NEW_DEVICE_INTERVAL seconds
|
||||
*/
|
||||
struct _ExampleDeviceProvider
|
||||
{
|
||||
GstDeviceProvider parent;
|
||||
GstElementFactory *factory;
|
||||
GList *patterns;
|
||||
guint timeout_id;
|
||||
};
|
||||
|
||||
static GType example_device_provider_get_type (void);
|
||||
|
||||
G_DEFINE_TYPE (ExampleDeviceProvider, example_device_provider,
|
||||
GST_TYPE_DEVICE_PROVIDER);
|
||||
|
||||
#define EXAMPLE_TYPE_DEVICE example_device_get_type()
|
||||
#define EXAMPLE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),EXAMPLE_TYPE_DEVICE,ExampleDevice))
|
||||
|
||||
typedef struct _ExampleDevice ExampleDevice;
|
||||
typedef struct _ExampleDeviceClass ExampleDeviceClass;
|
||||
|
||||
struct _ExampleDeviceClass
|
||||
{
|
||||
GstDeviceClass parent_class;
|
||||
};
|
||||
|
||||
/* Our example device, it simply exposes a videotestsrc with a specific
|
||||
* pattern.
|
||||
*/
|
||||
struct _ExampleDevice
|
||||
{
|
||||
GstDevice parent;
|
||||
|
||||
gchar *pattern;
|
||||
GstElementFactory *factory;
|
||||
};
|
||||
|
||||
static GType example_device_get_type (void);
|
||||
|
||||
G_DEFINE_TYPE (ExampleDevice, example_device, GST_TYPE_DEVICE);
|
||||
|
||||
static void
|
||||
example_device_init (ExampleDevice * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
example_device_finalize (GObject * object)
|
||||
{
|
||||
ExampleDevice *self = EXAMPLE_DEVICE (object);
|
||||
|
||||
g_free (self->pattern);
|
||||
|
||||
G_OBJECT_CLASS (example_device_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
example_device_dispose (GObject * object)
|
||||
{
|
||||
ExampleDevice *self = EXAMPLE_DEVICE (object);
|
||||
|
||||
gst_object_replace ((GstObject **) & self->factory, NULL);
|
||||
|
||||
G_OBJECT_CLASS (example_device_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static GstElement *
|
||||
example_device_create_element (GstDevice * device, const gchar * name)
|
||||
{
|
||||
ExampleDevice *self = EXAMPLE_DEVICE (device);
|
||||
GstElement *ret;
|
||||
|
||||
ret = gst_element_factory_create (self->factory, name);
|
||||
|
||||
gst_util_set_object_arg (G_OBJECT (ret), "pattern", self->pattern);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
example_device_class_init (ExampleDeviceClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstDeviceClass *gst_device_class = GST_DEVICE_CLASS (klass);
|
||||
|
||||
gobject_class->finalize = GST_DEBUG_FUNCPTR (example_device_finalize);
|
||||
gobject_class->dispose = GST_DEBUG_FUNCPTR (example_device_dispose);
|
||||
|
||||
gst_device_class->create_element =
|
||||
GST_DEBUG_FUNCPTR (example_device_create_element);
|
||||
}
|
||||
|
||||
static GstDevice *
|
||||
example_device_new (GstElementFactory * factory, const gchar * pattern)
|
||||
{
|
||||
GstDevice *ret;
|
||||
gchar *display_name;
|
||||
GstCaps *caps;
|
||||
const GList *templates;
|
||||
|
||||
templates = gst_element_factory_get_static_pad_templates (factory);
|
||||
caps = gst_static_pad_template_get_caps ((GstStaticPadTemplate *)
|
||||
templates->data);
|
||||
|
||||
display_name = g_strdup_printf ("example-device-%s", pattern);
|
||||
|
||||
ret = GST_DEVICE (g_object_new (EXAMPLE_TYPE_DEVICE,
|
||||
"display-name", display_name,
|
||||
"device-class", "Video/Source", "caps", caps, NULL));
|
||||
|
||||
g_free (display_name);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
EXAMPLE_DEVICE (ret)->pattern = g_strdup (pattern);
|
||||
EXAMPLE_DEVICE (ret)->factory =
|
||||
GST_ELEMENT_FACTORY (gst_object_ref (factory));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
example_device_provider_init (ExampleDeviceProvider * self)
|
||||
{
|
||||
self->factory = gst_element_factory_find ("videotestsrc");
|
||||
|
||||
/* Ensure we can introspect the factory */
|
||||
gst_object_unref (gst_plugin_feature_load (GST_PLUGIN_FEATURE
|
||||
(self->factory)));
|
||||
|
||||
}
|
||||
|
||||
/* Called when gst_device_provider_get_devices() is called on a provider that
|
||||
* hasn't been started, or doesn't implement #GstDeviceProvider.start().
|
||||
*
|
||||
* In that case, let's return a single example device, with a snow pattern.
|
||||
*/
|
||||
static GList *
|
||||
example_device_provider_probe (GstDeviceProvider * provider)
|
||||
{
|
||||
ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (provider);
|
||||
GList *ret = NULL;
|
||||
|
||||
ret = g_list_prepend (ret, example_device_new (self->factory, "snow"));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
example_device_provider_next_device (ExampleDeviceProvider * self)
|
||||
{
|
||||
GstDevice *device;
|
||||
gboolean ret = G_SOURCE_CONTINUE;
|
||||
|
||||
if (!self->patterns)
|
||||
goto no_more_patterns;
|
||||
|
||||
device = example_device_new (self->factory, (gchar *) self->patterns->data);
|
||||
gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), device);
|
||||
g_free (self->patterns->data);
|
||||
self->patterns = g_list_delete_link (self->patterns, self->patterns);
|
||||
|
||||
done:
|
||||
return ret;
|
||||
|
||||
no_more_patterns:
|
||||
GST_DEBUG_OBJECT (self, "Went through all videotestsrc patterns!");
|
||||
ret = G_SOURCE_REMOVE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Start adding devices every %NEW_DEVICE_INTERVAL seconds.
|
||||
* We will stop once we have consumed all the available videotestsrc
|
||||
* patterns, or when our #GstDeviceProvider.stop() implementation is
|
||||
* called.
|
||||
*/
|
||||
static gboolean
|
||||
example_device_provider_start (GstDeviceProvider * provider)
|
||||
{
|
||||
ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (provider);
|
||||
GType element_type;
|
||||
GTypeClass *element_class;
|
||||
GParamSpec *pspec;
|
||||
GEnumClass *value_class;
|
||||
guint i;
|
||||
|
||||
g_assert (!self->timeout_id);
|
||||
|
||||
element_type = gst_element_factory_get_element_type (self->factory);
|
||||
element_class = (GTypeClass *) g_type_class_ref (element_type);
|
||||
pspec =
|
||||
g_object_class_find_property ((GObjectClass *) element_class, "pattern");
|
||||
value_class = (GEnumClass *) g_type_class_ref (pspec->value_type);
|
||||
|
||||
for (i = 0; i < value_class->n_values; i++) {
|
||||
GEnumValue *val = &value_class->values[i];
|
||||
|
||||
self->patterns = g_list_append (self->patterns, g_strdup (val->value_nick));
|
||||
}
|
||||
|
||||
g_type_class_unref (value_class);
|
||||
g_type_class_unref (element_class);
|
||||
|
||||
self->timeout_id =
|
||||
g_timeout_add_seconds (NEW_DEVICE_INTERVAL,
|
||||
(GSourceFunc) example_device_provider_next_device, self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Simply stop adding devices by removing our timeout. */
|
||||
static void
|
||||
example_device_provider_stop (GstDeviceProvider * provider)
|
||||
{
|
||||
ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (provider);
|
||||
|
||||
g_assert (self->timeout_id);
|
||||
|
||||
if (self->patterns) {
|
||||
g_list_free_full (self->patterns, g_free);
|
||||
self->patterns = NULL;
|
||||
}
|
||||
|
||||
g_source_remove (self->timeout_id);
|
||||
self->timeout_id = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
example_device_provider_dispose (GObject * object)
|
||||
{
|
||||
ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (object);
|
||||
|
||||
gst_object_replace ((GstObject **) & self->factory, NULL);
|
||||
|
||||
G_OBJECT_CLASS (example_device_provider_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
example_device_provider_finalize (GObject * object)
|
||||
{
|
||||
ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (object);
|
||||
|
||||
if (self->patterns)
|
||||
g_list_free_full (self->patterns, g_free);
|
||||
|
||||
G_OBJECT_CLASS (example_device_provider_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
example_device_provider_class_init (ExampleDeviceProviderClass * klass)
|
||||
{
|
||||
GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = GST_DEBUG_FUNCPTR (example_device_provider_dispose);
|
||||
gobject_class->finalize =
|
||||
GST_DEBUG_FUNCPTR (example_device_provider_finalize);
|
||||
|
||||
dm_class->probe = GST_DEBUG_FUNCPTR (example_device_provider_probe);
|
||||
dm_class->start = GST_DEBUG_FUNCPTR (example_device_provider_start);
|
||||
dm_class->stop = GST_DEBUG_FUNCPTR (example_device_provider_stop);
|
||||
|
||||
gst_device_provider_class_set_static_metadata (dm_class,
|
||||
"Example Device Provider", "Source/Video",
|
||||
"List and provides example source devices",
|
||||
"Mathieu Duponchelle <mathieu@centricular.com>");
|
||||
}
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
return gst_device_provider_register (plugin, "exampledeviceprovider",
|
||||
GST_RANK_PRIMARY, EXAMPLE_TYPE_DEVICE_PROVIDER);
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
example_device_provider,
|
||||
"Example device provider",
|
||||
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
||||
@@ -0,0 +1,6 @@
|
||||
library('example_device_provider', 'example-device-provider.c',
|
||||
include_directories: [configinc],
|
||||
c_args : gst_plugins_base_args,
|
||||
install: false,
|
||||
dependencies: [gst_dep],
|
||||
)
|
||||
@@ -0,0 +1,258 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* addstream.c: sample application to dynamically add streams to a running
|
||||
* pipeline
|
||||
*
|
||||
* Copyright (C) <2007> Wim Taymans <wim dot taymans at gmail dot 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 <gst/gst.h>
|
||||
|
||||
static GstElement *pipeline;
|
||||
static GstClock *theclock;
|
||||
static GMainLoop *loop;
|
||||
static GstElement *bin1, *bin2, *bin3, *bin4, *bin5;
|
||||
|
||||
/* start a bin with the given description */
|
||||
static GstElement *
|
||||
create_stream (const gchar * descr)
|
||||
{
|
||||
GstElement *bin;
|
||||
GError *error = NULL;
|
||||
|
||||
bin = gst_parse_launch (descr, &error);
|
||||
if (error) {
|
||||
g_print ("pipeline could not be constructed: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* add the bin to the pipeline now, this will set the current base_time of the
|
||||
* pipeline on the new bin. */
|
||||
gst_bin_add (GST_BIN_CAST (pipeline), bin);
|
||||
|
||||
return bin;
|
||||
}
|
||||
|
||||
static void
|
||||
pause_play_stream (GstElement * bin, gint seconds)
|
||||
{
|
||||
gboolean punch_in;
|
||||
GstStateChangeReturn ret;
|
||||
GstClockTime now, base_time, running_time;
|
||||
|
||||
/* get current running time, we need this value to continue playback of
|
||||
* non-live pipelines. */
|
||||
now = gst_clock_get_time (theclock);
|
||||
base_time = gst_element_get_base_time (bin);
|
||||
|
||||
running_time = now - base_time;
|
||||
|
||||
/* set the new bin to PAUSED, the parent bin will notice (because of the ASYNC
|
||||
* message and will perform latency calculations again when going to PLAYING
|
||||
* later. */
|
||||
ret = gst_element_set_state (bin, GST_STATE_PAUSED);
|
||||
|
||||
switch (ret) {
|
||||
case GST_STATE_CHANGE_NO_PREROLL:
|
||||
/* live source, timestamps are running_time of the pipeline clock. */
|
||||
punch_in = FALSE;
|
||||
break;
|
||||
case GST_STATE_CHANGE_SUCCESS:
|
||||
/* success, no async state changes, same as async, timestamps start
|
||||
* from 0 */
|
||||
case GST_STATE_CHANGE_ASYNC:
|
||||
/* no live source, bin will preroll. We have to punch it in because in
|
||||
* this situation timestamps start from 0. */
|
||||
punch_in = TRUE;
|
||||
break;
|
||||
case GST_STATE_CHANGE_FAILURE:
|
||||
/* fall through to return */
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (seconds)
|
||||
g_usleep (seconds * G_USEC_PER_SEC);
|
||||
|
||||
if (punch_in) {
|
||||
/* new bin has to be aligned with previous running_time. We do this by taking
|
||||
* the current absolute clock time and calculating the base time that would
|
||||
* give the previous running_time. We set this base_time on the bin before
|
||||
* setting it to PLAYING. */
|
||||
now = gst_clock_get_time (theclock);
|
||||
base_time = now - running_time;
|
||||
|
||||
gst_element_set_base_time (bin, base_time);
|
||||
}
|
||||
|
||||
/* now set the pipeline to PLAYING */
|
||||
gst_element_set_state (bin, GST_STATE_PLAYING);
|
||||
}
|
||||
|
||||
static void
|
||||
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
||||
{
|
||||
const GstStructure *s;
|
||||
|
||||
s = gst_message_get_structure (message);
|
||||
g_print ("message from \"%s\" (%s): ",
|
||||
GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
if (s) {
|
||||
gchar *sstr;
|
||||
|
||||
sstr = gst_structure_to_string (s);
|
||||
g_print ("%s\n", sstr);
|
||||
g_free (sstr);
|
||||
} else {
|
||||
g_print ("no message details\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
eos_message_received (GstBus * bus, GstMessage * message,
|
||||
GstPipeline * pipeline)
|
||||
{
|
||||
message_received (bus, message, pipeline);
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
perform_step (gpointer pstep)
|
||||
{
|
||||
gint step = GPOINTER_TO_INT (pstep);
|
||||
|
||||
switch (step) {
|
||||
case 0:
|
||||
/* live stream locks on to running_time, pipeline configures latency. */
|
||||
g_print ("creating bin1\n");
|
||||
bin1 =
|
||||
create_stream
|
||||
("( v4l2src ! videoconvert ! timeoverlay ! queue ! xvimagesink name=v4llive )");
|
||||
pause_play_stream (bin1, 0);
|
||||
g_timeout_add_seconds (1, (GSourceFunc) perform_step,
|
||||
GINT_TO_POINTER (1));
|
||||
break;
|
||||
case 1:
|
||||
/* live stream locks on to running_time, pipeline reconfigures latency
|
||||
* together with the previously added bin so that they run synchronized. */
|
||||
g_print ("creating bin2\n");
|
||||
bin2 = create_stream ("( alsasrc ! queue ! alsasink name=alsalive )");
|
||||
pause_play_stream (bin2, 0);
|
||||
g_timeout_add_seconds (1, (GSourceFunc) perform_step,
|
||||
GINT_TO_POINTER (2));
|
||||
break;
|
||||
case 2:
|
||||
/* non-live stream, need base_time to align with current running live sources. */
|
||||
g_print ("creating bin3\n");
|
||||
bin3 = create_stream ("( audiotestsrc ! alsasink name=atnonlive )");
|
||||
pause_play_stream (bin3, 0);
|
||||
g_timeout_add_seconds (1, (GSourceFunc) perform_step,
|
||||
GINT_TO_POINTER (3));
|
||||
break;
|
||||
case 3:
|
||||
g_print ("creating bin4\n");
|
||||
bin4 =
|
||||
create_stream
|
||||
("( videotestsrc ! timeoverlay ! videoconvert ! ximagesink name=vtnonlive )");
|
||||
pause_play_stream (bin4, 0);
|
||||
g_timeout_add_seconds (1, (GSourceFunc) perform_step,
|
||||
GINT_TO_POINTER (4));
|
||||
break;
|
||||
case 4:
|
||||
/* live stream locks on to running_time */
|
||||
g_print ("creating bin5\n");
|
||||
bin5 =
|
||||
create_stream
|
||||
("( videotestsrc is-live=1 ! timeoverlay ! videoconvert ! ximagesink name=vtlive )");
|
||||
pause_play_stream (bin5, 0);
|
||||
g_timeout_add_seconds (1, (GSourceFunc) perform_step,
|
||||
GINT_TO_POINTER (5));
|
||||
break;
|
||||
case 5:
|
||||
/* pause the fist live stream for 2 seconds */
|
||||
g_print ("PAUSE bin1 for 2 seconds\n");
|
||||
pause_play_stream (bin1, 2);
|
||||
/* pause the non-live stream for 2 seconds */
|
||||
g_print ("PAUSE bin4 for 2 seconds\n");
|
||||
pause_play_stream (bin4, 2);
|
||||
/* pause the pseudo live stream for 2 seconds */
|
||||
g_print ("PAUSE bin5 for 2 seconds\n");
|
||||
pause_play_stream (bin5, 2);
|
||||
g_print ("Waiting 5 seconds\n");
|
||||
g_timeout_add_seconds (5, (GSourceFunc) perform_step,
|
||||
GINT_TO_POINTER (6));
|
||||
break;
|
||||
case 6:
|
||||
g_print ("quitting\n");
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
GstBus *bus;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
/* setup message handling */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
|
||||
g_signal_connect (bus, "message::error", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::warning", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::eos", (GCallback) eos_message_received,
|
||||
pipeline);
|
||||
|
||||
/* we set the pipeline to PLAYING, this will distribute a default clock and
|
||||
* start running. no preroll is needed */
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
/* get the clock now. Since we never set the pipeline to PAUSED again, the
|
||||
* clock will not change, even when we add new clock providers later. */
|
||||
theclock = gst_element_get_clock (pipeline);
|
||||
|
||||
/* start our actions while we are in the mainloop so that we can catch errors
|
||||
* and other messages. */
|
||||
g_idle_add ((GSourceFunc) perform_step, GINT_TO_POINTER (0));
|
||||
/* go to main loop */
|
||||
g_main_loop_run (loop);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
|
||||
gst_object_unref (bus);
|
||||
gst_object_unref (pipeline);
|
||||
gst_object_unref (theclock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* codec-select.c: sample application to dynamically select a codec
|
||||
*
|
||||
* Copyright (C) <2008> Wim Taymans <wim dot taymans at gmail dot 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This example sets up a pipeline to 'encode' an audiotestsrc into 3 different
|
||||
* formats. The format can be selected dynamically at runtime.
|
||||
*
|
||||
* Each of the encoders require the audio in a specific different format.
|
||||
*
|
||||
* This example uses identity as the encoder and enforces the caps on identity
|
||||
* with a capsfilter.
|
||||
*
|
||||
* This is a good example of input and output selector and how these elements
|
||||
* preserve segment and timing information while switching between streams.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
/* Create an encoder element.
|
||||
* We make a bin containing:
|
||||
*
|
||||
* audioresample ! <enccaps> ! identity
|
||||
*
|
||||
* The sinkpad of audioresample and source pad of identity are ghosted on the
|
||||
* bin.
|
||||
*/
|
||||
static GstElement *
|
||||
make_encoder (const GstCaps * caps)
|
||||
{
|
||||
GstElement *result;
|
||||
GstElement *audioresample;
|
||||
GstElement *capsfilter;
|
||||
GstElement *identity;
|
||||
GstPad *pad;
|
||||
|
||||
/* create result bin */
|
||||
result = gst_bin_new (NULL);
|
||||
g_assert (result);
|
||||
|
||||
/* create elements */
|
||||
audioresample = gst_element_factory_make ("audioresample", NULL);
|
||||
g_assert (audioresample);
|
||||
|
||||
capsfilter = gst_element_factory_make ("capsfilter", NULL);
|
||||
g_assert (capsfilter);
|
||||
g_object_set (capsfilter, "caps", caps, NULL);
|
||||
|
||||
identity = gst_element_factory_make ("identity", NULL);
|
||||
g_assert (identity);
|
||||
g_object_set (identity, "silent", TRUE, NULL);
|
||||
|
||||
/* add elements to result bin */
|
||||
gst_bin_add (GST_BIN (result), audioresample);
|
||||
gst_bin_add (GST_BIN (result), capsfilter);
|
||||
gst_bin_add (GST_BIN (result), identity);
|
||||
|
||||
/* link elements */
|
||||
gst_element_link_pads (audioresample, "src", capsfilter, "sink");
|
||||
gst_element_link_pads (capsfilter, "src", identity, "sink");
|
||||
|
||||
/* ghost src and sink pads */
|
||||
pad = gst_element_get_static_pad (audioresample, "sink");
|
||||
gst_element_add_pad (result, gst_ghost_pad_new ("sink", pad));
|
||||
gst_object_unref (pad);
|
||||
|
||||
pad = gst_element_get_static_pad (identity, "src");
|
||||
gst_element_add_pad (result, gst_ghost_pad_new ("src", pad));
|
||||
gst_object_unref (pad);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* We generate:
|
||||
*
|
||||
* audiotestsrc ! <audiocaps> ! output-selector ! [enc1 .. enc3] ! input-selector
|
||||
* select-all = true ! fakesink
|
||||
*
|
||||
* <audiocaps> makes sure we only produce one format from the audiotestsrc.
|
||||
*
|
||||
* Each encX element consists of:
|
||||
*
|
||||
* audioresample ! <enccaps> ! identity !
|
||||
*
|
||||
* This way we can simply switch encoders without having to renegotiate.
|
||||
*/
|
||||
static GstElement *
|
||||
make_pipeline (void)
|
||||
{
|
||||
GstElement *result;
|
||||
GstElement *audiotestsrc;
|
||||
GstElement *audiocaps;
|
||||
GstElement *outputselect;
|
||||
GstElement *inputselect;
|
||||
GstElement *sink;
|
||||
GstCaps *caps;
|
||||
GstCaps *capslist[3];
|
||||
gint i;
|
||||
|
||||
/* create result pipeline */
|
||||
result = gst_pipeline_new (NULL);
|
||||
g_assert (result);
|
||||
|
||||
/* create various elements */
|
||||
audiotestsrc = gst_element_factory_make ("audiotestsrc", NULL);
|
||||
g_object_set (audiotestsrc, "num-buffers", 1000, NULL);
|
||||
g_assert (audiotestsrc);
|
||||
|
||||
audiocaps = gst_element_factory_make ("capsfilter", NULL);
|
||||
g_assert (audiocaps);
|
||||
|
||||
caps =
|
||||
gst_caps_from_string ("audio/x-raw,format=S16LE,rate=48000,channels=1");
|
||||
g_object_set (audiocaps, "caps", caps, NULL);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
outputselect = gst_element_factory_make ("output-selector", "select");
|
||||
g_assert (outputselect);
|
||||
|
||||
inputselect = gst_element_factory_make ("input-selector", NULL);
|
||||
g_assert (inputselect);
|
||||
g_object_set (inputselect, "select-all", TRUE, NULL);
|
||||
|
||||
sink = gst_element_factory_make ("fakesink", NULL);
|
||||
g_object_set (sink, "sync", TRUE, NULL);
|
||||
g_object_set (sink, "silent", TRUE, NULL);
|
||||
g_assert (sink);
|
||||
|
||||
/* add elements */
|
||||
gst_bin_add (GST_BIN (result), audiotestsrc);
|
||||
gst_bin_add (GST_BIN (result), audiocaps);
|
||||
gst_bin_add (GST_BIN (result), outputselect);
|
||||
gst_bin_add (GST_BIN (result), inputselect);
|
||||
gst_bin_add (GST_BIN (result), sink);
|
||||
|
||||
/* link elements */
|
||||
gst_element_link_pads (audiotestsrc, "src", audiocaps, "sink");
|
||||
gst_element_link_pads (audiocaps, "src", outputselect, "sink");
|
||||
gst_element_link_pads (inputselect, "src", sink, "sink");
|
||||
|
||||
/* make caps */
|
||||
capslist[0] =
|
||||
gst_caps_from_string ("audio/x-raw,format=S16LE,rate=48000,channels=1");
|
||||
capslist[1] =
|
||||
gst_caps_from_string ("audio/x-raw,format=S16LE,rate=16000,channels=1");
|
||||
capslist[2] =
|
||||
gst_caps_from_string ("audio/x-raw,format=S16LE,rate=8000,channels=1");
|
||||
|
||||
/* create encoder elements */
|
||||
for (i = 0; i < 3; i++) {
|
||||
GstElement *encoder;
|
||||
GstPad *srcpad, *sinkpad;
|
||||
|
||||
encoder = make_encoder (capslist[i]);
|
||||
g_assert (encoder);
|
||||
|
||||
gst_bin_add (GST_BIN (result), encoder);
|
||||
|
||||
srcpad = gst_element_request_pad_simple (outputselect, "src_%u");
|
||||
sinkpad = gst_element_get_static_pad (encoder, "sink");
|
||||
gst_pad_link (srcpad, sinkpad);
|
||||
gst_object_unref (srcpad);
|
||||
gst_object_unref (sinkpad);
|
||||
|
||||
srcpad = gst_element_get_static_pad (encoder, "src");
|
||||
sinkpad = gst_element_request_pad_simple (inputselect, "sink_%u");
|
||||
gst_pad_link (srcpad, sinkpad);
|
||||
gst_object_unref (srcpad);
|
||||
gst_object_unref (sinkpad);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_switch (GstElement * pipeline)
|
||||
{
|
||||
gint rand;
|
||||
GstElement *select;
|
||||
gchar *name;
|
||||
GstPad *pad;
|
||||
|
||||
rand = g_random_int_range (0, 3);
|
||||
|
||||
g_print ("switching to %d\n", rand);
|
||||
|
||||
/* find the selector */
|
||||
select = gst_bin_get_by_name (GST_BIN (pipeline), "select");
|
||||
|
||||
/* get the named pad */
|
||||
name = g_strdup_printf ("src_%u", rand);
|
||||
pad = gst_element_get_static_pad (select, name);
|
||||
g_free (name);
|
||||
|
||||
/* set the active pad */
|
||||
g_object_set (select, "active-pad", pad, NULL);
|
||||
gst_object_unref (select);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
|
||||
{
|
||||
GstElement *sender = (GstElement *) GST_MESSAGE_SRC (message);
|
||||
gchar *name = gst_element_get_name (sender);
|
||||
GMainLoop *loop = (GMainLoop *) data;
|
||||
|
||||
g_print ("Got %s message from %s\n", GST_MESSAGE_TYPE_NAME (message), name);
|
||||
g_free (name);
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
|
||||
case GST_MESSAGE_ERROR:{
|
||||
GError *err;
|
||||
gchar *debug;
|
||||
|
||||
gst_message_parse_error (message, &err, &debug);
|
||||
g_print ("Error: %s (%s)\n", err->message, debug);
|
||||
g_error_free (err);
|
||||
g_free (debug);
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_EOS:
|
||||
/* end-of-stream */
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
default:
|
||||
/* unhandled message */
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar * argv[])
|
||||
{
|
||||
GstElement *pipeline;
|
||||
GstBus *bus;
|
||||
GMainLoop *loop;
|
||||
|
||||
/* init GStreamer */
|
||||
gst_init (&argc, &argv);
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* set up */
|
||||
pipeline = make_pipeline ();
|
||||
|
||||
g_signal_connect (pipeline, "deep_notify",
|
||||
G_CALLBACK (gst_object_default_deep_notify), NULL);
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_watch (bus, my_bus_callback, loop);
|
||||
gst_object_unref (bus);
|
||||
|
||||
g_print ("Starting pipeline\n");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
/* add a timeout to cycle between the formats */
|
||||
g_timeout_add_seconds (1, (GSourceFunc) do_switch, pipeline);
|
||||
|
||||
/* now run */
|
||||
g_main_loop_run (loop);
|
||||
|
||||
g_print ("Nulling pipeline\n");
|
||||
|
||||
/* also clean up */
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
dynamic_examples = ['addstream', 'codec-select', 'sprinkle', 'sprinkle2', 'sprinkle3']
|
||||
|
||||
foreach example : dynamic_examples
|
||||
executable(example, '@0@.c'.format(example),
|
||||
c_args : gst_plugins_base_args,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [glib_deps, gst_dep],
|
||||
install: false)
|
||||
endforeach
|
||||
@@ -0,0 +1,266 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* sprinkle.c: sample application to dynamically mix tones with adder
|
||||
*
|
||||
* Copyright (C) <2009> Wim Taymans <wim dot taymans at gmail dot 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Produces a sweeping sprinkle of tones by dynamically adding and removing
|
||||
* elements to adder.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
static GstElement *pipeline, *adder;
|
||||
static GMainLoop *loop;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstElement *element;
|
||||
GstPad *srcpad;
|
||||
GstPad *sinkpad;
|
||||
gdouble freq;
|
||||
} SourceInfo;
|
||||
|
||||
/* dynamically add the source to the pipeline and link it to a new pad on
|
||||
* adder */
|
||||
static SourceInfo *
|
||||
add_source (gdouble freq)
|
||||
{
|
||||
SourceInfo *info;
|
||||
|
||||
info = g_new0 (SourceInfo, 1);
|
||||
info->freq = freq;
|
||||
|
||||
/* make source with unique name */
|
||||
info->element = gst_element_factory_make ("audiotestsrc", NULL);
|
||||
|
||||
g_object_set (info->element, "freq", freq, NULL);
|
||||
|
||||
/* add to the bin */
|
||||
gst_bin_add (GST_BIN (pipeline), info->element);
|
||||
|
||||
/* get pad from the element */
|
||||
info->srcpad = gst_element_get_static_pad (info->element, "src");
|
||||
|
||||
/* get new pad from adder, adder will now wait for data on this pad */
|
||||
info->sinkpad = gst_element_request_pad_simple (adder, "sink_%u");
|
||||
|
||||
/* link pad to adder */
|
||||
gst_pad_link (info->srcpad, info->sinkpad);
|
||||
|
||||
/* and play the element */
|
||||
gst_element_set_state (info->element, GST_STATE_PLAYING);
|
||||
|
||||
g_print ("added freq %f\n", info->freq);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/* remove the source from the pipeline after removing it from adder */
|
||||
static void
|
||||
remove_source (SourceInfo * info)
|
||||
{
|
||||
g_print ("remove freq %f\n", info->freq);
|
||||
|
||||
/* lock the state so that we can put it to NULL without the parent messing
|
||||
* with our state */
|
||||
gst_element_set_locked_state (info->element, TRUE);
|
||||
|
||||
/* first stop the source. Remember that this might block when in the PAUSED
|
||||
* state. Alternatively one could send EOS to the source, install an event
|
||||
* probe and schedule a state change/unlink/release from the mainthread.
|
||||
* Note that changing the state of a source makes it emit an EOS, which can
|
||||
* make adder go EOS. */
|
||||
gst_element_set_state (info->element, GST_STATE_NULL);
|
||||
|
||||
/* unlink from adder */
|
||||
gst_pad_unlink (info->srcpad, info->sinkpad);
|
||||
gst_object_unref (info->srcpad);
|
||||
|
||||
/* remove from the bin */
|
||||
gst_bin_remove (GST_BIN (pipeline), info->element);
|
||||
|
||||
/* give back the pad */
|
||||
gst_element_release_request_pad (adder, info->sinkpad);
|
||||
gst_object_unref (info->sinkpad);
|
||||
|
||||
g_free (info);
|
||||
}
|
||||
|
||||
/* we'll keep the state of the sources in this structure. We keep 3 sources
|
||||
* alive */
|
||||
typedef struct
|
||||
{
|
||||
guint count;
|
||||
SourceInfo *infos[3];
|
||||
} SprinkleState;
|
||||
|
||||
static SprinkleState *
|
||||
create_state (void)
|
||||
{
|
||||
SprinkleState *state;
|
||||
|
||||
state = g_new0 (SprinkleState, 1);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void
|
||||
free_state (SprinkleState * state)
|
||||
{
|
||||
SourceInfo *info;
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
info = state->infos[i];
|
||||
if (info)
|
||||
remove_source (info);
|
||||
}
|
||||
|
||||
g_free (state);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_sprinkle (SprinkleState * state)
|
||||
{
|
||||
SourceInfo *info;
|
||||
gint i;
|
||||
|
||||
/* first remove the oldest info */
|
||||
info = state->infos[2];
|
||||
|
||||
if (info)
|
||||
remove_source (info);
|
||||
|
||||
/* move sources */
|
||||
for (i = 2; i > 0; i--) {
|
||||
state->infos[i] = state->infos[i - 1];
|
||||
}
|
||||
|
||||
/* add new source, stop adding sources after 10 rounds. */
|
||||
if (state->count < 10) {
|
||||
state->infos[0] = add_source ((state->count * 100) + 200);
|
||||
state->count++;
|
||||
} else {
|
||||
state->infos[0] = NULL;
|
||||
|
||||
/* if no more sources left, quit */
|
||||
if (!state->infos[2])
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
||||
{
|
||||
const GstStructure *s;
|
||||
|
||||
s = gst_message_get_structure (message);
|
||||
g_print ("message from \"%s\" (%s): ",
|
||||
GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
if (s) {
|
||||
gchar *sstr;
|
||||
|
||||
sstr = gst_structure_to_string (s);
|
||||
g_print ("%s\n", sstr);
|
||||
g_free (sstr);
|
||||
} else {
|
||||
g_print ("no message details\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
eos_message_received (GstBus * bus, GstMessage * message,
|
||||
GstPipeline * pipeline)
|
||||
{
|
||||
message_received (bus, message, pipeline);
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
GstBus *bus;
|
||||
GstElement *filter, *convert, *sink;
|
||||
GstCaps *caps;
|
||||
gboolean linked;
|
||||
SprinkleState *state;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
/* add the fixed part to the pipeline. Remember that we need a capsfilter
|
||||
* after adder so that multiple sources are not racing to negotiate
|
||||
* a format */
|
||||
adder = gst_element_factory_make ("adder", "adder");
|
||||
filter = gst_element_factory_make ("capsfilter", "filter");
|
||||
convert = gst_element_factory_make ("audioconvert", "convert");
|
||||
sink = gst_element_factory_make ("autoaudiosink", "sink");
|
||||
|
||||
caps = gst_caps_new_simple ("audio/x-raw",
|
||||
"format", G_TYPE_STRING, "S16LE",
|
||||
"channels", G_TYPE_INT, 1, "rate", G_TYPE_INT, 44100, NULL);
|
||||
g_object_set (filter, "caps", caps, NULL);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), adder, filter, convert, sink, NULL);
|
||||
|
||||
linked = gst_element_link_many (adder, filter, convert, sink, NULL);
|
||||
g_assert (linked);
|
||||
|
||||
/* setup message handling */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
|
||||
g_signal_connect (bus, "message::error", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::warning", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::eos", (GCallback) eos_message_received,
|
||||
pipeline);
|
||||
|
||||
/* we set the pipeline to PLAYING, the pipeline will not yet preroll because
|
||||
* there is no source providing data for it yet */
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
/* and add the function that modifies the pipeline every 100ms */
|
||||
state = create_state ();
|
||||
g_timeout_add (100, (GSourceFunc) do_sprinkle, state);
|
||||
|
||||
/* go to main loop */
|
||||
g_main_loop_run (loop);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
|
||||
free_state (state);
|
||||
gst_object_unref (bus);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* sprinkle.c: sample application to dynamically mix tones with adder
|
||||
*
|
||||
* Copyright (C) <2009> Wim Taymans <wim dot taymans at gmail dot 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Produces a sweeping sprinkle of tones by dynamically adding and removing
|
||||
* elements to adder.
|
||||
*
|
||||
* gcc `pkg-config --cflags --libs gstreamer-1.0` sprinkle2.c -osprinkle2
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
static GstElement *pipeline, *adder;
|
||||
static GMainLoop *loop;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstElement *src, *fx;
|
||||
GstPad *src_srcpad;
|
||||
GstPad *fx_sinkpad, *fx_srcpad;
|
||||
GstPad *adder_sinkpad;
|
||||
gdouble freq;
|
||||
gfloat pos;
|
||||
} SourceInfo;
|
||||
|
||||
/* dynamically add the source to the pipeline and link it to a new pad on
|
||||
* adder */
|
||||
static SourceInfo *
|
||||
add_source (gdouble freq, gfloat pos)
|
||||
{
|
||||
SourceInfo *info;
|
||||
|
||||
info = g_new0 (SourceInfo, 1);
|
||||
info->freq = freq;
|
||||
info->pos = pos;
|
||||
|
||||
/* make source with unique name */
|
||||
info->src = gst_element_factory_make ("audiotestsrc", NULL);
|
||||
info->fx = gst_element_factory_make ("audiopanorama", NULL);
|
||||
|
||||
g_object_set (info->src, "freq", freq, "volume", (gdouble) 0.35, NULL);
|
||||
g_object_set (info->fx, "panorama", pos, NULL);
|
||||
|
||||
/* add to the bin */
|
||||
gst_bin_add (GST_BIN (pipeline), info->src);
|
||||
gst_bin_add (GST_BIN (pipeline), info->fx);
|
||||
|
||||
/* get pads from the elements */
|
||||
info->src_srcpad = gst_element_get_static_pad (info->src, "src");
|
||||
info->fx_srcpad = gst_element_get_static_pad (info->fx, "src");
|
||||
info->fx_sinkpad = gst_element_get_static_pad (info->fx, "sink");
|
||||
|
||||
/* get new pad from adder, adder will now wait for data on this pad */
|
||||
info->adder_sinkpad = gst_element_request_pad_simple (adder, "sink_%u");
|
||||
|
||||
/* link src to fx and fx to adder */
|
||||
gst_pad_link (info->fx_srcpad, info->adder_sinkpad);
|
||||
gst_pad_link (info->src_srcpad, info->fx_sinkpad);
|
||||
|
||||
/* and play the elements, change the state from sink to source */
|
||||
gst_element_set_state (info->fx, GST_STATE_PLAYING);
|
||||
gst_element_set_state (info->src, GST_STATE_PLAYING);
|
||||
|
||||
g_print ("added freq %5.0f, pos %3.1f\n", info->freq, info->pos);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/* remove the source from the pipeline after removing it from adder */
|
||||
static void
|
||||
remove_source (SourceInfo * info)
|
||||
{
|
||||
g_print ("remove freq %5.0f, pos %3.1f\n", info->freq, info->pos);
|
||||
|
||||
/* lock the state so that we can put it to NULL without the parent messing
|
||||
* with our state */
|
||||
gst_element_set_locked_state (info->src, TRUE);
|
||||
gst_element_set_locked_state (info->fx, TRUE);
|
||||
|
||||
/* first stop the source. Remember that this might block when in the PAUSED
|
||||
* state. Alternatively one could send EOS to the source, install an event
|
||||
* probe and schedule a state change/unlink/release from the mainthread. */
|
||||
gst_element_set_state (info->fx, GST_STATE_NULL);
|
||||
/* NOTE that the source emits EOS when shutting down but the EOS will not
|
||||
* reach the adder sinkpad because the effect is in the NULL state. We will
|
||||
* send an EOS to adder later. */
|
||||
gst_element_set_state (info->src, GST_STATE_NULL);
|
||||
|
||||
/* unlink from adder */
|
||||
gst_pad_unlink (info->src_srcpad, info->fx_sinkpad);
|
||||
gst_pad_unlink (info->fx_srcpad, info->adder_sinkpad);
|
||||
gst_object_unref (info->src_srcpad);
|
||||
gst_object_unref (info->fx_srcpad);
|
||||
gst_object_unref (info->fx_sinkpad);
|
||||
|
||||
/* remove from the bin */
|
||||
gst_bin_remove (GST_BIN (pipeline), info->src);
|
||||
gst_bin_remove (GST_BIN (pipeline), info->fx);
|
||||
|
||||
/* send EOS to the sinkpad to make adder EOS when needed */
|
||||
gst_pad_send_event (info->adder_sinkpad, gst_event_new_eos ());
|
||||
|
||||
/* give back the pad */
|
||||
gst_element_release_request_pad (adder, info->adder_sinkpad);
|
||||
gst_object_unref (info->adder_sinkpad);
|
||||
|
||||
g_free (info);
|
||||
}
|
||||
|
||||
/* we'll keep the state of the sources in this structure. We keep 3 sources
|
||||
* alive */
|
||||
typedef struct
|
||||
{
|
||||
guint count;
|
||||
SourceInfo *infos[3];
|
||||
} SprinkleState;
|
||||
|
||||
static SprinkleState *
|
||||
create_state (void)
|
||||
{
|
||||
SprinkleState *state;
|
||||
|
||||
state = g_new0 (SprinkleState, 1);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void
|
||||
free_state (SprinkleState * state)
|
||||
{
|
||||
SourceInfo *info;
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
info = state->infos[i];
|
||||
if (info)
|
||||
remove_source (info);
|
||||
}
|
||||
|
||||
g_free (state);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_sprinkle (SprinkleState * state)
|
||||
{
|
||||
SourceInfo *info;
|
||||
gint i;
|
||||
|
||||
/* first remove the oldest info */
|
||||
info = state->infos[2];
|
||||
|
||||
if (info)
|
||||
remove_source (info);
|
||||
|
||||
/* move sources */
|
||||
for (i = 2; i > 0; i--) {
|
||||
state->infos[i] = state->infos[i - 1];
|
||||
}
|
||||
|
||||
/* add new source, stop adding sources after 10 rounds. */
|
||||
if (state->count < 20) {
|
||||
state->infos[0] = add_source (
|
||||
(gdouble) ((state->count * 100) + 200),
|
||||
((gfloat) (state->count % 5) / 2.0 - 1.0));
|
||||
state->count++;
|
||||
} else {
|
||||
state->infos[0] = NULL;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
||||
{
|
||||
const GstStructure *s;
|
||||
|
||||
s = gst_message_get_structure (message);
|
||||
g_print ("message from \"%s\" (%s): ",
|
||||
GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
if (s) {
|
||||
gchar *sstr;
|
||||
|
||||
sstr = gst_structure_to_string (s);
|
||||
g_print ("%s\n", sstr);
|
||||
g_free (sstr);
|
||||
} else {
|
||||
g_print ("no message details\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
eos_message_received (GstBus * bus, GstMessage * message,
|
||||
GstPipeline * pipeline)
|
||||
{
|
||||
message_received (bus, message, pipeline);
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
GstBus *bus;
|
||||
GstElement *filter, *convert, *sink;
|
||||
GstCaps *caps;
|
||||
gboolean res;
|
||||
SprinkleState *state;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
/* add the fixed part to the pipeline. Remember that we need a capsfilter
|
||||
* after adder so that multiple sources are not racing to negotiate
|
||||
* a format */
|
||||
adder = gst_element_factory_make ("adder", "adder");
|
||||
filter = gst_element_factory_make ("capsfilter", "filter");
|
||||
convert = gst_element_factory_make ("audioconvert", "convert");
|
||||
sink = gst_element_factory_make ("autoaudiosink", "sink");
|
||||
|
||||
caps = gst_caps_new_simple ("audio/x-raw",
|
||||
"format", G_TYPE_STRING, "S16LE",
|
||||
"channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, 44100, NULL);
|
||||
g_object_set (filter, "caps", caps, NULL);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), adder, filter, convert, sink, NULL);
|
||||
|
||||
res = gst_element_link_many (adder, filter, convert, sink, NULL);
|
||||
g_assert (res);
|
||||
|
||||
/* setup message handling */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
|
||||
g_signal_connect (bus, "message::error", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::warning", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::eos", (GCallback) eos_message_received,
|
||||
pipeline);
|
||||
|
||||
/* we set the pipeline to PLAYING, the pipeline will not yet preroll because
|
||||
* there is no source providing data for it yet */
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
/* and add the function that modifies the pipeline every 100ms */
|
||||
state = create_state ();
|
||||
g_timeout_add (100, (GSourceFunc) do_sprinkle, state);
|
||||
|
||||
/* go to main loop */
|
||||
g_main_loop_run (loop);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
|
||||
free_state (state);
|
||||
gst_object_unref (bus);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* sprinkle.c: sample application to dynamically mix tones with adder
|
||||
*
|
||||
* Copyright (C) <2009> Wim Taymans <wim dot taymans at gmail dot 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Produces a sweeping sprinkle of tones by dynamically adding and removing
|
||||
* elements to adder.
|
||||
*
|
||||
* gcc `pkg-config --cflags --libs gstreamer-1.0` sprinkle3.c -osprinkle3
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
static GstElement *pipeline, *adder;
|
||||
static GMainLoop *loop;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstElement *bin, *src, *fx;
|
||||
GstPad *src_srcpad;
|
||||
GstPad *fx_sinkpad, *fx_srcpad;
|
||||
GstPad *adder_sinkpad;
|
||||
GstPad *bin_srcpad;
|
||||
gdouble freq;
|
||||
gfloat pos;
|
||||
} SourceInfo;
|
||||
|
||||
/* dynamically add the source to the pipeline and link it to a new pad on
|
||||
* adder */
|
||||
static SourceInfo *
|
||||
add_source (gdouble freq, gfloat pos)
|
||||
{
|
||||
SourceInfo *info;
|
||||
|
||||
info = g_new0 (SourceInfo, 1);
|
||||
info->freq = freq;
|
||||
info->pos = pos;
|
||||
|
||||
/* make source with unique name */
|
||||
info->bin = gst_element_factory_make ("bin", NULL);
|
||||
info->src = gst_element_factory_make ("audiotestsrc", NULL);
|
||||
info->fx = gst_element_factory_make ("audiopanorama", NULL);
|
||||
|
||||
g_object_set (info->src, "freq", freq, "volume", (gdouble) 0.35, NULL);
|
||||
g_object_set (info->fx, "panorama", pos, NULL);
|
||||
|
||||
/* add to the bin */
|
||||
gst_bin_add (GST_BIN (info->bin), info->src);
|
||||
gst_bin_add (GST_BIN (info->bin), info->fx);
|
||||
|
||||
/* get pads from the elements */
|
||||
info->src_srcpad = gst_element_get_static_pad (info->src, "src");
|
||||
info->fx_srcpad = gst_element_get_static_pad (info->fx, "src");
|
||||
info->fx_sinkpad = gst_element_get_static_pad (info->fx, "sink");
|
||||
|
||||
/* create and add a pad for the bin */
|
||||
info->bin_srcpad = gst_ghost_pad_new ("src", info->fx_srcpad);
|
||||
gst_element_add_pad (info->bin, info->bin_srcpad);
|
||||
|
||||
/* get new pad from adder, adder will now wait for data on this pad */
|
||||
info->adder_sinkpad = gst_element_request_pad_simple (adder, "sink_%u");
|
||||
|
||||
/* link inside the bin */
|
||||
gst_pad_link (info->src_srcpad, info->fx_sinkpad);
|
||||
|
||||
/* add bin to pipeline */
|
||||
gst_bin_add (GST_BIN (pipeline), info->bin);
|
||||
|
||||
/* link bin to adder */
|
||||
gst_pad_link (info->bin_srcpad, info->adder_sinkpad);
|
||||
|
||||
/* and play the elements */
|
||||
gst_element_set_state (info->bin, GST_STATE_PLAYING);
|
||||
|
||||
g_print ("added freq %5.0f, pos %3.1f\n", info->freq, info->pos);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/* remove the source from the pipeline after removing it from adder */
|
||||
static void
|
||||
remove_source (SourceInfo * info)
|
||||
{
|
||||
g_print ("remove freq %5.0f, pos %3.1f\n", info->freq, info->pos);
|
||||
|
||||
/* lock the state so that we can put it to NULL without the parent messing
|
||||
* with our state */
|
||||
gst_element_set_locked_state (info->bin, TRUE);
|
||||
|
||||
/* first stop the source. Remember that this might block when in the PAUSED
|
||||
* state. Alternatively one could send EOS to the source, install an event
|
||||
* probe and schedule a state change/unlink/release from the mainthread. */
|
||||
/* NOTE that the source inside the bin will emit EOS but it will not reach
|
||||
* adder because the element after the source is shut down first. We will send
|
||||
* EOS later */
|
||||
gst_element_set_state (info->bin, GST_STATE_NULL);
|
||||
|
||||
/* unlink bin from adder */
|
||||
gst_pad_unlink (info->bin_srcpad, info->adder_sinkpad);
|
||||
|
||||
/* release pads */
|
||||
gst_object_unref (info->src_srcpad);
|
||||
gst_object_unref (info->fx_srcpad);
|
||||
gst_object_unref (info->fx_sinkpad);
|
||||
|
||||
/* remove from the bin */
|
||||
gst_bin_remove (GST_BIN (pipeline), info->bin);
|
||||
|
||||
/* send EOS to the sinkpad to make adder EOS when needed */
|
||||
gst_pad_send_event (info->adder_sinkpad, gst_event_new_eos ());
|
||||
|
||||
/* give back the pad */
|
||||
gst_element_release_request_pad (adder, info->adder_sinkpad);
|
||||
gst_object_unref (info->adder_sinkpad);
|
||||
|
||||
g_free (info);
|
||||
}
|
||||
|
||||
/* we'll keep the state of the sources in this structure. We keep 3 sources
|
||||
* alive */
|
||||
typedef struct
|
||||
{
|
||||
guint count;
|
||||
SourceInfo *infos[3];
|
||||
} SprinkleState;
|
||||
|
||||
static SprinkleState *
|
||||
create_state (void)
|
||||
{
|
||||
SprinkleState *state;
|
||||
|
||||
state = g_new0 (SprinkleState, 1);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void
|
||||
free_state (SprinkleState * state)
|
||||
{
|
||||
SourceInfo *info;
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
info = state->infos[i];
|
||||
if (info)
|
||||
remove_source (info);
|
||||
}
|
||||
|
||||
g_free (state);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_sprinkle (SprinkleState * state)
|
||||
{
|
||||
SourceInfo *info;
|
||||
gint i;
|
||||
|
||||
/* first remove the oldest info */
|
||||
info = state->infos[2];
|
||||
|
||||
if (info)
|
||||
remove_source (info);
|
||||
|
||||
/* move sources */
|
||||
for (i = 2; i > 0; i--) {
|
||||
state->infos[i] = state->infos[i - 1];
|
||||
}
|
||||
|
||||
/* add new source, stop adding sources after 10 rounds. */
|
||||
if (state->count < 20) {
|
||||
state->infos[0] = add_source (
|
||||
(gdouble) ((state->count * 100) + 200),
|
||||
((gfloat) (state->count % 5) / 2.0 - 1.0));
|
||||
state->count++;
|
||||
} else {
|
||||
state->infos[0] = NULL;
|
||||
}
|
||||
|
||||
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
|
||||
/*GST_DEBUG_GRAPH_SHOW_ALL, */
|
||||
GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS | GST_DEBUG_GRAPH_SHOW_STATES,
|
||||
"sprinkle3");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
||||
{
|
||||
const GstStructure *s;
|
||||
|
||||
s = gst_message_get_structure (message);
|
||||
g_print ("message from \"%s\" (%s): ",
|
||||
GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
|
||||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||||
if (s) {
|
||||
gchar *sstr;
|
||||
|
||||
sstr = gst_structure_to_string (s);
|
||||
g_print ("%s\n", sstr);
|
||||
g_free (sstr);
|
||||
} else {
|
||||
g_print ("no message details\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
eos_message_received (GstBus * bus, GstMessage * message,
|
||||
GstPipeline * pipeline)
|
||||
{
|
||||
message_received (bus, message, pipeline);
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
GstBus *bus;
|
||||
GstElement *filter, *convert, *sink;
|
||||
GstCaps *caps;
|
||||
gboolean res;
|
||||
SprinkleState *state;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
/* add the fixed part to the pipeline. Remember that we need a capsfilter
|
||||
* after adder so that multiple sources are not racing to negotiate
|
||||
* a format */
|
||||
adder = gst_element_factory_make ("adder", "adder");
|
||||
filter = gst_element_factory_make ("capsfilter", "filter");
|
||||
convert = gst_element_factory_make ("audioconvert", "convert");
|
||||
sink = gst_element_factory_make ("autoaudiosink", "sink");
|
||||
|
||||
caps = gst_caps_new_simple ("audio/x-raw",
|
||||
"format", G_TYPE_STRING, "S16LE",
|
||||
"channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, 44100, NULL);
|
||||
g_object_set (filter, "caps", caps, NULL);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), adder, filter, convert, sink, NULL);
|
||||
|
||||
res = gst_element_link_many (adder, filter, convert, sink, NULL);
|
||||
g_assert (res);
|
||||
|
||||
/* setup message handling */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
|
||||
g_signal_connect (bus, "message::error", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::warning", (GCallback) message_received,
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::eos", (GCallback) eos_message_received,
|
||||
pipeline);
|
||||
|
||||
/* we set the pipeline to PLAYING, the pipeline will not yet preroll because
|
||||
* there is no source providing data for it yet */
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
/* and add the function that modifies the pipeline every 100ms */
|
||||
state = create_state ();
|
||||
g_timeout_add (100, (GSourceFunc) do_sprinkle, state);
|
||||
|
||||
/* go to main loop */
|
||||
g_main_loop_run (loop);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
|
||||
free_state (state);
|
||||
gst_object_unref (bus);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* encoding.c: example application for using GstProfile and encodebin
|
||||
*
|
||||
* Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
|
||||
*
|
||||
* 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 <stdlib.h>
|
||||
#include <glib.h>
|
||||
#include <glib/gprintf.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/pbutils/encoding-profile.h>
|
||||
#include "gstcapslist.h"
|
||||
|
||||
static gboolean silent = FALSE;
|
||||
|
||||
static void
|
||||
list_codecs (void)
|
||||
{
|
||||
GstCaps *l, *caps;
|
||||
GstStructure *st;
|
||||
guint i, len;
|
||||
gchar *tmpstr, *desc;
|
||||
|
||||
caps = gst_caps_new_empty ();
|
||||
|
||||
g_print ("Available container formats:\n");
|
||||
l = gst_caps_list_container_formats (GST_RANK_NONE);
|
||||
len = gst_caps_get_size (l);
|
||||
for (i = 0; i < len; i++) {
|
||||
st = gst_caps_steal_structure (l, 0);
|
||||
gst_caps_append_structure (caps, st);
|
||||
|
||||
tmpstr = gst_caps_to_string (caps);
|
||||
desc = gst_pb_utils_get_codec_description (caps);
|
||||
g_print (" %s - %s\n", desc, tmpstr);
|
||||
g_free (tmpstr);
|
||||
g_free (desc);
|
||||
gst_caps_remove_structure (caps, 0);
|
||||
}
|
||||
g_print ("\n");
|
||||
gst_caps_unref (l);
|
||||
|
||||
g_print ("Available video codecs:\n");
|
||||
l = gst_caps_list_video_encoding_formats (GST_RANK_NONE);
|
||||
len = gst_caps_get_size (l);
|
||||
for (i = 0; i < len; i++) {
|
||||
st = gst_caps_steal_structure (l, 0);
|
||||
gst_caps_append_structure (caps, st);
|
||||
|
||||
tmpstr = gst_caps_to_string (caps);
|
||||
desc = gst_pb_utils_get_codec_description (caps);
|
||||
g_print (" %s - %s\n", desc, tmpstr);
|
||||
g_free (tmpstr);
|
||||
g_free (desc);
|
||||
gst_caps_remove_structure (caps, 0);
|
||||
}
|
||||
g_print ("\n");
|
||||
gst_caps_unref (l);
|
||||
|
||||
g_print ("Available audio codecs:\n");
|
||||
l = gst_caps_list_audio_encoding_formats (GST_RANK_NONE);
|
||||
len = gst_caps_get_size (l);
|
||||
for (i = 0; i < len; i++) {
|
||||
st = gst_caps_steal_structure (l, 0);
|
||||
gst_caps_append_structure (caps, st);
|
||||
|
||||
tmpstr = gst_caps_to_string (caps);
|
||||
desc = gst_pb_utils_get_codec_description (caps);
|
||||
g_print (" %s - %s\n", desc, tmpstr);
|
||||
g_free (tmpstr);
|
||||
g_free (desc);
|
||||
gst_caps_remove_structure (caps, 0);
|
||||
}
|
||||
g_print ("\n");
|
||||
gst_caps_unref (l);
|
||||
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
generate_filename (const GstCaps * container, const GstCaps * vcodec,
|
||||
const GstCaps * acodec)
|
||||
{
|
||||
gchar *a, *b, *c;
|
||||
gchar *res = NULL;
|
||||
guint i;
|
||||
|
||||
a = gst_pb_utils_get_codec_description (container);
|
||||
b = gst_pb_utils_get_codec_description (vcodec);
|
||||
c = gst_pb_utils_get_codec_description (acodec);
|
||||
|
||||
if (!a)
|
||||
a = g_strdup_printf ("%.10s",
|
||||
g_uri_escape_string (gst_caps_to_string (container), NULL, FALSE));
|
||||
if (!b)
|
||||
b = g_strdup_printf ("%.10s",
|
||||
g_uri_escape_string (gst_caps_to_string (vcodec), NULL, FALSE));
|
||||
if (!c)
|
||||
c = g_strdup_printf ("%.10s",
|
||||
g_uri_escape_string (gst_caps_to_string (acodec), NULL, FALSE));
|
||||
|
||||
for (i = 0; i < 256 && res == NULL; i++) {
|
||||
res = g_strdup_printf ("%s-%s-%s-%d.file", a, b, c, i);
|
||||
if (g_file_test (res, G_FILE_TEST_EXISTS)) {
|
||||
g_free (res);
|
||||
res = NULL;
|
||||
}
|
||||
}
|
||||
/* Make sure file doesn't already exist */
|
||||
|
||||
g_free (a);
|
||||
g_free (b);
|
||||
g_free (c);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static GstEncodingProfile *
|
||||
create_profile (GstCaps * cf, GstCaps * vf, GstCaps * af)
|
||||
{
|
||||
GstEncodingContainerProfile *cprof = NULL;
|
||||
|
||||
cprof =
|
||||
gst_encoding_container_profile_new ((gchar *) "test-application-profile",
|
||||
NULL, cf, NULL);
|
||||
|
||||
if (vf)
|
||||
gst_encoding_container_profile_add_profile (cprof,
|
||||
(GstEncodingProfile *) gst_encoding_video_profile_new (vf,
|
||||
NULL, NULL, 0));
|
||||
if (af)
|
||||
gst_encoding_container_profile_add_profile (cprof, (GstEncodingProfile *)
|
||||
gst_encoding_audio_profile_new (af, NULL, NULL, 0));
|
||||
|
||||
/* Let's print out some info */
|
||||
if (!silent) {
|
||||
gchar *desc = gst_pb_utils_get_codec_description (cf);
|
||||
gchar *cd = gst_caps_to_string (cf);
|
||||
g_print ("Encoding parameters\n");
|
||||
g_print (" Container format : %s (%s)\n", desc, cd);
|
||||
g_free (desc);
|
||||
g_free (cd);
|
||||
if (vf) {
|
||||
desc = gst_pb_utils_get_codec_description (vf);
|
||||
cd = gst_caps_to_string (vf);
|
||||
g_print (" Video format : %s (%s)\n", desc, cd);
|
||||
g_free (desc);
|
||||
g_free (cd);
|
||||
}
|
||||
if (af) {
|
||||
desc = gst_pb_utils_get_codec_description (af);
|
||||
cd = gst_caps_to_string (af);
|
||||
g_print (" Audio format : %s (%s)\n", desc, cd);
|
||||
g_free (desc);
|
||||
g_free (cd);
|
||||
}
|
||||
}
|
||||
|
||||
return (GstEncodingProfile *) cprof;
|
||||
}
|
||||
|
||||
static GstEncodingProfile *
|
||||
create_profile_from_string (gchar * format, gchar * vformat, gchar * aformat)
|
||||
{
|
||||
GstEncodingProfile *prof = NULL;
|
||||
GstCaps *cf = NULL, *vf = NULL, *af = NULL;
|
||||
|
||||
if (format)
|
||||
cf = gst_caps_from_string (format);
|
||||
if (vformat)
|
||||
vf = gst_caps_from_string (vformat);
|
||||
if (aformat)
|
||||
af = gst_caps_from_string (aformat);
|
||||
|
||||
if (G_UNLIKELY ((vformat && (vf == NULL)) || (aformat && (af == NULL))))
|
||||
goto beach;
|
||||
|
||||
prof = create_profile (cf, vf, af);
|
||||
|
||||
beach:
|
||||
if (cf)
|
||||
gst_caps_unref (cf);
|
||||
if (vf)
|
||||
gst_caps_unref (vf);
|
||||
if (af)
|
||||
gst_caps_unref (af);
|
||||
|
||||
return prof;
|
||||
}
|
||||
|
||||
static void
|
||||
pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstElement * encodebin)
|
||||
{
|
||||
GstPad *sinkpad;
|
||||
|
||||
sinkpad = gst_element_get_compatible_pad (encodebin, pad, NULL);
|
||||
|
||||
if (sinkpad == NULL) {
|
||||
GstCaps *caps;
|
||||
|
||||
/* Ask encodebin for a compatible pad */
|
||||
caps = gst_pad_query_caps (pad, NULL);
|
||||
g_signal_emit_by_name (encodebin, "request-pad", caps, &sinkpad);
|
||||
if (caps)
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
if (sinkpad == NULL) {
|
||||
g_print ("Couldn't get an encoding channel for pad %s:%s\n",
|
||||
GST_DEBUG_PAD_NAME (pad));
|
||||
return;
|
||||
}
|
||||
|
||||
if (G_UNLIKELY (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK)) {
|
||||
g_print ("Couldn't link pads\n");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
autoplug_continue_cb (GstElement * uridecodebin, GstPad * somepad,
|
||||
GstCaps * caps, GstElement * encodebin)
|
||||
{
|
||||
GstPad *sinkpad;
|
||||
|
||||
g_signal_emit_by_name (encodebin, "request-pad", caps, &sinkpad);
|
||||
|
||||
if (sinkpad == NULL)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
bus_message_cb (GstBus * bus, GstMessage * message, GMainLoop * mainloop)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
g_print ("ERROR\n");
|
||||
gst_bus_set_flushing (bus, TRUE);
|
||||
g_main_loop_quit (mainloop);
|
||||
break;
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("Done\n");
|
||||
g_main_loop_quit (mainloop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
transcode_file (gchar * uri, gchar * outputuri, GstEncodingProfile * prof)
|
||||
{
|
||||
GstElement *pipeline;
|
||||
GstElement *src;
|
||||
GstElement *ebin;
|
||||
GstElement *sink;
|
||||
GstBus *bus;
|
||||
GstCaps *profilecaps, *rescaps;
|
||||
GMainLoop *mainloop;
|
||||
|
||||
g_print (" Input URI : %s\n", uri);
|
||||
g_print (" Output URI : %s\n", outputuri);
|
||||
|
||||
sink = gst_element_make_from_uri (GST_URI_SINK, outputuri, "sink", NULL);
|
||||
if (G_UNLIKELY (sink == NULL)) {
|
||||
g_print ("Can't create output sink, most likely invalid output URI !\n");
|
||||
return;
|
||||
}
|
||||
|
||||
src = gst_element_factory_make ("uridecodebin", NULL);
|
||||
if (G_UNLIKELY (src == NULL)) {
|
||||
g_print ("Can't create uridecodebin for input URI, aborting!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Figure out the streams that can be passed as-is to encodebin */
|
||||
g_object_get (src, "caps", &rescaps, NULL);
|
||||
rescaps = gst_caps_copy (rescaps);
|
||||
profilecaps = gst_encoding_profile_get_input_caps (prof);
|
||||
gst_caps_append (rescaps, profilecaps);
|
||||
|
||||
/* Set properties */
|
||||
g_object_set (src, "uri", uri, "caps", rescaps, NULL);
|
||||
|
||||
ebin = gst_element_factory_make ("encodebin", NULL);
|
||||
g_object_set (ebin, "profile", prof, NULL);
|
||||
|
||||
g_signal_connect (src, "autoplug-continue", G_CALLBACK (autoplug_continue_cb),
|
||||
ebin);
|
||||
g_signal_connect (src, "pad-added", G_CALLBACK (pad_added_cb), ebin);
|
||||
|
||||
pipeline = gst_pipeline_new ("encoding-pipeline");
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), src, ebin, sink, NULL);
|
||||
|
||||
gst_element_link (ebin, sink);
|
||||
|
||||
mainloop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
bus = gst_pipeline_get_bus ((GstPipeline *) pipeline);
|
||||
gst_bus_add_signal_watch (bus);
|
||||
g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), mainloop);
|
||||
|
||||
if (gst_element_set_state (pipeline,
|
||||
GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
|
||||
g_print ("Failed to start the encoding\n");
|
||||
return;
|
||||
}
|
||||
|
||||
g_main_loop_run (mainloop);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
ensure_uri (gchar * location)
|
||||
{
|
||||
gchar *res;
|
||||
gchar *path;
|
||||
|
||||
if (gst_uri_is_valid (location))
|
||||
return g_strdup (location);
|
||||
|
||||
if (!g_path_is_absolute (location)) {
|
||||
gchar *cur_dir;
|
||||
cur_dir = g_get_current_dir ();
|
||||
path = g_build_filename (cur_dir, location, NULL);
|
||||
g_free (cur_dir);
|
||||
} else
|
||||
path = g_strdup (location);
|
||||
|
||||
res = g_filename_to_uri (path, NULL, NULL);
|
||||
g_free (path);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
GError *err = NULL;
|
||||
gchar *outputuri = NULL;
|
||||
gchar *format = NULL;
|
||||
gchar *aformat = NULL;
|
||||
gchar *vformat = NULL;
|
||||
gboolean allmissing = FALSE;
|
||||
gboolean listcodecs = FALSE;
|
||||
GOptionEntry options[] = {
|
||||
{"silent", 's', 0, G_OPTION_ARG_NONE, &silent,
|
||||
"Don't output the information structure", NULL},
|
||||
{"outputuri", 'o', 0, G_OPTION_ARG_STRING, &outputuri,
|
||||
"URI to encode to", "URI (<protocol>://<location>)"},
|
||||
{"format", 'f', 0, G_OPTION_ARG_STRING, &format,
|
||||
"Container format", "<GstCaps>"},
|
||||
{"vformat", 'v', 0, G_OPTION_ARG_STRING, &vformat,
|
||||
"Video format", "<GstCaps>"},
|
||||
{"aformat", 'a', 0, G_OPTION_ARG_STRING, &aformat,
|
||||
"Audio format", "<GstCaps>"},
|
||||
{"allmissing", 'm', 0, G_OPTION_ARG_NONE, &allmissing,
|
||||
"encode to all matching format/codec that aren't specified", NULL},
|
||||
{"list-codecs", 'l', 0, G_OPTION_ARG_NONE, &listcodecs,
|
||||
"list all available codecs and container formats", NULL},
|
||||
{NULL}
|
||||
};
|
||||
GOptionContext *ctx;
|
||||
GstEncodingProfile *prof;
|
||||
gchar *inputuri;
|
||||
|
||||
ctx = g_option_context_new ("- encode URIs with GstProfile and encodebin");
|
||||
g_option_context_add_main_entries (ctx, options, NULL);
|
||||
g_option_context_add_group (ctx, gst_init_get_option_group ());
|
||||
|
||||
if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
|
||||
g_print ("Error initializing: %s\n", err->message);
|
||||
g_option_context_free (ctx);
|
||||
g_clear_error (&err);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (listcodecs) {
|
||||
list_codecs ();
|
||||
g_option_context_free (ctx);
|
||||
exit (0);
|
||||
}
|
||||
|
||||
if (outputuri == NULL || argc != 2) {
|
||||
g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
|
||||
g_option_context_free (ctx);
|
||||
exit (-1);
|
||||
}
|
||||
|
||||
g_option_context_free (ctx);
|
||||
|
||||
/* Fixup outputuri to be a URI */
|
||||
inputuri = ensure_uri (argv[1]);
|
||||
outputuri = ensure_uri (outputuri);
|
||||
|
||||
if (allmissing) {
|
||||
GList *muxers;
|
||||
GstCaps *formats = NULL;
|
||||
GstCaps *vformats = NULL;
|
||||
GstCaps *aformats = NULL;
|
||||
guint f, v, a, flen, vlen, alen;
|
||||
|
||||
if (!format)
|
||||
formats = gst_caps_list_container_formats (GST_RANK_NONE);
|
||||
else
|
||||
formats = gst_caps_from_string (format);
|
||||
|
||||
if (!vformat)
|
||||
vformats = gst_caps_list_video_encoding_formats (GST_RANK_NONE);
|
||||
else
|
||||
vformats = gst_caps_from_string (vformat);
|
||||
|
||||
if (!aformat)
|
||||
aformats = gst_caps_list_audio_encoding_formats (GST_RANK_NONE);
|
||||
else
|
||||
aformats = gst_caps_from_string (aformat);
|
||||
muxers =
|
||||
gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_MUXER,
|
||||
GST_RANK_NONE);
|
||||
|
||||
flen = gst_caps_get_size (formats);
|
||||
|
||||
for (f = 0; f < flen; f++) {
|
||||
GstCaps *container =
|
||||
gst_caps_new_full (gst_caps_steal_structure (formats, 0), NULL);
|
||||
GstCaps *compatv =
|
||||
gst_caps_list_compatible_codecs (container, vformats, muxers);
|
||||
GstCaps *compata =
|
||||
gst_caps_list_compatible_codecs (container, aformats, muxers);
|
||||
|
||||
vlen = gst_caps_get_size (compatv);
|
||||
alen = gst_caps_get_size (compata);
|
||||
|
||||
|
||||
for (v = 0; v < vlen; v++) {
|
||||
GstCaps *vcodec =
|
||||
gst_caps_new_full (gst_structure_copy (gst_caps_get_structure
|
||||
(compatv, v)), NULL);
|
||||
for (a = 0; a < alen; a++) {
|
||||
GstCaps *acodec =
|
||||
gst_caps_new_full (gst_structure_copy (gst_caps_get_structure
|
||||
(compata, a)), NULL);
|
||||
|
||||
prof =
|
||||
create_profile ((GstCaps *) container, (GstCaps *) vcodec,
|
||||
(GstCaps *) acodec);
|
||||
if (G_UNLIKELY (prof == NULL)) {
|
||||
g_print ("Wrong arguments\n");
|
||||
break;
|
||||
}
|
||||
outputuri =
|
||||
ensure_uri (generate_filename (container, vcodec, acodec));
|
||||
transcode_file (inputuri, outputuri, prof);
|
||||
gst_encoding_profile_unref (prof);
|
||||
|
||||
gst_caps_unref (acodec);
|
||||
}
|
||||
gst_caps_unref (vcodec);
|
||||
}
|
||||
gst_caps_unref (container);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* Create the profile */
|
||||
prof = create_profile_from_string (format, vformat, aformat);
|
||||
if (G_UNLIKELY (prof == NULL)) {
|
||||
g_print ("Encoding arguments are not valid !\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Transcode file */
|
||||
transcode_file (inputuri, outputuri, prof);
|
||||
|
||||
/* cleanup */
|
||||
gst_encoding_profile_unref (prof);
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) <2010> Edward Hervey <edward.hervey@collabora.co.uk>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "gstcapslist.h"
|
||||
|
||||
/*
|
||||
* Caps listing convenience functions
|
||||
*/
|
||||
|
||||
static gboolean
|
||||
remove_range_foreach (GQuark field_id, const GValue * value, GstStructure * st)
|
||||
{
|
||||
GType ftype = G_VALUE_TYPE (value);
|
||||
/* const gchar *fname; */
|
||||
|
||||
if (ftype == GST_TYPE_INT_RANGE || ftype == GST_TYPE_DOUBLE_RANGE ||
|
||||
ftype == GST_TYPE_FRACTION_RANGE) {
|
||||
gst_structure_remove_field (st, g_quark_to_string (field_id));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* fname = g_quark_to_string (field_id); */
|
||||
/* if (strstr (fname, "framerate") || strstr (fname, "pixel-aspect-ratio") || */
|
||||
/* strstr (fname, "rate")) { */
|
||||
/* gst_structure_remove_field (st, g_quark_to_string (field_id)); */
|
||||
/* return FALSE; */
|
||||
/* } */
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
clear_caps (GstCaps * caps, GstCaps * rescaps)
|
||||
{
|
||||
GstCaps *res;
|
||||
GstStructure *st;
|
||||
guint i;
|
||||
|
||||
res = gst_caps_make_writable (caps);
|
||||
|
||||
GST_DEBUG ("incoming caps %" GST_PTR_FORMAT, res);
|
||||
|
||||
/* Remove width/height/framerate/depth/width fields */
|
||||
for (i = gst_caps_get_size (res); i; i--) {
|
||||
st = gst_caps_get_structure (res, i - 1);
|
||||
|
||||
/* Remove range fields */
|
||||
while (!gst_structure_foreach (st,
|
||||
(GstStructureForeachFunc) remove_range_foreach, st));
|
||||
}
|
||||
|
||||
GST_DEBUG ("stripped %" GST_PTR_FORMAT, res);
|
||||
|
||||
/* And append to list without duplicates */
|
||||
while ((st = gst_caps_steal_structure (res, 0))) {
|
||||
/* Skip fake codecs/containers */
|
||||
if (gst_structure_has_name (st, "audio/x-raw") ||
|
||||
gst_structure_has_name (st, "video/x-raw") ||
|
||||
gst_structure_has_name (st, "unknown/unknown")) {
|
||||
gst_structure_free (st);
|
||||
continue;
|
||||
}
|
||||
|
||||
gst_caps_append_structure (rescaps, st);
|
||||
}
|
||||
|
||||
gst_caps_unref (res);
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
get_all_caps (GList * elements, GstPadDirection direction)
|
||||
{
|
||||
GstCaps *res;
|
||||
GList *tmp;
|
||||
|
||||
res = gst_caps_new_empty ();
|
||||
|
||||
for (tmp = elements; tmp; tmp = tmp->next) {
|
||||
GstElementFactory *factory = (GstElementFactory *) tmp->data;
|
||||
const GList *templates;
|
||||
GList *walk;
|
||||
|
||||
templates = gst_element_factory_get_static_pad_templates (factory);
|
||||
for (walk = (GList *) templates; walk; walk = g_list_next (walk)) {
|
||||
GstStaticPadTemplate *templ = walk->data;
|
||||
if (templ->direction == direction)
|
||||
clear_caps (gst_static_caps_get (&templ->static_caps), res);
|
||||
}
|
||||
}
|
||||
|
||||
res = gst_caps_normalize (res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_caps_list_container_formats:
|
||||
* @minrank: The minimum #GstRank
|
||||
*
|
||||
* Returns a #GstCaps corresponding to all the container formats
|
||||
* one can mux to on this system.
|
||||
*
|
||||
* Returns: A #GstCaps. Unref with %gst_caps_unref when done with it.
|
||||
*/
|
||||
GstCaps *
|
||||
gst_caps_list_container_formats (GstRank minrank)
|
||||
{
|
||||
GstCaps *res;
|
||||
GList *muxers;
|
||||
|
||||
muxers =
|
||||
gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_MUXER,
|
||||
minrank);
|
||||
res = get_all_caps (muxers, GST_PAD_SRC);
|
||||
gst_plugin_feature_list_free (muxers);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_caps_list_encoding_formats (GstRank minrank)
|
||||
{
|
||||
GstCaps *res;
|
||||
GList *encoders;
|
||||
|
||||
encoders =
|
||||
gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_ENCODER,
|
||||
minrank);
|
||||
res = get_all_caps (encoders, GST_PAD_SRC);
|
||||
gst_plugin_feature_list_free (encoders);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_caps_list_video_encoding_formats:
|
||||
* @minrank: The minimum #GstRank
|
||||
*
|
||||
* Returns a #GstCaps corresponding to all the video or image formats one
|
||||
* can encode to on this system.
|
||||
*
|
||||
* Returns: A #GstCaps. Unref with %gst_caps_unref when done with it.
|
||||
*/
|
||||
GstCaps *
|
||||
gst_caps_list_video_encoding_formats (GstRank minrank)
|
||||
{
|
||||
GstCaps *res;
|
||||
GList *encoders;
|
||||
|
||||
encoders =
|
||||
gst_element_factory_list_get_elements
|
||||
(GST_ELEMENT_FACTORY_TYPE_VIDEO_ENCODER, minrank);
|
||||
res = get_all_caps (encoders, GST_PAD_SRC);
|
||||
gst_plugin_feature_list_free (encoders);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* gst_caps_list_audio_encoding_formats:
|
||||
* @minrank: The minimum #GstRank
|
||||
*
|
||||
* Returns a #GstCaps corresponding to all the audio formats one
|
||||
* can encode to on this system.
|
||||
*
|
||||
* Returns: A #GstCaps. Unref with %gst_caps_unref when done with it.
|
||||
*/
|
||||
GstCaps *
|
||||
gst_caps_list_audio_encoding_formats (GstRank minrank)
|
||||
{
|
||||
GstCaps *res;
|
||||
GList *encoders;
|
||||
|
||||
encoders =
|
||||
gst_element_factory_list_get_elements
|
||||
(GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER, minrank);
|
||||
res = get_all_caps (encoders, GST_PAD_SRC);
|
||||
gst_plugin_feature_list_free (encoders);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_caps_list_compatible_codecs:
|
||||
* @containerformat: A #GstCaps corresponding to a container format
|
||||
* @codecformats: An optional #GstCaps of codec formats
|
||||
* @muxers: An optional #GList of muxer #GstElementFactory.
|
||||
*
|
||||
* Returns an array of #GstCaps corresponding to the audio/video/text formats
|
||||
* one can encode to and that can be muxed in the provided @containerformat.
|
||||
*
|
||||
* If specified, only the #GstCaps contained in @codecformats will be checked
|
||||
* against, else all compatible audio/video formats will be returned.
|
||||
*
|
||||
* If specified, only the #GstElementFactory contained in @muxers will be checked,
|
||||
* else all available muxers on the system will be checked.
|
||||
*
|
||||
* Returns: A #GstCaps containing all compatible formats. Unref with %gst_caps_unref
|
||||
* when done.
|
||||
*/
|
||||
GstCaps *
|
||||
gst_caps_list_compatible_codecs (const GstCaps * containerformat,
|
||||
GstCaps * codecformats, GList * muxers)
|
||||
{
|
||||
const GList *templates;
|
||||
GstElementFactory *factory;
|
||||
GList *walk;
|
||||
GstCaps *res = NULL;
|
||||
GstCaps *tmpcaps;
|
||||
GList *tmp;
|
||||
gboolean hadmuxers = (muxers != NULL);
|
||||
gboolean hadcodecs = (codecformats != NULL);
|
||||
|
||||
GST_DEBUG ("containerformat: %" GST_PTR_FORMAT, containerformat);
|
||||
GST_DEBUG ("codecformats: %" GST_PTR_FORMAT, codecformats);
|
||||
|
||||
if (!hadmuxers)
|
||||
muxers =
|
||||
gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_MUXER,
|
||||
GST_RANK_NONE);
|
||||
if (!hadcodecs)
|
||||
codecformats = gst_caps_list_encoding_formats (GST_RANK_NONE);
|
||||
|
||||
/* Get the highest rank muxer matching containerformat */
|
||||
tmp =
|
||||
gst_element_factory_list_filter (muxers, containerformat, GST_PAD_SRC,
|
||||
TRUE);
|
||||
if (G_UNLIKELY (tmp == NULL))
|
||||
goto beach;
|
||||
|
||||
factory = (GstElementFactory *) tmp->data;
|
||||
|
||||
GST_DEBUG ("Trying with factory %s",
|
||||
gst_element_factory_get_metadata (factory,
|
||||
GST_ELEMENT_METADATA_LONGNAME));
|
||||
|
||||
/* Match all muxer sink pad templates against the available codec formats */
|
||||
templates = gst_element_factory_get_static_pad_templates (factory);
|
||||
gst_plugin_feature_list_free (tmp);
|
||||
|
||||
tmpcaps = gst_caps_new_empty ();
|
||||
|
||||
for (walk = (GList *) templates; walk; walk = walk->next) {
|
||||
GstStaticPadTemplate *templ = walk->data;
|
||||
|
||||
if (templ->direction == GST_PAD_SINK) {
|
||||
GstCaps *templ_caps;
|
||||
|
||||
templ_caps = gst_static_caps_get (&templ->static_caps);
|
||||
gst_caps_append (tmpcaps, gst_caps_copy (templ_caps));
|
||||
}
|
||||
}
|
||||
|
||||
res = gst_caps_intersect (tmpcaps, codecformats);
|
||||
gst_caps_unref (tmpcaps);
|
||||
|
||||
beach:
|
||||
if (!hadmuxers)
|
||||
gst_plugin_feature_list_free (muxers);
|
||||
if (!hadcodecs)
|
||||
gst_caps_unref (codecformats);
|
||||
|
||||
res = gst_caps_normalize (res);
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) <2010> Edward Hervey <edward.hervey@collabora.co.uk>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
GstCaps *gst_caps_list_compatible_codecs (const GstCaps *containerformat,
|
||||
GstCaps *codecformats,
|
||||
GList *muxers);
|
||||
|
||||
GstCaps *gst_caps_list_compatible_containers (GstCaps *mediaformat,
|
||||
GList *containerformats);
|
||||
|
||||
|
||||
GstCaps *gst_caps_list_container_formats (GstRank minrank);
|
||||
|
||||
GstCaps *gst_caps_list_video_encoding_formats (GstRank minrank);
|
||||
|
||||
GstCaps *gst_caps_list_audio_encoding_formats (GstRank minrank);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
executable('encoding', 'encoding.c', 'gstcapslist.c',
|
||||
c_args : gst_plugins_base_args,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [glib_deps, gst_dep, pbutils_dep, tag_dep, video_dep, audio_dep],
|
||||
install: false)
|
||||
@@ -0,0 +1,186 @@
|
||||
/* GStreamer
|
||||
* (c) 2011 Stefan Kost <ensonic@users.sf.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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <gst/fft/gstffts16.h>
|
||||
#include <gst/fft/gstffts32.h>
|
||||
#include <gst/fft/gstfftf32.h>
|
||||
#include <gst/fft/gstfftf64.h>
|
||||
|
||||
/* effectively max range seems to be 1/4 of what it should be */
|
||||
|
||||
#define MAKE_I_TEST(_g_,_G_,_t_,_T_,_f_) \
|
||||
static void \
|
||||
test_##_t_ (const gchar *test_name, gint num_freq, gint window) \
|
||||
{ \
|
||||
GstFFT ##_T_ *ctx; \
|
||||
GstFFT ##_T_ ##Complex *fdata; \
|
||||
_g_ *adata; \
|
||||
_g_ maxfr = 0, maxfi = 0; \
|
||||
gint num_samples = num_freq * 2 - 2; \
|
||||
gint s, f; \
|
||||
\
|
||||
ctx = gst_fft_ ##_t_ ##_new (num_samples, FALSE); \
|
||||
fdata = g_new (GstFFT ##_T_ ##Complex, num_freq); \
|
||||
adata = g_new (_g_, num_samples); \
|
||||
\
|
||||
for (s = 0; s < num_samples;) { \
|
||||
adata[s++]=G_MIN##_G_; \
|
||||
adata[s++]=G_MAX##_G_; \
|
||||
} \
|
||||
\
|
||||
gst_fft_ ##_t_ ##_window (ctx, adata, window); \
|
||||
gst_fft_ ##_t_ ##_fft (ctx, adata, fdata); \
|
||||
\
|
||||
for (f = 0; f < num_freq; f++) { \
|
||||
if (fdata[1+f].r > maxfr) \
|
||||
maxfr = fdata[1+f].r; \
|
||||
if (fdata[1+f].i > maxfi) \
|
||||
maxfi = fdata[1+f].i; \
|
||||
} \
|
||||
\
|
||||
printf (#_t_" %-15s: maxfr: %"_f_" %10.5f maxfi: %"_f_" %10.5f\n",\
|
||||
test_name, \
|
||||
maxfr, (gfloat)G_MAX##_G_/maxfr, \
|
||||
maxfi, (gfloat)G_MAX##_G_/maxfi); \
|
||||
\
|
||||
gst_fft_ ##_t_ ##_free (ctx); \
|
||||
g_free (fdata); \
|
||||
g_free (adata); \
|
||||
}
|
||||
|
||||
MAKE_I_TEST (gint16, INT16, s16, S16, "6d");
|
||||
MAKE_I_TEST (gint32, INT32, s32, S32, "9d");
|
||||
|
||||
#define MAKE_F_TEST(_g_,_G_,_t_,_T_,_f_) \
|
||||
static void \
|
||||
test_##_t_ (const gchar *test_name, gint num_freq, gint window) \
|
||||
{ \
|
||||
GstFFT ##_T_ *ctx; \
|
||||
GstFFT ##_T_ ##Complex *fdata; \
|
||||
_g_ *adata; \
|
||||
_g_ maxfr = 0, maxfi = 0; \
|
||||
gint num_samples = num_freq * 2 - 2; \
|
||||
gint s, f; \
|
||||
\
|
||||
ctx = gst_fft_ ##_t_ ##_new (num_samples, FALSE); \
|
||||
fdata = g_new (GstFFT ##_T_ ##Complex, num_freq); \
|
||||
adata = g_new (_g_, num_samples); \
|
||||
\
|
||||
for (s = 0; s < num_samples;) { \
|
||||
adata[s++]=-1.0; \
|
||||
adata[s++]=+1.0; \
|
||||
} \
|
||||
\
|
||||
gst_fft_ ##_t_ ##_window (ctx, adata, window); \
|
||||
gst_fft_ ##_t_ ##_fft (ctx, adata, fdata); \
|
||||
\
|
||||
for (f = 0; f < num_freq; f++) { \
|
||||
if (fdata[1+f].r > maxfr) \
|
||||
maxfr = fdata[1+f].r; \
|
||||
if (fdata[1+f].i > maxfi) \
|
||||
maxfi = fdata[1+f].i; \
|
||||
} \
|
||||
\
|
||||
printf (#_t_" %-15s: maxfr: %"_f_" %10.5f maxfi: %"_f_" %10.5f\n",\
|
||||
test_name, \
|
||||
maxfr, (gfloat)1.0/maxfr, \
|
||||
maxfi, (gfloat)1.0/maxfi); \
|
||||
\
|
||||
gst_fft_ ##_t_ ##_free (ctx); \
|
||||
g_free (fdata); \
|
||||
g_free (adata); \
|
||||
}
|
||||
|
||||
MAKE_F_TEST (gfloat, FLOAT, f32, F32, "10.5f");
|
||||
MAKE_F_TEST (gdouble, DOUBLE, f64, F64, "10.5f");
|
||||
|
||||
gint
|
||||
main (gint argc, gchar * argv[])
|
||||
{
|
||||
gint num_bands;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
num_bands = 200;
|
||||
test_s16 ("200, none", num_bands, GST_FFT_WINDOW_RECTANGULAR);
|
||||
test_s16 ("200, hamming", num_bands, GST_FFT_WINDOW_HAMMING);
|
||||
test_s16 ("200, hann", num_bands, GST_FFT_WINDOW_HANN);
|
||||
test_s16 ("200, bartlett", num_bands, GST_FFT_WINDOW_BARTLETT);
|
||||
test_s16 ("200, blackman", num_bands, GST_FFT_WINDOW_BLACKMAN);
|
||||
puts ("");
|
||||
|
||||
num_bands = 300;
|
||||
test_s16 ("300, none", num_bands, GST_FFT_WINDOW_RECTANGULAR);
|
||||
test_s16 ("300, hamming", num_bands, GST_FFT_WINDOW_HAMMING);
|
||||
test_s16 ("300, hann", num_bands, GST_FFT_WINDOW_HANN);
|
||||
test_s16 ("300, bartlett", num_bands, GST_FFT_WINDOW_BARTLETT);
|
||||
test_s16 ("300, blackman", num_bands, GST_FFT_WINDOW_BLACKMAN);
|
||||
puts ("\n");
|
||||
|
||||
num_bands = 200;
|
||||
test_s32 ("200, none", num_bands, GST_FFT_WINDOW_RECTANGULAR);
|
||||
test_s32 ("200, hamming", num_bands, GST_FFT_WINDOW_HAMMING);
|
||||
test_s32 ("200, hann", num_bands, GST_FFT_WINDOW_HANN);
|
||||
test_s32 ("200, bartlett", num_bands, GST_FFT_WINDOW_BARTLETT);
|
||||
test_s32 ("200, blackman", num_bands, GST_FFT_WINDOW_BLACKMAN);
|
||||
puts ("");
|
||||
|
||||
num_bands = 300;
|
||||
test_s32 ("300, none", num_bands, GST_FFT_WINDOW_RECTANGULAR);
|
||||
test_s32 ("300, hamming", num_bands, GST_FFT_WINDOW_HAMMING);
|
||||
test_s32 ("300, hann", num_bands, GST_FFT_WINDOW_HANN);
|
||||
test_s32 ("300, bartlett", num_bands, GST_FFT_WINDOW_BARTLETT);
|
||||
test_s32 ("300, blackman", num_bands, GST_FFT_WINDOW_BLACKMAN);
|
||||
puts ("\n");
|
||||
|
||||
num_bands = 200;
|
||||
test_f32 ("200, none", num_bands, GST_FFT_WINDOW_RECTANGULAR);
|
||||
test_f32 ("200, hamming", num_bands, GST_FFT_WINDOW_HAMMING);
|
||||
test_f32 ("200, hann", num_bands, GST_FFT_WINDOW_HANN);
|
||||
test_f32 ("200, bartlett", num_bands, GST_FFT_WINDOW_BARTLETT);
|
||||
test_f32 ("200, blackman", num_bands, GST_FFT_WINDOW_BLACKMAN);
|
||||
puts ("");
|
||||
|
||||
num_bands = 300;
|
||||
test_f32 ("300, none", num_bands, GST_FFT_WINDOW_RECTANGULAR);
|
||||
test_f32 ("300, hamming", num_bands, GST_FFT_WINDOW_HAMMING);
|
||||
test_f32 ("300, hann", num_bands, GST_FFT_WINDOW_HANN);
|
||||
test_f32 ("300, bartlett", num_bands, GST_FFT_WINDOW_BARTLETT);
|
||||
test_f32 ("300, blackman", num_bands, GST_FFT_WINDOW_BLACKMAN);
|
||||
puts ("\n");
|
||||
|
||||
num_bands = 200;
|
||||
test_f64 ("200, none", num_bands, GST_FFT_WINDOW_RECTANGULAR);
|
||||
test_f64 ("200, hamming", num_bands, GST_FFT_WINDOW_HAMMING);
|
||||
test_f64 ("200, hann", num_bands, GST_FFT_WINDOW_HANN);
|
||||
test_f64 ("200, bartlett", num_bands, GST_FFT_WINDOW_BARTLETT);
|
||||
test_f64 ("200, blackman", num_bands, GST_FFT_WINDOW_BLACKMAN);
|
||||
puts ("");
|
||||
|
||||
num_bands = 300;
|
||||
test_f64 ("300, none", num_bands, GST_FFT_WINDOW_RECTANGULAR);
|
||||
test_f64 ("300, hamming", num_bands, GST_FFT_WINDOW_HAMMING);
|
||||
test_f64 ("300, hann", num_bands, GST_FFT_WINDOW_HANN);
|
||||
test_f64 ("300, bartlett", num_bands, GST_FFT_WINDOW_BARTLETT);
|
||||
test_f64 ("300, blackman", num_bands, GST_FFT_WINDOW_BLACKMAN);
|
||||
puts ("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
executable('fftrange', 'fftrange.c',
|
||||
c_args : gst_plugins_base_args,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [glib_deps, gst_dep, fft_dep],
|
||||
install: false)
|
||||
@@ -0,0 +1,128 @@
|
||||
/* GStreamer
|
||||
*
|
||||
* giosrc-mounting: example application that shows how to handle the
|
||||
* "not-mounted" message
|
||||
*
|
||||
* Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static GstElement *pipeline = NULL;
|
||||
|
||||
static void
|
||||
mount_cb (GObject * obj, GAsyncResult * res, gpointer user_data)
|
||||
{
|
||||
gboolean ret;
|
||||
GError *err = NULL;
|
||||
|
||||
ret = g_file_mount_enclosing_volume_finish (G_FILE (obj), res, &err);
|
||||
|
||||
if (ret) {
|
||||
g_print ("mounted successfully\n");
|
||||
gst_bus_set_flushing ((GstBus *) user_data, FALSE);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
} else {
|
||||
g_print ("mounting failed: %s\n", err->message);
|
||||
g_clear_error (&err);
|
||||
gtk_main_quit ();
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
message_handler (GstBus * bus, GstMessage * message, gpointer user_data)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ELEMENT:{
|
||||
const GstStructure *s = gst_message_get_structure (message);
|
||||
const gchar *name = gst_structure_get_name (s);
|
||||
|
||||
if (strcmp (name, "not-mounted") == 0) {
|
||||
GMountOperation *mop = gtk_mount_operation_new (NULL);
|
||||
GFile *file =
|
||||
G_FILE (g_value_get_object (gst_structure_get_value (s, "file")));
|
||||
|
||||
g_print ("not-mounted\n");
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_bus_set_flushing (bus, TRUE);
|
||||
|
||||
g_file_mount_enclosing_volume (file, G_MOUNT_MOUNT_NONE,
|
||||
mop, NULL, mount_cb, bus);
|
||||
|
||||
g_object_unref (mop);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("EOS\n");
|
||||
gtk_main_quit ();
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:{
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (message, &err, NULL);
|
||||
g_print ("error: %s\n", err->message);
|
||||
g_clear_error (&err);
|
||||
|
||||
gtk_main_quit ();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
GstBus *bus;
|
||||
gint watch_id;
|
||||
|
||||
if (argc != 2) {
|
||||
g_print ("usage: giosrc-mounting URI\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gst_init (NULL, NULL);
|
||||
gtk_init (NULL, NULL);
|
||||
|
||||
pipeline = gst_element_factory_make ("playbin", NULL);
|
||||
g_assert (pipeline);
|
||||
g_object_set (G_OBJECT (pipeline), "uri", argv[1], NULL);
|
||||
|
||||
bus = gst_element_get_bus (pipeline);
|
||||
watch_id = gst_bus_add_watch (bus, message_handler, NULL);
|
||||
gst_object_unref (bus);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
gtk_main ();
|
||||
|
||||
g_source_remove (watch_id);
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
if gtk_dep.found()
|
||||
executable('giosrc-mounting', 'giosrc-mounting.c',
|
||||
c_args : gst_plugins_base_args,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [glib_deps, gio_dep, gst_dep, gtk_dep],
|
||||
install: false)
|
||||
endif
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.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 !defined(MAC_OS_X_VERSION_MAX_ALLOWED) || MAC_OS_X_VERSION_MAX_ALLOWED >= 1014
|
||||
# define GL_SILENCE_DEPRECATION
|
||||
#endif
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/videooverlay.h>
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
|
||||
#define NSEventMaskAny NSAnyEventMask
|
||||
#define NSWindowStyleMaskTitled NSTitledWindowMask
|
||||
#define NSWindowStyleMaskClosable NSClosableWindowMask
|
||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||
#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask
|
||||
#endif
|
||||
|
||||
/* ============================================================= */
|
||||
/* */
|
||||
/* MainWindow */
|
||||
/* */
|
||||
/* ============================================================= */
|
||||
|
||||
@interface MainWindow: NSWindow <NSApplicationDelegate> {
|
||||
GMainLoop *m_loop;
|
||||
GstElement *m_pipeline;
|
||||
gboolean m_isClosed;
|
||||
}
|
||||
- (id) initWithContentRect:(NSRect) contentRect Loop:(GMainLoop*)loop Pipeline:(GstElement*)pipeline;
|
||||
- (GMainLoop*) loop;
|
||||
- (GstElement*) pipeline;
|
||||
- (gboolean) isClosed;
|
||||
@end
|
||||
|
||||
@implementation MainWindow
|
||||
|
||||
- (id) initWithContentRect:(NSRect)contentRect Loop:(GMainLoop*)loop Pipeline:(GstElement*)pipeline
|
||||
{
|
||||
m_loop = loop;
|
||||
m_pipeline = pipeline;
|
||||
m_isClosed = FALSE;
|
||||
|
||||
self = [super initWithContentRect: contentRect
|
||||
styleMask: (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable)
|
||||
backing: NSBackingStoreBuffered defer: NO screen: nil];
|
||||
|
||||
[self setReleasedWhenClosed:NO];
|
||||
[[NSApplication sharedApplication] setDelegate:self];
|
||||
|
||||
[self setTitle:@"gst-plugins-gl implements videooverlay interface"];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (GMainLoop*) loop {
|
||||
return m_loop;
|
||||
}
|
||||
|
||||
- (GstElement*) pipeline {
|
||||
return m_pipeline;
|
||||
}
|
||||
|
||||
- (gboolean) isClosed {
|
||||
return m_isClosed;
|
||||
}
|
||||
|
||||
- (void) customClose {
|
||||
m_isClosed = TRUE;
|
||||
}
|
||||
|
||||
- (BOOL) windowShouldClose:(id)sender {
|
||||
gst_element_send_event (m_pipeline, gst_event_new_eos ());
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching: (NSNotification *) not {
|
||||
[self makeMainWindow];
|
||||
[self center];
|
||||
[self orderFront:self];
|
||||
}
|
||||
|
||||
- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* */
|
||||
/* gstreamer callbacks */
|
||||
/* */
|
||||
/* ============================================================= */
|
||||
|
||||
|
||||
static GstBusSyncReply create_window (GstBus* bus, GstMessage* message, MainWindow* window)
|
||||
{
|
||||
// ignore anything but 'prepare-window-handle' element messages
|
||||
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
|
||||
return GST_BUS_PASS;
|
||||
|
||||
if (!gst_is_video_overlay_prepare_window_handle_message (message))
|
||||
return GST_BUS_PASS;
|
||||
|
||||
g_print ("setting window handle %lud\n", (gulong) window);
|
||||
|
||||
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)), (guintptr) [window contentView]);
|
||||
|
||||
gst_message_unref (message);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
|
||||
static void end_stream_cb(GstBus* bus, GstMessage* message, MainWindow* window)
|
||||
{
|
||||
g_print ("end of stream\n");
|
||||
|
||||
gst_element_set_state ([window pipeline], GST_STATE_NULL);
|
||||
gst_object_unref ([window pipeline]);
|
||||
g_main_loop_quit ([window loop]);
|
||||
|
||||
[window performSelectorOnMainThread:@selector(customClose) withObject:nil waitUntilDone:YES];
|
||||
}
|
||||
|
||||
static gpointer thread_func (MainWindow* window)
|
||||
{
|
||||
g_main_loop_run ([window loop]);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* */
|
||||
/* application */
|
||||
/* */
|
||||
/* ============================================================= */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int width = 640;
|
||||
int height = 480;
|
||||
|
||||
GMainLoop *loop = NULL;
|
||||
GstElement *pipeline = NULL;
|
||||
|
||||
GstElement *videosrc = NULL;
|
||||
GstElement *videosink = NULL;
|
||||
GstCaps *caps=NULL;
|
||||
gboolean ok=FALSE;
|
||||
GstBus *bus=NULL;
|
||||
GThread *loop_thread=NULL;
|
||||
NSRect rect;
|
||||
MainWindow *window=nil;
|
||||
|
||||
[NSApplication sharedApplication];
|
||||
|
||||
g_print("app created\n");
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
videosrc = gst_element_factory_make ("videotestsrc", "videotestsrc");
|
||||
videosink = gst_element_factory_make ("glimagesink", "glimagesink");
|
||||
|
||||
g_object_set(G_OBJECT(videosrc), "num-buffers", 500, NULL);
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), videosrc, videosink, NULL);
|
||||
|
||||
caps = gst_caps_new_simple("video/x-raw",
|
||||
"width", G_TYPE_INT, width,
|
||||
"height", G_TYPE_INT, height,
|
||||
"framerate", GST_TYPE_FRACTION, 25, 1,
|
||||
"format", G_TYPE_STRING, "I420",
|
||||
NULL);
|
||||
|
||||
ok = gst_element_link_filtered(videosrc, videosink, caps);
|
||||
gst_caps_unref(caps);
|
||||
if (!ok)
|
||||
g_warning("could not link videosrc to videosink\n");
|
||||
|
||||
rect.origin.x = 0; rect.origin.y = 0;
|
||||
rect.size.width = width; rect.size.height = height;
|
||||
|
||||
window = [[MainWindow alloc] initWithContentRect:rect Loop:loop Pipeline:pipeline];
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch (bus);
|
||||
/* NOTE: window is not bridge_retained because its lifetime is just this function */
|
||||
g_signal_connect(bus, "message::error", G_CALLBACK(end_stream_cb), (__bridge gpointer)window);
|
||||
g_signal_connect(bus, "message::warning", G_CALLBACK(end_stream_cb), (__bridge gpointer)window);
|
||||
g_signal_connect(bus, "message::eos", G_CALLBACK(end_stream_cb), (__bridge gpointer)window);
|
||||
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) create_window, (__bridge gpointer)window, NULL);
|
||||
gst_object_unref (bus);
|
||||
|
||||
loop_thread = g_thread_new (NULL,
|
||||
(GThreadFunc) thread_func, (__bridge gpointer)window);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
[window orderFront:window];
|
||||
|
||||
while (![window isClosed]) {
|
||||
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
untilDate:[NSDate dateWithTimeIntervalSinceNow:1]
|
||||
inMode:NSDefaultRunLoopMode dequeue:YES];
|
||||
if (event)
|
||||
[NSApp sendEvent:event];
|
||||
}
|
||||
|
||||
g_thread_join (loop_thread);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
if host_system == 'darwin'
|
||||
appkit_dep = dependency('appleframeworks', modules : ['AppKit'], required : true)
|
||||
|
||||
executable('cocoa-videooverlay', 'cocoa-videooverlay.m',
|
||||
objc_args : [gst_plugins_base_args],
|
||||
include_directories: [configinc],
|
||||
dependencies : [gstgl_dep, corefoundation_dep, appkit_dep],
|
||||
install: false)
|
||||
endif
|
||||
@@ -0,0 +1,21 @@
|
||||
--- Description of the generic (no GUI) examples ---
|
||||
|
||||
- cube:
|
||||
Show how to have a graphic FPS greater than the input video frame rate.
|
||||
The source is the videotestsrc rgb.
|
||||
|
||||
- cubeyuv:
|
||||
Show how to have a graphic FPS greater than the input video frame rate.
|
||||
The source is a local video file needed in argument.
|
||||
The colorspace conversion is maded by the glupload element.
|
||||
|
||||
- doublecube:
|
||||
A local video source is displayed into two renderers.
|
||||
The first one is a normal 2D screen, the second is a 3D cube.
|
||||
We can visually check that the video is displayed at the same speed
|
||||
in the two renderers.
|
||||
|
||||
- recordgraphic:
|
||||
Show how to use the glfilterapp to define the draw callback in a gstreamer client code.
|
||||
The scene is recorded into an avi file using mpeg4 encoder.
|
||||
The colorspace conversion is made by the gldownload element.
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
static gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data)
|
||||
{
|
||||
GMainLoop *loop = (GMainLoop*)data;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (msg))
|
||||
{
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("End-of-stream\n");
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *debug = NULL;
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, &debug);
|
||||
|
||||
g_print ("Error: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
|
||||
if (debug)
|
||||
{
|
||||
g_print ("Debug deails: %s\n", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//client reshape callback
|
||||
static gboolean reshapeCallback (void *gl_sink, void *context, GLuint width, GLuint height, gpointer data)
|
||||
{
|
||||
glViewport(0, 0, width, height);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//client draw callback
|
||||
static gboolean drawCallback (GstElement * gl_sink, GstGLContext *context, GstSample * sample, gpointer data)
|
||||
{
|
||||
static GLfloat xrot = 0;
|
||||
static GLfloat yrot = 0;
|
||||
static GLfloat zrot = 0;
|
||||
static GstClockTime current_time;
|
||||
static GstClockTime last_time = gst_util_get_timestamp();
|
||||
static gint nbFrames = 0;
|
||||
|
||||
GstVideoFrame v_frame;
|
||||
GstVideoInfo v_info;
|
||||
guint texture = 0;
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample);
|
||||
GstCaps *caps = gst_sample_get_caps (sample);
|
||||
|
||||
gst_video_info_from_caps (&v_info, caps);
|
||||
|
||||
if (!gst_video_frame_map (&v_frame, &v_info, buf, (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
|
||||
g_warning ("Failed to map the video buffer");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
texture = *(guint *) v_frame.data[0];
|
||||
|
||||
current_time = gst_util_get_timestamp ();
|
||||
nbFrames++ ;
|
||||
|
||||
if ((current_time - last_time) >= GST_SECOND)
|
||||
{
|
||||
std::cout << "GRAPHIC FPS = " << nbFrames << std::endl;
|
||||
nbFrames = 0;
|
||||
last_time = current_time;
|
||||
}
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
glEnable (GL_TEXTURE_2D);
|
||||
glBindTexture (GL_TEXTURE_2D, texture);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
/* invert the y-axis to get the front face the correct way up */
|
||||
glScalef (0.5f, -0.5f, 0.5f);
|
||||
|
||||
glRotatef(xrot,1.0f,0.0f,0.0f);
|
||||
glRotatef(yrot,0.0f,1.0f,0.0f);
|
||||
glRotatef(zrot,0.0f,0.0f,1.0f);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
// Front Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
// Back Face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
// Top Face
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
// Bottom Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
// Right face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
// Left Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glEnd();
|
||||
|
||||
gst_video_frame_unmap (&v_frame);
|
||||
|
||||
xrot+=0.3f;
|
||||
yrot+=0.2f;
|
||||
zrot+=0.4f;
|
||||
|
||||
glDisable (GL_DEPTH_TEST);
|
||||
glDisable (GL_TEXTURE_2D);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
//gst-launch-1.0 videotestsrc num_buffers=400 ! video/x-raw, width=320, height=240 !
|
||||
//glgraphicmaker ! glfiltercube ! video/x-raw, width=800, height=600 ! glimagesink
|
||||
gint main (gint argc, gchar *argv[])
|
||||
{
|
||||
GstStateChangeReturn ret;
|
||||
GstElement *pipeline, *videosrc, *glimagesink;
|
||||
|
||||
GMainLoop *loop;
|
||||
GstBus *bus;
|
||||
|
||||
/* FIXME: remove once the example supports gl3 and/or gles2 */
|
||||
g_setenv ("GST_GL_API", "opengl", FALSE);
|
||||
|
||||
/* initialization */
|
||||
gst_init (&argc, &argv);
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* create elements */
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
/* watch for messages on the pipeline's bus (note that this will only
|
||||
* work like this when a GLib main loop is running) */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_watch (bus, bus_call, loop);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* create elements */
|
||||
videosrc = gst_element_factory_make ("videotestsrc", "videotestsrc0");
|
||||
glimagesink = gst_element_factory_make ("glimagesink", "glimagesink0");
|
||||
|
||||
|
||||
if (!videosrc || !glimagesink)
|
||||
{
|
||||
g_print ("one element could not be found \n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* change video source caps */
|
||||
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
||||
"format", G_TYPE_STRING, "RGB",
|
||||
"width", G_TYPE_INT, 320,
|
||||
"height", G_TYPE_INT, 240,
|
||||
"framerate", GST_TYPE_FRACTION, 25, 1,
|
||||
NULL) ;
|
||||
|
||||
/* configure elements */
|
||||
g_object_set(G_OBJECT(videosrc), "num-buffers", 400, NULL);
|
||||
g_signal_connect(G_OBJECT(glimagesink), "client-reshape", G_CALLBACK (reshapeCallback), NULL);
|
||||
g_signal_connect(G_OBJECT(glimagesink), "client-draw", G_CALLBACK (drawCallback), NULL);
|
||||
|
||||
/* add elements */
|
||||
gst_bin_add_many (GST_BIN (pipeline), videosrc, glimagesink, NULL);
|
||||
|
||||
/* link elements */
|
||||
gboolean link_ok = gst_element_link_filtered(videosrc, glimagesink, caps) ;
|
||||
gst_caps_unref(caps) ;
|
||||
if(!link_ok)
|
||||
{
|
||||
g_warning("Failed to link videosrc to glimagesink!\n") ;
|
||||
return -1 ;
|
||||
}
|
||||
|
||||
/* run */
|
||||
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
{
|
||||
g_print ("Failed to start up pipeline!\n");
|
||||
|
||||
/* check if there is an error message with details on the bus */
|
||||
GstMessage* msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
|
||||
if (msg)
|
||||
{
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, NULL);
|
||||
g_print ("ERROR: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
gst_message_unref (msg);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
||||
/* clean up */
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
if have_cxx
|
||||
executable('cube', 'main.cpp',
|
||||
dependencies : [gstgl_dep, gstglproto_dep],
|
||||
install: false)
|
||||
endif
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
static gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data)
|
||||
{
|
||||
GMainLoop *loop = (GMainLoop*)data;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (msg))
|
||||
{
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("End-of-stream\n");
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *debug = NULL;
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, &debug);
|
||||
|
||||
g_print ("Error: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
|
||||
if (debug)
|
||||
{
|
||||
g_print ("Debug deails: %s\n", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
//display video framerate
|
||||
static void identityCallback (GstElement *src, GstBuffer *buffer, GstElement* textoverlay)
|
||||
{
|
||||
static GstClockTime last_timestamp = 0;
|
||||
static gint nbFrames = 0 ;
|
||||
|
||||
//display estimated video FPS
|
||||
nbFrames++ ;
|
||||
if (GST_BUFFER_TIMESTAMP(buffer) - last_timestamp >= 1000000000)
|
||||
{
|
||||
gchar *s = g_strdup_printf ("video framerate = %d", nbFrames);
|
||||
g_object_set(G_OBJECT(textoverlay), "text", s, NULL);
|
||||
g_free (s);
|
||||
last_timestamp = GST_BUFFER_TIMESTAMP(buffer) ;
|
||||
nbFrames = 0 ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//client reshape callback
|
||||
static gboolean reshapeCallback (void * gl_sink, void *context, GLuint width, GLuint height, gpointer data)
|
||||
{
|
||||
glViewport(0, 0, width, height);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
//client draw callback
|
||||
static gboolean drawCallback (GstElement * gl_sink, GstGLContext *context, GstSample * sample, gpointer data)
|
||||
{
|
||||
static GLfloat xrot = 0;
|
||||
static GLfloat yrot = 0;
|
||||
static GLfloat zrot = 0;
|
||||
static GstClockTime current_time;
|
||||
static GstClockTime last_time = gst_util_get_timestamp();
|
||||
static gint nbFrames = 0;
|
||||
|
||||
GstVideoFrame v_frame;
|
||||
GstVideoInfo v_info;
|
||||
guint texture = 0;
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample);
|
||||
GstCaps *caps = gst_sample_get_caps (sample);
|
||||
|
||||
gst_video_info_from_caps (&v_info, caps);
|
||||
|
||||
if (!gst_video_frame_map (&v_frame, &v_info, buf, (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
|
||||
g_warning ("Failed to map the video buffer");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
texture = *(guint *) v_frame.data[0];
|
||||
|
||||
current_time = gst_util_get_timestamp ();
|
||||
nbFrames++ ;
|
||||
|
||||
if ((current_time - last_time) >= GST_SECOND)
|
||||
{
|
||||
std::cout << "GRAPHIC FPS = " << nbFrames << std::endl;
|
||||
nbFrames = 0;
|
||||
last_time = current_time;
|
||||
}
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
glEnable (GL_TEXTURE_2D);
|
||||
glBindTexture (GL_TEXTURE_2D, texture);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glRotatef(xrot,1.0f,0.0f,0.0f);
|
||||
glRotatef(yrot,0.0f,1.0f,0.0f);
|
||||
glRotatef(zrot,0.0f,0.0f,1.0f);
|
||||
|
||||
/* invert the y-axis to get the front face the correct way up */
|
||||
glScalef (0.5f, -0.5f, 0.5f);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
// Front Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
// Back Face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
// Top Face
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
// Bottom Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
// Right face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
// Left Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glEnd();
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
gst_video_frame_unmap (&v_frame);
|
||||
|
||||
xrot+=0.03f;
|
||||
yrot+=0.02f;
|
||||
zrot+=0.04f;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void cb_new_pad (GstElement* decodebin, GstPad* pad, GstElement* identity)
|
||||
{
|
||||
GstPad* identity_pad = gst_element_get_static_pad (identity, "sink");
|
||||
|
||||
//only link once
|
||||
if (GST_PAD_IS_LINKED (identity_pad))
|
||||
{
|
||||
gst_object_unref (identity_pad);
|
||||
return;
|
||||
}
|
||||
|
||||
GstCaps* caps = gst_pad_get_current_caps (pad);
|
||||
GstStructure* str = gst_caps_get_structure (caps, 0);
|
||||
if (!g_strrstr (gst_structure_get_name (str), "video"))
|
||||
{
|
||||
gst_caps_unref (caps);
|
||||
gst_object_unref (identity_pad);
|
||||
return;
|
||||
}
|
||||
gst_caps_unref (caps);
|
||||
|
||||
GstPadLinkReturn ret = gst_pad_link (pad, identity_pad);
|
||||
if (ret != GST_PAD_LINK_OK)
|
||||
g_warning ("Failed to link with decodebin!\n");
|
||||
}
|
||||
|
||||
|
||||
gint main (gint argc, gchar *argv[])
|
||||
{
|
||||
if (argc != 2)
|
||||
{
|
||||
g_warning ("usage: cubeyuv.exe videolocation\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* FIXME: remove once the example supports gl3 and/or gles2 */
|
||||
g_setenv ("GST_GL_API", "opengl", FALSE);
|
||||
|
||||
std::string video_location(argv[1]);
|
||||
|
||||
/* initialization */
|
||||
gst_init (&argc, &argv);
|
||||
GMainLoop* loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* create elements */
|
||||
GstElement* pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
/* watch for messages on the pipeline's bus (note that this will only
|
||||
* work like this when a GLib main loop is running) */
|
||||
GstBus* bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_watch (bus, bus_call, loop);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* create elements */
|
||||
GstElement* videosrc = gst_element_factory_make ("filesrc", "filesrc0");
|
||||
GstElement* decodebin = gst_element_factory_make ("decodebin", "decodebin");
|
||||
GstElement* identity = gst_element_factory_make ("identity", "identity0");
|
||||
GstElement* textoverlay = gst_element_factory_make ("textoverlay", "textoverlay0");
|
||||
GstElement* glimagesink = gst_element_factory_make ("glimagesink", "glimagesink0");
|
||||
|
||||
|
||||
if (!videosrc || !decodebin || !identity || !textoverlay || !glimagesink)
|
||||
{
|
||||
g_print ("one element could not be found \n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* configure elements */
|
||||
g_object_set(G_OBJECT(videosrc), "num-buffers", 800, NULL);
|
||||
g_object_set(G_OBJECT(videosrc), "location", video_location.c_str(), NULL);
|
||||
g_signal_connect(identity, "handoff", G_CALLBACK(identityCallback), textoverlay) ;
|
||||
g_object_set(G_OBJECT(textoverlay), "font_desc", "Ahafoni CLM Bold 30", NULL);
|
||||
g_signal_connect(G_OBJECT(glimagesink), "client-reshape", G_CALLBACK (reshapeCallback), NULL);
|
||||
g_signal_connect(G_OBJECT(glimagesink), "client-draw", G_CALLBACK (drawCallback), NULL);
|
||||
|
||||
/* add elements */
|
||||
gst_bin_add_many (GST_BIN (pipeline), videosrc, decodebin, identity,
|
||||
textoverlay, glimagesink, NULL);
|
||||
|
||||
/* link elements */
|
||||
gst_element_link_pads (videosrc, "src", decodebin, "sink");
|
||||
|
||||
g_signal_connect (decodebin, "pad-added", G_CALLBACK (cb_new_pad), identity);
|
||||
|
||||
if (!gst_element_link_pads(identity, "src", textoverlay, "video_sink"))
|
||||
{
|
||||
g_print ("Failed to link identity to textoverlay!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gboolean link_ok = gst_element_link (textoverlay, glimagesink);
|
||||
if(!link_ok)
|
||||
{
|
||||
g_warning("Failed to link textoverlay to glimagesink!\n") ;
|
||||
return -1 ;
|
||||
}
|
||||
|
||||
/* run */
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
{
|
||||
g_print ("Failed to start up pipeline!\n");
|
||||
|
||||
/* check if there is an error message with details on the bus */
|
||||
GstMessage* msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
|
||||
if (msg)
|
||||
{
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, NULL);
|
||||
g_print ("ERROR: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
gst_message_unref (msg);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
||||
/* clean up */
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
if have_cxx
|
||||
executable('cubeyuv', 'main.cpp',
|
||||
dependencies : [gstgl_dep, gstglproto_dep],
|
||||
install: false)
|
||||
endif
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
static gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data)
|
||||
{
|
||||
GMainLoop *loop = (GMainLoop*)data;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (msg))
|
||||
{
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("End-of-stream\n");
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *debug = NULL;
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, &debug);
|
||||
|
||||
g_print ("Error: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
|
||||
if (debug)
|
||||
{
|
||||
g_print ("Debug details: %s\n", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
//display video framerate
|
||||
static GstPadProbeReturn textoverlay_sink_pad_probe_cb (GstPad *pad, GstPadProbeInfo *info, GstElement* textoverlay)
|
||||
{
|
||||
static GstClockTime last_timestamp = 0;
|
||||
static gint nbFrames = 0 ;
|
||||
|
||||
//display estimated video FPS
|
||||
nbFrames++ ;
|
||||
if (GST_BUFFER_TIMESTAMP(info->data) - last_timestamp >= 1000000000)
|
||||
{
|
||||
gchar *s = g_strdup_printf ("video framerate = %d", nbFrames);
|
||||
g_object_set(G_OBJECT(textoverlay), "text", s, NULL);
|
||||
g_free (s);
|
||||
last_timestamp = GST_BUFFER_TIMESTAMP(info->data) ;
|
||||
nbFrames = 0;
|
||||
}
|
||||
|
||||
return GST_PAD_PROBE_OK;
|
||||
}
|
||||
|
||||
|
||||
//client reshape callback
|
||||
static gboolean reshapeCallback (void *gl_sink, void *context, GLuint width, GLuint height)
|
||||
{
|
||||
glViewport(0, 0, width, height);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
//client draw callback
|
||||
static gboolean drawCallback (GstElement * gl_sink, GstGLContext *context, GstSample * sample, gpointer data)
|
||||
{
|
||||
static GLfloat xrot = 0;
|
||||
static GLfloat yrot = 0;
|
||||
static GLfloat zrot = 0;
|
||||
static GstClockTime current_time;
|
||||
static GstClockTime last_time = gst_util_get_timestamp();
|
||||
static gint nbFrames = 0;
|
||||
|
||||
GstVideoFrame v_frame;
|
||||
GstVideoInfo v_info;
|
||||
guint texture = 0;
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample);
|
||||
GstCaps *caps = gst_sample_get_caps (sample);
|
||||
|
||||
gst_video_info_from_caps (&v_info, caps);
|
||||
|
||||
if (!gst_video_frame_map (&v_frame, &v_info, buf, (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
|
||||
g_warning ("Failed to map the video buffer");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
texture = *(guint *) v_frame.data[0];
|
||||
|
||||
current_time = gst_util_get_timestamp ();
|
||||
nbFrames++ ;
|
||||
|
||||
if ((current_time - last_time) >= GST_SECOND)
|
||||
{
|
||||
std::cout << "GRAPHIC FPS of the scene which contains the custom cube) = " << nbFrames << std::endl;
|
||||
nbFrames = 0;
|
||||
last_time = current_time;
|
||||
}
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
glEnable (GL_TEXTURE_2D);
|
||||
glBindTexture (GL_TEXTURE_2D, texture);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glTranslatef(0.0f,0.0f,-5.0f);
|
||||
|
||||
glRotatef(xrot,1.0f,0.0f,0.0f);
|
||||
glRotatef(yrot,0.0f,1.0f,0.0f);
|
||||
glRotatef(zrot,0.0f,0.0f,1.0f);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
// Front Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
// Back Face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
// Top Face
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
// Bottom Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
// Right face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
// Left Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glEnd();
|
||||
|
||||
gst_video_frame_unmap (&v_frame);
|
||||
|
||||
xrot+=0.03f;
|
||||
yrot+=0.02f;
|
||||
zrot+=0.04f;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void cb_new_pad (GstElement* decodebin, GstPad* pad, GstElement* element)
|
||||
{
|
||||
GstPad* element_pad = gst_element_get_static_pad (element, "sink");
|
||||
|
||||
//only link once
|
||||
if (!element_pad || GST_PAD_IS_LINKED (element_pad))
|
||||
{
|
||||
gst_object_unref (element_pad);
|
||||
return;
|
||||
}
|
||||
|
||||
GstCaps* caps = gst_pad_get_current_caps (pad);
|
||||
GstStructure* str = gst_caps_get_structure (caps, 0);
|
||||
|
||||
GstCaps* caps2 = gst_pad_query_caps (element_pad, NULL);
|
||||
gst_caps_unref (caps2);
|
||||
|
||||
if (!g_strrstr (gst_structure_get_name (str), "video"))
|
||||
{
|
||||
gst_caps_unref (caps);
|
||||
gst_object_unref (element_pad);
|
||||
return;
|
||||
}
|
||||
gst_caps_unref (caps);
|
||||
|
||||
GstPadLinkReturn ret = gst_pad_link (pad, element_pad);
|
||||
if (ret != GST_PAD_LINK_OK)
|
||||
g_warning ("Failed to link with decodebin %d!\n", ret);
|
||||
gst_object_unref (element_pad);
|
||||
}
|
||||
|
||||
|
||||
gint main (gint argc, gchar *argv[])
|
||||
{
|
||||
if (argc != 2)
|
||||
{
|
||||
g_warning ("usage: doublecube.exe videolocation\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string video_location(argv[1]);
|
||||
|
||||
/* initialization */
|
||||
gst_init (&argc, &argv);
|
||||
GMainLoop* loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* create elements */
|
||||
GstElement* pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
/* watch for messages on the pipeline's bus (note that this will only
|
||||
* work like this when a GLib main loop is running) */
|
||||
GstBus* bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_watch (bus, bus_call, loop);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* create elements */
|
||||
GstElement* videosrc = gst_element_factory_make ("filesrc", "filesrc0");
|
||||
GstElement* decodebin = gst_element_factory_make ("decodebin", "decodebin0");
|
||||
GstElement* videoconvert = gst_element_factory_make ("videoscale", "videoconvert0");
|
||||
GstElement* textoverlay = gst_element_factory_make ("textoverlay", "textoverlay0"); //textoverlay required I420
|
||||
GstElement* tee = gst_element_factory_make ("tee", "tee0");
|
||||
|
||||
GstElement* queue0 = gst_element_factory_make ("queue", "queue0");
|
||||
GstElement* glimagesink0 = gst_element_factory_make ("glimagesink", "glimagesink0");
|
||||
|
||||
GstElement* queue1 = gst_element_factory_make ("queue", "queue1");
|
||||
GstElement* glfiltercube = gst_element_factory_make ("glfiltercube", "glfiltercube");
|
||||
GstElement* glimagesink1 = gst_element_factory_make ("glimagesink", "glimagesink1");
|
||||
|
||||
GstElement* queue2 = gst_element_factory_make ("queue", "queue2");
|
||||
GstElement* glimagesink2 = gst_element_factory_make ("glimagesink", "glimagesink2");
|
||||
|
||||
|
||||
if (!videosrc || !decodebin || !videoconvert || !textoverlay || !tee ||
|
||||
!queue0 || !glimagesink0 ||
|
||||
!queue1 || !glfiltercube || !glimagesink1 ||
|
||||
!queue2 || !glimagesink2)
|
||||
{
|
||||
g_warning ("one element could not be found \n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
GstCaps* cubecaps = gst_caps_new_simple("video/x-raw",
|
||||
"width", G_TYPE_INT, 600,
|
||||
"height", G_TYPE_INT, 400,
|
||||
NULL);
|
||||
|
||||
/* configure elements */
|
||||
g_object_set(G_OBJECT(videosrc), "num-buffers", 1000, NULL);
|
||||
g_object_set(G_OBJECT(videosrc), "location", video_location.c_str(), NULL);
|
||||
g_object_set(G_OBJECT(textoverlay), "font_desc", "Ahafoni CLM Bold 30", NULL);
|
||||
g_signal_connect(G_OBJECT(glimagesink0), "client-reshape", G_CALLBACK (reshapeCallback), NULL);
|
||||
g_signal_connect(G_OBJECT(glimagesink0), "client-draw", G_CALLBACK (drawCallback), NULL);
|
||||
|
||||
/* add elements */
|
||||
gst_bin_add_many (GST_BIN (pipeline), videosrc, decodebin, videoconvert, textoverlay, tee,
|
||||
queue0, glimagesink0,
|
||||
queue1, glfiltercube, glimagesink1,
|
||||
queue2, glimagesink2, NULL);
|
||||
|
||||
GstPad* textoverlay_sink_pad = gst_element_get_static_pad (textoverlay, "video_sink");
|
||||
gst_pad_add_probe (textoverlay_sink_pad, GST_PAD_PROBE_TYPE_BUFFER,
|
||||
(GstPadProbeCallback) textoverlay_sink_pad_probe_cb, (gpointer)textoverlay, NULL);
|
||||
gst_object_unref (textoverlay_sink_pad);
|
||||
|
||||
if (!gst_element_link_many(videoconvert, textoverlay, tee, NULL))
|
||||
{
|
||||
g_print ("Failed to link videoconvert to tee!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!gst_element_link(videosrc, decodebin))
|
||||
{
|
||||
g_print ("Failed to link videosrc to decodebin!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_signal_connect (decodebin, "pad-added", G_CALLBACK (cb_new_pad), videoconvert);
|
||||
|
||||
if (!gst_element_link_many(tee, queue0, NULL))
|
||||
{
|
||||
g_warning ("Failed to link one or more elements bettween tee and queue0!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gboolean link_ok = gst_element_link_filtered(queue0, glimagesink0, cubecaps) ;
|
||||
gst_caps_unref(cubecaps) ;
|
||||
if(!link_ok)
|
||||
{
|
||||
g_warning("Failed to link queue0 to glimagesink0!\n") ;
|
||||
return -1 ;
|
||||
}
|
||||
|
||||
if (!gst_element_link_many(tee, queue1, glfiltercube, glimagesink1, NULL))
|
||||
{
|
||||
g_warning ("Failed to link one or more elements bettween tee and glimagesink1!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!gst_element_link_many(tee, queue2, glimagesink2, NULL))
|
||||
{
|
||||
g_warning ("Failed to link one or more elements bettween tee and glimagesink2!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* run */
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
{
|
||||
g_print ("Failed to start up pipeline!\n");
|
||||
|
||||
/* check if there is an error message with details on the bus */
|
||||
GstMessage* msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
|
||||
if (msg)
|
||||
{
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, NULL);
|
||||
g_print ("ERROR: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
gst_message_unref (msg);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
||||
/* clean up */
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
if have_cxx
|
||||
executable('doublecube', 'main.cpp',
|
||||
dependencies : [gstgl_dep, gstglproto_dep],
|
||||
install: false)
|
||||
endif
|
||||
@@ -0,0 +1,5 @@
|
||||
# TODO :get rid of pointless subdirs
|
||||
subdir('cube', if_found : gl_dep)
|
||||
subdir('cubeyuv', if_found : gl_dep)
|
||||
subdir('doublecube', if_found : gl_dep)
|
||||
subdir('recordgraphic', if_found : gl_dep)
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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 defined(_MSC_VER)
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
static gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data)
|
||||
{
|
||||
GMainLoop *loop = (GMainLoop*)data;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (msg))
|
||||
{
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("End-of-stream\n");
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *debug = NULL;
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, &debug);
|
||||
|
||||
g_print ("Error: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
|
||||
if (debug)
|
||||
{
|
||||
g_print ("Debug details: %s\n", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//client draw callback
|
||||
static gboolean drawCallback (void *filter, GLuint texture, GLuint width, GLuint height, gpointer data)
|
||||
{
|
||||
static GLfloat xrot = 0;
|
||||
static GLfloat yrot = 0;
|
||||
static GLfloat zrot = 0;
|
||||
static GstClockTime current_time;
|
||||
static GstClockTime last_time = gst_util_get_timestamp();
|
||||
static gint nbFrames = 0;
|
||||
|
||||
current_time = gst_util_get_timestamp ();
|
||||
nbFrames++ ;
|
||||
|
||||
if ((current_time - last_time) >= GST_SECOND)
|
||||
{
|
||||
std::cout << "GRAPHIC FPS = " << nbFrames << std::endl;
|
||||
nbFrames = 0;
|
||||
last_time = current_time;
|
||||
}
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
glEnable (GL_TEXTURE_2D);
|
||||
glBindTexture (GL_TEXTURE_2D, texture);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glScalef (0.5f, 0.5f, 0.5f);
|
||||
|
||||
glRotatef(xrot,1.0f,0.0f,0.0f);
|
||||
glRotatef(yrot,0.0f,1.0f,0.0f);
|
||||
glRotatef(zrot,0.0f,0.0f,1.0f);
|
||||
|
||||
//cube
|
||||
glBegin(GL_QUADS);
|
||||
// Front Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
// Back Face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
// Top Face
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
// Bottom Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
// Right face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
// Left Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glEnd();
|
||||
|
||||
glDisable (GL_DEPTH_TEST);
|
||||
glDisable (GL_TEXTURE_2D);
|
||||
|
||||
xrot+=0.3f;
|
||||
yrot+=0.2f;
|
||||
zrot+=0.4f;
|
||||
|
||||
//return TRUE because we dealt with the texture
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
//equivalent command line:
|
||||
//gst-launch-1.0 videotestsrc num_buffers=400 ! gleffects effect=0 !
|
||||
//avenc_mpeg4 ! avimux ! filesink location="record.avi"
|
||||
// or
|
||||
//gst-launch-1.0 videotestsrc num_buffers=400 ! gleffects effect=0 ! "video/x-raw, width=320, height=240" ! glfiltercube ! "video/x-raw, width=720, height=576" !
|
||||
//avenc_mpeg4 ! avimux ! filesink location="record.avi"
|
||||
gint main (gint argc, gchar *argv[])
|
||||
{
|
||||
GstStateChangeReturn ret;
|
||||
GstElement *pipeline, *videosrc, *glupload, *glfilterapp, *glcolorconvert, *gldownload, *avenc_mpeg4, *avimux, *filesink;
|
||||
GMainLoop *loop;
|
||||
GstBus *bus;
|
||||
|
||||
/* FIXME: remove once the example supports gl3 and/or gles2 */
|
||||
g_setenv ("GST_GL_API", "opengl", FALSE);
|
||||
|
||||
/* initialization */
|
||||
gst_init (&argc, &argv);
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* create elements */
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
/* watch for messages on the pipeline's bus (note that this will only
|
||||
* work like this when a GLib main loop is running) */
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_watch (bus, bus_call, loop);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* create elements */
|
||||
videosrc = gst_element_factory_make ("videotestsrc", "videotestsrc0");
|
||||
glupload = gst_element_factory_make("glupload", "glupload0");
|
||||
glfilterapp = gst_element_factory_make ("glfilterapp", "glfilterapp0");
|
||||
glcolorconvert = gst_element_factory_make ("glcolorconvert", "glcolorconvert0");
|
||||
gldownload = gst_element_factory_make("gldownload", "gldownload0");
|
||||
avenc_mpeg4 = gst_element_factory_make ("avenc_mpeg4", "avenc_mpeg40");
|
||||
avimux = gst_element_factory_make ("avimux", "avimux0");
|
||||
filesink = gst_element_factory_make ("filesink", "filesink0");
|
||||
|
||||
if (!videosrc || !glupload || !glfilterapp || !glcolorconvert || !gldownload || !avenc_mpeg4 || !avimux || !filesink)
|
||||
{
|
||||
g_print ("one element could not be found \n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* change video source caps */
|
||||
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
||||
"width", G_TYPE_INT, 320,
|
||||
"height", G_TYPE_INT, 240,
|
||||
"framerate", GST_TYPE_FRACTION, 25, 1,
|
||||
NULL);
|
||||
|
||||
/* change video source caps */
|
||||
GstCaps *outcaps = gst_caps_new_simple("video/x-raw",
|
||||
"width", G_TYPE_INT, 640,
|
||||
"height", G_TYPE_INT, 480,
|
||||
NULL);
|
||||
|
||||
/* configure elements */
|
||||
g_object_set(G_OBJECT(videosrc), "num-buffers", 400, NULL);
|
||||
g_signal_connect(G_OBJECT(glfilterapp), "client-draw", G_CALLBACK (drawCallback), NULL);
|
||||
g_object_set(G_OBJECT(filesink), "location", "record.avi", NULL);
|
||||
|
||||
/* add elements */
|
||||
gst_bin_add_many (GST_BIN (pipeline), videosrc, glupload, glfilterapp,
|
||||
glcolorconvert, gldownload, avenc_mpeg4, avimux, filesink, NULL);
|
||||
|
||||
/* link elements */
|
||||
gboolean link_ok = gst_element_link_filtered(videosrc, glupload, caps) ;
|
||||
gst_caps_unref(caps) ;
|
||||
if(!link_ok)
|
||||
{
|
||||
g_warning("Failed to link videosrc to glfilterapp!") ;
|
||||
return -1 ;
|
||||
}
|
||||
|
||||
if (!gst_element_link (glupload, glfilterapp)) {
|
||||
g_warning("Failed to link glupload to glfilterapp!") ;
|
||||
return -1 ;
|
||||
}
|
||||
if (!gst_element_link (glfilterapp, glcolorconvert)) {
|
||||
g_warning("Failed to link glfilterapp to glcolorconvert!") ;
|
||||
return -1 ;
|
||||
}
|
||||
if (!gst_element_link (glcolorconvert, gldownload)) {
|
||||
g_warning("Failed to link glfilterapp to glcolorconvert!") ;
|
||||
return -1 ;
|
||||
}
|
||||
|
||||
link_ok = gst_element_link_filtered(gldownload, avenc_mpeg4, outcaps);
|
||||
gst_caps_unref(outcaps) ;
|
||||
if(!link_ok)
|
||||
{
|
||||
g_warning("Failed to link glfilterapp to avenc_mpeg4!") ;
|
||||
return -1 ;
|
||||
}
|
||||
if (!gst_element_link_many(avenc_mpeg4, avimux, filesink, NULL))
|
||||
{
|
||||
g_warning ("Failed to link one or more elements!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* run */
|
||||
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
{
|
||||
g_print ("Failed to start up pipeline!\n");
|
||||
|
||||
/* check if there is an error message with details on the bus */
|
||||
GstMessage* msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
|
||||
if (msg)
|
||||
{
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, NULL);
|
||||
g_warning ("ERROR: %s", err->message);
|
||||
g_error_free (err);
|
||||
gst_message_unref (msg);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
||||
/* clean up */
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
if have_cxx
|
||||
executable('recordgraphic', 'main.cpp',
|
||||
dependencies : [gstgl_dep, gstglproto_dep],
|
||||
install: false)
|
||||
endif
|
||||
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2014-2015 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.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
#if defined (GDK_WINDOWING_X11)
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/video/video-info.h>
|
||||
|
||||
#include "../gstgtk.h"
|
||||
#include "mviewwidget.h"
|
||||
|
||||
/* Until playbin properties support dynamic changes,
|
||||
* use our own glviewconvert */
|
||||
#define USE_GLCONVERT_FOR_INPUT 1
|
||||
|
||||
typedef struct _localstate
|
||||
{
|
||||
GstVideoMultiviewFramePacking in_mode;
|
||||
GstVideoMultiviewFlags out_mode;
|
||||
GstVideoMultiviewFlags in_flags, out_flags;
|
||||
} LocalState;
|
||||
|
||||
static GstBusSyncReply
|
||||
create_window (GstBus * bus, GstMessage * message, GtkWidget * widget)
|
||||
{
|
||||
if (gst_gtk_handle_need_context (bus, message, NULL))
|
||||
return GST_BUS_DROP;
|
||||
|
||||
/* ignore anything but 'prepare-window-handle' element messages */
|
||||
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
|
||||
return GST_BUS_PASS;
|
||||
|
||||
if (!gst_is_video_overlay_prepare_window_handle_message (message))
|
||||
return GST_BUS_PASS;
|
||||
|
||||
/* do not call gdk_window_ensure_native for the first time here because
|
||||
* we are in a different thread than the main thread */
|
||||
gst_video_overlay_set_gtk_window (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC
|
||||
(message)), widget);
|
||||
|
||||
gst_message_unref (message);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
static void
|
||||
end_stream_cb (GstBus * bus, GstMessage * message, GstElement * pipeline)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("End of stream\n");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
gtk_main_quit ();
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *debug = NULL;
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (message, &err, &debug);
|
||||
|
||||
g_print ("Error: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
|
||||
if (debug) {
|
||||
g_print ("Debug details: %s\n", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
gtk_main_quit ();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
draw_cb (GtkWidget * widget, cairo_t * cr, GstElement * videosink)
|
||||
{
|
||||
gst_video_overlay_expose (GST_VIDEO_OVERLAY (videosink));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
resize_cb (GtkWidget * widget, GdkEvent * event, gpointer sink)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
gint scale = 1;
|
||||
|
||||
#if GTK_CHECK_VERSION(3, 10, 0)
|
||||
scale = gtk_widget_get_scale_factor (widget);
|
||||
#endif
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
|
||||
allocation.x * scale, allocation.y * scale, allocation.width * scale,
|
||||
allocation.height * scale);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_cb (GtkWidget * widget, GdkEvent * event, GstElement * pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
static void
|
||||
button_state_ready_cb (GtkWidget * widget, GstElement * pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_READY);
|
||||
}
|
||||
|
||||
static void
|
||||
button_state_paused_cb (GtkWidget * widget, GstElement * pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
||||
}
|
||||
|
||||
static void
|
||||
button_state_playing_cb (GtkWidget * widget, GstElement * pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
set_mview_mode (GtkWidget * combo, GObject * target, const gchar * prop_name)
|
||||
{
|
||||
gchar *mview_mode = NULL;
|
||||
GEnumClass *p_class;
|
||||
GEnumValue *v;
|
||||
GParamSpec *p =
|
||||
g_object_class_find_property (G_OBJECT_GET_CLASS (target), prop_name);
|
||||
|
||||
g_return_val_if_fail (p != NULL, FALSE);
|
||||
|
||||
p_class = G_PARAM_SPEC_ENUM (p)->enum_class;
|
||||
g_return_val_if_fail (p_class != NULL, FALSE);
|
||||
|
||||
g_object_get (G_OBJECT (combo), "active-id", &mview_mode, NULL);
|
||||
g_return_val_if_fail (mview_mode != NULL, FALSE);
|
||||
|
||||
v = g_enum_get_value_by_nick (p_class, mview_mode);
|
||||
g_return_val_if_fail (v != NULL, FALSE);
|
||||
|
||||
g_object_set (target, prop_name, v->value, NULL);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
set_mview_input_mode (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
#if USE_GLCONVERT_FOR_INPUT
|
||||
return set_mview_mode (widget, G_OBJECT (data), "input-mode-override");
|
||||
#else
|
||||
return set_mview_mode (widget, G_OBJECT (data), "video-multiview-mode");
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean
|
||||
set_mview_output_mode (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
GstElement *sink = gst_bin_get_by_name (GST_BIN (data), "sink");
|
||||
set_mview_mode (widget, G_OBJECT (sink), "output-multiview-mode");
|
||||
gst_object_unref (GST_OBJECT (sink));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
input_flags_changed (GObject * gobject, GParamSpec * pspec, gpointer user_data)
|
||||
{
|
||||
GObject *target = G_OBJECT (user_data);
|
||||
GstVideoMultiviewFlags flags;
|
||||
|
||||
g_object_get (gobject, "flags", &flags, NULL);
|
||||
#if USE_GLCONVERT_FOR_INPUT
|
||||
g_object_set (target, "input-flags-override", flags, NULL);
|
||||
#else
|
||||
g_object_set (target, "video-multiview-flags", flags, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
output_flags_changed (GObject * gobject, GParamSpec * pspec, gpointer user_data)
|
||||
{
|
||||
GObject *target = G_OBJECT (user_data);
|
||||
GstVideoMultiviewFlags flags;
|
||||
GstElement *sink = gst_bin_get_by_name (GST_BIN (target), "sink");
|
||||
|
||||
g_object_get (gobject, "flags", &flags, NULL);
|
||||
g_object_set (G_OBJECT (sink), "output-multiview-flags", flags, NULL);
|
||||
|
||||
gst_object_unref (GST_OBJECT (sink));
|
||||
}
|
||||
|
||||
static void
|
||||
downmix_method_changed (GObject * gobject, GParamSpec * pspec, gpointer user_data)
|
||||
{
|
||||
GObject *target = G_OBJECT (user_data);
|
||||
GstGLStereoDownmix downmix_method;
|
||||
GstElement *sink = gst_bin_get_by_name (GST_BIN (target), "sink");
|
||||
|
||||
g_object_get (gobject, "downmix-mode", &downmix_method, NULL);
|
||||
g_object_set (sink, "output-multiview-downmix-mode", downmix_method, NULL);
|
||||
gst_object_unref (GST_OBJECT (sink));
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
enum_value_to_nick (GType enum_type, guint value)
|
||||
{
|
||||
GEnumClass *enum_info;
|
||||
GEnumValue *v;
|
||||
const gchar *nick;
|
||||
|
||||
enum_info = (GEnumClass *) (g_type_class_ref (enum_type));
|
||||
g_return_val_if_fail (enum_info != NULL, NULL);
|
||||
|
||||
v = g_enum_get_value (enum_info, value);
|
||||
g_return_val_if_fail (v != NULL, NULL);
|
||||
|
||||
nick = v->value_nick;
|
||||
|
||||
g_type_class_unref (enum_info);
|
||||
|
||||
return nick;
|
||||
}
|
||||
|
||||
static void
|
||||
detect_mode_from_uri (LocalState * state, const gchar * uri)
|
||||
{
|
||||
if (strstr (uri, "HSBS")) {
|
||||
state->in_mode = GST_VIDEO_MULTIVIEW_FRAME_PACKING_SIDE_BY_SIDE;
|
||||
state->in_flags = GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT;
|
||||
} else if (strstr (uri, "SBS")) {
|
||||
state->in_mode = GST_VIDEO_MULTIVIEW_FRAME_PACKING_SIDE_BY_SIDE;
|
||||
if (g_regex_match_simple ("half", uri, G_REGEX_CASELESS,
|
||||
(GRegexMatchFlags) 0)) {
|
||||
state->in_flags = GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar * argv[])
|
||||
{
|
||||
LocalState state;
|
||||
GtkWidget *area, *combo, *w;
|
||||
const gchar *uri;
|
||||
|
||||
#if defined (GDK_WINDOWING_X11)
|
||||
XInitThreads ();
|
||||
#endif
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
gtk_init (&argc, &argv);
|
||||
|
||||
if (argc < 2) {
|
||||
g_print ("Usage: 3dvideo <uri-to-play>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uri = argv[1];
|
||||
|
||||
GstElement *pipeline = gst_element_factory_make ("playbin", NULL);
|
||||
GstBin *sinkbin = (GstBin *) gst_parse_bin_from_description ("glupload ! glcolorconvert ! glviewconvert name=viewconvert ! glimagesink name=sink", TRUE, NULL);
|
||||
#if USE_GLCONVERT_FOR_INPUT
|
||||
GstElement *glconvert = gst_bin_get_by_name (sinkbin, "viewconvert");
|
||||
#endif
|
||||
GstElement *videosink = gst_bin_get_by_name (sinkbin, "sink");
|
||||
|
||||
/* Get defaults */
|
||||
g_object_get (pipeline, "video-multiview-mode", &state.in_mode,
|
||||
"video-multiview-flags", &state.in_flags, NULL);
|
||||
gst_child_proxy_get (GST_CHILD_PROXY (videosink), "sink::output-multiview-mode", &state.out_mode,
|
||||
"sink::output-multiview-flags", &state.out_flags, NULL);
|
||||
|
||||
detect_mode_from_uri (&state, uri);
|
||||
|
||||
g_return_val_if_fail (pipeline != NULL, 1);
|
||||
g_return_val_if_fail (videosink != NULL, 1);
|
||||
|
||||
g_object_set (G_OBJECT (pipeline), "video-sink", sinkbin, NULL);
|
||||
g_object_set (G_OBJECT (pipeline), "uri", uri, NULL);
|
||||
|
||||
#if USE_GLCONVERT_FOR_INPUT
|
||||
g_object_set (G_OBJECT (glconvert), "input-mode-override", state.in_mode,
|
||||
NULL);
|
||||
g_object_set (G_OBJECT (glconvert), "input-flags-override", state.in_flags,
|
||||
NULL);
|
||||
#else
|
||||
g_object_set (G_OBJECT (pipeline), "video-multiview-mode", state.in_mode,
|
||||
NULL);
|
||||
g_object_set (G_OBJECT (pipeline), "video-multiview-flags", state.in_flags,
|
||||
NULL);
|
||||
#endif
|
||||
|
||||
/* Connect to bus for signal handling */
|
||||
GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch (bus);
|
||||
g_signal_connect (bus, "message::error", G_CALLBACK (end_stream_cb),
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::warning", G_CALLBACK (end_stream_cb),
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::eos", G_CALLBACK (end_stream_cb), pipeline);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_READY);
|
||||
|
||||
area = gtk_drawing_area_new ();
|
||||
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) create_window, area, NULL);
|
||||
gst_object_unref (bus);
|
||||
|
||||
/* Toplevel window */
|
||||
GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Stereoscopic video demo");
|
||||
GdkGeometry geometry;
|
||||
geometry.min_width = 1;
|
||||
geometry.min_height = 1;
|
||||
geometry.max_width = -1;
|
||||
geometry.max_height = -1;
|
||||
gtk_window_set_geometry_hints (GTK_WINDOW (window), window, &geometry,
|
||||
GDK_HINT_MIN_SIZE);
|
||||
|
||||
GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
|
||||
gtk_container_add (GTK_CONTAINER (window), vbox);
|
||||
|
||||
/* area where the video is drawn */
|
||||
gtk_box_pack_start (GTK_BOX (vbox), area, TRUE, TRUE, 0);
|
||||
|
||||
/* Buttons to control the pipeline state */
|
||||
GtkWidget *table = gtk_grid_new ();
|
||||
gtk_container_add (GTK_CONTAINER (vbox), table);
|
||||
|
||||
GtkWidget *button_state_ready = gtk_button_new_with_label ("Stop");
|
||||
g_signal_connect (G_OBJECT (button_state_ready), "clicked",
|
||||
G_CALLBACK (button_state_ready_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_ready, 1, 0, 1, 1);
|
||||
gtk_widget_show (button_state_ready);
|
||||
|
||||
//control state paused
|
||||
GtkWidget *button_state_paused = gtk_button_new_with_label ("Pause");
|
||||
g_signal_connect (G_OBJECT (button_state_paused), "clicked",
|
||||
G_CALLBACK (button_state_paused_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_paused, 2, 0, 1, 1);
|
||||
gtk_widget_show (button_state_paused);
|
||||
|
||||
//control state playing
|
||||
GtkWidget *button_state_playing = gtk_button_new_with_label ("Play");
|
||||
g_signal_connect (G_OBJECT (button_state_playing), "clicked",
|
||||
G_CALLBACK (button_state_playing_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_playing, 3, 0, 1, 1);
|
||||
//gtk_widget_show (button_state_playing);
|
||||
|
||||
w = gst_mview_widget_new (FALSE);
|
||||
combo = GST_MVIEW_WIDGET (w)->mode_selector;
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo),
|
||||
enum_value_to_nick (GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
|
||||
state.in_mode));
|
||||
#if USE_GLCONVERT_FOR_INPUT
|
||||
g_signal_connect (G_OBJECT (combo), "changed",
|
||||
G_CALLBACK (set_mview_input_mode), glconvert);
|
||||
#else
|
||||
g_signal_connect (G_OBJECT (combo), "changed",
|
||||
G_CALLBACK (set_mview_input_mode), pipeline);
|
||||
#endif
|
||||
|
||||
g_object_set (G_OBJECT (w), "flags", state.in_flags, NULL);
|
||||
#if USE_GLCONVERT_FOR_INPUT
|
||||
g_signal_connect (G_OBJECT (w), "notify::flags",
|
||||
G_CALLBACK (input_flags_changed), glconvert);
|
||||
#else
|
||||
g_signal_connect (G_OBJECT (w), "notify::flags",
|
||||
G_CALLBACK (input_flags_changed), pipeline);
|
||||
#endif
|
||||
gtk_container_add (GTK_CONTAINER (vbox), w);
|
||||
|
||||
w = gst_mview_widget_new (TRUE);
|
||||
combo = GST_MVIEW_WIDGET (w)->mode_selector;
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo),
|
||||
enum_value_to_nick (GST_TYPE_VIDEO_MULTIVIEW_MODE, state.out_mode));
|
||||
g_signal_connect (G_OBJECT (combo), "changed",
|
||||
G_CALLBACK (set_mview_output_mode), videosink);
|
||||
|
||||
g_object_set (G_OBJECT (w), "flags", state.out_flags, NULL);
|
||||
g_signal_connect (G_OBJECT (w), "notify::flags",
|
||||
G_CALLBACK (output_flags_changed), videosink);
|
||||
g_signal_connect (G_OBJECT (w), "notify::downmix-mode",
|
||||
G_CALLBACK (downmix_method_changed), videosink);
|
||||
gtk_container_add (GTK_CONTAINER (vbox), w);
|
||||
|
||||
//configure the pipeline
|
||||
g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (destroy_cb),
|
||||
pipeline);
|
||||
|
||||
gtk_widget_realize (area);
|
||||
|
||||
/* Redraw needed when paused or stopped (PAUSED or READY) */
|
||||
g_signal_connect (area, "draw", G_CALLBACK (draw_cb), videosink);
|
||||
g_signal_connect(area, "configure-event", G_CALLBACK(resize_cb), videosink);
|
||||
|
||||
gtk_widget_show_all (window);
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
gtk_main ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
executable('3dvideo', ['main.cpp', 'mviewwidget.c'],
|
||||
cpp_args : [gst_plugins_base_args],
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [gstgtkhelper_dep, gstgl_dep, x11_dep],
|
||||
install: false)
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2014-2015 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.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "mviewwidget.h"
|
||||
|
||||
G_DEFINE_TYPE (GstMViewWidget, gst_mview_widget, GTK_TYPE_GRID);
|
||||
|
||||
static void gst_mview_widget_constructed (GObject * o);
|
||||
static void gst_mview_widget_set_property (GObject * object,
|
||||
guint prop_id, const GValue * value, GParamSpec * pspec);
|
||||
static void gst_mview_widget_get_property (GObject * object,
|
||||
guint prop_id, GValue * value, GParamSpec * pspec);
|
||||
|
||||
#define DEFAULT_DOWNMIX GST_GL_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_IS_OUTPUT,
|
||||
PROP_MODE_SELECTOR,
|
||||
PROP_FLAGS,
|
||||
PROP_DOWNMIX_MODE
|
||||
};
|
||||
|
||||
typedef struct _ToggleClosure
|
||||
{
|
||||
GstMViewWidget *mv;
|
||||
GstVideoMultiviewFlags flag;
|
||||
} ToggleClosure;
|
||||
|
||||
static GtkWidget *combo_box_from_enum (GType enum_type);
|
||||
|
||||
static void
|
||||
gst_mview_widget_class_init (GstMViewWidgetClass * klass)
|
||||
{
|
||||
GObjectClass *object_klass = (GObjectClass *) (klass);
|
||||
|
||||
object_klass->constructed = gst_mview_widget_constructed;
|
||||
object_klass->set_property = gst_mview_widget_set_property;
|
||||
object_klass->get_property = gst_mview_widget_get_property;
|
||||
|
||||
g_object_class_install_property (object_klass, PROP_IS_OUTPUT,
|
||||
g_param_spec_boolean ("is-output", "Is an Output widget",
|
||||
"TRUE if the widget should have downmix mode", FALSE,
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (object_klass, PROP_MODE_SELECTOR,
|
||||
g_param_spec_object ("mode-selector", "Multiview Mode selector",
|
||||
"Multiview Mode selector widget",
|
||||
GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (object_klass, PROP_FLAGS,
|
||||
g_param_spec_flags ("flags", "Multiview Flags",
|
||||
"multiview flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGS,
|
||||
GST_VIDEO_MULTIVIEW_FLAGS_NONE,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (object_klass, PROP_DOWNMIX_MODE,
|
||||
g_param_spec_enum ("downmix-mode",
|
||||
"Mode for mono downmixed output",
|
||||
"Output anaglyph type to generate when downmixing to mono",
|
||||
GST_TYPE_GL_STEREO_DOWNMIX, DEFAULT_DOWNMIX,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
static void
|
||||
gst_mview_widget_init (GstMViewWidget * mv)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
flag_changed (GObject * w, ToggleClosure * c)
|
||||
{
|
||||
GstMViewWidget *mv = GST_MVIEW_WIDGET (c->mv);
|
||||
gboolean flag_set;
|
||||
|
||||
g_object_get (w, "active", &flag_set, NULL);
|
||||
|
||||
if (flag_set)
|
||||
mv->flags |= c->flag;
|
||||
else
|
||||
mv->flags &= ~(c->flag);
|
||||
if (!mv->synching)
|
||||
g_object_notify (G_OBJECT (mv), "flags");
|
||||
}
|
||||
|
||||
static void
|
||||
link_button_to_flag (GstMViewWidget * mv, GtkWidget * w,
|
||||
GstVideoMultiviewFlags flag)
|
||||
{
|
||||
ToggleClosure *c = g_new0 (ToggleClosure, 1);
|
||||
|
||||
c->mv = mv;
|
||||
c->flag = flag;
|
||||
|
||||
g_signal_connect_data (G_OBJECT (w), "toggled", G_CALLBACK (flag_changed),
|
||||
c, (GClosureNotify) g_free, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
sync_flags (GstMViewWidget * mv)
|
||||
{
|
||||
mv->synching = TRUE;
|
||||
g_object_set (G_OBJECT (mv->lflip), "active",
|
||||
! !(mv->flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED), NULL);
|
||||
g_object_set (G_OBJECT (mv->lflop), "active",
|
||||
! !(mv->flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED), NULL);
|
||||
g_object_set (G_OBJECT (mv->rflip), "active",
|
||||
! !(mv->flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED), NULL);
|
||||
g_object_set (G_OBJECT (mv->rflop), "active",
|
||||
! !(mv->flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED), NULL);
|
||||
g_object_set (G_OBJECT (mv->right_first), "active",
|
||||
! !(mv->flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST), NULL);
|
||||
g_object_set (G_OBJECT (mv->half_aspect), "active",
|
||||
! !(mv->flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT), NULL);
|
||||
mv->synching = FALSE;
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
enum_value_to_nick (GType enum_type, guint value)
|
||||
{
|
||||
GEnumClass *enum_info;
|
||||
GEnumValue *v;
|
||||
const gchar *nick;
|
||||
|
||||
enum_info = (GEnumClass *) (g_type_class_ref (enum_type));
|
||||
g_return_val_if_fail (enum_info != NULL, NULL);
|
||||
|
||||
v = g_enum_get_value (enum_info, value);
|
||||
g_return_val_if_fail (v != NULL, NULL);
|
||||
|
||||
nick = v->value_nick;
|
||||
|
||||
g_type_class_unref (enum_info);
|
||||
|
||||
return nick;
|
||||
}
|
||||
|
||||
static void
|
||||
sync_downmix (GstMViewWidget * mv)
|
||||
{
|
||||
mv->synching = TRUE;
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (mv->downmix_combo),
|
||||
enum_value_to_nick (GST_TYPE_GL_STEREO_DOWNMIX, mv->downmix_mode));
|
||||
mv->synching = FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
set_downmix_mode (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
GstMViewWidget *mv = GST_MVIEW_WIDGET (data);
|
||||
gchar *downmix_mode = NULL;
|
||||
GEnumClass *p_class;
|
||||
GEnumValue *v;
|
||||
GParamSpec *p =
|
||||
g_object_class_find_property (G_OBJECT_GET_CLASS (mv), "downmix-mode");
|
||||
|
||||
g_return_val_if_fail (p != NULL, FALSE);
|
||||
|
||||
p_class = G_PARAM_SPEC_ENUM (p)->enum_class;
|
||||
g_return_val_if_fail (p_class != NULL, FALSE);
|
||||
|
||||
g_object_get (G_OBJECT (widget), "active-id", &downmix_mode, NULL);
|
||||
g_return_val_if_fail (downmix_mode != NULL, FALSE);
|
||||
|
||||
v = g_enum_get_value_by_nick (p_class, downmix_mode);
|
||||
g_return_val_if_fail (v != NULL, FALSE);
|
||||
|
||||
mv->downmix_mode = v->value;
|
||||
if (!mv->synching)
|
||||
g_object_notify (G_OBJECT (mv), "downmix-mode");
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_mview_widget_constructed (GObject * o)
|
||||
{
|
||||
GstMViewWidget *mv = GST_MVIEW_WIDGET (o);
|
||||
GtkGrid *g = GTK_GRID (mv);
|
||||
GtkWidget *w;
|
||||
|
||||
gtk_widget_set_has_window (GTK_WIDGET (mv), FALSE);
|
||||
|
||||
if (mv->is_output) {
|
||||
mv->mode_selector = w = combo_box_from_enum (GST_TYPE_VIDEO_MULTIVIEW_MODE);
|
||||
gtk_grid_attach (g, gtk_label_new ("Output:"), 0, 0, 1, 1);
|
||||
} else {
|
||||
mv->mode_selector = w =
|
||||
combo_box_from_enum (GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING);
|
||||
gtk_grid_attach (g, gtk_label_new ("Input:"), 0, 0, 1, 1);
|
||||
}
|
||||
gtk_grid_attach (g, mv->mode_selector, 1, 0, 3, 1);
|
||||
|
||||
gtk_grid_attach (g, gtk_label_new (" Left "), 4, 0, 1, 1);
|
||||
mv->lflip = w = gtk_toggle_button_new_with_label ("Flip");
|
||||
link_button_to_flag (mv, w, GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED);
|
||||
gtk_grid_attach (g, w, 5, 0, 1, 1);
|
||||
mv->lflop = w = gtk_toggle_button_new_with_label ("Flop");
|
||||
link_button_to_flag (mv, w, GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED);
|
||||
gtk_grid_attach (g, w, 6, 0, 1, 1);
|
||||
|
||||
gtk_grid_attach (g, gtk_label_new (" Right "), 4, 1, 1, 1);
|
||||
mv->rflip = w = gtk_toggle_button_new_with_label ("Flip");
|
||||
link_button_to_flag (mv, w, GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED);
|
||||
gtk_grid_attach (g, w, 5, 1, 1, 1);
|
||||
mv->rflop = w = gtk_toggle_button_new_with_label ("Flop");
|
||||
link_button_to_flag (mv, w, GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED);
|
||||
gtk_grid_attach (g, w, 6, 1, 1, 1);
|
||||
|
||||
mv->right_first = w = gtk_toggle_button_new_with_label ("Left/Right swap");
|
||||
link_button_to_flag (mv, w, GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST);
|
||||
gtk_grid_attach (g, w, 1, 1, 1, 1);
|
||||
mv->half_aspect = w = gtk_toggle_button_new_with_label ("Half-Aspect");
|
||||
link_button_to_flag (mv, w, GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT);
|
||||
gtk_grid_attach (g, w, 2, 1, 1, 1);
|
||||
|
||||
if (mv->is_output) {
|
||||
mv->downmix_combo = w = combo_box_from_enum (GST_TYPE_GL_STEREO_DOWNMIX);
|
||||
gtk_grid_attach (g, w, 1, 2, 3, 1);
|
||||
sync_downmix (mv);
|
||||
g_signal_connect (G_OBJECT (w), "changed",
|
||||
G_CALLBACK (set_downmix_mode), mv);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_mview_widget_set_property (GObject * object,
|
||||
guint prop_id, const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstMViewWidget *mv = GST_MVIEW_WIDGET (object);
|
||||
switch (prop_id) {
|
||||
case PROP_IS_OUTPUT:
|
||||
mv->is_output = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_FLAGS:
|
||||
mv->flags = (GstVideoMultiviewFlags) g_value_get_flags (value);
|
||||
sync_flags (mv);
|
||||
break;
|
||||
case PROP_DOWNMIX_MODE:
|
||||
mv->downmix_mode = g_value_get_enum (value);
|
||||
sync_downmix (mv);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_mview_widget_get_property (GObject * object,
|
||||
guint prop_id, GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstMViewWidget *mv = GST_MVIEW_WIDGET (object);
|
||||
switch (prop_id) {
|
||||
case PROP_IS_OUTPUT:
|
||||
g_value_set_boolean (value, mv->is_output);
|
||||
break;
|
||||
case PROP_MODE_SELECTOR:
|
||||
g_value_set_object (value, mv->mode_selector);
|
||||
break;
|
||||
case PROP_FLAGS:
|
||||
g_value_set_flags (value, mv->flags);
|
||||
break;
|
||||
case PROP_DOWNMIX_MODE:
|
||||
g_value_set_enum (value, mv->downmix_mode);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
combo_box_from_enum (GType enum_type)
|
||||
{
|
||||
GEnumClass *enum_info;
|
||||
GtkWidget *combo;
|
||||
guint i;
|
||||
|
||||
enum_info = (GEnumClass *) (g_type_class_ref (enum_type));
|
||||
g_return_val_if_fail (enum_info != NULL, NULL);
|
||||
|
||||
combo = gtk_combo_box_text_new ();
|
||||
for (i = 0; i < enum_info->n_values; i++) {
|
||||
GEnumValue *v = enum_info->values + i;
|
||||
gtk_combo_box_text_insert (GTK_COMBO_BOX_TEXT (combo),
|
||||
i, v->value_nick, v->value_name);
|
||||
}
|
||||
|
||||
g_type_class_unref (enum_info);
|
||||
|
||||
return combo;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gst_mview_widget_new (gboolean is_output)
|
||||
{
|
||||
GtkWidget *ret;
|
||||
|
||||
ret = g_object_new (GST_TYPE_MVIEW_WIDGET, "is-output", is_output, NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2014-2015 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.
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include <gst/video/video-info.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_MVIEW_WIDGET (gst_mview_widget_get_type())
|
||||
#define GST_MVIEW_WIDGET(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MVIEW_WIDGET, GstMViewWidget))
|
||||
#define GST_MVIEW_WIDGET_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MVIEW_WIDGET, GstMViewWidgetClass))
|
||||
#define GST_IS_MVIEW_WIDGET(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MVIEW_WIDGET))
|
||||
#define GST_IS_MVIEW_WIDGET_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MVIEW_WIDGET))
|
||||
#define GST_MVIEW_WIDGET_GET_CLASS(obj) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_MVIEW_WIDGET,GstMViewWidgetClass))
|
||||
|
||||
typedef struct _GstMViewWidget GstMViewWidget;
|
||||
typedef struct _GstMViewWidgetClass GstMViewWidgetClass;
|
||||
|
||||
struct _GstMViewWidget {
|
||||
GtkGrid parent;
|
||||
|
||||
gboolean is_output;
|
||||
|
||||
GtkWidget *mode_selector;
|
||||
|
||||
GstVideoMultiviewMode mode;
|
||||
GstVideoMultiviewFlags flags;
|
||||
GstGLStereoDownmix downmix_mode;
|
||||
|
||||
/* Array of toggle buttons for flags */
|
||||
GtkWidget *lflip;
|
||||
GtkWidget *lflop;
|
||||
GtkWidget *rflip;
|
||||
GtkWidget *rflop;
|
||||
GtkWidget *half_aspect;
|
||||
GtkWidget *right_first;
|
||||
|
||||
GtkWidget *downmix_combo;
|
||||
|
||||
gboolean synching;
|
||||
};
|
||||
|
||||
struct _GstMViewWidgetClass {
|
||||
GtkGridClass parent;
|
||||
};
|
||||
|
||||
GType gst_mview_widget_get_type (void);
|
||||
GtkWidget *gst_mview_widget_new (gboolean is_output);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,27 @@
|
||||
--- Description of the GTK examples ---
|
||||
|
||||
- gtkvideooverlay:
|
||||
Show how to use the videooverlay interface through GTK.
|
||||
It's possible to switch bettween GST_STATE through four buttons.
|
||||
|
||||
-filternovideooverlay:
|
||||
A more complex pipeline is switched bettween the GST states
|
||||
without using the videooverlay interface.
|
||||
|
||||
-filtervideooverlay:
|
||||
A more complex pipeline is switched bettween the GST states.
|
||||
using the videooverlay interface.
|
||||
|
||||
-fxtest:
|
||||
switch bettween effects of the gleffects filter.
|
||||
|
||||
-pixbufdrop:
|
||||
drag and drop a png file and overlay it using alpha channel.
|
||||
It uses gloverlay filter.
|
||||
|
||||
-switchvideooverlay:
|
||||
change the videooverlay window while the stream is playing.
|
||||
|
||||
--- How to build the GTK examples ---
|
||||
|
||||
Using autotools or Meson.
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2014 Matthew Waters <ystreet00@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
|
||||
static void end_stream_cb(GstBus* bus, GstMessage* message, GstElement* pipeline)
|
||||
{
|
||||
g_print("End of stream\n");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref(pipeline);
|
||||
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
||||
static void button_state_null_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
g_print ("GST_STATE_NULL\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_ready_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_READY);
|
||||
g_print ("GST_STATE_READY\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_paused_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
||||
g_print ("GST_STATE_PAUSED\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_playing_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
g_print ("GST_STATE_PLAYING\n");
|
||||
}
|
||||
|
||||
|
||||
static gchar* slider_fps_cb (GtkScale* scale, gdouble value, GstElement* pipeline)
|
||||
{
|
||||
//change the video frame rate dynamically
|
||||
return g_strdup_printf ("video framerate: %0.*g", gtk_scale_get_digits (scale), value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
gint main (gint argc, gchar *argv[])
|
||||
{
|
||||
gtk_init (&argc, &argv);
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
GstElement* pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
//window to control the states
|
||||
GtkWidget* window_control = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
GdkGeometry geometry;
|
||||
geometry.min_width = 1;
|
||||
geometry.min_height = 1;
|
||||
geometry.max_width = -1;
|
||||
geometry.max_height = -1;
|
||||
gtk_window_set_geometry_hints (GTK_WINDOW (window_control), window_control, &geometry, GDK_HINT_MIN_SIZE);
|
||||
gtk_window_set_resizable (GTK_WINDOW (window_control), FALSE);
|
||||
gtk_window_move (GTK_WINDOW (window_control), 10, 10);
|
||||
GtkWidget* table = gtk_grid_new ();
|
||||
gtk_container_add (GTK_CONTAINER (window_control), table);
|
||||
|
||||
//control state null
|
||||
GtkWidget* button_state_null = gtk_button_new_with_label ("GST_STATE_NULL");
|
||||
g_signal_connect (G_OBJECT (button_state_null), "clicked",
|
||||
G_CALLBACK (button_state_null_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_null, 0, 0, 1, 1);
|
||||
gtk_widget_show (button_state_null);
|
||||
|
||||
//control state ready
|
||||
GtkWidget* button_state_ready = gtk_button_new_with_label ("GST_STATE_READY");
|
||||
g_signal_connect (G_OBJECT (button_state_ready), "clicked",
|
||||
G_CALLBACK (button_state_ready_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_ready, 0, 1, 1, 1);
|
||||
gtk_widget_show (button_state_ready);
|
||||
|
||||
//control state paused
|
||||
GtkWidget* button_state_paused = gtk_button_new_with_label ("GST_STATE_PAUSED");
|
||||
g_signal_connect (G_OBJECT (button_state_paused), "clicked",
|
||||
G_CALLBACK (button_state_paused_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_paused, 0, 2, 1, 1);
|
||||
gtk_widget_show (button_state_paused);
|
||||
|
||||
//control state playing
|
||||
GtkWidget* button_state_playing = gtk_button_new_with_label ("GST_STATE_PLAYING");
|
||||
g_signal_connect (G_OBJECT (button_state_playing), "clicked",
|
||||
G_CALLBACK (button_state_playing_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_playing, 0, 3, 1, 1);
|
||||
gtk_widget_show (button_state_playing);
|
||||
|
||||
//change framerate
|
||||
GtkWidget* slider_fps = gtk_scale_new_with_range (GTK_ORIENTATION_VERTICAL,
|
||||
1, 30, 2);
|
||||
g_signal_connect (G_OBJECT (slider_fps), "format-value",
|
||||
G_CALLBACK (slider_fps_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), slider_fps, 1, 0, 1, 3);
|
||||
gtk_widget_show (slider_fps);
|
||||
|
||||
gtk_widget_show (table);
|
||||
gtk_widget_show (window_control);
|
||||
|
||||
GstElement* videosrc = gst_element_factory_make ("videotestsrc", "videotestsrc");
|
||||
GstElement* upload = gst_element_factory_make ("glupload", "glupload");
|
||||
GstElement* glfiltercube = gst_element_factory_make ("glfiltercube", "glfiltercube");
|
||||
GstElement* videosink = gst_element_factory_make ("glimagesink", "glimagesink");
|
||||
|
||||
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
||||
"width", G_TYPE_INT, 640,
|
||||
"height", G_TYPE_INT, 480,
|
||||
"framerate", GST_TYPE_FRACTION, 25, 1,
|
||||
"format", G_TYPE_STRING, "RGBA",
|
||||
NULL) ;
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), videosrc, upload, glfiltercube, videosink, NULL);
|
||||
|
||||
gboolean link_ok = gst_element_link_filtered(videosrc, upload, caps) ;
|
||||
gst_caps_unref(caps) ;
|
||||
if(!link_ok)
|
||||
{
|
||||
g_warning("Failed to link videosrc to glfiltercube!\n") ;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!gst_element_link_many(upload, glfiltercube, videosink, NULL))
|
||||
{
|
||||
g_warning("Failed to link glfiltercube to videosink!\n") ;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//set window id on this event
|
||||
GstBus* bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch (bus);
|
||||
g_signal_connect(bus, "message::error", G_CALLBACK(end_stream_cb), pipeline);
|
||||
g_signal_connect(bus, "message::warning", G_CALLBACK(end_stream_cb), pipeline);
|
||||
g_signal_connect(bus, "message::eos", G_CALLBACK(end_stream_cb), pipeline);
|
||||
gst_object_unref (bus);
|
||||
|
||||
//start
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
{
|
||||
g_print ("Failed to start up pipeline!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gtk_main();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
executable('filternovideooverlay', 'main.cpp',
|
||||
cpp_args : [gst_plugins_base_args],
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [gstgtkhelper_dep],
|
||||
install: false)
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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 <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
#include "../gstgtk.h"
|
||||
|
||||
#ifdef HAVE_X11
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
|
||||
static GstBusSyncReply create_window (GstBus* bus, GstMessage* message, GtkWidget* widget)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
|
||||
if (gst_gtk_handle_need_context (bus, message, NULL))
|
||||
return GST_BUS_DROP;
|
||||
|
||||
// ignore anything but 'prepare-window-handle' element messages
|
||||
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
|
||||
return GST_BUS_PASS;
|
||||
|
||||
if (!gst_is_video_overlay_prepare_window_handle_message (message))
|
||||
return GST_BUS_PASS;
|
||||
|
||||
g_print ("setting window handle %p\n", widget);
|
||||
|
||||
gst_video_overlay_set_gtk_window (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)), widget);
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)), allocation.x, allocation.y, allocation.width, allocation.height);
|
||||
|
||||
gst_message_unref (message);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
resize_cb (GtkWidget * widget, GdkEvent * event, gpointer sink)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink), allocation.x, allocation.y, allocation.width, allocation.height);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void end_stream_cb(GstBus* bus, GstMessage* message, GstElement* pipeline)
|
||||
{
|
||||
GError *error = NULL;
|
||||
gchar *details;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
gst_message_parse_error (message, &error, &details);
|
||||
|
||||
g_print("Error %s\n", error->message);
|
||||
g_print("Details %s\n", details);
|
||||
/* fallthrough */
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print("End of stream\n");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref(pipeline);
|
||||
|
||||
gtk_main_quit();
|
||||
break;
|
||||
case GST_MESSAGE_WARNING:
|
||||
gst_message_parse_warning (message, &error, &details);
|
||||
|
||||
g_print("Warning %s\n", error->message);
|
||||
g_print("Details %s\n", details);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean expose_cb(GtkWidget* widget, cairo_t *cr, GstElement* videosink)
|
||||
{
|
||||
gst_video_overlay_expose (GST_VIDEO_OVERLAY (videosink));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void destroy_cb(GtkWidget* widget, GdkEvent* event, GstElement* pipeline)
|
||||
{
|
||||
g_print("Close\n");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref(pipeline);
|
||||
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
||||
|
||||
static void button_state_null_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
g_print ("GST_STATE_NULL\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_ready_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_READY);
|
||||
g_print ("GST_STATE_READY\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_paused_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
||||
g_print ("GST_STATE_PAUSED\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_playing_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
g_print ("GST_STATE_PLAYING\n");
|
||||
}
|
||||
|
||||
|
||||
static gchar* slider_fps_cb (GtkScale* scale, gdouble value, GstElement* pipeline)
|
||||
{
|
||||
//change the video frame rate dynamically
|
||||
return g_strdup_printf ("video framerate: %0.*g", gtk_scale_get_digits (scale), value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
gint main (gint argc, gchar *argv[])
|
||||
{
|
||||
#ifdef HAVE_X11
|
||||
XInitThreads ();
|
||||
#endif
|
||||
|
||||
gtk_init (&argc, &argv);
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
GstElement* pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
//window that contains an area where the video is drawn
|
||||
GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_widget_set_size_request (window, 640, 480);
|
||||
gtk_window_move (GTK_WINDOW (window), 300, 10);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "glimagesink implement the gstvideooverlay interface");
|
||||
GdkGeometry geometry;
|
||||
geometry.min_width = 1;
|
||||
geometry.min_height = 1;
|
||||
geometry.max_width = -1;
|
||||
geometry.max_height = -1;
|
||||
gtk_window_set_geometry_hints (GTK_WINDOW (window), window, &geometry, GDK_HINT_MIN_SIZE);
|
||||
|
||||
//window to control the states
|
||||
GtkWidget* window_control = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
geometry.min_width = 1;
|
||||
geometry.min_height = 1;
|
||||
geometry.max_width = -1;
|
||||
geometry.max_height = -1;
|
||||
gtk_window_set_geometry_hints (GTK_WINDOW (window_control), window_control, &geometry, GDK_HINT_MIN_SIZE);
|
||||
gtk_window_set_resizable (GTK_WINDOW (window_control), FALSE);
|
||||
gtk_window_move (GTK_WINDOW (window_control), 10, 10);
|
||||
GtkWidget* grid = gtk_grid_new ();
|
||||
gtk_container_add (GTK_CONTAINER (window_control), grid);
|
||||
|
||||
//control state null
|
||||
GtkWidget* button_state_null = gtk_button_new_with_label ("GST_STATE_NULL");
|
||||
g_signal_connect (G_OBJECT (button_state_null), "clicked",
|
||||
G_CALLBACK (button_state_null_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (grid), button_state_null, 0, 1, 1, 1);
|
||||
gtk_widget_show (button_state_null);
|
||||
|
||||
//control state ready
|
||||
GtkWidget* button_state_ready = gtk_button_new_with_label ("GST_STATE_READY");
|
||||
g_signal_connect (G_OBJECT (button_state_ready), "clicked",
|
||||
G_CALLBACK (button_state_ready_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (grid), button_state_ready, 0, 2, 1, 1);
|
||||
gtk_widget_show (button_state_ready);
|
||||
|
||||
//control state paused
|
||||
GtkWidget* button_state_paused = gtk_button_new_with_label ("GST_STATE_PAUSED");
|
||||
g_signal_connect (G_OBJECT (button_state_paused), "clicked",
|
||||
G_CALLBACK (button_state_paused_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (grid), button_state_paused, 0, 3, 1, 1);
|
||||
gtk_widget_show (button_state_paused);
|
||||
|
||||
//control state playing
|
||||
GtkWidget* button_state_playing = gtk_button_new_with_label ("GST_STATE_PLAYING");
|
||||
g_signal_connect (G_OBJECT (button_state_playing), "clicked",
|
||||
G_CALLBACK (button_state_playing_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (grid), button_state_playing, 0, 4, 1, 1);
|
||||
gtk_widget_show (button_state_playing);
|
||||
|
||||
//change framerate
|
||||
GtkWidget* slider_fps = gtk_scale_new_with_range (GTK_ORIENTATION_VERTICAL, 1, 30, 2);
|
||||
g_signal_connect (G_OBJECT (slider_fps), "format-value",
|
||||
G_CALLBACK (slider_fps_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (grid), slider_fps, 1, 0, 1, 5);
|
||||
gtk_widget_show (slider_fps);
|
||||
|
||||
gtk_widget_show (grid);
|
||||
gtk_widget_show (window_control);
|
||||
|
||||
//configure the pipeline
|
||||
g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(destroy_cb), pipeline);
|
||||
|
||||
GstElement* videosrc = gst_element_factory_make ("videotestsrc", "videotestsrc");
|
||||
GstElement* upload = gst_element_factory_make ("glupload", "glupload");
|
||||
GstElement* glfiltercube = gst_element_factory_make ("glfiltercube", "glfiltercube");
|
||||
GstElement* videosink = gst_element_factory_make ("glimagesink", "glimagesink");
|
||||
|
||||
GstCaps *caps = gst_caps_new_simple("video/x-raw",
|
||||
"width", G_TYPE_INT, 640,
|
||||
"height", G_TYPE_INT, 480,
|
||||
"framerate", GST_TYPE_FRACTION, 25, 1,
|
||||
"format", G_TYPE_STRING, "RGBA",
|
||||
NULL) ;
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), videosrc, upload, glfiltercube, videosink, NULL);
|
||||
|
||||
gboolean link_ok = gst_element_link_filtered(videosrc, upload, caps) ;
|
||||
gst_caps_unref(caps) ;
|
||||
if(!link_ok)
|
||||
{
|
||||
g_warning("Failed to link videosrc to glfiltercube!\n") ;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!gst_element_link_many(upload, glfiltercube, videosink, NULL))
|
||||
{
|
||||
g_warning("Failed to link glfiltercube to videosink!\n") ;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//area where the video is drawn
|
||||
GtkWidget* area = gtk_drawing_area_new();
|
||||
gtk_widget_set_redraw_on_allocate (area, TRUE);
|
||||
gtk_container_add (GTK_CONTAINER (window), area);
|
||||
|
||||
gtk_widget_realize(area);
|
||||
|
||||
//set window id on this event
|
||||
GstBus* bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) create_window, area, NULL);
|
||||
gst_bus_add_signal_watch (bus);
|
||||
g_signal_connect(bus, "message::error", G_CALLBACK(end_stream_cb), pipeline);
|
||||
g_signal_connect(bus, "message::warning", G_CALLBACK(end_stream_cb), pipeline);
|
||||
g_signal_connect(bus, "message::eos", G_CALLBACK(end_stream_cb), pipeline);
|
||||
gst_object_unref (bus);
|
||||
|
||||
//needed when being in GST_STATE_READY, GST_STATE_PAUSED
|
||||
//or resizing/obscuring the window
|
||||
g_signal_connect(area, "draw", G_CALLBACK(expose_cb), videosink);
|
||||
g_signal_connect(area, "configure-event", G_CALLBACK(resize_cb), videosink);
|
||||
|
||||
//start
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
{
|
||||
g_print ("Failed to start up pipeline!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gtk_widget_show_all (window);
|
||||
|
||||
gtk_main();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
executable('filtervideooverlay', 'main.cpp',
|
||||
cpp_args : [gst_plugins_base_args],
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [gstgtkhelper_dep, x11_dep],
|
||||
install: false)
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Filippo Argiolas <filippo.argiolas@gmail.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
|
||||
|
||||
#define GLIB_DISABLE_DEPRECATION_WARNINGS
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
|
||||
#include "../gstgtk.h"
|
||||
|
||||
#include <gst/video/videooverlay.h>
|
||||
|
||||
#ifdef HAVE_X11
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
|
||||
|
||||
static GstBusSyncReply
|
||||
create_window (GstBus * bus, GstMessage * message, GtkWidget * widget)
|
||||
{
|
||||
if (gst_gtk_handle_need_context (bus, message, NULL))
|
||||
return GST_BUS_DROP;
|
||||
|
||||
/* ignore anything but 'prepare-window-handle' element messages */
|
||||
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
|
||||
return GST_BUS_PASS;
|
||||
|
||||
if (!gst_is_video_overlay_prepare_window_handle_message (message))
|
||||
return GST_BUS_PASS;
|
||||
|
||||
g_print ("setting window handle\n");
|
||||
|
||||
/* do not call gdk_window_ensure_native for the first time here because
|
||||
* we are in a different thread than the main thread
|
||||
* (and the main thread the one) */
|
||||
gst_video_overlay_set_gtk_window (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC
|
||||
(message)), widget);
|
||||
|
||||
gst_message_unref (message);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
end_stream_cb (GstBus * bus, GstMessage * message, GstElement * pipeline)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print ("End of stream\n");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
gtk_main_quit ();
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *debug = NULL;
|
||||
GError *err = NULL;
|
||||
|
||||
gst_message_parse_error (message, &err, &debug);
|
||||
|
||||
g_print ("Error: %s\n", err->message);
|
||||
g_clear_error (&err);
|
||||
|
||||
if (debug) {
|
||||
g_print ("Debug details: %s\n", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
gtk_main_quit ();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
resize_cb (GtkWidget * widget, GdkEvent * event, gpointer data)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
GstVideoOverlay *overlay =
|
||||
GST_VIDEO_OVERLAY (gst_bin_get_by_interface (GST_BIN (data),
|
||||
GST_TYPE_VIDEO_OVERLAY));
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
gst_video_overlay_set_render_rectangle (overlay, allocation.x, allocation.y,
|
||||
allocation.width, allocation.height);
|
||||
gst_object_unref (overlay);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
expose_cb (GtkWidget * widget, gpointer unused, gpointer data)
|
||||
{
|
||||
GstVideoOverlay *overlay =
|
||||
GST_VIDEO_OVERLAY (gst_bin_get_by_interface (GST_BIN (data),
|
||||
GST_TYPE_VIDEO_OVERLAY));
|
||||
|
||||
gst_video_overlay_expose (overlay);
|
||||
gst_object_unref (overlay);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_cb (GtkWidget * widget, GdkEvent * event, GstElement * pipeline)
|
||||
{
|
||||
g_message ("destroy callback");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
static gboolean
|
||||
apply_fx (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
gchar *fx;
|
||||
GEnumClass *p_class;
|
||||
|
||||
/* heeeellppppp!! */
|
||||
p_class =
|
||||
G_PARAM_SPEC_ENUM (g_object_class_find_property (G_OBJECT_GET_CLASS
|
||||
(G_OBJECT (data)), "effect")
|
||||
)->enum_class;
|
||||
|
||||
fx = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (widget));
|
||||
g_print ("setting: %s - %s\n", fx, g_enum_get_value_by_nick (p_class,
|
||||
fx)->value_name);
|
||||
g_object_set (G_OBJECT (data), "effect", g_enum_get_value_by_nick (p_class,
|
||||
fx)->value, NULL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
play_cb (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
g_message ("playing");
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_PLAYING);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
null_cb (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
g_message ("nulling");
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_NULL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ready_cb (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
g_message ("readying");
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_READY);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pause_cb (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
g_message ("pausing");
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_PAUSED);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar * argv[])
|
||||
{
|
||||
GstStateChangeReturn ret;
|
||||
GstElement *pipeline;
|
||||
GstElement *upload, *filter, *sink;
|
||||
GstElement *sourcebin;
|
||||
GstBus *bus;
|
||||
GError *error = NULL;
|
||||
|
||||
GtkWidget *window;
|
||||
GtkWidget *screen;
|
||||
GtkWidget *vbox, *combo;
|
||||
GtkWidget *hbox;
|
||||
GtkWidget *play, *pause, *null, *ready;
|
||||
|
||||
gchar **source_desc_array = NULL;
|
||||
gchar *source_desc = NULL;
|
||||
|
||||
GOptionContext *context;
|
||||
GOptionEntry options[] = {
|
||||
{"source-bin", 's', 0, G_OPTION_ARG_STRING_ARRAY, &source_desc_array,
|
||||
"Use a custom source bin description (gst-launch style)", NULL}
|
||||
,
|
||||
{NULL}
|
||||
};
|
||||
|
||||
#ifdef HAVE_X11
|
||||
XInitThreads ();
|
||||
#endif
|
||||
|
||||
context = g_option_context_new (NULL);
|
||||
g_option_context_add_main_entries (context, options, NULL);
|
||||
g_option_context_add_group (context, gst_init_get_option_group ());
|
||||
g_option_context_add_group (context, gtk_get_option_group (TRUE));
|
||||
if (!g_option_context_parse (context, &argc, &argv, &error)) {
|
||||
g_print ("Inizialization error: %s\n", GST_STR_NULL (error->message));
|
||||
g_option_context_free (context);
|
||||
g_clear_error (&error);
|
||||
return -1;
|
||||
}
|
||||
g_option_context_free (context);
|
||||
|
||||
if (source_desc_array != NULL) {
|
||||
source_desc = g_strjoinv (" ", source_desc_array);
|
||||
g_strfreev (source_desc_array);
|
||||
}
|
||||
if (source_desc == NULL) {
|
||||
source_desc =
|
||||
g_strdup
|
||||
("videotestsrc ! video/x-raw, width=352, height=288 ! identity");
|
||||
}
|
||||
|
||||
sourcebin =
|
||||
gst_parse_bin_from_description (g_strdup (source_desc), TRUE, &error);
|
||||
g_free (source_desc);
|
||||
if (error) {
|
||||
g_print ("Error while parsing source bin description: %s\n",
|
||||
GST_STR_NULL (error->message));
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_set_application_name ("gst-gl-effects test app");
|
||||
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_container_set_border_width (GTK_CONTAINER (window), 3);
|
||||
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
upload = gst_element_factory_make ("glupload", NULL);
|
||||
filter = gst_element_factory_make ("gleffects", "flt");
|
||||
sink = gst_element_factory_make ("glimagesink", "glsink");
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), sourcebin, upload, filter, sink, NULL);
|
||||
|
||||
if (!gst_element_link_many (sourcebin, upload, filter, sink, NULL)) {
|
||||
g_print ("Failed to link one or more elements!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_signal_connect (G_OBJECT (window), "delete-event",
|
||||
G_CALLBACK (destroy_cb), pipeline);
|
||||
g_signal_connect (G_OBJECT (window), "destroy-event",
|
||||
G_CALLBACK (destroy_cb), pipeline);
|
||||
|
||||
screen = gtk_drawing_area_new ();
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_signal_watch (bus);
|
||||
g_signal_connect (bus, "message::error", G_CALLBACK (end_stream_cb),
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::warning", G_CALLBACK (end_stream_cb),
|
||||
pipeline);
|
||||
g_signal_connect (bus, "message::eos", G_CALLBACK (end_stream_cb), pipeline);
|
||||
|
||||
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) create_window, screen,
|
||||
NULL);
|
||||
gst_object_unref (bus);
|
||||
|
||||
gtk_widget_set_size_request (screen, 640, 480); // 500 x 376
|
||||
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
|
||||
|
||||
gtk_box_pack_start (GTK_BOX (vbox), screen, TRUE, TRUE, 0);
|
||||
|
||||
combo = gtk_combo_box_text_new ();
|
||||
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "identity");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "mirror");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "squeeze");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "stretch");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "fisheye");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "twirl");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "bulge");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "tunnel");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "square");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "heat");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "xpro");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "lumaxpro");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "sepia");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "xray");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "sin");
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), "glow");
|
||||
|
||||
g_signal_connect (G_OBJECT (combo), "changed", G_CALLBACK (apply_fx), filter);
|
||||
|
||||
gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0);
|
||||
|
||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
|
||||
play = gtk_button_new_with_label ("PLAY");
|
||||
|
||||
g_signal_connect (G_OBJECT (play), "clicked", G_CALLBACK (play_cb), pipeline);
|
||||
|
||||
pause = gtk_button_new_with_label ("PAUSE");
|
||||
|
||||
g_signal_connect (G_OBJECT (pause), "clicked",
|
||||
G_CALLBACK (pause_cb), pipeline);
|
||||
|
||||
null = gtk_button_new_with_label ("NULL");
|
||||
|
||||
g_signal_connect (G_OBJECT (null), "clicked", G_CALLBACK (null_cb), pipeline);
|
||||
|
||||
ready = gtk_button_new_with_label ("READY");
|
||||
|
||||
g_signal_connect (G_OBJECT (ready), "clicked",
|
||||
G_CALLBACK (ready_cb), pipeline);
|
||||
|
||||
gtk_box_pack_start (GTK_BOX (hbox), null, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), ready, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), play, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), pause, TRUE, TRUE, 0);
|
||||
|
||||
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (window), vbox);
|
||||
|
||||
g_signal_connect (screen, "draw", G_CALLBACK (expose_cb), pipeline);
|
||||
g_signal_connect (screen, "configure-event", G_CALLBACK (resize_cb),
|
||||
pipeline);
|
||||
gtk_widget_realize (screen);
|
||||
|
||||
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
g_print ("Failed to start up pipeline!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
gtk_widget_show_all (GTK_WIDGET (window));
|
||||
|
||||
gtk_main ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
executable('fxtest', 'fxtest.c',
|
||||
c_args : [gst_plugins_base_args],
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [gstgtkhelper_dep, x11_dep],
|
||||
install: false)
|
||||
|
||||
executable('pixbufdrop', 'pixbufdrop.c',
|
||||
c_args : [gst_plugins_base_args],
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [gstgtkhelper_dep, x11_dep],
|
||||
install: false)
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Filippo Argiolas <filippo.argiolas@gmail.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
|
||||
|
||||
#define GLIB_DISABLE_DEPRECATION_WARNINGS
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
|
||||
#include "../gstgtk.h"
|
||||
|
||||
#include <gst/video/videooverlay.h>
|
||||
|
||||
#ifdef HAVE_X11
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
|
||||
static gint delay = 0;
|
||||
static gint saveddelay = 0;
|
||||
static gint method = 1;
|
||||
|
||||
struct _SourceData
|
||||
{
|
||||
gpointer data;
|
||||
gpointer nick;
|
||||
gpointer value;
|
||||
};
|
||||
typedef struct _SourceData SourceData;
|
||||
|
||||
static GstBusSyncReply
|
||||
create_window (GstBus * bus, GstMessage * message, GtkWidget * widget)
|
||||
{
|
||||
// ignore anything but 'prepare-window-handle' element messages
|
||||
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
|
||||
return GST_BUS_PASS;
|
||||
|
||||
if (!gst_is_video_overlay_prepare_window_handle_message (message))
|
||||
return GST_BUS_PASS;
|
||||
|
||||
gst_video_overlay_set_gtk_window (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC
|
||||
(message)), widget);
|
||||
|
||||
gst_message_unref (message);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
static void
|
||||
message_cb (GstBus * bus, GstMessage * message, GstElement * pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
static gboolean
|
||||
expose_cb (GtkWidget * widget, cairo_t * cr, GstElement * videosink)
|
||||
{
|
||||
gst_video_overlay_expose (GST_VIDEO_OVERLAY (videosink));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_cb (GtkWidget * widget, GdkEvent * event, GstElement * pipeline)
|
||||
{
|
||||
g_message ("destroy callback");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
static gboolean
|
||||
play_cb (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
g_message ("playing");
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_PLAYING);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
null_cb (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
g_message ("nulling");
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_NULL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ready_cb (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
g_message ("readying");
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_READY);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pause_cb (GtkWidget * widget, gpointer data)
|
||||
{
|
||||
g_message ("pausing");
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_PAUSED);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
set_location_delayed (gpointer data)
|
||||
{
|
||||
SourceData *sdata = (SourceData *) data;
|
||||
delay--;
|
||||
g_print ("%d\n", delay);
|
||||
if (delay > 0) {
|
||||
return TRUE;
|
||||
}
|
||||
g_object_set (G_OBJECT (sdata->data), sdata->nick, sdata->value, NULL);
|
||||
delay = saveddelay;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_drag_data_received (GtkWidget * widget,
|
||||
GdkDragContext * context, int x, int y,
|
||||
GtkSelectionData * seldata, guint inf, guint time, gpointer data)
|
||||
{
|
||||
SourceData *userdata = g_new0 (SourceData, 1);
|
||||
GdkPixbufFormat *format;
|
||||
gchar **uris = gtk_selection_data_get_uris (seldata);
|
||||
gchar *filename = NULL;
|
||||
|
||||
g_return_if_fail (uris != NULL);
|
||||
filename = g_filename_from_uri (uris[0], NULL, NULL);
|
||||
g_return_if_fail (filename != NULL);
|
||||
format = gdk_pixbuf_get_file_info (filename, NULL, NULL);
|
||||
g_return_if_fail (format);
|
||||
g_print ("received %s image: %s\n", filename,
|
||||
gdk_pixbuf_format_get_name (format));
|
||||
|
||||
userdata->nick = (gchar *) "location";
|
||||
userdata->value = g_strdup (filename);
|
||||
userdata->data = data;
|
||||
saveddelay = delay;
|
||||
if (delay > 0) {
|
||||
g_print ("%d\n", delay);
|
||||
g_timeout_add_seconds (1, set_location_delayed, userdata);
|
||||
} else
|
||||
g_object_set (G_OBJECT (userdata->data), userdata->nick, userdata->value,
|
||||
NULL);
|
||||
g_free (filename);
|
||||
}
|
||||
|
||||
|
||||
gint
|
||||
main (gint argc, gchar * argv[])
|
||||
{
|
||||
GstElement *pipeline;
|
||||
GstElement *filter, *sink;
|
||||
GstElement *sourcebin;
|
||||
GstBus *bus;
|
||||
GError *error = NULL;
|
||||
|
||||
GtkWidget *window;
|
||||
GtkWidget *screen;
|
||||
GtkWidget *vbox;
|
||||
GtkWidget *hbox;
|
||||
GtkWidget *play, *pause, *null, *ready;
|
||||
|
||||
gchar **source_desc_array = NULL;
|
||||
gchar *source_desc = NULL;
|
||||
|
||||
GOptionContext *context;
|
||||
GOptionEntry options[] = {
|
||||
{"source-bin", 's', 0, G_OPTION_ARG_STRING_ARRAY, &source_desc_array,
|
||||
"Use a custom source bin description (gst-launch style)", NULL}
|
||||
,
|
||||
{"method", 'm', 0, G_OPTION_ARG_INT, &method,
|
||||
"1 for gstdifferencematte, 2 for gloverlay", "M"}
|
||||
,
|
||||
{"delay", 'd', 0, G_OPTION_ARG_INT, &delay,
|
||||
"Wait N seconds before to send the image to gstreamer (useful with differencematte)",
|
||||
"N"}
|
||||
,
|
||||
{NULL}
|
||||
};
|
||||
|
||||
#ifdef HAVE_X11
|
||||
XInitThreads ();
|
||||
#endif
|
||||
|
||||
context = g_option_context_new (NULL);
|
||||
g_option_context_add_main_entries (context, options, NULL);
|
||||
g_option_context_add_group (context, gst_init_get_option_group ());
|
||||
g_option_context_add_group (context, gtk_get_option_group (TRUE));
|
||||
if (!g_option_context_parse (context, &argc, &argv, &error)) {
|
||||
g_print ("Inizialization error: %s\n", GST_STR_NULL (error->message));
|
||||
g_option_context_free (context);
|
||||
g_clear_error (&error);
|
||||
return -1;
|
||||
}
|
||||
g_option_context_free (context);
|
||||
|
||||
if (source_desc_array != NULL) {
|
||||
source_desc = g_strjoinv (" ", source_desc_array);
|
||||
g_strfreev (source_desc_array);
|
||||
}
|
||||
if (source_desc == NULL) {
|
||||
source_desc =
|
||||
g_strdup
|
||||
("videotestsrc ! video/x-raw, width=352, height=288 ! identity ! glupload");
|
||||
}
|
||||
|
||||
sourcebin =
|
||||
gst_parse_bin_from_description (g_strdup (source_desc), TRUE, &error);
|
||||
g_free (source_desc);
|
||||
if (error) {
|
||||
g_print ("Error while parsing source bin description: %s\n",
|
||||
GST_STR_NULL (error->message));
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_set_application_name ("gst-gl-effects test app");
|
||||
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_container_set_border_width (GTK_CONTAINER (window), 3);
|
||||
|
||||
pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
if (method == 2) {
|
||||
filter = gst_element_factory_make ("gloverlay", "flt");
|
||||
} else {
|
||||
filter = gst_element_factory_make ("gldifferencematte", "flt");
|
||||
}
|
||||
sink = gst_element_factory_make ("glimagesink", "glsink");
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), sourcebin, filter, sink, NULL);
|
||||
|
||||
if (!gst_element_link_many (sourcebin, filter, sink, NULL)) {
|
||||
g_print ("Failed to link one or more elements!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_signal_connect (G_OBJECT (window), "delete-event",
|
||||
G_CALLBACK (destroy_cb), pipeline);
|
||||
g_signal_connect (G_OBJECT (window), "destroy-event",
|
||||
G_CALLBACK (destroy_cb), pipeline);
|
||||
|
||||
screen = gtk_drawing_area_new ();
|
||||
|
||||
gtk_widget_set_size_request (screen, 640, 480); // 500 x 376
|
||||
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
|
||||
|
||||
gtk_box_pack_start (GTK_BOX (vbox), screen, TRUE, TRUE, 0);
|
||||
|
||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
|
||||
play = gtk_button_new_with_label ("PLAY");
|
||||
|
||||
g_signal_connect (G_OBJECT (play), "clicked", G_CALLBACK (play_cb), pipeline);
|
||||
|
||||
pause = gtk_button_new_with_label ("PAUSE");
|
||||
|
||||
g_signal_connect (G_OBJECT (pause), "clicked",
|
||||
G_CALLBACK (pause_cb), pipeline);
|
||||
|
||||
null = gtk_button_new_with_label ("NULL");
|
||||
|
||||
g_signal_connect (G_OBJECT (null), "clicked", G_CALLBACK (null_cb), pipeline);
|
||||
|
||||
ready = gtk_button_new_with_label ("READY");
|
||||
|
||||
g_signal_connect (G_OBJECT (ready), "clicked",
|
||||
G_CALLBACK (ready_cb), pipeline);
|
||||
|
||||
gtk_box_pack_start (GTK_BOX (hbox), null, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), ready, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), play, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), pause, TRUE, TRUE, 0);
|
||||
|
||||
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (window), vbox);
|
||||
|
||||
gtk_widget_realize (screen);
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) create_window, screen,
|
||||
NULL);
|
||||
gst_bus_add_signal_watch (bus);
|
||||
g_signal_connect (bus, "message::error", G_CALLBACK (message_cb), pipeline);
|
||||
g_signal_connect (bus, "message::warning", G_CALLBACK (message_cb), pipeline);
|
||||
g_signal_connect (bus, "message::eos", G_CALLBACK (message_cb), pipeline);
|
||||
gst_object_unref (bus);
|
||||
g_signal_connect (screen, "draw", G_CALLBACK (expose_cb), sink);
|
||||
|
||||
gtk_drag_dest_set (screen, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
|
||||
gtk_drag_dest_add_uri_targets (screen);
|
||||
|
||||
g_signal_connect (screen, "drag-data-received",
|
||||
G_CALLBACK (on_drag_data_received), filter);
|
||||
|
||||
gtk_widget_show_all (GTK_WIDGET (window));
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
gtk_main ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* 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 <gst/gl/gl.h>
|
||||
#include "gstgtk.h"
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_WIN32 && defined(GDK_WINDOWING_WIN32)
|
||||
#include <gdk/gdkwin32.h>
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined(GDK_WINDOWING_X11)
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined(GDK_WINDOWING_WAYLAND)
|
||||
#include <gdk/gdkwayland.h>
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_COCOA && defined(GDK_WINDOWING_QUARTZ)
|
||||
#include <gdk/gdkquartz.h>
|
||||
#endif
|
||||
|
||||
gboolean
|
||||
gst_gtk_handle_need_context (GstBus * bus, GstMessage * msg, gpointer data)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (msg)) {
|
||||
case GST_MESSAGE_NEED_CONTEXT:
|
||||
{
|
||||
const gchar *context_type;
|
||||
|
||||
gst_message_parse_context_type (msg, &context_type);
|
||||
g_print ("got need context %s\n", context_type);
|
||||
|
||||
if (g_strcmp0 (context_type, "GstWaylandDisplayHandleContextType") == 0) {
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined(GDK_WINDOWING_WAYLAND)
|
||||
GstContext *context = NULL;
|
||||
GdkDisplay *gdk_display = gdk_display_get_default ();
|
||||
if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
|
||||
struct wl_display *wayland_display =
|
||||
gdk_wayland_display_get_wl_display (gdk_display);
|
||||
if (wayland_display) {
|
||||
GstStructure *s;
|
||||
|
||||
context =
|
||||
gst_context_new ("GstWaylandDisplayHandleContextType", TRUE);
|
||||
|
||||
s = gst_context_writable_structure (context);
|
||||
gst_structure_set (s, "display", G_TYPE_POINTER, wayland_display,
|
||||
NULL);
|
||||
|
||||
gst_element_set_context (GST_ELEMENT (msg->src), context);
|
||||
|
||||
ret = TRUE;
|
||||
}
|
||||
}
|
||||
#else
|
||||
GST_ERROR
|
||||
("Asked for wayland display context, but compiled without wayland support");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
gst_video_overlay_set_gtk_window (GstVideoOverlay * videooverlay,
|
||||
GtkWidget * widget)
|
||||
{
|
||||
GdkWindow *window;
|
||||
GdkDisplay *display;
|
||||
const gchar *user_choice = g_getenv ("GST_GL_WINDOW");
|
||||
|
||||
window = gtk_widget_get_window (widget);
|
||||
display = gdk_window_get_display (window);
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_WIN32 && defined(GDK_WINDOWING_WIN32)
|
||||
if (GDK_IS_WIN32_DISPLAY (display) && (!user_choice
|
||||
|| g_strcmp0 (user_choice, "win32") == 0)) {
|
||||
gst_video_overlay_set_window_handle (videooverlay,
|
||||
(guintptr) GDK_WINDOW_HWND (window));
|
||||
} else
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_COCOA && defined(GDK_WINDOWING_QUARTZ)
|
||||
if (GDK_IS_QUARTZ_DISPLAY (display) && (!user_choice
|
||||
|| g_strcmp0 (user_choice, "cocoa") == 0)) {
|
||||
gst_video_overlay_set_window_handle (videooverlay, (guintptr)
|
||||
gdk_quartz_window_get_nsview (window));
|
||||
} else
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined(GDK_WINDOWING_X11)
|
||||
if (GDK_IS_X11_DISPLAY (display) && (!user_choice
|
||||
|| g_strcmp0 (user_choice, "x11") == 0)) {
|
||||
gst_video_overlay_set_window_handle (videooverlay, GDK_WINDOW_XID (window));
|
||||
} else
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined(GDK_WINDOWING_WAYLAND)
|
||||
if (GDK_IS_WAYLAND_DISPLAY (display) && (!user_choice
|
||||
|| g_strcmp0 (user_choice, "wayland") == 0)) {
|
||||
gst_video_overlay_set_window_handle (videooverlay,
|
||||
(guintptr) gdk_wayland_window_get_wl_surface (window));
|
||||
} else
|
||||
#endif
|
||||
g_error ("Unsupported Gtk+ backend");
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GTK_H__
|
||||
#define __GST_GTK_H__
|
||||
|
||||
#include <gst/video/videooverlay.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void gst_video_overlay_set_gtk_window (GstVideoOverlay *videooverlay, GtkWidget *window);
|
||||
gboolean gst_gtk_handle_need_context (GstBus *bus, GstMessage *msg, gpointer data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
extra_args = []
|
||||
if cc.has_argument ('-Wno-parentheses')
|
||||
extra_args += '-Wno-parentheses'
|
||||
endif
|
||||
|
||||
if host_system == 'darwin'
|
||||
extra_c_args = ['-xobjective-c']
|
||||
else
|
||||
extra_c_args = []
|
||||
endif
|
||||
|
||||
gstgtkhelper = static_library ('gstgtkhelper',
|
||||
['gstgtk.c'],
|
||||
c_args : gst_plugins_base_args + extra_c_args,
|
||||
include_directories : [configinc, libsinc],
|
||||
dependencies : [gst_base_dep, video_dep, gtk_dep, gstgl_dep],
|
||||
install : false)
|
||||
|
||||
gstgtkhelper_dep = declare_dependency(link_with: gstgtkhelper,
|
||||
compile_args : extra_args,
|
||||
include_directories : include_directories('.'),
|
||||
dependencies : [gst_base_dep, video_dep, gtk_dep, gstgl_dep])
|
||||
|
||||
subdir('filternovideooverlay')
|
||||
subdir('filtervideooverlay')
|
||||
subdir('fxtest')
|
||||
subdir('switchvideooverlay')
|
||||
subdir('3dvideo')
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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 <gst/gst.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
#include "../gstgtk.h"
|
||||
|
||||
#ifdef HAVE_X11
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
|
||||
static GstBusSyncReply create_window (GstBus* bus, GstMessage* message, GtkWidget* widget)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
|
||||
if (gst_gtk_handle_need_context (bus, message, NULL))
|
||||
return GST_BUS_DROP;
|
||||
|
||||
// ignore anything but 'prepare-window-handle' element messages
|
||||
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
|
||||
return GST_BUS_PASS;
|
||||
|
||||
if (!gst_is_video_overlay_prepare_window_handle_message (message))
|
||||
return GST_BUS_PASS;
|
||||
|
||||
g_print ("setting window handle %p\n", widget);
|
||||
|
||||
gst_video_overlay_set_gtk_window (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)), widget);
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)), allocation.x, allocation.y, allocation.width, allocation.height);
|
||||
|
||||
gst_message_unref (message);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
resize_cb (GtkWidget * widget, GdkEvent * event, gpointer sink)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink), allocation.x, allocation.y, allocation.width, allocation.height);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void end_stream_cb(GstBus* bus, GstMessage* message, GstElement* pipeline)
|
||||
{
|
||||
GError *error = NULL;
|
||||
gchar *details;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
gst_message_parse_error (message, &error, &details);
|
||||
|
||||
g_print("Error %s\n", error->message);
|
||||
g_print("Details %s\n", details);
|
||||
/* fallthrough */
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print("End of stream\n");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref(pipeline);
|
||||
|
||||
gtk_main_quit();
|
||||
break;
|
||||
case GST_MESSAGE_WARNING:
|
||||
gst_message_parse_warning (message, &error, &details);
|
||||
|
||||
g_print("Warning %s\n", error->message);
|
||||
g_print("Details %s\n", details);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean expose_cb(GtkWidget* widget, cairo_t *cr, GstElement* videosink)
|
||||
{
|
||||
g_print ("expose %p\n", widget);
|
||||
g_print ("event mask: 0x%x, button_press 0x%x\n", gtk_widget_get_events (widget), GDK_BUTTON_PRESS_MASK);
|
||||
gst_video_overlay_expose (GST_VIDEO_OVERLAY (videosink));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean on_click_drawing_area(GtkWidget* widget, GdkEventButton* event, GstElement* videosink)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
GtkWidget *parent = gtk_widget_get_parent (widget);
|
||||
|
||||
g_print ("switch the drawing area %p\n", widget);
|
||||
gst_video_overlay_set_gtk_window (GST_VIDEO_OVERLAY (videosink), widget);
|
||||
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (videosink), allocation.x, allocation.y, allocation.width, allocation.height);
|
||||
|
||||
/* XXX: required on wayland as changing the window handle (subsurface)
|
||||
* requires a wl_surface::commit from the parent */
|
||||
if (parent)
|
||||
gtk_widget_queue_draw (parent);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void destroy_cb(GtkWidget* widget, GdkEvent* event, GstElement* pipeline)
|
||||
{
|
||||
g_print("Close\n");
|
||||
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref(pipeline);
|
||||
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
||||
|
||||
static void button_state_null_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
g_print ("GST_STATE_NULL\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_ready_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_READY);
|
||||
g_print ("GST_STATE_READY\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_paused_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
||||
g_print ("GST_STATE_PAUSED\n");
|
||||
}
|
||||
|
||||
|
||||
static void button_state_playing_cb(GtkWidget* widget, GstElement* pipeline)
|
||||
{
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
g_print ("GST_STATE_PLAYING\n");
|
||||
}
|
||||
|
||||
gint main (gint argc, gchar *argv[])
|
||||
{
|
||||
#ifdef HAVE_X11
|
||||
XInitThreads();
|
||||
#endif
|
||||
|
||||
gtk_init (&argc, &argv);
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
GstElement* pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
//window that contains several ares where the video is drawn
|
||||
GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_widget_set_size_request (window, 640, 240);
|
||||
gtk_window_move (GTK_WINDOW (window), 300, 10);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "click on left, right or outside the main window to switch the drawing area");
|
||||
GdkGeometry geometry;
|
||||
geometry.min_width = 1;
|
||||
geometry.min_height = 1;
|
||||
geometry.max_width = -1;
|
||||
geometry.max_height = -1;
|
||||
gtk_window_set_geometry_hints (GTK_WINDOW (window), window, &geometry, GDK_HINT_MIN_SIZE);
|
||||
|
||||
//window to control the states
|
||||
GtkWidget* window_control = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
geometry.min_width = 1;
|
||||
geometry.min_height = 1;
|
||||
geometry.max_width = -1;
|
||||
geometry.max_height = -1;
|
||||
gtk_window_set_geometry_hints (GTK_WINDOW (window_control), window_control, &geometry, GDK_HINT_MIN_SIZE);
|
||||
gtk_window_set_resizable (GTK_WINDOW (window_control), FALSE);
|
||||
gtk_window_move (GTK_WINDOW (window_control), 10, 10);
|
||||
GtkWidget* table = gtk_grid_new ();
|
||||
gtk_container_add (GTK_CONTAINER (window_control), table);
|
||||
|
||||
//control state null
|
||||
GtkWidget* button_state_null = gtk_button_new_with_label ("GST_STATE_NULL");
|
||||
g_signal_connect (G_OBJECT (button_state_null), "clicked",
|
||||
G_CALLBACK (button_state_null_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_null, 0, 0, 1, 1);
|
||||
gtk_widget_show (button_state_null);
|
||||
|
||||
//control state ready
|
||||
GtkWidget* button_state_ready = gtk_button_new_with_label ("GST_STATE_READY");
|
||||
g_signal_connect (G_OBJECT (button_state_ready), "clicked",
|
||||
G_CALLBACK (button_state_ready_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_ready, 0, 1, 1, 1);
|
||||
gtk_widget_show (button_state_ready);
|
||||
|
||||
//control state paused
|
||||
GtkWidget* button_state_paused = gtk_button_new_with_label ("GST_STATE_PAUSED");
|
||||
g_signal_connect (G_OBJECT (button_state_paused), "clicked",
|
||||
G_CALLBACK (button_state_paused_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_paused, 0, 2, 1, 1);
|
||||
gtk_widget_show (button_state_paused);
|
||||
|
||||
//control state playing
|
||||
GtkWidget* button_state_playing = gtk_button_new_with_label ("GST_STATE_PLAYING");
|
||||
g_signal_connect (G_OBJECT (button_state_playing), "clicked",
|
||||
G_CALLBACK (button_state_playing_cb), pipeline);
|
||||
gtk_grid_attach (GTK_GRID (table), button_state_playing, 0, 3, 1, 1);
|
||||
gtk_widget_show (button_state_playing);
|
||||
|
||||
gtk_widget_show (table);
|
||||
gtk_widget_show (window_control);
|
||||
|
||||
//configure the pipeline
|
||||
g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(destroy_cb), pipeline);
|
||||
|
||||
GstElement* videosrc = gst_element_factory_make ("videotestsrc", "videotestsrc");
|
||||
GstElement* videosink = gst_element_factory_make ("glimagesink", "glimagesink");
|
||||
|
||||
gst_bin_add_many (GST_BIN (pipeline), videosrc, videosink, NULL);
|
||||
|
||||
gboolean link_ok = gst_element_link_many(videosrc, videosink, NULL);
|
||||
if(!link_ok)
|
||||
{
|
||||
g_warning("Failed to link videosrc to videosink!\n") ;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//areas where the video is drawn
|
||||
GtkWidget* table_areas = gtk_grid_new ();
|
||||
gtk_container_add (GTK_CONTAINER (window), table_areas);
|
||||
GtkWidget* area_top_left = gtk_drawing_area_new();
|
||||
gtk_widget_add_events(area_top_left, GDK_BUTTON_PRESS_MASK);
|
||||
gtk_widget_set_size_request (area_top_left, 320, 240);
|
||||
gtk_grid_attach (GTK_GRID (table_areas), area_top_left, 0, 0, 1, 1);
|
||||
GtkWidget* area_top_right = gtk_drawing_area_new();
|
||||
gtk_widget_add_events(area_top_right, GDK_BUTTON_PRESS_MASK);
|
||||
gtk_widget_set_size_request (area_top_right, 320, 240);
|
||||
gtk_grid_attach (GTK_GRID (table_areas), area_top_right, 1, 0, 1, 1);
|
||||
|
||||
gtk_widget_set_redraw_on_allocate (area_top_left, TRUE);
|
||||
gtk_widget_set_redraw_on_allocate (area_top_right, TRUE);
|
||||
gtk_widget_realize(area_top_left);
|
||||
gtk_widget_realize(area_top_right);
|
||||
|
||||
GstBus* bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) create_window, area_top_right, NULL);
|
||||
gst_bus_add_signal_watch (bus);
|
||||
g_signal_connect(bus, "message::error", G_CALLBACK(end_stream_cb), pipeline);
|
||||
g_signal_connect(bus, "message::warning", G_CALLBACK(end_stream_cb), pipeline);
|
||||
g_signal_connect(bus, "message::eos", G_CALLBACK(end_stream_cb), pipeline);
|
||||
gst_object_unref (bus);
|
||||
|
||||
//needed when being in GST_STATE_READY, GST_STATE_PAUSED
|
||||
//or resizing/obscuring the window
|
||||
g_signal_connect(area_top_left, "draw", G_CALLBACK(expose_cb), videosink);
|
||||
g_signal_connect(area_top_left, "configure-event", G_CALLBACK(resize_cb), videosink);
|
||||
g_signal_connect(area_top_right, "draw", G_CALLBACK(expose_cb), videosink);
|
||||
g_signal_connect(area_top_right, "configure-event", G_CALLBACK(resize_cb), videosink);
|
||||
|
||||
//switch the drawing area
|
||||
g_signal_connect(area_top_left, "button-press-event", G_CALLBACK(on_click_drawing_area), videosink);
|
||||
g_signal_connect(area_top_right, "button-press-event", G_CALLBACK(on_click_drawing_area), videosink);
|
||||
|
||||
gtk_widget_show_all (window);
|
||||
|
||||
gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
||||
|
||||
gtk_main();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
executable('switchvideooverlay', 'main.cpp',
|
||||
cpp_args : [gst_plugins_base_args],
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [gstgtkhelper_dep, gstgl_dep, x11_dep],
|
||||
install: false)
|
||||
@@ -0,0 +1,6 @@
|
||||
subdir('cocoa')
|
||||
subdir('generic')
|
||||
subdir('gtk', if_found : gtk_dep)
|
||||
qt5core_dep = dependency('qt5', modules : ['Core'], required : false)
|
||||
subdir('qt', if_found: qt5core_dep)
|
||||
subdir('sdl')
|
||||
@@ -0,0 +1,24 @@
|
||||
--- Description of the Qt examples ---
|
||||
|
||||
- videooverlay:
|
||||
Show how to use the videooverlay interface through Qt.
|
||||
The video is displayed as normal 2D scene.
|
||||
The window is dynamically resized to have the same size as the original video.
|
||||
|
||||
- mousevideooverlay:
|
||||
Show how to use the videooverlay interface through Qt.
|
||||
The cube is rotating when moving the mouse (+ click maintained)
|
||||
|
||||
- qglwidgetvideooverlay:
|
||||
Show how to use the videooverlay interface through Qt.
|
||||
The cube is rotating automatically into a QGLWidget
|
||||
|
||||
|
||||
--- How to build the Qt examples ---
|
||||
|
||||
sudo apt-get install g++ libqt5-dev
|
||||
|
||||
cd qglvideooverlay
|
||||
qmake
|
||||
make
|
||||
./debug/qglvideooverlay
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2020 Matthew Waters <matthew@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.
|
||||
*/
|
||||
|
||||
#include <gst/gl/gstglconfig.h>
|
||||
|
||||
/* The glext.h guard was renamed in 2018, but some software which
|
||||
* includes their own copy of the GL headers (such as qt) might have
|
||||
* older version which use the old guard. This would result in the
|
||||
* header being included again (and symbols redefined).
|
||||
*
|
||||
* To avoid this, we define the "old" guard if the "new" guard is
|
||||
* defined.*/
|
||||
#if GST_GL_HAVE_OPENGL
|
||||
#ifdef __gl_glext_h_
|
||||
#ifndef __glext_h_
|
||||
#define __glext_h_ 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
if get_option('qt5').disabled()
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
qt5_mod = import('qt5')
|
||||
|
||||
qt5gui_dep = dependency('qt5', modules : ['Core', 'Gui', 'Widgets'], required : false)
|
||||
qt5opengl_dep = dependency('qt5', modules : ['OpenGL'], required : false)
|
||||
#FIXME; other platforms
|
||||
libgl = cc.find_library ('GL', required : false)
|
||||
|
||||
qt_cxx_warn_less = cxx.get_supported_arguments(['-Wno-aggregate-return'])
|
||||
|
||||
if qt5gui_dep.found()
|
||||
subdir('videooverlay')
|
||||
if libgl.found()
|
||||
subdir('mousevideooverlay')
|
||||
if qt5opengl_dep.found()
|
||||
subdir('qglwidgetvideooverlay')
|
||||
subdir('qglwtextureshare')
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include "gstthread.h"
|
||||
|
||||
GstThread::GstThread(const WId winId, const QString videoLocation, QObject *parent):
|
||||
QThread(parent),
|
||||
m_winId(winId),
|
||||
m_videoLocation(videoLocation)
|
||||
{
|
||||
}
|
||||
|
||||
GstThread::~GstThread()
|
||||
{
|
||||
}
|
||||
|
||||
void GstThread::exposeRequested()
|
||||
{
|
||||
m_pipeline->exposeRequested();
|
||||
}
|
||||
|
||||
void GstThread::onMouseMove()
|
||||
{
|
||||
m_pipeline->rotateRequested();
|
||||
}
|
||||
|
||||
void GstThread::show()
|
||||
{
|
||||
emit showRequested();
|
||||
}
|
||||
|
||||
void GstThread::stop()
|
||||
{
|
||||
m_pipeline->stop();
|
||||
}
|
||||
|
||||
void GstThread::run()
|
||||
{
|
||||
m_pipeline = new Pipeline(m_winId, m_videoLocation);
|
||||
connect(m_pipeline, SIGNAL(showRequested()), this, SLOT(show()));
|
||||
m_pipeline->start(); //it runs the gmainloop on win32
|
||||
|
||||
#ifndef WIN32
|
||||
//works like the gmainloop on linux (GstEvent are handled)
|
||||
connect(m_pipeline, SIGNAL(stopRequested()), this, SLOT(quit()));
|
||||
exec();
|
||||
#endif
|
||||
|
||||
m_pipeline->unconfigure();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef GSTTHREAD_H
|
||||
#define GSTTHREAD_H
|
||||
|
||||
#include <QtGui>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include "pipeline.h"
|
||||
|
||||
class GstThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GstThread(const WId winId, const QString videoLocation, QObject *parent = 0);
|
||||
~GstThread();
|
||||
|
||||
public slots:
|
||||
void exposeRequested();
|
||||
void onMouseMove();
|
||||
void show();
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void showRequested();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
const WId m_winId;
|
||||
const QString m_videoLocation;
|
||||
Pipeline* m_pipeline;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include "qrenderer.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/* FIXME: port the example to shaders and remove this */
|
||||
g_setenv ("GST_GL_API", "opengl", FALSE);
|
||||
|
||||
QApplication a(argc, argv);
|
||||
a.connect(&a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()));
|
||||
|
||||
QString videolocation = QFileDialog::getOpenFileName(0, "Select a video file",
|
||||
".", "Format (*.avi *.mkv *.ogg *.asf *.mov *.mp4)");
|
||||
|
||||
if (videolocation.isEmpty())
|
||||
return -1;
|
||||
|
||||
QRenderer w(videolocation);
|
||||
w.setWindowTitle("glimagesink implements the gstvideooverlay interface");
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
sources = [
|
||||
'main.cpp',
|
||||
'gstthread.cpp',
|
||||
'pipeline.cpp',
|
||||
'qrenderer.cpp',
|
||||
]
|
||||
|
||||
moc_headers = [
|
||||
'gstthread.h',
|
||||
'pipeline.h',
|
||||
'qrenderer.h',
|
||||
]
|
||||
|
||||
moc_files = qt5_mod.preprocess(moc_headers : moc_headers)
|
||||
executable('mousevideoverlay', sources, moc_files,
|
||||
cpp_args : [gst_plugins_base_args] + qt_cxx_warn_less,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [qt5core_dep, qt5gui_dep, gst_dep, video_dep, gstgl_dep, gstglproto_dep],
|
||||
install: false)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#Header files
|
||||
HEADERS += ./gstthread.h \
|
||||
./pipeline.h \
|
||||
./qrenderer.h
|
||||
|
||||
#Source files
|
||||
SOURCES += ./gstthread.cpp \
|
||||
./main.cpp \
|
||||
./pipeline.cpp \
|
||||
./qrenderer.cpp
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
TEMPLATE = app
|
||||
TARGET = mousevideooverlay
|
||||
DESTDIR = ./debug
|
||||
QT += gui widgets opengl
|
||||
CONFIG += debug link_pkgconfig
|
||||
DEFINES += UNICODE QT_THREAD_SUPPORT QT_CORE_LIB QT_GUI_LIB
|
||||
PKGCONFIG = gstreamer-1.0 gstreamer-video-1.0
|
||||
|
||||
win32 {
|
||||
DEFINES += WIN32
|
||||
INCLUDEPATH += ./GeneratedFiles \
|
||||
./GeneratedFiles/Debug \
|
||||
C:/gstreamer/include \
|
||||
C:/gstreamer/include/libxml2 \
|
||||
C:/gstreamer/include/glib-2.0 \
|
||||
C:/gstreamer/lib/glib-2.0/include \
|
||||
C:/gstreamer/include/gstreamer-1.0
|
||||
LIBS += -L"C:/gstreamer/lib" \
|
||||
-L"C:/gstreamer/bin" \
|
||||
-lgstreamer-1.0 \
|
||||
-lgstvideo-1.0 \
|
||||
-lglib-2.0 \
|
||||
-lgmodule-2.0 \
|
||||
-lgobject-2.0 \
|
||||
-lgthread-2.0 \
|
||||
-lopengl32 \
|
||||
-lglu32
|
||||
}
|
||||
|
||||
unix {
|
||||
DEFINES += UNIX
|
||||
INCLUDEPATH += GeneratedFiles \
|
||||
GeneratedFiles/Debug
|
||||
LIBS += -lGLU -lGL
|
||||
}
|
||||
|
||||
DEPENDPATH += .
|
||||
MOC_DIR += ./GeneratedFiles/debug
|
||||
OBJECTS_DIR += debug
|
||||
UI_DIR += ./GeneratedFiles
|
||||
RCC_DIR += ./GeneratedFiles
|
||||
|
||||
#Include file(s)
|
||||
include(mousevideooverlay.pri)
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/video/videooverlay.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <GL/gl.h>
|
||||
#include "../gl-compat-defines.h"
|
||||
#include "pipeline.h"
|
||||
|
||||
#define GST_MAP_GL (GST_MAP_FLAG_LAST << 1)
|
||||
|
||||
Pipeline::Pipeline(const WId id, const QString videoLocation):
|
||||
m_winId(id),
|
||||
m_videoLocation(videoLocation),
|
||||
m_loop(NULL),
|
||||
m_bus(NULL),
|
||||
m_pipeline(NULL),
|
||||
m_glimagesink(NULL)
|
||||
{
|
||||
create();
|
||||
}
|
||||
|
||||
Pipeline::~Pipeline()
|
||||
{
|
||||
}
|
||||
|
||||
void Pipeline::create()
|
||||
{
|
||||
qDebug("Loading video: %s", m_videoLocation.toLatin1().data());
|
||||
|
||||
gst_init (NULL, NULL);
|
||||
|
||||
#ifdef WIN32
|
||||
m_loop = g_main_loop_new (NULL, FALSE);
|
||||
#endif
|
||||
m_pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
m_bus = gst_pipeline_get_bus (GST_PIPELINE (m_pipeline));
|
||||
gst_bus_add_watch (m_bus, (GstBusFunc) bus_call, this);
|
||||
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) create_window, this, NULL);
|
||||
gst_object_unref (m_bus);
|
||||
|
||||
GstElement* videosrc = gst_element_factory_make ("filesrc", "filesrc0");
|
||||
GstElement* decodebin = gst_element_factory_make ("decodebin", "decodebin0");
|
||||
m_glimagesink = gst_element_factory_make ("glimagesink", "sink0");
|
||||
|
||||
if (!videosrc || !decodebin || !m_glimagesink )
|
||||
{
|
||||
qDebug ("one element could not be found");
|
||||
return;
|
||||
}
|
||||
|
||||
g_object_set(G_OBJECT(videosrc), "num-buffers", 800, NULL);
|
||||
g_object_set(G_OBJECT(videosrc), "location", m_videoLocation.toLatin1().data(), NULL);
|
||||
g_signal_connect(G_OBJECT(m_glimagesink), "client-reshape", G_CALLBACK (reshapeCallback), NULL);
|
||||
g_signal_connect(G_OBJECT(m_glimagesink), "client-draw", G_CALLBACK (drawCallback), NULL);
|
||||
|
||||
gst_bin_add_many (GST_BIN (m_pipeline), videosrc, decodebin, m_glimagesink, NULL);
|
||||
|
||||
gst_element_link_pads (videosrc, "src", decodebin, "sink");
|
||||
|
||||
g_signal_connect (decodebin, "pad-added", G_CALLBACK (cb_new_pad), this);
|
||||
}
|
||||
|
||||
void Pipeline::start()
|
||||
{
|
||||
GstStateChangeReturn ret = gst_element_set_state (m_pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
{
|
||||
qDebug ("Failed to start up pipeline!");
|
||||
|
||||
/* check if there is an error message with details on the bus */
|
||||
GstMessage* msg = gst_bus_poll (m_bus, GST_MESSAGE_ERROR, 0);
|
||||
if (msg)
|
||||
{
|
||||
GError *err = NULL;
|
||||
gst_message_parse_error (msg, &err, NULL);
|
||||
qDebug ("ERROR: %s", err->message);
|
||||
g_error_free (err);
|
||||
gst_message_unref (msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
g_main_loop_run(m_loop);
|
||||
#endif
|
||||
}
|
||||
|
||||
//we don't want a thread safe stop in this example
|
||||
void Pipeline::stop()
|
||||
{
|
||||
#ifdef WIN32
|
||||
g_main_loop_quit(m_loop);
|
||||
#else
|
||||
emit stopRequested();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Pipeline::unconfigure() const
|
||||
{
|
||||
gst_element_set_state (m_pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (m_pipeline);
|
||||
}
|
||||
|
||||
void Pipeline::show()
|
||||
{
|
||||
emit showRequested();
|
||||
}
|
||||
|
||||
//redraw the current frame in the drawable
|
||||
void Pipeline::doExpose() const
|
||||
{
|
||||
if (m_pipeline && m_glimagesink)
|
||||
gst_video_overlay_expose (GST_VIDEO_OVERLAY (m_glimagesink));
|
||||
}
|
||||
|
||||
//post message to g_main_loop in order to call expose
|
||||
//in the gt thread
|
||||
void Pipeline::exposeRequested()
|
||||
{
|
||||
g_idle_add(cb_expose, this);
|
||||
}
|
||||
|
||||
//rotate the cube
|
||||
void Pipeline::doRotate()
|
||||
{
|
||||
m_xrot += 3.0f;
|
||||
m_yrot += 2.0f;
|
||||
m_zrot += 4.0f;
|
||||
}
|
||||
|
||||
//post message to g_main_loop in order to call rotate
|
||||
//in the gt thread
|
||||
void Pipeline::rotateRequested()
|
||||
{
|
||||
g_idle_add(cb_rotate, this);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
//----------------------------- static members --------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
float Pipeline::m_xrot = 0;
|
||||
float Pipeline::m_yrot = 0;
|
||||
float Pipeline::m_zrot = 0;
|
||||
|
||||
//client reshape callback
|
||||
gboolean Pipeline::reshapeCallback (void *sink, void *context, guint width, guint height, gpointer data)
|
||||
{
|
||||
glViewport(0, 0, width, height);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//client draw callback
|
||||
gboolean Pipeline::drawCallback (void * sink, void *context, GstSample * sample, gpointer data)
|
||||
{
|
||||
static gint64 current_time;
|
||||
static glong last_sec = 0;
|
||||
static gint nbFrames = 0;
|
||||
|
||||
GstVideoFrame v_frame;
|
||||
GstVideoInfo v_info;
|
||||
guint texture = 0;
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample);
|
||||
GstCaps *caps = gst_sample_get_caps (sample);
|
||||
|
||||
gst_video_info_from_caps (&v_info, caps);
|
||||
|
||||
if (!gst_video_frame_map (&v_frame, &v_info, buf, (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
|
||||
g_warning ("Failed to map the video buffer");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
texture = *(guint *) v_frame.data[0];
|
||||
|
||||
current_time = g_get_monotonic_time ();
|
||||
nbFrames++ ;
|
||||
|
||||
if ((current_time / G_USEC_PER_SEC - last_sec) >= 1)
|
||||
{
|
||||
qDebug ("GRAPHIC FPS = %d", nbFrames);
|
||||
nbFrames = 0;
|
||||
last_sec = current_time / G_USEC_PER_SEC;
|
||||
}
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
glEnable (GL_TEXTURE_2D);
|
||||
glBindTexture (GL_TEXTURE_2D, texture);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glScalef(0.5f,0.5f,0.5f);
|
||||
|
||||
glRotatef(m_xrot,1.0f,0.0f,0.0f);
|
||||
glRotatef(m_yrot,0.0f,1.0f,0.0f);
|
||||
glRotatef(m_zrot,0.0f,0.0f,1.0f);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
// Front Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
// Back Face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
// Top Face
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
// Bottom Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
// Right face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
// Left Face
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glEnd();
|
||||
|
||||
glLoadIdentity();
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glBindTexture (GL_TEXTURE_2D, 0);
|
||||
|
||||
gst_video_frame_unmap (&v_frame);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean Pipeline::bus_call (GstBus *bus, GstMessage *msg, Pipeline* p)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE (msg))
|
||||
{
|
||||
case GST_MESSAGE_EOS:
|
||||
qDebug ("End-of-stream");
|
||||
p->stop();
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *debug = NULL;
|
||||
GError *err = NULL;
|
||||
gst_message_parse_error (msg, &err, &debug);
|
||||
qDebug ("Error: %s", err->message);
|
||||
g_error_free (err);
|
||||
if (debug)
|
||||
{
|
||||
qDebug ("Debug deails: %s", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
p->stop();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void Pipeline::cb_new_pad (GstElement* decodebin, GstPad* pad, Pipeline* p)
|
||||
{
|
||||
GstElement* glimagesink = p->getVideoSink();
|
||||
GstPad* glpad = gst_element_get_static_pad (glimagesink, "sink");
|
||||
|
||||
//only link once
|
||||
if (GST_PAD_IS_LINKED (glpad))
|
||||
{
|
||||
gst_object_unref (glpad);
|
||||
return;
|
||||
}
|
||||
|
||||
GstCaps* caps = gst_pad_get_current_caps (pad);
|
||||
GstStructure* str = gst_caps_get_structure (caps, 0);
|
||||
if (!g_strrstr (gst_structure_get_name (str), "video"))
|
||||
{
|
||||
gst_caps_unref (caps);
|
||||
gst_object_unref (glpad);
|
||||
return;
|
||||
}
|
||||
gst_caps_unref (caps);
|
||||
|
||||
GstPadLinkReturn ret = gst_pad_link (pad, glpad);
|
||||
if (ret != GST_PAD_LINK_OK)
|
||||
g_warning ("Failed to link with decodebin!\n");
|
||||
|
||||
p->show();
|
||||
}
|
||||
|
||||
gboolean Pipeline::cb_expose (gpointer data)
|
||||
{
|
||||
((Pipeline*)data)->doExpose();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean Pipeline::cb_rotate (gpointer data)
|
||||
{
|
||||
((Pipeline*)data)->doRotate();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GstBusSyncReply Pipeline::create_window (GstBus* bus, GstMessage* message, const Pipeline* p)
|
||||
{
|
||||
// ignore anything but 'prepare-window-handle' element messages
|
||||
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
|
||||
return GST_BUS_PASS;
|
||||
|
||||
if (!gst_is_video_overlay_prepare_window_handle_message (message))
|
||||
return GST_BUS_PASS;
|
||||
|
||||
qDebug ("setting window handle");
|
||||
|
||||
//Passing 0 as the window_handle will tell the overlay to stop using that window and create an internal one.
|
||||
//In the directdrawsink's gst_video_overlay_set_window_handle implementation, window_handle (parameter 2) is casted to HWND before it used.
|
||||
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)), (guintptr)p->winId());
|
||||
|
||||
gst_message_unref (message);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef PIPELINE_H
|
||||
#define PIPELINE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QtGui>
|
||||
#include <gst/gst.h>
|
||||
//#include <QtCore/private/qeventdispatcher_glib_p.h>
|
||||
|
||||
class Pipeline : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Pipeline(const WId windId, const QString videoLocation);
|
||||
~Pipeline();
|
||||
void start();
|
||||
void exposeRequested();
|
||||
void rotateRequested();
|
||||
void stop();
|
||||
void unconfigure() const;
|
||||
void show();
|
||||
GstElement* getVideoSink() { return m_glimagesink; } ;
|
||||
|
||||
signals:
|
||||
void showRequested();
|
||||
void stopRequested();
|
||||
|
||||
private:
|
||||
const WId m_winId;
|
||||
const QString m_videoLocation;
|
||||
GMainLoop* m_loop;
|
||||
GstBus* m_bus;
|
||||
GstElement* m_pipeline;
|
||||
GstElement* m_glimagesink;
|
||||
static float m_xrot;
|
||||
static float m_yrot;
|
||||
static float m_zrot;
|
||||
|
||||
void create();
|
||||
WId winId() const { return m_winId; }
|
||||
void doExpose() const;
|
||||
void doRotate();
|
||||
|
||||
static gboolean reshapeCallback (void *sink, void *context, guint width, guint height, gpointer data);
|
||||
static gboolean drawCallback (void * sink, void *context, GstSample * sample, gpointer data);
|
||||
static gboolean bus_call (GstBus *bus, GstMessage *msg, Pipeline* p);
|
||||
static void cb_new_pad (GstElement* decodebin, GstPad* pad, Pipeline* p);
|
||||
static gboolean cb_expose (gpointer data);
|
||||
static gboolean cb_rotate (gpointer data);
|
||||
static GstBusSyncReply create_window (GstBus* bus, GstMessage* message, const Pipeline* pipeline);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include "qrenderer.h"
|
||||
|
||||
QRenderer::QRenderer(const QString videoLocation, QWidget *parent, Qt::WindowFlags flags)
|
||||
: QWidget(parent, flags),
|
||||
m_gt(winId(), videoLocation)
|
||||
{
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setVisible(false);
|
||||
move(20, 10);
|
||||
resize(640, 480);
|
||||
|
||||
QObject::connect(&m_gt, SIGNAL(finished()), this, SLOT(close()));
|
||||
QObject::connect(this, SIGNAL(exposeRequested()), &m_gt, SLOT(exposeRequested()));
|
||||
QObject::connect(this, SIGNAL(closeRequested()), &m_gt, SLOT(stop()), Qt::DirectConnection);
|
||||
QObject::connect(&m_gt, SIGNAL(showRequested()), this, SLOT(show()));
|
||||
QObject::connect(this, SIGNAL(mouseMoved()), &m_gt, SLOT(onMouseMove()));
|
||||
m_gt.start();
|
||||
}
|
||||
|
||||
QRenderer::~QRenderer()
|
||||
{
|
||||
}
|
||||
|
||||
void QRenderer::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
emit exposeRequested();
|
||||
}
|
||||
|
||||
void QRenderer::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
emit mouseMoved();
|
||||
}
|
||||
|
||||
void QRenderer::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
emit closeRequested();
|
||||
m_gt.wait();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef QRENDERER_H
|
||||
#define QRENDERER_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include "gstthread.h"
|
||||
|
||||
class QRenderer : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QRenderer(const QString videoLocation, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
|
||||
~QRenderer();
|
||||
void paintEvent(QPaintEvent* event);
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
void closeEvent (QCloseEvent* event);
|
||||
|
||||
signals:
|
||||
void exposeRequested();
|
||||
void closeRequested();
|
||||
void mouseMoved();
|
||||
|
||||
private:
|
||||
GstThread m_gt;
|
||||
};
|
||||
|
||||
#endif // QRENDERER_H
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include "gstthread.h"
|
||||
|
||||
GstThread::GstThread(const WId winId, const QString videoLocation, QObject *parent):
|
||||
QThread(parent),
|
||||
m_winId(winId),
|
||||
m_videoLocation(videoLocation)
|
||||
{
|
||||
}
|
||||
|
||||
GstThread::~GstThread()
|
||||
{
|
||||
}
|
||||
|
||||
void GstThread::exposeRequested()
|
||||
{
|
||||
m_pipeline->exposeRequested();
|
||||
}
|
||||
|
||||
void GstThread::show()
|
||||
{
|
||||
emit showRequested();
|
||||
}
|
||||
|
||||
void GstThread::stop()
|
||||
{
|
||||
m_pipeline->stop();
|
||||
}
|
||||
|
||||
void GstThread::run()
|
||||
{
|
||||
m_pipeline = new Pipeline(m_winId, m_videoLocation);
|
||||
connect(m_pipeline, SIGNAL(showRequested()), this, SLOT(show()));
|
||||
m_pipeline->start(); //it runs the gmainloop on win32
|
||||
|
||||
#ifndef WIN32
|
||||
//works like the gmainloop on linux (GstEvent are handled)
|
||||
connect(m_pipeline, SIGNAL(stopRequested()), this, SLOT(quit()));
|
||||
exec();
|
||||
#endif
|
||||
|
||||
m_pipeline->unconfigure();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef GSTTHREAD_H
|
||||
#define GSTTHREAD_H
|
||||
|
||||
#include <QtGui>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include "pipeline.h"
|
||||
|
||||
class GstThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GstThread(const WId winId, const QString videoLocation, QObject *parent = 0);
|
||||
~GstThread();
|
||||
|
||||
public slots:
|
||||
void exposeRequested();
|
||||
void show();
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void showRequested();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
const WId m_winId;
|
||||
const QString m_videoLocation;
|
||||
Pipeline* m_pipeline;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include "qglrenderer.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/* FIXME: port the example to shaders and remove this */
|
||||
g_setenv ("GST_GL_API", "opengl", FALSE);
|
||||
|
||||
QApplication a(argc, argv);
|
||||
a.connect(&a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()));
|
||||
|
||||
QString videolocation = QFileDialog::getOpenFileName(0, "Select a video file",
|
||||
".", "Format (*.avi *.mkv *.ogg *.asf *.mov *.mp4)");
|
||||
|
||||
if (videolocation.isEmpty())
|
||||
return -1;
|
||||
|
||||
QGLRenderer w(videolocation);
|
||||
w.setWindowTitle("glimagesink implements the gstvideooverlay interface");
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
sources = [
|
||||
'main.cpp',
|
||||
'gstthread.cpp',
|
||||
'pipeline.cpp',
|
||||
'qglrenderer.cpp',
|
||||
]
|
||||
|
||||
moc_headers = [
|
||||
'gstthread.h',
|
||||
'pipeline.h',
|
||||
'qglrenderer.h',
|
||||
]
|
||||
|
||||
moc_files = qt5_mod.preprocess(moc_headers : moc_headers)
|
||||
executable('qglwidgetvideoverlay', sources, moc_files,
|
||||
cpp_args : [gst_plugins_base_args] + qt_cxx_warn_less,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [qt5core_dep, qt5gui_dep, qt5opengl_dep, gst_dep, video_dep, gstgl_dep, gstglproto_dep],
|
||||
install: false)
|
||||
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/video/videooverlay.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <GL/gl.h>
|
||||
#include "../gl-compat-defines.h"
|
||||
#include "pipeline.h"
|
||||
|
||||
#define GST_MAP_GL (GST_MAP_FLAG_LAST << 1)
|
||||
|
||||
Pipeline::Pipeline(const WId id, const QString videoLocation):
|
||||
m_winId(id),
|
||||
m_videoLocation(videoLocation),
|
||||
m_loop(NULL),
|
||||
m_bus(NULL),
|
||||
m_pipeline(NULL),
|
||||
m_glimagesink(NULL)
|
||||
{
|
||||
create();
|
||||
}
|
||||
|
||||
Pipeline::~Pipeline()
|
||||
{
|
||||
}
|
||||
|
||||
void Pipeline::create()
|
||||
{
|
||||
qDebug("Loading video: %s", m_videoLocation.toLatin1().data());
|
||||
|
||||
gst_init (NULL, NULL);
|
||||
|
||||
#ifdef WIN32
|
||||
m_loop = g_main_loop_new (NULL, FALSE);
|
||||
#endif
|
||||
m_pipeline = gst_pipeline_new ("pipeline");
|
||||
|
||||
m_bus = gst_pipeline_get_bus (GST_PIPELINE (m_pipeline));
|
||||
gst_bus_add_watch (m_bus, (GstBusFunc) bus_call, this);
|
||||
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) create_window, this, NULL);
|
||||
gst_object_unref (m_bus);
|
||||
|
||||
GstElement* videosrc = gst_element_factory_make ("filesrc", "filesrc0");
|
||||
GstElement* decodebin = gst_element_factory_make ("decodebin", "decodebin0");
|
||||
m_glimagesink = gst_element_factory_make ("glimagesink", "sink0");
|
||||
|
||||
if (!videosrc || !decodebin || !m_glimagesink )
|
||||
{
|
||||
qDebug ("one element could not be found");
|
||||
return;
|
||||
}
|
||||
|
||||
g_object_set(G_OBJECT(videosrc), "num-buffers", 800, NULL);
|
||||
g_object_set(G_OBJECT(videosrc), "location", m_videoLocation.toLatin1().data(), NULL);
|
||||
g_signal_connect_object (G_OBJECT(m_glimagesink), "client-reshape", (GCallback) reshapeCallback, NULL, G_CONNECT_AFTER);
|
||||
g_signal_connect_object (G_OBJECT(m_glimagesink), "client-draw", (GCallback) drawCallback, NULL, G_CONNECT_AFTER);
|
||||
|
||||
gst_bin_add_many (GST_BIN (m_pipeline), videosrc, decodebin, m_glimagesink, NULL);
|
||||
|
||||
gst_element_link_pads (videosrc, "src", decodebin, "sink");
|
||||
|
||||
g_signal_connect (decodebin, "pad-added", G_CALLBACK (cb_new_pad), this);
|
||||
}
|
||||
|
||||
void Pipeline::start()
|
||||
{
|
||||
GstStateChangeReturn ret = gst_element_set_state (m_pipeline, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
{
|
||||
qDebug ("Failed to start up pipeline!");
|
||||
|
||||
/* check if there is an error message with details on the bus */
|
||||
GstMessage* msg = gst_bus_poll (m_bus, GST_MESSAGE_ERROR, 0);
|
||||
if (msg)
|
||||
{
|
||||
GError *err = NULL;
|
||||
gst_message_parse_error (msg, &err, NULL);
|
||||
qDebug ("ERROR: %s", err->message);
|
||||
g_error_free (err);
|
||||
gst_message_unref (msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
g_main_loop_run(m_loop);
|
||||
#endif
|
||||
}
|
||||
|
||||
//we don't want a thread safe stop in this example
|
||||
void Pipeline::stop()
|
||||
{
|
||||
#ifdef WIN32
|
||||
g_main_loop_quit(m_loop);
|
||||
#else
|
||||
emit stopRequested();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Pipeline::unconfigure() const
|
||||
{
|
||||
gst_element_set_state (m_pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (m_pipeline);
|
||||
}
|
||||
|
||||
void Pipeline::show()
|
||||
{
|
||||
emit showRequested();
|
||||
}
|
||||
|
||||
//redraw the current frame in the drawable
|
||||
void Pipeline::doExpose() const
|
||||
{
|
||||
if (m_pipeline && m_glimagesink)
|
||||
gst_video_overlay_expose (GST_VIDEO_OVERLAY (m_glimagesink));
|
||||
}
|
||||
|
||||
//post message to g_main_loop in order to call expose
|
||||
//in the gt thread
|
||||
void Pipeline::exposeRequested()
|
||||
{
|
||||
g_idle_add(cb_expose, this);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
//----------------------------- static members --------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
//client reshape callback
|
||||
gboolean Pipeline::reshapeCallback (GstElement *sink, void *context, guint width, guint height, gpointer data)
|
||||
{
|
||||
glViewport(0, 0, width, height);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//client draw callback
|
||||
gboolean Pipeline::drawCallback (GstElement * gl_sink, void *context, GstSample * sample, gpointer data)
|
||||
{
|
||||
static GLfloat xrot = 0;
|
||||
static GLfloat yrot = 0;
|
||||
static GLfloat zrot = 0;
|
||||
static gint64 current_time;
|
||||
static glong last_sec = 0;
|
||||
static gint nbFrames = 0;
|
||||
|
||||
GstVideoFrame v_frame;
|
||||
GstVideoInfo v_info;
|
||||
guint texture = 0;
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample);
|
||||
GstCaps *caps = gst_sample_get_caps (sample);
|
||||
|
||||
gst_video_info_from_caps (&v_info, caps);
|
||||
|
||||
if (!gst_video_frame_map (&v_frame, &v_info, buf, (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
|
||||
g_warning ("Failed to map the video buffer");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
texture = *(guint *) v_frame.data[0];
|
||||
|
||||
current_time = g_get_monotonic_time ();
|
||||
nbFrames++ ;
|
||||
|
||||
if ((current_time / G_USEC_PER_SEC - last_sec) >= 1)
|
||||
{
|
||||
qDebug ("GRPHIC FPS = %d", nbFrames);
|
||||
nbFrames = 0;
|
||||
last_sec = current_time / G_USEC_PER_SEC;
|
||||
}
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
glEnable (GL_TEXTURE_2D);
|
||||
glBindTexture (GL_TEXTURE_2D, texture);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
|
||||
glScalef (0.5, 0.5, 0.5);
|
||||
|
||||
glRotatef(xrot,1.0f,0.0f,0.0f);
|
||||
glRotatef(yrot,0.0f,1.0f,0.0f);
|
||||
glRotatef(zrot,0.0f,0.0f,1.0f);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
// Front Face
|
||||
glTexCoord2f(1.0, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
// Back Face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
// Top Face
|
||||
glTexCoord2f(1.0, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
// Bottom Face
|
||||
glTexCoord2f(1.0, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(1.0,1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
// Right face
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f(1.0, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
|
||||
// Left Face
|
||||
glTexCoord2f(1.0, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f(1.0, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
|
||||
glEnd();
|
||||
|
||||
glLoadIdentity();
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glBindTexture (GL_TEXTURE_2D, 0);
|
||||
|
||||
gst_video_frame_unmap (&v_frame);
|
||||
|
||||
xrot+=0.03f;
|
||||
yrot+=0.02f;
|
||||
zrot+=0.04f;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean Pipeline::bus_call (GstBus *bus, GstMessage *msg, Pipeline* p)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE (msg))
|
||||
{
|
||||
case GST_MESSAGE_EOS:
|
||||
qDebug ("End-of-stream");
|
||||
p->stop();
|
||||
break;
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *debug = NULL;
|
||||
GError *err = NULL;
|
||||
gst_message_parse_error (msg, &err, &debug);
|
||||
qDebug ("Error: %s", err->message);
|
||||
g_error_free (err);
|
||||
if (debug)
|
||||
{
|
||||
qDebug ("Debug deails: %s", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
p->stop();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void Pipeline::cb_new_pad (GstElement* decodebin, GstPad* pad, Pipeline* p)
|
||||
{
|
||||
GstElement* sink = p->getVideoSink();
|
||||
GstPad* glpad = gst_element_get_static_pad (sink, "sink");
|
||||
|
||||
//only link once
|
||||
if (GST_PAD_IS_LINKED (glpad))
|
||||
{
|
||||
gst_object_unref (glpad);
|
||||
return;
|
||||
}
|
||||
|
||||
GstCaps* caps = gst_pad_get_current_caps (pad);
|
||||
GstStructure* str = gst_caps_get_structure (caps, 0);
|
||||
if (!g_strrstr (gst_structure_get_name (str), "video"))
|
||||
{
|
||||
gst_caps_unref (caps);
|
||||
gst_object_unref (glpad);
|
||||
return;
|
||||
}
|
||||
gst_caps_unref (caps);
|
||||
|
||||
GstPadLinkReturn ret = gst_pad_link (pad, glpad);
|
||||
if (ret != GST_PAD_LINK_OK)
|
||||
g_warning ("Failed to link with decodebin!\n");
|
||||
|
||||
p->show();
|
||||
}
|
||||
|
||||
gboolean Pipeline::cb_expose (gpointer data)
|
||||
{
|
||||
((Pipeline*)data)->doExpose();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GstBusSyncReply Pipeline::create_window (GstBus* bus, GstMessage* message, const Pipeline* p)
|
||||
{
|
||||
// ignore anything but 'prepare-window-handle' element messages
|
||||
if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT)
|
||||
return GST_BUS_PASS;
|
||||
|
||||
if (!gst_is_video_overlay_prepare_window_handle_message (message))
|
||||
return GST_BUS_PASS;
|
||||
|
||||
qDebug ("setting window handle");
|
||||
|
||||
//Passing 0 as the window_handle will tell the overlay to stop using that window and create an internal one.
|
||||
//In the directdrawsink's gst_video_overlay_set_window_handle implementation, window_handle (parameter 2) is casted to HWND before it used.
|
||||
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)), (guintptr)p->winId());
|
||||
|
||||
gst_message_unref (message);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef PIPELINE_H
|
||||
#define PIPELINE_H
|
||||
|
||||
#include <QtGui>
|
||||
#include <gst/gst.h>
|
||||
//#include <QtCore/private/qeventdispatcher_glib_p.h>
|
||||
|
||||
class Pipeline : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Pipeline(const WId windId, const QString videoLocation);
|
||||
~Pipeline();
|
||||
void start();
|
||||
void exposeRequested();
|
||||
void stop();
|
||||
void unconfigure() const;
|
||||
void show();
|
||||
GstElement* getVideoSink() { return m_glimagesink; } ;
|
||||
|
||||
signals:
|
||||
void showRequested();
|
||||
void stopRequested();
|
||||
|
||||
private:
|
||||
const WId m_winId;
|
||||
const QString m_videoLocation;
|
||||
GMainLoop* m_loop;
|
||||
GstBus* m_bus;
|
||||
GstElement* m_pipeline;
|
||||
GstElement* m_glimagesink;
|
||||
static float m_xrot;
|
||||
static float m_yrot;
|
||||
static float m_zrot;
|
||||
|
||||
void create();
|
||||
WId winId() const { return m_winId; }
|
||||
void doExpose() const;
|
||||
|
||||
static gboolean reshapeCallback (GstElement *sink, void *context, guint width, guint height, gpointer data);
|
||||
static gboolean drawCallback (GstElement *sink, void *context, GstSample * sample, gpointer data);
|
||||
static gboolean bus_call (GstBus *bus, GstMessage *msg, Pipeline* p);
|
||||
static void cb_new_pad (GstElement* decodebin, GstPad* pad, Pipeline* p);
|
||||
static gboolean cb_expose (gpointer data);
|
||||
static GstBusSyncReply create_window (GstBus* bus, GstMessage* message, const Pipeline* pipeline);
|
||||
};
|
||||
|
||||
#endif
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#include "qglrenderer.h"
|
||||
|
||||
QGLRenderer::QGLRenderer(const QString videoLocation, QWidget *parent)
|
||||
: QGLWidget(parent),
|
||||
m_gt(winId(), videoLocation)
|
||||
{
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setVisible(false);
|
||||
move(20, 10);
|
||||
resize(640, 480);
|
||||
|
||||
QObject::connect(&m_gt, SIGNAL(finished()), this, SLOT(close()));
|
||||
QObject::connect(this, SIGNAL(exposeRequested()), &m_gt, SLOT(exposeRequested()));
|
||||
QObject::connect(this, SIGNAL(closeRequested()), &m_gt, SLOT(stop()), Qt::DirectConnection);
|
||||
QObject::connect(&m_gt, SIGNAL(showRequested()), this, SLOT(show()));
|
||||
m_gt.start();
|
||||
}
|
||||
|
||||
QGLRenderer::~QGLRenderer()
|
||||
{
|
||||
}
|
||||
|
||||
void QGLRenderer::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
emit exposeRequested();
|
||||
}
|
||||
|
||||
void QGLRenderer::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
emit closeRequested();
|
||||
m_gt.wait();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2008-2009 Julien Isorce <julien.isorce@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef QGLRENDERER_H
|
||||
#define QGLRENDERER_H
|
||||
|
||||
#include <QGLWidget>
|
||||
#include "gstthread.h"
|
||||
|
||||
class QGLRenderer : public QGLWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QGLRenderer(const QString videoLocation, QWidget *parent = nullptr);
|
||||
~QGLRenderer();
|
||||
void paintEvent(QPaintEvent* event);
|
||||
void closeEvent (QCloseEvent* event);
|
||||
|
||||
signals:
|
||||
void exposeRequested();
|
||||
void closeRequested();
|
||||
|
||||
private:
|
||||
GstThread m_gt;
|
||||
};
|
||||
|
||||
#endif // QGLRENDERER_H
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#Header files
|
||||
HEADERS += ./gstthread.h \
|
||||
./pipeline.h \
|
||||
./qglrenderer.h
|
||||
|
||||
#Source files
|
||||
SOURCES += ./gstthread.cpp \
|
||||
./main.cpp \
|
||||
./pipeline.cpp \
|
||||
./qglrenderer.cpp
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
TEMPLATE = app
|
||||
TARGET = qglwidgetvideooverlay
|
||||
DESTDIR = ./debug
|
||||
QT += opengl
|
||||
CONFIG += debug
|
||||
CONFIG += link_pkgconfig
|
||||
DEFINES += UNICODE QT_THREAD_SUPPORT QT_CORE_LIB QT_GUI_LIB
|
||||
PKGCONFIG = gstreamer-1.0 gstreamer-video-1.0
|
||||
|
||||
win32 {
|
||||
DEFINES += WIN32
|
||||
INCLUDEPATH += ./GeneratedFiles \
|
||||
./GeneratedFiles/Debug \
|
||||
C:/gstreamer/include \
|
||||
C:/gstreamer/include/libxml2 \
|
||||
C:/gstreamer/include/glib-2.0 \
|
||||
C:/gstreamer/lib/glib-2.0/include \
|
||||
C:/gstreamer/include/gstreamer-1.0
|
||||
LIBS += -L"C:/gstreamer/lib" \
|
||||
-L"C:/gstreamer/bin" \
|
||||
-lgstreamer-1.0 \
|
||||
-lgstvideo-1.0 \
|
||||
-lglib-2.0 \
|
||||
-lgmodule-2.0 \
|
||||
-lgobject-2.0 \
|
||||
-lgthread-2.0 \
|
||||
-lopengl32 \
|
||||
-lglu32
|
||||
}
|
||||
|
||||
unix {
|
||||
DEFINES += UNIX
|
||||
INCLUDEPATH += GeneratedFiles \
|
||||
GeneratedFiles/Debug
|
||||
LIBS += \
|
||||
-lGLU \
|
||||
-lGL
|
||||
}
|
||||
|
||||
DEPENDPATH += .
|
||||
MOC_DIR += ./GeneratedFiles/debug
|
||||
OBJECTS_DIR += debug
|
||||
UI_DIR += ./GeneratedFiles
|
||||
RCC_DIR += ./GeneratedFiles
|
||||
|
||||
#Include file(s)
|
||||
include(qglwidgetvideooverlay.pri)
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef __ASYNCQUEUE_H
|
||||
#define __ASYNCQUEUE_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QList>
|
||||
|
||||
/**
|
||||
* This is the thread safe implementation of the Queue. It can be
|
||||
* used in classical producers/consumers multithreaded scenario. The
|
||||
* template parameter is the class which can be put/get to/from the
|
||||
* queue.
|
||||
*/
|
||||
template<class T>
|
||||
class AsyncQueue
|
||||
{
|
||||
public:
|
||||
AsyncQueue() : waitingReaders(0) {}
|
||||
|
||||
int size()
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
return this->buffer.size();
|
||||
}
|
||||
|
||||
void put(const T& item)
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
this->buffer.push_back(item);
|
||||
if(this->waitingReaders)
|
||||
this->bufferIsNotEmpty.wakeOne();
|
||||
}
|
||||
|
||||
T get()
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
while(this->buffer.size() == 0)
|
||||
{
|
||||
++(this->waitingReaders);
|
||||
this->bufferIsNotEmpty.wait(&mutex);
|
||||
--(this->waitingReaders);
|
||||
}
|
||||
T item = this->buffer.front();
|
||||
this->buffer.pop_front();
|
||||
return item;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef QList<T> Container;
|
||||
QMutex mutex;
|
||||
QWaitCondition bufferIsNotEmpty;
|
||||
Container buffer;
|
||||
short waitingReaders;
|
||||
};
|
||||
|
||||
|
||||
#endif // __ASYNCQUEUE_H
|
||||
@@ -0,0 +1,32 @@
|
||||
Requires: >= Qt 5.1 for the x11extras module (or else you need to
|
||||
get x11extras from gitorious yourself)
|
||||
|
||||
This example illustrates how to integrate Gstreamer GL plugin with
|
||||
Qt. In particular it uses glupload with fakesink elements to create
|
||||
texture with decoded video frame. This texture is shared with
|
||||
QGLWidget derived class, which paints a cube with video texture on
|
||||
each face.
|
||||
|
||||
To compile the example, include and library paths might be adjusted in
|
||||
.pro file according to your installation of the gstreamer and
|
||||
corresponding development files. Most probably, the adjustments will
|
||||
be necessary on Windows.
|
||||
|
||||
To run the example simply start executable file after compilation. If
|
||||
there is no command line arguments provided, then videotestsrc element
|
||||
will be used to generate video. The following pipeline will be created
|
||||
in this case:
|
||||
|
||||
videotestsrc ! video/x-raw, width=640, height=480, framerate=(fraction)30/1 ! glupload ! fakesink sync=1
|
||||
|
||||
It is also possible to provide the video file name as a first command
|
||||
line parameter, i.e. ./qglwtextureshare myvideo.ogv . In this case,
|
||||
the following pipeline will be executed:
|
||||
|
||||
filesrc location=myvideo.ogv ! decodebin2 ! glupload ! fakesink sync=1
|
||||
|
||||
I would appreciate any feedback and improvement suggestions for this
|
||||
example.
|
||||
|
||||
Have fun :-)
|
||||
Andrey Nechypurenko (andreynech@gmail.com)
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2010 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2010 Nuno Santos <nunosantos@imaginando.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.
|
||||
*/
|
||||
|
||||
#if !defined(MAC_OS_X_VERSION_MAX_ALLOWED) || MAC_OS_X_VERSION_MAX_ALLOWED >= 1014
|
||||
# define GL_SILENCE_DEPRECATION
|
||||
#endif
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
void *qt_current_nsopengl_context()
|
||||
{
|
||||
return CGLGetCurrentContext ();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.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.
|
||||
*/
|
||||
|
||||
#include "pipeline.h"
|
||||
#include "gstthread.h"
|
||||
|
||||
|
||||
GstThread::GstThread(GstGLDisplay *display,
|
||||
GstGLContext *context,
|
||||
const QString &videoLocation,
|
||||
const char *renderer_slot,
|
||||
QObject *parent):
|
||||
QThread(parent),
|
||||
m_videoLocation(videoLocation)
|
||||
{
|
||||
m_pipeline = new Pipeline(display, context, m_videoLocation, this);
|
||||
QObject::connect(m_pipeline, SIGNAL(newFrameReady()), this->parent(), renderer_slot, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
GstThread::~GstThread()
|
||||
{
|
||||
}
|
||||
|
||||
void GstThread::stop()
|
||||
{
|
||||
if(m_pipeline)
|
||||
m_pipeline->stop();
|
||||
}
|
||||
|
||||
void GstThread::run()
|
||||
{
|
||||
qDebug("Starting gst pipeline");
|
||||
m_pipeline->start(); //it runs the gmainloop on win32
|
||||
|
||||
#ifndef Q_WS_WIN
|
||||
//works like the gmainloop on linux (GstEvent are handled)
|
||||
connect(m_pipeline, SIGNAL(stopRequested()), this, SLOT(quit()));
|
||||
exec();
|
||||
#endif
|
||||
|
||||
m_pipeline->unconfigure();
|
||||
|
||||
m_pipeline = NULL;
|
||||
// This is not a memory leak. Pipeline will be deleted
|
||||
// when the parent object (this) will be destroyed.
|
||||
// We set m_pipeline to NULL to prevent further attempts
|
||||
// to stop already stopped pipeline
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef GSTTHREAD_H
|
||||
#define GSTTHREAD_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include <gst/gl/gstglcontext.h>
|
||||
|
||||
|
||||
class Pipeline;
|
||||
|
||||
class GstThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GstThread(GstGLDisplay *display,
|
||||
GstGLContext *context,
|
||||
const QString &videoLocation,
|
||||
const char *renderer_slot,
|
||||
QObject *parent = 0);
|
||||
|
||||
~GstThread();
|
||||
|
||||
Pipeline *getPipeline() {return this->m_pipeline;}
|
||||
|
||||
public Q_SLOTS:
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
const QString m_videoLocation;
|
||||
Pipeline* m_pipeline;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.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.
|
||||
*/
|
||||
|
||||
#include <QApplication>
|
||||
#include "qglrenderer.h"
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
/* FIXME: port the example to shaders and remove this */
|
||||
g_setenv ("GST_GL_API", "opengl", FALSE);
|
||||
|
||||
gst_init (NULL, NULL);
|
||||
QApplication a(argc, argv);
|
||||
a.connect(&a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()));
|
||||
QGLRenderer w(argc > 1 ? argv[1] : "");
|
||||
w.setWindowTitle("Texture sharing example");
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
sources = [
|
||||
'main.cpp',
|
||||
'gstthread.cpp',
|
||||
'pipeline.cpp',
|
||||
'qglrenderer.cpp',
|
||||
]
|
||||
|
||||
moc_headers = [
|
||||
'gstthread.h',
|
||||
'pipeline.h',
|
||||
'qglrenderer.h',
|
||||
]
|
||||
|
||||
qtwinsys_deps = []
|
||||
if enabled_gl_winsys.contains('x11') and enabled_gl_platforms.contains('glx')
|
||||
qt5x11extras = dependency('qt5', modules : ['X11Extras'], required : false)
|
||||
if qt5x11extras.found()
|
||||
qtwinsys_deps += [qt5x11extras, glx_dep]
|
||||
else
|
||||
subdir_done()
|
||||
endif
|
||||
endif
|
||||
|
||||
moc_files = qt5_mod.preprocess(moc_headers : moc_headers)
|
||||
executable('qglwtextureshare', sources, moc_files,
|
||||
cpp_args : [gst_plugins_base_args] + qt_cxx_warn_less,
|
||||
include_directories: [configinc, libsinc],
|
||||
dependencies : [qt5core_dep, qt5gui_dep, qt5opengl_dep, qtwinsys_deps, gst_dep, video_dep, gstgl_dep, gstglproto_dep],
|
||||
install: false)
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.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.
|
||||
*/
|
||||
|
||||
#include <gst/gl/gl.h>
|
||||
#include "../gl-compat-defines.h"
|
||||
#include "pipeline.h"
|
||||
|
||||
Pipeline::Pipeline (GstGLDisplay *display,
|
||||
GstGLContext * context, const QString & videoLocation, QObject * parent)
|
||||
:
|
||||
QObject (parent),
|
||||
m_videoLocation (videoLocation),
|
||||
m_loop (NULL),
|
||||
m_bus (NULL),
|
||||
m_pipeline (NULL)
|
||||
{
|
||||
this->display = display;
|
||||
this->context = context;
|
||||
this->configure ();
|
||||
}
|
||||
|
||||
Pipeline::~Pipeline ()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Pipeline::configure ()
|
||||
{
|
||||
|
||||
#ifdef Q_WS_WIN
|
||||
m_loop = g_main_loop_new (NULL, FALSE);
|
||||
#endif
|
||||
|
||||
if (m_videoLocation.isEmpty ()) {
|
||||
qDebug ("No video file specified. Using video test source.");
|
||||
m_pipeline =
|
||||
GST_PIPELINE (gst_parse_launch
|
||||
("videotestsrc ! "
|
||||
"video/x-raw, width=640, height=480, "
|
||||
"framerate=(fraction)30/1 ! "
|
||||
"glupload ! gleffects effect=5 ! fakesink sync=1", NULL));
|
||||
} else {
|
||||
QByteArray ba = m_videoLocation.toLocal8Bit ();
|
||||
qDebug ("Loading video: %s", ba.data ());
|
||||
gchar *pipeline = g_strdup_printf ("filesrc name=f ! "
|
||||
"decodebin ! gleffects effect=5 ! " "fakesink sync=1");
|
||||
m_pipeline = GST_PIPELINE (gst_parse_launch (pipeline, NULL));
|
||||
GstElement *f = gst_bin_get_by_name (GST_BIN (m_pipeline), "f");
|
||||
g_object_set (G_OBJECT (f), "location", ba.data (), NULL);
|
||||
gst_object_unref (GST_OBJECT (f));
|
||||
g_free (pipeline);
|
||||
}
|
||||
|
||||
m_bus = gst_pipeline_get_bus (GST_PIPELINE (m_pipeline));
|
||||
gst_bus_add_watch (m_bus, (GstBusFunc) bus_call, this);
|
||||
gst_bus_enable_sync_message_emission (m_bus);
|
||||
g_signal_connect (m_bus, "sync-message", G_CALLBACK (sync_bus_call), this);
|
||||
gst_object_unref (m_bus);
|
||||
|
||||
gst_element_set_state (GST_ELEMENT (this->m_pipeline), GST_STATE_PAUSED);
|
||||
GstState state = GST_STATE_PAUSED;
|
||||
if (gst_element_get_state (GST_ELEMENT (this->m_pipeline),
|
||||
&state, NULL, GST_CLOCK_TIME_NONE)
|
||||
!= GST_STATE_CHANGE_SUCCESS) {
|
||||
qDebug ("failed to pause pipeline");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Pipeline::start ()
|
||||
{
|
||||
// set a callback to retrieve the gst gl textures
|
||||
GstElement *fakesink = gst_bin_get_by_name (GST_BIN (this->m_pipeline),
|
||||
"fakesink0");
|
||||
g_object_set (G_OBJECT (fakesink), "signal-handoffs", TRUE, NULL);
|
||||
g_signal_connect (fakesink, "handoff", G_CALLBACK (on_gst_buffer), this);
|
||||
gst_object_unref (fakesink);
|
||||
|
||||
GstStateChangeReturn ret =
|
||||
gst_element_set_state (GST_ELEMENT (this->m_pipeline), GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
qDebug ("Failed to start up pipeline!");
|
||||
|
||||
/* check if there is an error message with details on the bus */
|
||||
GstMessage *msg = gst_bus_poll (this->m_bus, GST_MESSAGE_ERROR, 0);
|
||||
if (msg) {
|
||||
GError *err = NULL;
|
||||
gst_message_parse_error (msg, &err, NULL);
|
||||
qDebug ("ERROR: %s", err->message);
|
||||
g_error_free (err);
|
||||
gst_message_unref (msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#ifdef Q_WS_WIN
|
||||
g_main_loop_run (m_loop);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* fakesink handoff callback */
|
||||
void
|
||||
Pipeline::on_gst_buffer (GstElement * element,
|
||||
GstBuffer * buf, GstPad * pad, Pipeline * p)
|
||||
{
|
||||
Q_UNUSED (pad)
|
||||
Q_UNUSED (element)
|
||||
|
||||
/* ref then push buffer to use it in qt */
|
||||
gst_buffer_ref (buf);
|
||||
p->queue_input_buf.put (buf);
|
||||
|
||||
if (p->queue_input_buf.size () > 3)
|
||||
p->notifyNewFrame ();
|
||||
|
||||
/* pop then unref buffer we have finished to use in qt */
|
||||
if (p->queue_output_buf.size () > 3) {
|
||||
GstBuffer *buf_old = (p->queue_output_buf.get ());
|
||||
if (buf_old)
|
||||
gst_buffer_unref (buf_old);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Pipeline::stop ()
|
||||
{
|
||||
#ifdef Q_WS_WIN
|
||||
g_main_loop_quit (m_loop);
|
||||
#else
|
||||
emit stopRequested ();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
Pipeline::unconfigure ()
|
||||
{
|
||||
gst_element_set_state (GST_ELEMENT (this->m_pipeline), GST_STATE_NULL);
|
||||
|
||||
GstBuffer *buf;
|
||||
while (this->queue_input_buf.size ()) {
|
||||
buf = (GstBuffer *) (this->queue_input_buf.get ());
|
||||
gst_buffer_unref (buf);
|
||||
}
|
||||
while (this->queue_output_buf.size ()) {
|
||||
buf = (GstBuffer *) (this->queue_output_buf.get ());
|
||||
gst_buffer_unref (buf);
|
||||
}
|
||||
|
||||
gst_object_unref (m_pipeline);
|
||||
}
|
||||
|
||||
gboolean Pipeline::bus_call (GstBus * bus, GstMessage * msg, Pipeline * p)
|
||||
{
|
||||
Q_UNUSED (bus)
|
||||
|
||||
switch (GST_MESSAGE_TYPE (msg)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
qDebug ("End-of-stream received. Stopping.");
|
||||
p->stop ();
|
||||
break;
|
||||
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
gchar *
|
||||
debug = NULL;
|
||||
GError *
|
||||
err = NULL;
|
||||
gst_message_parse_error (msg, &err, &debug);
|
||||
qDebug ("Error: %s", err->message);
|
||||
g_error_free (err);
|
||||
if (debug) {
|
||||
qDebug ("Debug deails: %s", debug);
|
||||
g_free (debug);
|
||||
}
|
||||
p->stop ();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean Pipeline::sync_bus_call (GstBus * bus, GstMessage * msg, Pipeline * p)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE (msg)) {
|
||||
case GST_MESSAGE_NEED_CONTEXT:
|
||||
{
|
||||
const gchar *
|
||||
context_type;
|
||||
|
||||
gst_message_parse_context_type (msg, &context_type);
|
||||
g_print ("got need context %s\n", context_type);
|
||||
|
||||
if (g_strcmp0 (context_type, GST_GL_DISPLAY_CONTEXT_TYPE) == 0) {
|
||||
GstContext *display_context = gst_context_new (GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
|
||||
gst_context_set_gl_display (display_context, p->display);
|
||||
gst_element_set_context (GST_ELEMENT (msg->src), display_context);
|
||||
} else if (g_strcmp0 (context_type, "gst.gl.app_context") == 0) {
|
||||
GstContext *app_context = gst_context_new ("gst.gl.app_context", TRUE);
|
||||
GstStructure *s = gst_context_writable_structure (app_context);
|
||||
gst_structure_set (s, "context", GST_TYPE_GL_CONTEXT, p->context, NULL);
|
||||
gst_element_set_context (GST_ELEMENT (msg->src), app_context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef PIPELINE_H
|
||||
#define PIPELINE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <gst/gl/gstglcontext.h>
|
||||
|
||||
#include "AsyncQueue.h"
|
||||
|
||||
|
||||
class Pipeline : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Pipeline(GstGLDisplay *display, GstGLContext *context,
|
||||
const QString &videoLocation,
|
||||
QObject *parent);
|
||||
~Pipeline();
|
||||
|
||||
void configure();
|
||||
void start();
|
||||
void notifyNewFrame() {emit newFrameReady();}
|
||||
void stop();
|
||||
void unconfigure();
|
||||
|
||||
AsyncQueue<GstBuffer*> queue_input_buf;
|
||||
AsyncQueue<GstBuffer*> queue_output_buf;
|
||||
|
||||
Q_SIGNALS:
|
||||
void newFrameReady();
|
||||
void stopRequested();
|
||||
|
||||
private:
|
||||
GstGLDisplay *display;
|
||||
GstGLContext *context;
|
||||
const QString m_videoLocation;
|
||||
GMainLoop* m_loop;
|
||||
GstBus* m_bus;
|
||||
GstPipeline* m_pipeline;
|
||||
static float m_xrot;
|
||||
static float m_yrot;
|
||||
static float m_zrot;
|
||||
|
||||
static void on_gst_buffer(GstElement * element, GstBuffer * buf, GstPad * pad, Pipeline* p);
|
||||
static gboolean bus_call (GstBus *bus, GstMessage *msg, Pipeline* p);
|
||||
static gboolean sync_bus_call (GstBus *bus, GstMessage *msg, Pipeline* p);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.com>
|
||||
* Copyright (C) 2010 Nuno Santos <nunosantos@imaginando.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.
|
||||
*/
|
||||
|
||||
#include <QGLWidget>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QCloseEvent>
|
||||
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
#include <GL/glx.h>
|
||||
#include <QX11Info>
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
#endif
|
||||
|
||||
#include "gstthread.h"
|
||||
#include "qglrenderer.h"
|
||||
#include "pipeline.h"
|
||||
|
||||
#if defined(Q_WS_MAC)
|
||||
extern void *qt_current_nsopengl_context ();
|
||||
#endif
|
||||
|
||||
QGLRenderer::QGLRenderer (const QString & videoLocation, QWidget * parent)
|
||||
:
|
||||
QGLWidget (parent),
|
||||
videoLoc (videoLocation),
|
||||
gst_thread (NULL),
|
||||
closing (false),
|
||||
frame (NULL)
|
||||
{
|
||||
move (20, 10);
|
||||
resize (640, 480);
|
||||
}
|
||||
|
||||
QGLRenderer::~QGLRenderer ()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
QGLRenderer::initializeGL ()
|
||||
{
|
||||
GstGLContext *context;
|
||||
GstGLDisplay *display;
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
display =
|
||||
(GstGLDisplay *) gst_gl_display_x11_new_with_display (QX11Info::
|
||||
display ());
|
||||
#else
|
||||
display = gst_gl_display_new ();
|
||||
#endif
|
||||
|
||||
/* FIXME: Allow the choice at runtime */
|
||||
#if GST_GL_HAVE_PLATFORM_WGL
|
||||
context =
|
||||
gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
|
||||
GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
|
||||
#elif GST_GL_HAVE_PLATFORM_CGL
|
||||
context =
|
||||
gst_gl_context_new_wrapped (display,
|
||||
(guintptr) qt_current_nsopengl_context (), GST_GL_PLATFORM_CGL,
|
||||
GST_GL_API_OPENGL);
|
||||
#elif GST_GL_HAVE_PLATFORM_GLX
|
||||
context =
|
||||
gst_gl_context_new_wrapped (display, (guintptr) glXGetCurrentContext (),
|
||||
GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
|
||||
#endif
|
||||
gst_object_unref (display);
|
||||
|
||||
// We need to unset Qt context before initializing gst-gl plugin.
|
||||
// Otherwise the attempt to share gst-gl context with Qt will fail.
|
||||
this->doneCurrent ();
|
||||
this->gst_thread =
|
||||
new GstThread (display, context, this->videoLoc,
|
||||
SLOT (newFrame ()), this);
|
||||
this->makeCurrent ();
|
||||
|
||||
QObject::connect (this->gst_thread, SIGNAL (finished ()),
|
||||
this, SLOT (close ()));
|
||||
QObject::connect (this, SIGNAL (closeRequested ()),
|
||||
this->gst_thread, SLOT (stop ()), Qt::QueuedConnection);
|
||||
|
||||
qglClearColor (QApplication::palette ().color (QPalette::Active,
|
||||
QPalette::Window));
|
||||
//glShadeModel(GL_FLAT);
|
||||
//glEnable(GL_DEPTH_TEST);
|
||||
//glEnable(GL_CULL_FACE);
|
||||
glEnable (GL_TEXTURE_2D); // Enable Texture Mapping
|
||||
|
||||
this->gst_thread->start ();
|
||||
}
|
||||
|
||||
void
|
||||
QGLRenderer::resizeGL (int width, int height)
|
||||
{
|
||||
// Reset The Current Viewport And Perspective Transformation
|
||||
glViewport (0, 0, width, height);
|
||||
|
||||
glMatrixMode (GL_PROJECTION);
|
||||
glLoadIdentity ();
|
||||
|
||||
glMatrixMode (GL_MODELVIEW);
|
||||
}
|
||||
|
||||
void
|
||||
QGLRenderer::newFrame ()
|
||||
{
|
||||
Pipeline *pipeline = this->gst_thread->getPipeline ();
|
||||
if (!pipeline)
|
||||
return;
|
||||
|
||||
/* frame is initialized as null */
|
||||
if (this->frame)
|
||||
pipeline->queue_output_buf.put (this->frame);
|
||||
|
||||
this->frame = pipeline->queue_input_buf.get ();
|
||||
|
||||
/* direct call to paintGL (no queued) */
|
||||
this->updateGL ();
|
||||
}
|
||||
|
||||
static void
|
||||
flushGstreamerGL (GstGLContext * context, void *data G_GNUC_UNUSED)
|
||||
{
|
||||
context->gl_vtable->Flush ();
|
||||
}
|
||||
|
||||
void
|
||||
QGLRenderer::paintGL ()
|
||||
{
|
||||
static GLfloat xrot = 0;
|
||||
static GLfloat yrot = 0;
|
||||
static GLfloat zrot = 0;
|
||||
|
||||
if (this->frame) {
|
||||
guint tex_id;
|
||||
GstMemory *mem;
|
||||
GstVideoInfo v_info;
|
||||
GstVideoFrame v_frame;
|
||||
GstVideoMeta *v_meta;
|
||||
|
||||
mem = gst_buffer_peek_memory (this->frame, 0);
|
||||
v_meta = gst_buffer_get_video_meta (this->frame);
|
||||
|
||||
Q_ASSERT (gst_is_gl_memory (mem));
|
||||
|
||||
GstGLMemory *gl_memory = (GstGLMemory *) mem;
|
||||
|
||||
gst_gl_context_thread_add (gl_memory->mem.context, flushGstreamerGL, NULL);
|
||||
|
||||
gst_video_info_set_format (&v_info, v_meta->format, v_meta->width,
|
||||
v_meta->height);
|
||||
|
||||
gst_video_frame_map (&v_frame, &v_info, this->frame,
|
||||
(GstMapFlags) (GST_MAP_READ | GST_MAP_GL));
|
||||
|
||||
tex_id = *(guint *) v_frame.data[0];
|
||||
|
||||
glEnable (GL_DEPTH_TEST);
|
||||
|
||||
glEnable (GL_TEXTURE_2D);
|
||||
glBindTexture (GL_TEXTURE_2D, tex_id);
|
||||
if (glGetError () != GL_NO_ERROR) {
|
||||
qDebug ("failed to bind texture that comes from gst-gl");
|
||||
emit closeRequested ();
|
||||
return;
|
||||
}
|
||||
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
|
||||
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glMatrixMode (GL_MODELVIEW);
|
||||
glLoadIdentity ();
|
||||
|
||||
glScalef (0.5f, 0.5f, 0.5f);
|
||||
|
||||
glRotatef (xrot, 1.0f, 0.0f, 0.0f);
|
||||
glRotatef (yrot, 0.0f, 1.0f, 0.0f);
|
||||
glRotatef (zrot, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
glBegin (GL_QUADS);
|
||||
// Front Face
|
||||
glTexCoord2f (1.0f, 0.0f);
|
||||
glVertex3f (-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f (0.0f, 0.0f);
|
||||
glVertex3f (1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f (0.0f, 1.0f);
|
||||
glVertex3f (1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f (1.0f, 1.0f);
|
||||
glVertex3f (-1.0f, 1.0f, 1.0f);
|
||||
// Back Face
|
||||
glTexCoord2f (0.0f, 0.0f);
|
||||
glVertex3f (-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f (0.0f, 1.0f);
|
||||
glVertex3f (-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f (1.0f, 1.0f);
|
||||
glVertex3f (1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f (1.0f, 0.0f);
|
||||
glVertex3f (1.0f, -1.0f, -1.0f);
|
||||
// Top Face
|
||||
glTexCoord2f (1.0f, 1.0f);
|
||||
glVertex3f (-1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f (1.0f, 0.0f);
|
||||
glVertex3f (-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f (0.0f, 0.0f);
|
||||
glVertex3f (1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f (0.0f, 1.0f);
|
||||
glVertex3f (1.0f, 1.0f, -1.0f);
|
||||
// Bottom Face
|
||||
glTexCoord2f (1.0f, 0.0f);
|
||||
glVertex3f (-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f (0.0f, 0.0f);
|
||||
glVertex3f (1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f (0.0f, 1.0f);
|
||||
glVertex3f (1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f (1.0f, 1.0f);
|
||||
glVertex3f (-1.0f, -1.0f, 1.0f);
|
||||
// Right face
|
||||
glTexCoord2f (0.0f, 0.0f);
|
||||
glVertex3f (1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f (0.0f, 1.0f);
|
||||
glVertex3f (1.0f, 1.0f, -1.0f);
|
||||
glTexCoord2f (1.0f, 1.0f);
|
||||
glVertex3f (1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f (1.0f, 0.0f);
|
||||
glVertex3f (1.0f, -1.0f, 1.0f);
|
||||
// Left Face
|
||||
glTexCoord2f (1.0f, 0.0f);
|
||||
glVertex3f (-1.0f, -1.0f, -1.0f);
|
||||
glTexCoord2f (0.0f, 0.0f);
|
||||
glVertex3f (-1.0f, -1.0f, 1.0f);
|
||||
glTexCoord2f (0.0f, 1.0f);
|
||||
glVertex3f (-1.0f, 1.0f, 1.0f);
|
||||
glTexCoord2f (1.0f, 1.0f);
|
||||
glVertex3f (-1.0f, 1.0f, -1.0f);
|
||||
glEnd ();
|
||||
|
||||
xrot += 0.3f;
|
||||
yrot += 0.2f;
|
||||
zrot += 0.4f;
|
||||
|
||||
glLoadIdentity();
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glBindTexture (GL_TEXTURE_2D, 0);
|
||||
|
||||
gst_video_frame_unmap (&v_frame);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
QGLRenderer::closeEvent (QCloseEvent * event)
|
||||
{
|
||||
if (this->closing == false) {
|
||||
this->closing = true;
|
||||
emit closeRequested ();
|
||||
event->ignore ();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.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.
|
||||
*/
|
||||
|
||||
#ifndef QGLRENDERER_H
|
||||
#define QGLRENDERER_H
|
||||
|
||||
#include <QGLWidget>
|
||||
|
||||
#include <gst/gl/gstglcontext.h>
|
||||
|
||||
|
||||
class GstThread;
|
||||
|
||||
class QGLRenderer : public QGLWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QGLRenderer(const QString &videoLocation, QWidget *parent = nullptr);
|
||||
~QGLRenderer();
|
||||
|
||||
void closeEvent(QCloseEvent* event);
|
||||
|
||||
Q_SIGNALS:
|
||||
void closeRequested();
|
||||
|
||||
public Q_SLOTS:
|
||||
void newFrame();
|
||||
|
||||
protected:
|
||||
virtual void initializeGL();
|
||||
virtual void resizeGL(int width, int height);
|
||||
virtual void paintGL();
|
||||
|
||||
private:
|
||||
QString videoLoc;
|
||||
GstThread *gst_thread;
|
||||
bool closing;
|
||||
GstBuffer *frame;
|
||||
};
|
||||
|
||||
#endif // QGLRENDERER_H
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
TEMPLATE = app
|
||||
TARGET = qglwtextureshare
|
||||
QT += opengl
|
||||
|
||||
# Add console to the CONFIG to see debug messages printed in
|
||||
# the console on Windows
|
||||
# CONFIG += console
|
||||
DESTDIR = ./debug
|
||||
DEFINES += UNICODE QT_THREAD_SUPPORT QT_CORE_LIB QT_GUI_LIB
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG=gstreamer-1.0 gstreamer-video-1.0 gstreamer-gl-1.0
|
||||
|
||||
win32 {
|
||||
DEFINES += WIN32
|
||||
INCLUDEPATH += \
|
||||
C:/gstreamer/include \
|
||||
C:/gstreamer/include/libxml2 \
|
||||
C:/gstreamer/include/glib-2.0 \
|
||||
C:/gstreamer/lib/glib-2.0/include \
|
||||
C:/gstreamer/include/gstreamer-1.0
|
||||
LIBS += -L"C:/gstreamer/lib" \
|
||||
-L"C:/gstreamer/bin" \
|
||||
-lgstreamer-1.0 \
|
||||
-lgstgl-1.0 \
|
||||
-lgstvideo-1.0 \
|
||||
-lglib-2.0 \
|
||||
-lgmodule-2.0 \
|
||||
-lgobject-2.0 \
|
||||
-lgthread-2.0 \
|
||||
-lgstvideo-1.0 \
|
||||
-lopengl32 \
|
||||
-lglu32
|
||||
}
|
||||
unix:!mac {
|
||||
DEFINES += UNIX
|
||||
LIBS += \
|
||||
-lgstvideo-1.0 \
|
||||
-lgstgl-1.0 \
|
||||
-lGLU \
|
||||
-lGL
|
||||
QT += x11extras
|
||||
}
|
||||
mac {
|
||||
DEFINES += MACOSX
|
||||
INCLUDEPATH += /opt/local/include/ \
|
||||
/opt/local/include/gstreamer-1.0/ \
|
||||
/opt/local/include/glib-2.0/ \
|
||||
/opt/local/lib/glib-2.0/include \
|
||||
/opt/local/include/libxml2
|
||||
LIBS += -L/opt/local/lib \
|
||||
-lgstreamer-1.0 \
|
||||
-lgstapp-1.0 \
|
||||
-lgstvideo-1.0 \
|
||||
-lglib-2.0 \
|
||||
-lgobject-2.0 \
|
||||
-lcxcore \
|
||||
-lcvaux \
|
||||
-lcv
|
||||
OBJECTIVE_SOURCES += cocoa_utils.mm
|
||||
LIBS += -framework AppKit
|
||||
}
|
||||
DEPENDPATH += .
|
||||
|
||||
# Header files
|
||||
HEADERS += gstthread.h \
|
||||
pipeline.h \
|
||||
qglrenderer.h \
|
||||
AsyncQueue.h \
|
||||
|
||||
# Source files
|
||||
SOURCES += gstthread.cpp \
|
||||
main.cpp \
|
||||
pipeline.cpp \
|
||||
qglrenderer.cpp
|
||||
|
||||
DEPENDPATH += .
|
||||
MOC_DIR += ./GeneratedFiles/debug
|
||||
OBJECTS_DIR += debug
|
||||
UI_DIR += ./GeneratedFiles
|
||||
RCC_DIR += ./GeneratedFiles
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user