This commit is contained in:
Akkariin Meiko
2022-03-12 03:16:09 +08:00
Unverified
parent 12b76e0c7a
commit 27c4ec74a1
10075 changed files with 5122287 additions and 1 deletions
@@ -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;
}
@@ -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;
}
@@ -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)
@@ -0,0 +1,10 @@
#Header files
HEADERS += ./gstthread.h \
./pipeline.h \
./qrenderer.h
#Source files
SOURCES += ./gstthread.cpp \
./main.cpp \
./pipeline.cpp \
./qrenderer.cpp
@@ -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
@@ -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
@@ -0,0 +1,10 @@
#Header files
HEADERS += ./gstthread.h \
./pipeline.h \
./qglrenderer.h
#Source files
SOURCES += ./gstthread.cpp \
./main.cpp \
./pipeline.cpp \
./qglrenderer.cpp
@@ -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
@@ -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