Pass our C files to gst-indent

This commit is contained in:
Thibault Saunier 2019-02-07 16:32:58 -03:00 committed by Thibault Saunier
parent 42285ae1dc
commit d2469972f0
26 changed files with 1477 additions and 786 deletions

View file

@ -3,14 +3,12 @@
static GMainLoop *loop; static GMainLoop *loop;
static gboolean static gboolean
my_bus_callback (GstBus *bus, my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
GstMessage *message,
gpointer data)
{ {
g_print ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message)); g_print ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message));
switch (GST_MESSAGE_TYPE (message)) { switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR: { case GST_MESSAGE_ERROR:{
GError *err; GError *err;
gchar *debug; gchar *debug;
@ -39,8 +37,7 @@ my_bus_callback (GstBus *bus,
} }
gint gint
main (gint argc, main (gint argc, gchar * argv[])
gchar *argv[])
{ {
GstElement *pipeline; GstElement *pipeline;
GstBus *bus; GstBus *bus;

View file

@ -1,8 +1,7 @@
#include <gst/gst.h> #include <gst/gst.h>
static void static void
link_to_multiplexer (GstPad *tolink_pad, link_to_multiplexer (GstPad * tolink_pad, GstElement * mux)
GstElement *mux)
{ {
GstPad *pad; GstPad *pad;
gchar *srcname, *sinkname; gchar *srcname, *sinkname;
@ -19,9 +18,9 @@ link_to_multiplexer (GstPad *tolink_pad,
} }
static void static void
some_function (GstElement *tee) some_function (GstElement * tee)
{ {
GstPad * pad; GstPad *pad;
gchar *name; gchar *name;
pad = gst_element_get_request_pad (tee, "src%d"); pad = gst_element_get_request_pad (tee, "src%d");

View file

@ -6,26 +6,34 @@
/* /*
* Java Bindings * Java Bindings
*/ */
static jstring gst_native_get_gstreamer_info (JNIEnv* env, jobject thiz) { static jstring
char *version_utf8 = gst_version_string(); gst_native_get_gstreamer_info (JNIEnv * env, jobject thiz)
jstring *version_jstring = (*env)->NewStringUTF(env, version_utf8); {
char *version_utf8 = gst_version_string ();
jstring *version_jstring = (*env)->NewStringUTF (env, version_utf8);
g_free (version_utf8); g_free (version_utf8);
return version_jstring; return version_jstring;
} }
static JNINativeMethod native_methods[] = { static JNINativeMethod native_methods[] = {
{ "nativeGetGStreamerInfo", "()Ljava/lang/String;", (void *) gst_native_get_gstreamer_info} {"nativeGetGStreamerInfo", "()Ljava/lang/String;",
(void *) gst_native_get_gstreamer_info}
}; };
jint JNI_OnLoad(JavaVM *vm, void *reserved) { jint
JNI_OnLoad (JavaVM * vm, void *reserved)
{
JNIEnv *env = NULL; JNIEnv *env = NULL;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print (ANDROID_LOG_ERROR, "tutorial-1", "Could not retrieve JNIEnv"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-1",
"Could not retrieve JNIEnv");
return 0; return 0;
} }
jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1"); jclass klass = (*env)->FindClass (env,
(*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods)); "org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1");
(*env)->RegisterNatives (env, klass, native_methods,
G_N_ELEMENTS (native_methods));
return JNI_VERSION_1_4; return JNI_VERSION_1_4;
} }

View file

@ -20,12 +20,13 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
#endif #endif
/* Structure to contain all our information, so we can pass it to callbacks */ /* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData { typedef struct _CustomData
jobject app; /* Application instance, used to call its methods. A global reference is kept. */ {
GstElement *pipeline; /* The running pipeline */ jobject app; /* Application instance, used to call its methods. A global reference is kept. */
GMainContext *context; /* GLib context used to run the main loop */ GstElement *pipeline; /* The running pipeline */
GMainLoop *main_loop; /* GLib main loop */ GMainContext *context; /* GLib context used to run the main loop */
gboolean initialized; /* To avoid informing the UI multiple times about the initialization */ GMainLoop *main_loop; /* GLib main loop */
gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
} CustomData; } CustomData;
/* These global variables cache values which are not changing during execution */ /* These global variables cache values which are not changing during execution */
@ -41,7 +42,9 @@ static jmethodID on_gstreamer_initialized_method_id;
*/ */
/* Register this thread with the VM */ /* Register this thread with the VM */
static JNIEnv *attach_current_thread (void) { static JNIEnv *
attach_current_thread (void)
{
JNIEnv *env; JNIEnv *env;
JavaVMAttachArgs args; JavaVMAttachArgs args;
@ -59,13 +62,17 @@ static JNIEnv *attach_current_thread (void) {
} }
/* Unregister this thread from the VM */ /* Unregister this thread from the VM */
static void detach_current_thread (void *env) { static void
detach_current_thread (void *env)
{
GST_DEBUG ("Detaching thread %p", g_thread_self ()); GST_DEBUG ("Detaching thread %p", g_thread_self ());
(*java_vm)->DetachCurrentThread (java_vm); (*java_vm)->DetachCurrentThread (java_vm);
} }
/* Retrieve the JNI environment for this thread */ /* Retrieve the JNI environment for this thread */
static JNIEnv *get_jni_env (void) { static JNIEnv *
get_jni_env (void)
{
JNIEnv *env; JNIEnv *env;
if ((env = pthread_getspecific (current_jni_env)) == NULL) { if ((env = pthread_getspecific (current_jni_env)) == NULL) {
@ -77,10 +84,12 @@ static JNIEnv *get_jni_env (void) {
} }
/* Change the content of the UI's TextView */ /* Change the content of the UI's TextView */
static void set_ui_message (const gchar *message, CustomData *data) { static void
set_ui_message (const gchar * message, CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
GST_DEBUG ("Setting message to: %s", message); GST_DEBUG ("Setting message to: %s", message);
jstring jmessage = (*env)->NewStringUTF(env, message); jstring jmessage = (*env)->NewStringUTF (env, message);
(*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
@ -90,13 +99,17 @@ static void set_ui_message (const gchar *message, CustomData *data) {
} }
/* Retrieve errors from the bus and show them on the UI */ /* Retrieve errors from the bus and show them on the UI */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
gchar *message_string; gchar *message_string;
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message); message_string =
g_strdup_printf ("Error received from element %s: %s",
GST_OBJECT_NAME (msg->src), err->message);
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
set_ui_message (message_string, data); set_ui_message (message_string, data);
@ -105,23 +118,29 @@ static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Notify UI about pipeline state changes */ /* Notify UI about pipeline state changes */
static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
/* Only pay attention to messages coming from the pipeline, not its children */ /* Only pay attention to messages coming from the pipeline, not its children */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state)); gchar *message = g_strdup_printf ("State changed to %s",
set_ui_message(message, data); gst_element_state_get_name (new_state));
set_ui_message (message, data);
g_free (message); g_free (message);
} }
} }
/* Check if all conditions are met to report GStreamer as initialized. /* Check if all conditions are met to report GStreamer as initialized.
* These conditions will change depending on the application */ * These conditions will change depending on the application */
static void check_initialization_complete (CustomData *data) { static void
check_initialization_complete (CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
if (!data->initialized && data->main_loop) { if (!data->initialized && data->main_loop) {
GST_DEBUG ("Initialization complete, notifying application. main_loop:%p", data->main_loop); GST_DEBUG ("Initialization complete, notifying application. main_loop:%p",
data->main_loop);
(*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
@ -132,10 +151,12 @@ static void check_initialization_complete (CustomData *data) {
} }
/* Main method for the native code. This is executed on its own thread. */ /* Main method for the native code. This is executed on its own thread. */
static void *app_function (void *userdata) { static void *
app_function (void *userdata)
{
JavaVMAttachArgs args; JavaVMAttachArgs args;
GstBus *bus; GstBus *bus;
CustomData *data = (CustomData *)userdata; CustomData *data = (CustomData *) userdata;
GSource *bus_source; GSource *bus_source;
GError *error = NULL; GError *error = NULL;
@ -143,14 +164,17 @@ static void *app_function (void *userdata) {
/* Create our own GLib Main Context and make it the default one */ /* Create our own GLib Main Context and make it the default one */
data->context = g_main_context_new (); data->context = g_main_context_new ();
g_main_context_push_thread_default(data->context); g_main_context_push_thread_default (data->context);
/* Build pipeline */ /* Build pipeline */
data->pipeline = gst_parse_launch("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error); data->pipeline =
gst_parse_launch
("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error);
if (error) { if (error) {
gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message); gchar *message =
g_strdup_printf ("Unable to build pipeline: %s", error->message);
g_clear_error (&error); g_clear_error (&error);
set_ui_message(message, data); set_ui_message (message, data);
g_free (message); g_free (message);
return NULL; return NULL;
} }
@ -158,11 +182,14 @@ static void *app_function (void *userdata) {
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data->pipeline); bus = gst_element_get_bus (data->pipeline);
bus_source = gst_bus_create_watch (bus); bus_source = gst_bus_create_watch (bus);
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL); g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach (bus_source, data->context); g_source_attach (bus_source, data->context);
g_source_unref (bus_source); g_source_unref (bus_source);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data); data);
g_signal_connect (G_OBJECT (bus), "message::state-changed",
(GCallback) state_changed_cb, data);
gst_object_unref (bus); gst_object_unref (bus);
/* Create a GLib Main Loop and set it to run */ /* Create a GLib Main Loop and set it to run */
@ -175,7 +202,7 @@ static void *app_function (void *userdata) {
data->main_loop = NULL; data->main_loop = NULL;
/* Free resources */ /* Free resources */
g_main_context_pop_thread_default(data->context); g_main_context_pop_thread_default (data->context);
g_main_context_unref (data->context); g_main_context_unref (data->context);
gst_element_set_state (data->pipeline, GST_STATE_NULL); gst_element_set_state (data->pipeline, GST_STATE_NULL);
gst_object_unref (data->pipeline); gst_object_unref (data->pipeline);
@ -188,11 +215,14 @@ static void *app_function (void *userdata) {
*/ */
/* Instruct the native code to create its internal data structure, pipeline and thread */ /* Instruct the native code to create its internal data structure, pipeline and thread */
static void gst_native_init (JNIEnv* env, jobject thiz) { static void
gst_native_init (JNIEnv * env, jobject thiz)
{
CustomData *data = g_new0 (CustomData, 1); CustomData *data = g_new0 (CustomData, 1);
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2", 0, "Android tutorial 2"); GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2", 0,
gst_debug_set_threshold_for_name("tutorial-2", GST_LEVEL_DEBUG); "Android tutorial 2");
gst_debug_set_threshold_for_name ("tutorial-2", GST_LEVEL_DEBUG);
GST_DEBUG ("Created CustomData at %p", data); GST_DEBUG ("Created CustomData at %p", data);
data->app = (*env)->NewGlobalRef (env, thiz); data->app = (*env)->NewGlobalRef (env, thiz);
GST_DEBUG ("Created GlobalRef for app object at %p", data->app); GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
@ -200,9 +230,12 @@ static void gst_native_init (JNIEnv* env, jobject thiz) {
} }
/* Quit the main loop, remove the native thread and free resources */ /* Quit the main loop, remove the native thread and free resources */
static void gst_native_finalize (JNIEnv* env, jobject thiz) { static void
gst_native_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Quitting main loop..."); GST_DEBUG ("Quitting main loop...");
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
GST_DEBUG ("Waiting for thread to finish..."); GST_DEBUG ("Waiting for thread to finish...");
@ -216,32 +249,45 @@ static void gst_native_finalize (JNIEnv* env, jobject thiz) {
} }
/* Set pipeline to PLAYING state */ /* Set pipeline to PLAYING state */
static void gst_native_play (JNIEnv* env, jobject thiz) { static void
gst_native_play (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Setting state to PLAYING"); GST_DEBUG ("Setting state to PLAYING");
gst_element_set_state (data->pipeline, GST_STATE_PLAYING); gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
} }
/* Set pipeline to PAUSED state */ /* Set pipeline to PAUSED state */
static void gst_native_pause (JNIEnv* env, jobject thiz) { static void
gst_native_pause (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Setting state to PAUSED"); GST_DEBUG ("Setting state to PAUSED");
gst_element_set_state (data->pipeline, GST_STATE_PAUSED); gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
} }
/* Static class initializer: retrieve method and field IDs */ /* Static class initializer: retrieve method and field IDs */
static jboolean gst_native_class_init (JNIEnv* env, jclass klass) { static jboolean
custom_data_field_id = (*env)->GetFieldID (env, klass, "native_custom_data", "J"); gst_native_class_init (JNIEnv * env, jclass klass)
set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); {
on_gstreamer_initialized_method_id = (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); custom_data_field_id =
(*env)->GetFieldID (env, klass, "native_custom_data", "J");
set_message_method_id =
(*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
on_gstreamer_initialized_method_id =
(*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
if (!custom_data_field_id || !set_message_method_id || !on_gstreamer_initialized_method_id) { if (!custom_data_field_id || !set_message_method_id
|| !on_gstreamer_initialized_method_id) {
/* We emit this message through the Android log instead of the GStreamer log because the later /* We emit this message through the Android log instead of the GStreamer log because the later
* has not been initialized yet. * has not been initialized yet.
*/ */
__android_log_print (ANDROID_LOG_ERROR, "tutorial-2", "The calling class does not implement all necessary interface methods"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-2",
"The calling class does not implement all necessary interface methods");
return JNI_FALSE; return JNI_FALSE;
} }
return JNI_TRUE; return JNI_TRUE;
@ -249,25 +295,30 @@ static jboolean gst_native_class_init (JNIEnv* env, jclass klass) {
/* List of implemented native methods */ /* List of implemented native methods */
static JNINativeMethod native_methods[] = { static JNINativeMethod native_methods[] = {
{ "nativeInit", "()V", (void *) gst_native_init}, {"nativeInit", "()V", (void *) gst_native_init},
{ "nativeFinalize", "()V", (void *) gst_native_finalize}, {"nativeFinalize", "()V", (void *) gst_native_finalize},
{ "nativePlay", "()V", (void *) gst_native_play}, {"nativePlay", "()V", (void *) gst_native_play},
{ "nativePause", "()V", (void *) gst_native_pause}, {"nativePause", "()V", (void *) gst_native_pause},
{ "nativeClassInit", "()Z", (void *) gst_native_class_init} {"nativeClassInit", "()Z", (void *) gst_native_class_init}
}; };
/* Library initializer */ /* Library initializer */
jint JNI_OnLoad(JavaVM *vm, void *reserved) { jint
JNI_OnLoad (JavaVM * vm, void *reserved)
{
JNIEnv *env = NULL; JNIEnv *env = NULL;
java_vm = vm; java_vm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print (ANDROID_LOG_ERROR, "tutorial-2", "Could not retrieve JNIEnv"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-2",
"Could not retrieve JNIEnv");
return 0; return 0;
} }
jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_2/Tutorial2"); jclass klass = (*env)->FindClass (env,
(*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods)); "org/freedesktop/gstreamer/tutorials/tutorial_2/Tutorial2");
(*env)->RegisterNatives (env, klass, native_methods,
G_N_ELEMENTS (native_methods));
pthread_key_create (&current_jni_env, detach_current_thread); pthread_key_create (&current_jni_env, detach_current_thread);

View file

@ -24,13 +24,14 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
#endif #endif
/* Structure to contain all our information, so we can pass it to callbacks */ /* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData { typedef struct _CustomData
jobject app; /* Application instance, used to call its methods. A global reference is kept. */ {
GstElement *pipeline; /* The running pipeline */ jobject app; /* Application instance, used to call its methods. A global reference is kept. */
GMainContext *context; /* GLib context used to run the main loop */ GstElement *pipeline; /* The running pipeline */
GMainLoop *main_loop; /* GLib main loop */ GMainContext *context; /* GLib context used to run the main loop */
gboolean initialized; /* To avoid informing the UI multiple times about the initialization */ GMainLoop *main_loop; /* GLib main loop */
GstElement *video_sink; /* The video sink element which receives XOverlay commands */ gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
GstElement *video_sink; /* The video sink element which receives XOverlay commands */
ANativeWindow *native_window; /* The Android native window where video will be rendered */ ANativeWindow *native_window; /* The Android native window where video will be rendered */
} CustomData; } CustomData;
@ -47,7 +48,9 @@ static jmethodID on_gstreamer_initialized_method_id;
*/ */
/* Register this thread with the VM */ /* Register this thread with the VM */
static JNIEnv *attach_current_thread (void) { static JNIEnv *
attach_current_thread (void)
{
JNIEnv *env; JNIEnv *env;
JavaVMAttachArgs args; JavaVMAttachArgs args;
@ -65,13 +68,17 @@ static JNIEnv *attach_current_thread (void) {
} }
/* Unregister this thread from the VM */ /* Unregister this thread from the VM */
static void detach_current_thread (void *env) { static void
detach_current_thread (void *env)
{
GST_DEBUG ("Detaching thread %p", g_thread_self ()); GST_DEBUG ("Detaching thread %p", g_thread_self ());
(*java_vm)->DetachCurrentThread (java_vm); (*java_vm)->DetachCurrentThread (java_vm);
} }
/* Retrieve the JNI environment for this thread */ /* Retrieve the JNI environment for this thread */
static JNIEnv *get_jni_env (void) { static JNIEnv *
get_jni_env (void)
{
JNIEnv *env; JNIEnv *env;
if ((env = pthread_getspecific (current_jni_env)) == NULL) { if ((env = pthread_getspecific (current_jni_env)) == NULL) {
@ -83,10 +90,12 @@ static JNIEnv *get_jni_env (void) {
} }
/* Change the content of the UI's TextView */ /* Change the content of the UI's TextView */
static void set_ui_message (const gchar *message, CustomData *data) { static void
set_ui_message (const gchar * message, CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
GST_DEBUG ("Setting message to: %s", message); GST_DEBUG ("Setting message to: %s", message);
jstring jmessage = (*env)->NewStringUTF(env, message); jstring jmessage = (*env)->NewStringUTF (env, message);
(*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
@ -96,13 +105,17 @@ static void set_ui_message (const gchar *message, CustomData *data) {
} }
/* Retrieve errors from the bus and show them on the UI */ /* Retrieve errors from the bus and show them on the UI */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
gchar *message_string; gchar *message_string;
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message); message_string =
g_strdup_printf ("Error received from element %s: %s",
GST_OBJECT_NAME (msg->src), err->message);
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
set_ui_message (message_string, data); set_ui_message (message_string, data);
@ -111,26 +124,34 @@ static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Notify UI about pipeline state changes */ /* Notify UI about pipeline state changes */
static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
/* Only pay attention to messages coming from the pipeline, not its children */ /* Only pay attention to messages coming from the pipeline, not its children */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state)); gchar *message = g_strdup_printf ("State changed to %s",
set_ui_message(message, data); gst_element_state_get_name (new_state));
set_ui_message (message, data);
g_free (message); g_free (message);
} }
} }
/* Check if all conditions are met to report GStreamer as initialized. /* Check if all conditions are met to report GStreamer as initialized.
* These conditions will change depending on the application */ * These conditions will change depending on the application */
static void check_initialization_complete (CustomData *data) { static void
check_initialization_complete (CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
if (!data->initialized && data->native_window && data->main_loop) { if (!data->initialized && data->native_window && data->main_loop) {
GST_DEBUG ("Initialization complete, notifying application. native_window:%p main_loop:%p", data->native_window, data->main_loop); GST_DEBUG
("Initialization complete, notifying application. native_window:%p main_loop:%p",
data->native_window, data->main_loop);
/* The main loop is running and we received a native window, inform the sink about it */ /* The main loop is running and we received a native window, inform the sink about it */
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink), (guintptr)data->native_window); gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink),
(guintptr) data->native_window);
(*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
@ -142,10 +163,12 @@ static void check_initialization_complete (CustomData *data) {
} }
/* Main method for the native code. This is executed on its own thread. */ /* Main method for the native code. This is executed on its own thread. */
static void *app_function (void *userdata) { static void *
app_function (void *userdata)
{
JavaVMAttachArgs args; JavaVMAttachArgs args;
GstBus *bus; GstBus *bus;
CustomData *data = (CustomData *)userdata; CustomData *data = (CustomData *) userdata;
GSource *bus_source; GSource *bus_source;
GError *error = NULL; GError *error = NULL;
@ -153,22 +176,27 @@ static void *app_function (void *userdata) {
/* Create our own GLib Main Context and make it the default one */ /* Create our own GLib Main Context and make it the default one */
data->context = g_main_context_new (); data->context = g_main_context_new ();
g_main_context_push_thread_default(data->context); g_main_context_push_thread_default (data->context);
/* Build pipeline */ /* Build pipeline */
data->pipeline = gst_parse_launch("videotestsrc ! warptv ! videoconvert ! autovideosink", &error); data->pipeline =
gst_parse_launch ("videotestsrc ! warptv ! videoconvert ! autovideosink",
&error);
if (error) { if (error) {
gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message); gchar *message =
g_strdup_printf ("Unable to build pipeline: %s", error->message);
g_clear_error (&error); g_clear_error (&error);
set_ui_message(message, data); set_ui_message (message, data);
g_free (message); g_free (message);
return NULL; return NULL;
} }
/* Set the pipeline to READY, so it can already accept a window handle, if we have one */ /* Set the pipeline to READY, so it can already accept a window handle, if we have one */
gst_element_set_state(data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
data->video_sink = gst_bin_get_by_interface(GST_BIN(data->pipeline), GST_TYPE_VIDEO_OVERLAY); data->video_sink =
gst_bin_get_by_interface (GST_BIN (data->pipeline),
GST_TYPE_VIDEO_OVERLAY);
if (!data->video_sink) { if (!data->video_sink) {
GST_ERROR ("Could not retrieve video sink"); GST_ERROR ("Could not retrieve video sink");
return NULL; return NULL;
@ -177,11 +205,14 @@ static void *app_function (void *userdata) {
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data->pipeline); bus = gst_element_get_bus (data->pipeline);
bus_source = gst_bus_create_watch (bus); bus_source = gst_bus_create_watch (bus);
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL); g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach (bus_source, data->context); g_source_attach (bus_source, data->context);
g_source_unref (bus_source); g_source_unref (bus_source);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data); data);
g_signal_connect (G_OBJECT (bus), "message::state-changed",
(GCallback) state_changed_cb, data);
gst_object_unref (bus); gst_object_unref (bus);
/* Create a GLib Main Loop and set it to run */ /* Create a GLib Main Loop and set it to run */
@ -194,7 +225,7 @@ static void *app_function (void *userdata) {
data->main_loop = NULL; data->main_loop = NULL;
/* Free resources */ /* Free resources */
g_main_context_pop_thread_default(data->context); g_main_context_pop_thread_default (data->context);
g_main_context_unref (data->context); g_main_context_unref (data->context);
gst_element_set_state (data->pipeline, GST_STATE_NULL); gst_element_set_state (data->pipeline, GST_STATE_NULL);
gst_object_unref (data->video_sink); gst_object_unref (data->video_sink);
@ -208,11 +239,14 @@ static void *app_function (void *userdata) {
*/ */
/* Instruct the native code to create its internal data structure, pipeline and thread */ /* Instruct the native code to create its internal data structure, pipeline and thread */
static void gst_native_init (JNIEnv* env, jobject thiz) { static void
gst_native_init (JNIEnv * env, jobject thiz)
{
CustomData *data = g_new0 (CustomData, 1); CustomData *data = g_new0 (CustomData, 1);
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-3", 0, "Android tutorial 3"); GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-3", 0,
gst_debug_set_threshold_for_name("tutorial-3", GST_LEVEL_DEBUG); "Android tutorial 3");
gst_debug_set_threshold_for_name ("tutorial-3", GST_LEVEL_DEBUG);
GST_DEBUG ("Created CustomData at %p", data); GST_DEBUG ("Created CustomData at %p", data);
data->app = (*env)->NewGlobalRef (env, thiz); data->app = (*env)->NewGlobalRef (env, thiz);
GST_DEBUG ("Created GlobalRef for app object at %p", data->app); GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
@ -220,9 +254,12 @@ static void gst_native_init (JNIEnv* env, jobject thiz) {
} }
/* Quit the main loop, remove the native thread and free resources */ /* Quit the main loop, remove the native thread and free resources */
static void gst_native_finalize (JNIEnv* env, jobject thiz) { static void
gst_native_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Quitting main loop..."); GST_DEBUG ("Quitting main loop...");
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
GST_DEBUG ("Waiting for thread to finish..."); GST_DEBUG ("Waiting for thread to finish...");
@ -236,50 +273,68 @@ static void gst_native_finalize (JNIEnv* env, jobject thiz) {
} }
/* Set pipeline to PLAYING state */ /* Set pipeline to PLAYING state */
static void gst_native_play (JNIEnv* env, jobject thiz) { static void
gst_native_play (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Setting state to PLAYING"); GST_DEBUG ("Setting state to PLAYING");
gst_element_set_state (data->pipeline, GST_STATE_PLAYING); gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
} }
/* Set pipeline to PAUSED state */ /* Set pipeline to PAUSED state */
static void gst_native_pause (JNIEnv* env, jobject thiz) { static void
gst_native_pause (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Setting state to PAUSED"); GST_DEBUG ("Setting state to PAUSED");
gst_element_set_state (data->pipeline, GST_STATE_PAUSED); gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
} }
/* Static class initializer: retrieve method and field IDs */ /* Static class initializer: retrieve method and field IDs */
static jboolean gst_native_class_init (JNIEnv* env, jclass klass) { static jboolean
custom_data_field_id = (*env)->GetFieldID (env, klass, "native_custom_data", "J"); gst_native_class_init (JNIEnv * env, jclass klass)
set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); {
on_gstreamer_initialized_method_id = (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); custom_data_field_id =
(*env)->GetFieldID (env, klass, "native_custom_data", "J");
set_message_method_id =
(*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
on_gstreamer_initialized_method_id =
(*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
if (!custom_data_field_id || !set_message_method_id || !on_gstreamer_initialized_method_id) { if (!custom_data_field_id || !set_message_method_id
|| !on_gstreamer_initialized_method_id) {
/* We emit this message through the Android log instead of the GStreamer log because the later /* We emit this message through the Android log instead of the GStreamer log because the later
* has not been initialized yet. * has not been initialized yet.
*/ */
__android_log_print (ANDROID_LOG_ERROR, "tutorial-3", "The calling class does not implement all necessary interface methods"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-3",
"The calling class does not implement all necessary interface methods");
return JNI_FALSE; return JNI_FALSE;
} }
return JNI_TRUE; return JNI_TRUE;
} }
static void gst_native_surface_init (JNIEnv *env, jobject thiz, jobject surface) { static void
gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface); return;
GST_DEBUG ("Received surface %p (native window %p)", surface, new_native_window); ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface);
GST_DEBUG ("Received surface %p (native window %p)", surface,
new_native_window);
if (data->native_window) { if (data->native_window) {
ANativeWindow_release (data->native_window); ANativeWindow_release (data->native_window);
if (data->native_window == new_native_window) { if (data->native_window == new_native_window) {
GST_DEBUG ("New native window is the same as the previous one %p", data->native_window); GST_DEBUG ("New native window is the same as the previous one %p",
data->native_window);
if (data->video_sink) { if (data->video_sink) {
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->video_sink)); gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink));
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->video_sink)); gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink));
} }
return; return;
} else { } else {
@ -292,13 +347,17 @@ static void gst_native_surface_init (JNIEnv *env, jobject thiz, jobject surface)
check_initialization_complete (data); check_initialization_complete (data);
} }
static void gst_native_surface_finalize (JNIEnv *env, jobject thiz) { static void
gst_native_surface_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Releasing Native Window %p", data->native_window); GST_DEBUG ("Releasing Native Window %p", data->native_window);
if (data->video_sink) { if (data->video_sink) {
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink), (guintptr)NULL); gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink),
(guintptr) NULL);
gst_element_set_state (data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
} }
@ -309,27 +368,33 @@ static void gst_native_surface_finalize (JNIEnv *env, jobject thiz) {
/* List of implemented native methods */ /* List of implemented native methods */
static JNINativeMethod native_methods[] = { static JNINativeMethod native_methods[] = {
{ "nativeInit", "()V", (void *) gst_native_init}, {"nativeInit", "()V", (void *) gst_native_init},
{ "nativeFinalize", "()V", (void *) gst_native_finalize}, {"nativeFinalize", "()V", (void *) gst_native_finalize},
{ "nativePlay", "()V", (void *) gst_native_play}, {"nativePlay", "()V", (void *) gst_native_play},
{ "nativePause", "()V", (void *) gst_native_pause}, {"nativePause", "()V", (void *) gst_native_pause},
{ "nativeSurfaceInit", "(Ljava/lang/Object;)V", (void *) gst_native_surface_init}, {"nativeSurfaceInit", "(Ljava/lang/Object;)V",
{ "nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize}, (void *) gst_native_surface_init},
{ "nativeClassInit", "()Z", (void *) gst_native_class_init} {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
{"nativeClassInit", "()Z", (void *) gst_native_class_init}
}; };
/* Library initializer */ /* Library initializer */
jint JNI_OnLoad(JavaVM *vm, void *reserved) { jint
JNI_OnLoad (JavaVM * vm, void *reserved)
{
JNIEnv *env = NULL; JNIEnv *env = NULL;
java_vm = vm; java_vm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print (ANDROID_LOG_ERROR, "tutorial-3", "Could not retrieve JNIEnv"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-3",
"Could not retrieve JNIEnv");
return 0; return 0;
} }
jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_3/Tutorial3"); jclass klass = (*env)->FindClass (env,
(*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods)); "org/freedesktop/gstreamer/tutorials/tutorial_3/Tutorial3");
(*env)->RegisterNatives (env, klass, native_methods,
G_N_ELEMENTS (native_methods));
pthread_key_create (&current_jni_env, detach_current_thread); pthread_key_create (&current_jni_env, detach_current_thread);

View file

@ -28,7 +28,8 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
#define SEEK_MIN_DELAY (500 * GST_MSECOND) #define SEEK_MIN_DELAY (500 * GST_MSECOND)
/* Structure to contain all our information, so we can pass it to callbacks */ /* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData { typedef struct _CustomData
{
jobject app; /* Application instance, used to call its methods. A global reference is kept. */ jobject app; /* Application instance, used to call its methods. A global reference is kept. */
GstElement *pipeline; /* The running pipeline */ GstElement *pipeline; /* The running pipeline */
GMainContext *context; /* GLib context used to run the main loop */ GMainContext *context; /* GLib context used to run the main loop */
@ -44,8 +45,9 @@ typedef struct _CustomData {
} CustomData; } CustomData;
/* playbin flags */ /* playbin flags */
typedef enum { typedef enum
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */ {
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */
} GstPlayFlags; } GstPlayFlags;
/* These global variables cache values which are not changing during execution */ /* These global variables cache values which are not changing during execution */
@ -63,7 +65,9 @@ static jmethodID on_media_size_changed_method_id;
*/ */
/* Register this thread with the VM */ /* Register this thread with the VM */
static JNIEnv *attach_current_thread (void) { static JNIEnv *
attach_current_thread (void)
{
JNIEnv *env; JNIEnv *env;
JavaVMAttachArgs args; JavaVMAttachArgs args;
@ -81,13 +85,17 @@ static JNIEnv *attach_current_thread (void) {
} }
/* Unregister this thread from the VM */ /* Unregister this thread from the VM */
static void detach_current_thread (void *env) { static void
detach_current_thread (void *env)
{
GST_DEBUG ("Detaching thread %p", g_thread_self ()); GST_DEBUG ("Detaching thread %p", g_thread_self ());
(*java_vm)->DetachCurrentThread (java_vm); (*java_vm)->DetachCurrentThread (java_vm);
} }
/* Retrieve the JNI environment for this thread */ /* Retrieve the JNI environment for this thread */
static JNIEnv *get_jni_env (void) { static JNIEnv *
get_jni_env (void)
{
JNIEnv *env; JNIEnv *env;
if ((env = pthread_getspecific (current_jni_env)) == NULL) { if ((env = pthread_getspecific (current_jni_env)) == NULL) {
@ -99,10 +107,12 @@ static JNIEnv *get_jni_env (void) {
} }
/* Change the content of the UI's TextView */ /* Change the content of the UI's TextView */
static void set_ui_message (const gchar *message, CustomData *data) { static void
set_ui_message (const gchar * message, CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
GST_DEBUG ("Setting message to: %s", message); GST_DEBUG ("Setting message to: %s", message);
jstring jmessage = (*env)->NewStringUTF(env, message); jstring jmessage = (*env)->NewStringUTF (env, message);
(*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
@ -112,9 +122,12 @@ static void set_ui_message (const gchar *message, CustomData *data) {
} }
/* Tell the application what is the current position and clip duration */ /* Tell the application what is the current position and clip duration */
static void set_current_ui_position (gint position, gint duration, CustomData *data) { static void
set_current_ui_position (gint position, gint duration, CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
(*env)->CallVoidMethod (env, data->app, set_current_position_method_id, position, duration); (*env)->CallVoidMethod (env, data->app, set_current_position_method_id,
position, duration);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear (env); (*env)->ExceptionClear (env);
@ -123,7 +136,9 @@ static void set_current_ui_position (gint position, gint duration, CustomData *d
/* If we have pipeline and it is running, query the current position and clip duration and inform /* If we have pipeline and it is running, query the current position and clip duration and inform
* the application */ * the application */
static gboolean refresh_ui (CustomData *data) { static gboolean
refresh_ui (CustomData * data)
{
gint64 current = -1; gint64 current = -1;
gint64 position; gint64 position;
@ -133,24 +148,28 @@ static gboolean refresh_ui (CustomData *data) {
/* If we didn't know it yet, query the stream duration */ /* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) { if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME, &data->duration)) { if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME,
&data->duration)) {
GST_WARNING ("Could not query current duration"); GST_WARNING ("Could not query current duration");
} }
} }
if (gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) { if (gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
/* Java expects these values in milliseconds, and GStreamer provides nanoseconds */ /* Java expects these values in milliseconds, and GStreamer provides nanoseconds */
set_current_ui_position (position / GST_MSECOND, data->duration / GST_MSECOND, data); set_current_ui_position (position / GST_MSECOND,
data->duration / GST_MSECOND, data);
} }
return TRUE; return TRUE;
} }
/* Forward declaration for the delayed seek callback */ /* Forward declaration for the delayed seek callback */
static gboolean delayed_seek_cb (CustomData *data); static gboolean delayed_seek_cb (CustomData * data);
/* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for /* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for
* some time in the future. */ * some time in the future. */
static void execute_seek (gint64 desired_position, CustomData *data) { static void
execute_seek (gint64 desired_position, CustomData * data)
{
gint64 diff; gint64 diff;
if (desired_position == GST_CLOCK_TIME_NONE) if (desired_position == GST_CLOCK_TIME_NONE)
@ -164,40 +183,52 @@ static void execute_seek (gint64 desired_position, CustomData *data) {
if (data->desired_position == GST_CLOCK_TIME_NONE) { if (data->desired_position == GST_CLOCK_TIME_NONE) {
/* There was no previous seek scheduled. Setup a timer for some time in the future */ /* There was no previous seek scheduled. Setup a timer for some time in the future */
timeout_source = g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND); timeout_source =
g_source_set_callback (timeout_source, (GSourceFunc)delayed_seek_cb, data, NULL); g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND);
g_source_set_callback (timeout_source, (GSourceFunc) delayed_seek_cb,
data, NULL);
g_source_attach (timeout_source, data->context); g_source_attach (timeout_source, data->context);
g_source_unref (timeout_source); g_source_unref (timeout_source);
} }
/* Update the desired seek position. If multiple requests are received before it is time /* Update the desired seek position. If multiple requests are received before it is time
* to perform a seek, only the last one is remembered. */ * to perform a seek, only the last one is remembered. */
data->desired_position = desired_position; data->desired_position = desired_position;
GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %" GST_TIME_FORMAT, GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %"
GST_TIME_ARGS (desired_position), GST_TIME_ARGS (SEEK_MIN_DELAY - diff)); GST_TIME_FORMAT, GST_TIME_ARGS (desired_position),
GST_TIME_ARGS (SEEK_MIN_DELAY - diff));
} else { } else {
/* Perform the seek now */ /* Perform the seek now */
GST_DEBUG ("Seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (desired_position)); GST_DEBUG ("Seeking to %" GST_TIME_FORMAT,
GST_TIME_ARGS (desired_position));
data->last_seek_time = gst_util_get_timestamp (); data->last_seek_time = gst_util_get_timestamp ();
gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position); gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position);
data->desired_position = GST_CLOCK_TIME_NONE; data->desired_position = GST_CLOCK_TIME_NONE;
} }
} }
/* Delayed seek callback. This gets called by the timer setup in the above function. */ /* Delayed seek callback. This gets called by the timer setup in the above function. */
static gboolean delayed_seek_cb (CustomData *data) { static gboolean
GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (data->desired_position)); delayed_seek_cb (CustomData * data)
{
GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT,
GST_TIME_ARGS (data->desired_position));
execute_seek (data->desired_position, data); execute_seek (data->desired_position, data);
return FALSE; return FALSE;
} }
/* Retrieve errors from the bus and show them on the UI */ /* Retrieve errors from the bus and show them on the UI */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
gchar *message_string; gchar *message_string;
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message); message_string =
g_strdup_printf ("Error received from element %s: %s",
GST_OBJECT_NAME (msg->src), err->message);
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
set_ui_message (message_string, data); set_ui_message (message_string, data);
@ -207,20 +238,28 @@ static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */ /* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */
static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
eos_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
data->target_state = GST_STATE_PAUSED; data->target_state = GST_STATE_PAUSED;
data->is_live = (gst_element_set_state (data->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); data->is_live =
(gst_element_set_state (data->pipeline,
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
execute_seek (0, data); execute_seek (0, data);
} }
/* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */ /* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */
static void duration_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
duration_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
data->duration = GST_CLOCK_TIME_NONE; data->duration = GST_CLOCK_TIME_NONE;
} }
/* Called when buffering messages are received. We inform the UI about the current buffering level and /* Called when buffering messages are received. We inform the UI about the current buffering level and
* keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */ * keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */
static void buffering_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
buffering_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
gint percent; gint percent;
if (data->is_live) if (data->is_live)
@ -228,7 +267,7 @@ static void buffering_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
gst_message_parse_buffering (msg, &percent); gst_message_parse_buffering (msg, &percent);
if (percent < 100 && data->target_state >= GST_STATE_PAUSED) { if (percent < 100 && data->target_state >= GST_STATE_PAUSED) {
gchar * message_string = g_strdup_printf ("Buffering %d%%", percent); gchar *message_string = g_strdup_printf ("Buffering %d%%", percent);
gst_element_set_state (data->pipeline, GST_STATE_PAUSED); gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
set_ui_message (message_string, data); set_ui_message (message_string, data);
g_free (message_string); g_free (message_string);
@ -240,7 +279,9 @@ static void buffering_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Called when the clock is lost */ /* Called when the clock is lost */
static void clock_lost_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
clock_lost_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
if (data->target_state >= GST_STATE_PLAYING) { if (data->target_state >= GST_STATE_PLAYING) {
gst_element_set_state (data->pipeline, GST_STATE_PAUSED); gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING); gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
@ -248,7 +289,9 @@ static void clock_lost_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Retrieve the video sink's Caps and tell the application about the media size */ /* Retrieve the video sink's Caps and tell the application about the media size */
static void check_media_size (CustomData *data) { static void
check_media_size (CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
GstElement *video_sink; GstElement *video_sink;
GstPad *video_sink_pad; GstPad *video_sink_pad;
@ -262,35 +305,40 @@ static void check_media_size (CustomData *data) {
if (gst_video_info_from_caps (&info, caps)) { if (gst_video_info_from_caps (&info, caps)) {
info.width = info.width * info.par_n / info.par_d; info.width = info.width * info.par_n / info.par_d;
GST_DEBUG ("Media size is %dx%d, notifying application", info.width, info.height); GST_DEBUG ("Media size is %dx%d, notifying application", info.width,
info.height);
(*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id, (jint)info.width, (jint)info.height); (*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id,
(jint) info.width, (jint) info.height);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear (env); (*env)->ExceptionClear (env);
} }
} }
gst_caps_unref(caps); gst_caps_unref (caps);
gst_object_unref (video_sink_pad); gst_object_unref (video_sink_pad);
gst_object_unref(video_sink); gst_object_unref (video_sink);
} }
/* Notify UI about pipeline state changes */ /* Notify UI about pipeline state changes */
static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
/* Only pay attention to messages coming from the pipeline, not its children */ /* Only pay attention to messages coming from the pipeline, not its children */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
data->state = new_state; data->state = new_state;
gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state)); gchar *message = g_strdup_printf ("State changed to %s",
set_ui_message(message, data); gst_element_state_get_name (new_state));
set_ui_message (message, data);
g_free (message); g_free (message);
/* The Ready to Paused state change is particularly interesting: */ /* The Ready to Paused state change is particularly interesting: */
if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) { if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
/* By now the sink already knows the media size */ /* By now the sink already knows the media size */
check_media_size(data); check_media_size (data);
/* If there was a scheduled seek, perform it now that we have moved to the Paused state */ /* If there was a scheduled seek, perform it now that we have moved to the Paused state */
if (GST_CLOCK_TIME_IS_VALID (data->desired_position)) if (GST_CLOCK_TIME_IS_VALID (data->desired_position))
@ -301,13 +349,18 @@ static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
/* Check if all conditions are met to report GStreamer as initialized. /* Check if all conditions are met to report GStreamer as initialized.
* These conditions will change depending on the application */ * These conditions will change depending on the application */
static void check_initialization_complete (CustomData *data) { static void
check_initialization_complete (CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
if (!data->initialized && data->native_window && data->main_loop) { if (!data->initialized && data->native_window && data->main_loop) {
GST_DEBUG ("Initialization complete, notifying application. native_window:%p main_loop:%p", data->native_window, data->main_loop); GST_DEBUG
("Initialization complete, notifying application. native_window:%p main_loop:%p",
data->native_window, data->main_loop);
/* The main loop is running and we received a native window, inform the sink about it */ /* The main loop is running and we received a native window, inform the sink about it */
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), (guintptr)data->native_window); gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline),
(guintptr) data->native_window);
(*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
@ -319,10 +372,12 @@ static void check_initialization_complete (CustomData *data) {
} }
/* Main method for the native code. This is executed on its own thread. */ /* Main method for the native code. This is executed on its own thread. */
static void *app_function (void *userdata) { static void *
app_function (void *userdata)
{
JavaVMAttachArgs args; JavaVMAttachArgs args;
GstBus *bus; GstBus *bus;
CustomData *data = (CustomData *)userdata; CustomData *data = (CustomData *) userdata;
GSource *timeout_source; GSource *timeout_source;
GSource *bus_source; GSource *bus_source;
GError *error = NULL; GError *error = NULL;
@ -332,14 +387,15 @@ static void *app_function (void *userdata) {
/* Create our own GLib Main Context and make it the default one */ /* Create our own GLib Main Context and make it the default one */
data->context = g_main_context_new (); data->context = g_main_context_new ();
g_main_context_push_thread_default(data->context); g_main_context_push_thread_default (data->context);
/* Build pipeline */ /* Build pipeline */
data->pipeline = gst_parse_launch("playbin", &error); data->pipeline = gst_parse_launch ("playbin", &error);
if (error) { if (error) {
gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message); gchar *message =
g_strdup_printf ("Unable to build pipeline: %s", error->message);
g_clear_error (&error); g_clear_error (&error);
set_ui_message(message, data); set_ui_message (message, data);
g_free (message); g_free (message);
return NULL; return NULL;
} }
@ -351,25 +407,31 @@ static void *app_function (void *userdata) {
/* Set the pipeline to READY, so it can already accept a window handle, if we have one */ /* Set the pipeline to READY, so it can already accept a window handle, if we have one */
data->target_state = GST_STATE_READY; data->target_state = GST_STATE_READY;
gst_element_set_state(data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data->pipeline); bus = gst_element_get_bus (data->pipeline);
bus_source = gst_bus_create_watch (bus); bus_source = gst_bus_create_watch (bus);
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL); g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach (bus_source, data->context); g_source_attach (bus_source, data->context);
g_source_unref (bus_source); g_source_unref (bus_source);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, data); data);
g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data); g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb, data);
g_signal_connect (G_OBJECT (bus), "message::duration", (GCallback)duration_cb, data); g_signal_connect (G_OBJECT (bus), "message::state-changed",
g_signal_connect (G_OBJECT (bus), "message::buffering", (GCallback)buffering_cb, data); (GCallback) state_changed_cb, data);
g_signal_connect (G_OBJECT (bus), "message::clock-lost", (GCallback)clock_lost_cb, data); g_signal_connect (G_OBJECT (bus), "message::duration",
(GCallback) duration_cb, data);
g_signal_connect (G_OBJECT (bus), "message::buffering",
(GCallback) buffering_cb, data);
g_signal_connect (G_OBJECT (bus), "message::clock-lost",
(GCallback) clock_lost_cb, data);
gst_object_unref (bus); gst_object_unref (bus);
/* Register a function that GLib will call 4 times per second */ /* Register a function that GLib will call 4 times per second */
timeout_source = g_timeout_source_new (250); timeout_source = g_timeout_source_new (250);
g_source_set_callback (timeout_source, (GSourceFunc)refresh_ui, data, NULL); g_source_set_callback (timeout_source, (GSourceFunc) refresh_ui, data, NULL);
g_source_attach (timeout_source, data->context); g_source_attach (timeout_source, data->context);
g_source_unref (timeout_source); g_source_unref (timeout_source);
@ -383,7 +445,7 @@ static void *app_function (void *userdata) {
data->main_loop = NULL; data->main_loop = NULL;
/* Free resources */ /* Free resources */
g_main_context_pop_thread_default(data->context); g_main_context_pop_thread_default (data->context);
g_main_context_unref (data->context); g_main_context_unref (data->context);
data->target_state = GST_STATE_NULL; data->target_state = GST_STATE_NULL;
gst_element_set_state (data->pipeline, GST_STATE_NULL); gst_element_set_state (data->pipeline, GST_STATE_NULL);
@ -397,13 +459,16 @@ static void *app_function (void *userdata) {
*/ */
/* Instruct the native code to create its internal data structure, pipeline and thread */ /* Instruct the native code to create its internal data structure, pipeline and thread */
static void gst_native_init (JNIEnv* env, jobject thiz) { static void
gst_native_init (JNIEnv * env, jobject thiz)
{
CustomData *data = g_new0 (CustomData, 1); CustomData *data = g_new0 (CustomData, 1);
data->desired_position = GST_CLOCK_TIME_NONE; data->desired_position = GST_CLOCK_TIME_NONE;
data->last_seek_time = GST_CLOCK_TIME_NONE; data->last_seek_time = GST_CLOCK_TIME_NONE;
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-4", 0, "Android tutorial 4"); GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-4", 0,
gst_debug_set_threshold_for_name("tutorial-4", GST_LEVEL_DEBUG); "Android tutorial 4");
gst_debug_set_threshold_for_name ("tutorial-4", GST_LEVEL_DEBUG);
GST_DEBUG ("Created CustomData at %p", data); GST_DEBUG ("Created CustomData at %p", data);
data->app = (*env)->NewGlobalRef (env, thiz); data->app = (*env)->NewGlobalRef (env, thiz);
GST_DEBUG ("Created GlobalRef for app object at %p", data->app); GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
@ -411,9 +476,12 @@ static void gst_native_init (JNIEnv* env, jobject thiz) {
} }
/* Quit the main loop, remove the native thread and free resources */ /* Quit the main loop, remove the native thread and free resources */
static void gst_native_finalize (JNIEnv* env, jobject thiz) { static void
gst_native_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Quitting main loop..."); GST_DEBUG ("Quitting main loop...");
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
GST_DEBUG ("Waiting for thread to finish..."); GST_DEBUG ("Waiting for thread to finish...");
@ -427,82 +495,115 @@ static void gst_native_finalize (JNIEnv* env, jobject thiz) {
} }
/* Set playbin's URI */ /* Set playbin's URI */
void gst_native_set_uri (JNIEnv* env, jobject thiz, jstring uri) { void
gst_native_set_uri (JNIEnv * env, jobject thiz, jstring uri)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data || !data->pipeline) return; if (!data || !data->pipeline)
return;
const gchar *char_uri = (*env)->GetStringUTFChars (env, uri, NULL); const gchar *char_uri = (*env)->GetStringUTFChars (env, uri, NULL);
GST_DEBUG ("Setting URI to %s", char_uri); GST_DEBUG ("Setting URI to %s", char_uri);
if (data->target_state >= GST_STATE_READY) if (data->target_state >= GST_STATE_READY)
gst_element_set_state (data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
g_object_set(data->pipeline, "uri", char_uri, NULL); g_object_set (data->pipeline, "uri", char_uri, NULL);
(*env)->ReleaseStringUTFChars (env, uri, char_uri); (*env)->ReleaseStringUTFChars (env, uri, char_uri);
data->duration = GST_CLOCK_TIME_NONE; data->duration = GST_CLOCK_TIME_NONE;
data->is_live = (gst_element_set_state (data->pipeline, data->target_state) == GST_STATE_CHANGE_NO_PREROLL); data->is_live =
(gst_element_set_state (data->pipeline,
data->target_state) == GST_STATE_CHANGE_NO_PREROLL);
} }
/* Set pipeline to PLAYING state */ /* Set pipeline to PLAYING state */
static void gst_native_play (JNIEnv* env, jobject thiz) { static void
gst_native_play (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Setting state to PLAYING"); GST_DEBUG ("Setting state to PLAYING");
data->target_state = GST_STATE_PLAYING; data->target_state = GST_STATE_PLAYING;
data->is_live = (gst_element_set_state (data->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL); data->is_live =
(gst_element_set_state (data->pipeline,
GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL);
} }
/* Set pipeline to PAUSED state */ /* Set pipeline to PAUSED state */
static void gst_native_pause (JNIEnv* env, jobject thiz) { static void
gst_native_pause (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Setting state to PAUSED"); GST_DEBUG ("Setting state to PAUSED");
data->target_state = GST_STATE_PAUSED; data->target_state = GST_STATE_PAUSED;
data->is_live = (gst_element_set_state (data->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); data->is_live =
(gst_element_set_state (data->pipeline,
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
} }
/* Instruct the pipeline to seek to a different position */ /* Instruct the pipeline to seek to a different position */
void gst_native_set_position (JNIEnv* env, jobject thiz, int milliseconds) { void
gst_native_set_position (JNIEnv * env, jobject thiz, int milliseconds)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
gint64 desired_position = (gint64)(milliseconds * GST_MSECOND); return;
gint64 desired_position = (gint64) (milliseconds * GST_MSECOND);
if (data->state >= GST_STATE_PAUSED) { if (data->state >= GST_STATE_PAUSED) {
execute_seek(desired_position, data); execute_seek (desired_position, data);
} else { } else {
GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later", GST_TIME_ARGS (desired_position)); GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later",
GST_TIME_ARGS (desired_position));
data->desired_position = desired_position; data->desired_position = desired_position;
} }
} }
/* Static class initializer: retrieve method and field IDs */ /* Static class initializer: retrieve method and field IDs */
static jboolean gst_native_class_init (JNIEnv* env, jclass klass) { static jboolean
custom_data_field_id = (*env)->GetFieldID (env, klass, "native_custom_data", "J"); gst_native_class_init (JNIEnv * env, jclass klass)
set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); {
set_current_position_method_id = (*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V"); custom_data_field_id =
on_gstreamer_initialized_method_id = (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); (*env)->GetFieldID (env, klass, "native_custom_data", "J");
on_media_size_changed_method_id = (*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V"); set_message_method_id =
(*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
set_current_position_method_id =
(*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V");
on_gstreamer_initialized_method_id =
(*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
on_media_size_changed_method_id =
(*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V");
if (!custom_data_field_id || !set_message_method_id || !on_gstreamer_initialized_method_id || if (!custom_data_field_id || !set_message_method_id
!on_media_size_changed_method_id || !set_current_position_method_id) { || !on_gstreamer_initialized_method_id || !on_media_size_changed_method_id
|| !set_current_position_method_id) {
/* We emit this message through the Android log instead of the GStreamer log because the later /* We emit this message through the Android log instead of the GStreamer log because the later
* has not been initialized yet. * has not been initialized yet.
*/ */
__android_log_print (ANDROID_LOG_ERROR, "tutorial-4", "The calling class does not implement all necessary interface methods"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-4",
"The calling class does not implement all necessary interface methods");
return JNI_FALSE; return JNI_FALSE;
} }
return JNI_TRUE; return JNI_TRUE;
} }
static void gst_native_surface_init (JNIEnv *env, jobject thiz, jobject surface) { static void
gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface); return;
GST_DEBUG ("Received surface %p (native window %p)", surface, new_native_window); ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface);
GST_DEBUG ("Received surface %p (native window %p)", surface,
new_native_window);
if (data->native_window) { if (data->native_window) {
ANativeWindow_release (data->native_window); ANativeWindow_release (data->native_window);
if (data->native_window == new_native_window) { if (data->native_window == new_native_window) {
GST_DEBUG ("New native window is the same as the previous one %p", data->native_window); GST_DEBUG ("New native window is the same as the previous one %p",
data->native_window);
if (data->pipeline) { if (data->pipeline) {
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline)); gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline));
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline)); gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline));
} }
return; return;
} else { } else {
@ -515,13 +616,17 @@ static void gst_native_surface_init (JNIEnv *env, jobject thiz, jobject surface)
check_initialization_complete (data); check_initialization_complete (data);
} }
static void gst_native_surface_finalize (JNIEnv *env, jobject thiz) { static void
gst_native_surface_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Releasing Native Window %p", data->native_window); GST_DEBUG ("Releasing Native Window %p", data->native_window);
if (data->pipeline) { if (data->pipeline) {
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), (guintptr)NULL); gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline),
(guintptr) NULL);
gst_element_set_state (data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
} }
@ -532,29 +637,35 @@ static void gst_native_surface_finalize (JNIEnv *env, jobject thiz) {
/* List of implemented native methods */ /* List of implemented native methods */
static JNINativeMethod native_methods[] = { static JNINativeMethod native_methods[] = {
{ "nativeInit", "()V", (void *) gst_native_init}, {"nativeInit", "()V", (void *) gst_native_init},
{ "nativeFinalize", "()V", (void *) gst_native_finalize}, {"nativeFinalize", "()V", (void *) gst_native_finalize},
{ "nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri}, {"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
{ "nativePlay", "()V", (void *) gst_native_play}, {"nativePlay", "()V", (void *) gst_native_play},
{ "nativePause", "()V", (void *) gst_native_pause}, {"nativePause", "()V", (void *) gst_native_pause},
{ "nativeSetPosition", "(I)V", (void*) gst_native_set_position}, {"nativeSetPosition", "(I)V", (void *) gst_native_set_position},
{ "nativeSurfaceInit", "(Ljava/lang/Object;)V", (void *) gst_native_surface_init}, {"nativeSurfaceInit", "(Ljava/lang/Object;)V",
{ "nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize}, (void *) gst_native_surface_init},
{ "nativeClassInit", "()Z", (void *) gst_native_class_init} {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
{"nativeClassInit", "()Z", (void *) gst_native_class_init}
}; };
/* Library initializer */ /* Library initializer */
jint JNI_OnLoad(JavaVM *vm, void *reserved) { jint
JNI_OnLoad (JavaVM * vm, void *reserved)
{
JNIEnv *env = NULL; JNIEnv *env = NULL;
java_vm = vm; java_vm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print (ANDROID_LOG_ERROR, "tutorial-4", "Could not retrieve JNIEnv"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-4",
"Could not retrieve JNIEnv");
return 0; return 0;
} }
jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_4/Tutorial4"); jclass klass = (*env)->FindClass (env,
(*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods)); "org/freedesktop/gstreamer/tutorials/tutorial_4/Tutorial4");
(*env)->RegisterNatives (env, klass, native_methods,
G_N_ELEMENTS (native_methods));
pthread_key_create (&current_jni_env, detach_current_thread); pthread_key_create (&current_jni_env, detach_current_thread);

View file

@ -29,7 +29,8 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
#define SEEK_MIN_DELAY (500 * GST_MSECOND) #define SEEK_MIN_DELAY (500 * GST_MSECOND)
/* Structure to contain all our information, so we can pass it to callbacks */ /* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData { typedef struct _CustomData
{
jobject app; /* Application instance, used to call its methods. A global reference is kept. */ jobject app; /* Application instance, used to call its methods. A global reference is kept. */
GstElement *pipeline; /* The running pipeline */ GstElement *pipeline; /* The running pipeline */
GMainContext *context; /* GLib context used to run the main loop */ GMainContext *context; /* GLib context used to run the main loop */
@ -45,8 +46,9 @@ typedef struct _CustomData {
} CustomData; } CustomData;
/* playbin2 flags */ /* playbin2 flags */
typedef enum { typedef enum
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */ {
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */
} GstPlayFlags; } GstPlayFlags;
/* These global variables cache values which are not changing during execution */ /* These global variables cache values which are not changing during execution */
@ -64,7 +66,9 @@ static jmethodID on_media_size_changed_method_id;
*/ */
/* Register this thread with the VM */ /* Register this thread with the VM */
static JNIEnv *attach_current_thread (void) { static JNIEnv *
attach_current_thread (void)
{
JNIEnv *env; JNIEnv *env;
JavaVMAttachArgs args; JavaVMAttachArgs args;
@ -82,13 +86,17 @@ static JNIEnv *attach_current_thread (void) {
} }
/* Unregister this thread from the VM */ /* Unregister this thread from the VM */
static void detach_current_thread (void *env) { static void
detach_current_thread (void *env)
{
GST_DEBUG ("Detaching thread %p", g_thread_self ()); GST_DEBUG ("Detaching thread %p", g_thread_self ());
(*java_vm)->DetachCurrentThread (java_vm); (*java_vm)->DetachCurrentThread (java_vm);
} }
/* Retrieve the JNI environment for this thread */ /* Retrieve the JNI environment for this thread */
static JNIEnv *get_jni_env (void) { static JNIEnv *
get_jni_env (void)
{
JNIEnv *env; JNIEnv *env;
if ((env = pthread_getspecific (current_jni_env)) == NULL) { if ((env = pthread_getspecific (current_jni_env)) == NULL) {
@ -100,10 +108,12 @@ static JNIEnv *get_jni_env (void) {
} }
/* Change the content of the UI's TextView */ /* Change the content of the UI's TextView */
static void set_ui_message (const gchar *message, CustomData *data) { static void
set_ui_message (const gchar * message, CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
GST_DEBUG ("Setting message to: %s", message); GST_DEBUG ("Setting message to: %s", message);
jstring jmessage = (*env)->NewStringUTF(env, message); jstring jmessage = (*env)->NewStringUTF (env, message);
(*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
@ -113,9 +123,12 @@ static void set_ui_message (const gchar *message, CustomData *data) {
} }
/* Tell the application what is the current position and clip duration */ /* Tell the application what is the current position and clip duration */
static void set_current_ui_position (gint position, gint duration, CustomData *data) { static void
set_current_ui_position (gint position, gint duration, CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
(*env)->CallVoidMethod (env, data->app, set_current_position_method_id, position, duration); (*env)->CallVoidMethod (env, data->app, set_current_position_method_id,
position, duration);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear (env); (*env)->ExceptionClear (env);
@ -124,7 +137,9 @@ static void set_current_ui_position (gint position, gint duration, CustomData *d
/* If we have pipeline and it is running, query the current position and clip duration and inform /* If we have pipeline and it is running, query the current position and clip duration and inform
* the application */ * the application */
static gboolean refresh_ui (CustomData *data) { static gboolean
refresh_ui (CustomData * data)
{
gint64 current = -1; gint64 current = -1;
gint64 position; gint64 position;
@ -134,28 +149,34 @@ static gboolean refresh_ui (CustomData *data) {
/* If we didn't know it yet, query the stream duration */ /* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) { if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME, &data->duration)) { if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME,
GST_WARNING ("Could not query current duration (normal for still pictures)"); &data->duration)) {
GST_WARNING
("Could not query current duration (normal for still pictures)");
data->duration = 0; data->duration = 0;
} }
} }
if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) { if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
GST_WARNING ("Could not query current position (normal for still pictures)"); GST_WARNING
("Could not query current position (normal for still pictures)");
position = 0; position = 0;
} }
/* Java expects these values in milliseconds, and GStreamer provides nanoseconds */ /* Java expects these values in milliseconds, and GStreamer provides nanoseconds */
set_current_ui_position (position / GST_MSECOND, data->duration / GST_MSECOND, data); set_current_ui_position (position / GST_MSECOND, data->duration / GST_MSECOND,
data);
return TRUE; return TRUE;
} }
/* Forward declaration for the delayed seek callback */ /* Forward declaration for the delayed seek callback */
static gboolean delayed_seek_cb (CustomData *data); static gboolean delayed_seek_cb (CustomData * data);
/* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for /* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for
* some time in the future. */ * some time in the future. */
static void execute_seek (gint64 desired_position, CustomData *data) { static void
execute_seek (gint64 desired_position, CustomData * data)
{
gint64 diff; gint64 diff;
if (desired_position == GST_CLOCK_TIME_NONE) if (desired_position == GST_CLOCK_TIME_NONE)
@ -169,40 +190,52 @@ static void execute_seek (gint64 desired_position, CustomData *data) {
if (data->desired_position == GST_CLOCK_TIME_NONE) { if (data->desired_position == GST_CLOCK_TIME_NONE) {
/* There was no previous seek scheduled. Setup a timer for some time in the future */ /* There was no previous seek scheduled. Setup a timer for some time in the future */
timeout_source = g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND); timeout_source =
g_source_set_callback (timeout_source, (GSourceFunc)delayed_seek_cb, data, NULL); g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND);
g_source_set_callback (timeout_source, (GSourceFunc) delayed_seek_cb,
data, NULL);
g_source_attach (timeout_source, data->context); g_source_attach (timeout_source, data->context);
g_source_unref (timeout_source); g_source_unref (timeout_source);
} }
/* Update the desired seek position. If multiple petitions are received before it is time /* Update the desired seek position. If multiple petitions are received before it is time
* to perform a seek, only the last one is remembered. */ * to perform a seek, only the last one is remembered. */
data->desired_position = desired_position; data->desired_position = desired_position;
GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %" GST_TIME_FORMAT, GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %"
GST_TIME_ARGS (desired_position), GST_TIME_ARGS (SEEK_MIN_DELAY - diff)); GST_TIME_FORMAT, GST_TIME_ARGS (desired_position),
GST_TIME_ARGS (SEEK_MIN_DELAY - diff));
} else { } else {
/* Perform the seek now */ /* Perform the seek now */
GST_DEBUG ("Seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (desired_position)); GST_DEBUG ("Seeking to %" GST_TIME_FORMAT,
GST_TIME_ARGS (desired_position));
data->last_seek_time = gst_util_get_timestamp (); data->last_seek_time = gst_util_get_timestamp ();
gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position); gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position);
data->desired_position = GST_CLOCK_TIME_NONE; data->desired_position = GST_CLOCK_TIME_NONE;
} }
} }
/* Delayed seek callback. This gets called by the timer setup in the above function. */ /* Delayed seek callback. This gets called by the timer setup in the above function. */
static gboolean delayed_seek_cb (CustomData *data) { static gboolean
GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (data->desired_position)); delayed_seek_cb (CustomData * data)
{
GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT,
GST_TIME_ARGS (data->desired_position));
execute_seek (data->desired_position, data); execute_seek (data->desired_position, data);
return FALSE; return FALSE;
} }
/* Retrieve errors from the bus and show them on the UI */ /* Retrieve errors from the bus and show them on the UI */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
gchar *message_string; gchar *message_string;
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message); message_string =
g_strdup_printf ("Error received from element %s: %s",
GST_OBJECT_NAME (msg->src), err->message);
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
set_ui_message (message_string, data); set_ui_message (message_string, data);
@ -212,20 +245,28 @@ static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */ /* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */
static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
eos_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
data->target_state = GST_STATE_PAUSED; data->target_state = GST_STATE_PAUSED;
data->is_live |= (gst_element_set_state (data->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); data->is_live |=
(gst_element_set_state (data->pipeline,
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
execute_seek (0, data); execute_seek (0, data);
} }
/* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */ /* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */
static void duration_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
duration_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
data->duration = GST_CLOCK_TIME_NONE; data->duration = GST_CLOCK_TIME_NONE;
} }
/* Called when buffering messages are received. We inform the UI about the current buffering level and /* Called when buffering messages are received. We inform the UI about the current buffering level and
* keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */ * keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */
static void buffering_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
buffering_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
gint percent; gint percent;
if (data->is_live) if (data->is_live)
@ -233,7 +274,7 @@ static void buffering_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
gst_message_parse_buffering (msg, &percent); gst_message_parse_buffering (msg, &percent);
if (percent < 100 && data->target_state >= GST_STATE_PAUSED) { if (percent < 100 && data->target_state >= GST_STATE_PAUSED) {
gchar * message_string = g_strdup_printf ("Buffering %d%%", percent); gchar *message_string = g_strdup_printf ("Buffering %d%%", percent);
gst_element_set_state (data->pipeline, GST_STATE_PAUSED); gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
set_ui_message (message_string, data); set_ui_message (message_string, data);
g_free (message_string); g_free (message_string);
@ -245,7 +286,9 @@ static void buffering_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Called when the clock is lost */ /* Called when the clock is lost */
static void clock_lost_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
clock_lost_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
if (data->target_state >= GST_STATE_PLAYING) { if (data->target_state >= GST_STATE_PLAYING) {
gst_element_set_state (data->pipeline, GST_STATE_PAUSED); gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING); gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
@ -253,7 +296,9 @@ static void clock_lost_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Retrieve the video sink's Caps and tell the application about the media size */ /* Retrieve the video sink's Caps and tell the application about the media size */
static void check_media_size (CustomData *data) { static void
check_media_size (CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
GstElement *video_sink; GstElement *video_sink;
GstPad *video_sink_pad; GstPad *video_sink_pad;
@ -265,31 +310,36 @@ static void check_media_size (CustomData *data) {
video_sink_pad = gst_element_get_static_pad (video_sink, "sink"); video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
caps = gst_pad_get_current_caps (video_sink_pad); caps = gst_pad_get_current_caps (video_sink_pad);
if (gst_video_info_from_caps(&info, caps)) { if (gst_video_info_from_caps (&info, caps)) {
info.width = info.width * info.par_n / info.par_d; info.width = info.width * info.par_n / info.par_d;
GST_DEBUG ("Media size is %dx%d, notifying application", info.width, info.height); GST_DEBUG ("Media size is %dx%d, notifying application", info.width,
info.height);
(*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id, (jint)info.width, (jint)info.height); (*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id,
(jint) info.width, (jint) info.height);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method"); GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear (env); (*env)->ExceptionClear (env);
} }
} }
gst_caps_unref(caps); gst_caps_unref (caps);
gst_object_unref (video_sink_pad); gst_object_unref (video_sink_pad);
gst_object_unref(video_sink); gst_object_unref (video_sink);
} }
/* Notify UI about pipeline state changes */ /* Notify UI about pipeline state changes */
static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
/* Only pay attention to messages coming from the pipeline, not its children */ /* Only pay attention to messages coming from the pipeline, not its children */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
data->state = new_state; data->state = new_state;
gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state)); gchar *message = g_strdup_printf ("State changed to %s",
set_ui_message(message, data); gst_element_state_get_name (new_state));
set_ui_message (message, data);
g_free (message); g_free (message);
if (new_state == GST_STATE_NULL || new_state == GST_STATE_READY) if (new_state == GST_STATE_NULL || new_state == GST_STATE_READY)
@ -298,7 +348,7 @@ static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
/* The Ready to Paused state change is particularly interesting: */ /* The Ready to Paused state change is particularly interesting: */
if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) { if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
/* By now the sink already knows the media size */ /* By now the sink already knows the media size */
check_media_size(data); check_media_size (data);
/* If there was a scheduled seek, perform it now that we have moved to the Paused state */ /* If there was a scheduled seek, perform it now that we have moved to the Paused state */
if (GST_CLOCK_TIME_IS_VALID (data->desired_position)) if (GST_CLOCK_TIME_IS_VALID (data->desired_position))
@ -309,13 +359,18 @@ static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
/* Check if all conditions are met to report GStreamer as initialized. /* Check if all conditions are met to report GStreamer as initialized.
* These conditions will change depending on the application */ * These conditions will change depending on the application */
static void check_initialization_complete (CustomData *data) { static void
check_initialization_complete (CustomData * data)
{
JNIEnv *env = get_jni_env (); JNIEnv *env = get_jni_env ();
if (!data->initialized && data->native_window && data->main_loop) { if (!data->initialized && data->native_window && data->main_loop) {
GST_DEBUG ("Initialization complete, notifying application. native_window:%p main_loop:%p", data->native_window, data->main_loop); GST_DEBUG
("Initialization complete, notifying application. native_window:%p main_loop:%p",
data->native_window, data->main_loop);
/* The main loop is running and we received a native window, inform the sink about it */ /* The main loop is running and we received a native window, inform the sink about it */
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), (guintptr)data->native_window); gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline),
(guintptr) data->native_window);
(*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
if ((*env)->ExceptionCheck (env)) { if ((*env)->ExceptionCheck (env)) {
@ -327,10 +382,12 @@ static void check_initialization_complete (CustomData *data) {
} }
/* Main method for the native code. This is executed on its own thread. */ /* Main method for the native code. This is executed on its own thread. */
static void *app_function (void *userdata) { static void *
app_function (void *userdata)
{
JavaVMAttachArgs args; JavaVMAttachArgs args;
GstBus *bus; GstBus *bus;
CustomData *data = (CustomData *)userdata; CustomData *data = (CustomData *) userdata;
GSource *timeout_source; GSource *timeout_source;
GSource *bus_source; GSource *bus_source;
GError *error = NULL; GError *error = NULL;
@ -340,14 +397,15 @@ static void *app_function (void *userdata) {
/* Create our own GLib Main Context and make it the default one */ /* Create our own GLib Main Context and make it the default one */
data->context = g_main_context_new (); data->context = g_main_context_new ();
g_main_context_push_thread_default(data->context); g_main_context_push_thread_default (data->context);
/* Build pipeline */ /* Build pipeline */
data->pipeline = gst_parse_launch("playbin", &error); data->pipeline = gst_parse_launch ("playbin", &error);
if (error) { if (error) {
gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message); gchar *message =
g_strdup_printf ("Unable to build pipeline: %s", error->message);
g_clear_error (&error); g_clear_error (&error);
set_ui_message(message, data); set_ui_message (message, data);
g_free (message); g_free (message);
return NULL; return NULL;
} }
@ -359,25 +417,31 @@ static void *app_function (void *userdata) {
/* Set the pipeline to READY, so it can already accept a window handle, if we have one */ /* Set the pipeline to READY, so it can already accept a window handle, if we have one */
data->target_state = GST_STATE_READY; data->target_state = GST_STATE_READY;
gst_element_set_state(data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data->pipeline); bus = gst_element_get_bus (data->pipeline);
bus_source = gst_bus_create_watch (bus); bus_source = gst_bus_create_watch (bus);
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL); g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach (bus_source, data->context); g_source_attach (bus_source, data->context);
g_source_unref (bus_source); g_source_unref (bus_source);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, data); data);
g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data); g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb, data);
g_signal_connect (G_OBJECT (bus), "message::duration", (GCallback)duration_cb, data); g_signal_connect (G_OBJECT (bus), "message::state-changed",
g_signal_connect (G_OBJECT (bus), "message::buffering", (GCallback)buffering_cb, data); (GCallback) state_changed_cb, data);
g_signal_connect (G_OBJECT (bus), "message::clock-lost", (GCallback)clock_lost_cb, data); g_signal_connect (G_OBJECT (bus), "message::duration",
(GCallback) duration_cb, data);
g_signal_connect (G_OBJECT (bus), "message::buffering",
(GCallback) buffering_cb, data);
g_signal_connect (G_OBJECT (bus), "message::clock-lost",
(GCallback) clock_lost_cb, data);
gst_object_unref (bus); gst_object_unref (bus);
/* Register a function that GLib will call 4 times per second */ /* Register a function that GLib will call 4 times per second */
timeout_source = g_timeout_source_new (250); timeout_source = g_timeout_source_new (250);
g_source_set_callback (timeout_source, (GSourceFunc)refresh_ui, data, NULL); g_source_set_callback (timeout_source, (GSourceFunc) refresh_ui, data, NULL);
g_source_attach (timeout_source, data->context); g_source_attach (timeout_source, data->context);
g_source_unref (timeout_source); g_source_unref (timeout_source);
@ -391,7 +455,7 @@ static void *app_function (void *userdata) {
data->main_loop = NULL; data->main_loop = NULL;
/* Free resources */ /* Free resources */
g_main_context_pop_thread_default(data->context); g_main_context_pop_thread_default (data->context);
g_main_context_unref (data->context); g_main_context_unref (data->context);
data->target_state = GST_STATE_NULL; data->target_state = GST_STATE_NULL;
gst_element_set_state (data->pipeline, GST_STATE_NULL); gst_element_set_state (data->pipeline, GST_STATE_NULL);
@ -405,13 +469,16 @@ static void *app_function (void *userdata) {
*/ */
/* Instruct the native code to create its internal data structure, pipeline and thread */ /* Instruct the native code to create its internal data structure, pipeline and thread */
static void gst_native_init (JNIEnv* env, jobject thiz) { static void
gst_native_init (JNIEnv * env, jobject thiz)
{
CustomData *data = g_new0 (CustomData, 1); CustomData *data = g_new0 (CustomData, 1);
data->desired_position = GST_CLOCK_TIME_NONE; data->desired_position = GST_CLOCK_TIME_NONE;
data->last_seek_time = GST_CLOCK_TIME_NONE; data->last_seek_time = GST_CLOCK_TIME_NONE;
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-5", 0, "Android tutorial 5"); GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-5", 0,
gst_debug_set_threshold_for_name("tutorial-5", GST_LEVEL_DEBUG); "Android tutorial 5");
gst_debug_set_threshold_for_name ("tutorial-5", GST_LEVEL_DEBUG);
GST_DEBUG ("Created CustomData at %p", data); GST_DEBUG ("Created CustomData at %p", data);
data->app = (*env)->NewGlobalRef (env, thiz); data->app = (*env)->NewGlobalRef (env, thiz);
GST_DEBUG ("Created GlobalRef for app object at %p", data->app); GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
@ -419,9 +486,12 @@ static void gst_native_init (JNIEnv* env, jobject thiz) {
} }
/* Quit the main loop, remove the native thread and free resources */ /* Quit the main loop, remove the native thread and free resources */
static void gst_native_finalize (JNIEnv* env, jobject thiz) { static void
gst_native_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Quitting main loop..."); GST_DEBUG ("Quitting main loop...");
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
GST_DEBUG ("Waiting for thread to finish..."); GST_DEBUG ("Waiting for thread to finish...");
@ -435,82 +505,115 @@ static void gst_native_finalize (JNIEnv* env, jobject thiz) {
} }
/* Set playbin2's URI */ /* Set playbin2's URI */
void gst_native_set_uri (JNIEnv* env, jobject thiz, jstring uri) { void
gst_native_set_uri (JNIEnv * env, jobject thiz, jstring uri)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data || !data->pipeline) return; if (!data || !data->pipeline)
return;
const gchar *char_uri = (*env)->GetStringUTFChars (env, uri, NULL); const gchar *char_uri = (*env)->GetStringUTFChars (env, uri, NULL);
GST_DEBUG ("Setting URI to %s", char_uri); GST_DEBUG ("Setting URI to %s", char_uri);
if (data->target_state >= GST_STATE_READY) if (data->target_state >= GST_STATE_READY)
gst_element_set_state (data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
g_object_set(data->pipeline, "uri", char_uri, NULL); g_object_set (data->pipeline, "uri", char_uri, NULL);
(*env)->ReleaseStringUTFChars (env, uri, char_uri); (*env)->ReleaseStringUTFChars (env, uri, char_uri);
data->duration = GST_CLOCK_TIME_NONE; data->duration = GST_CLOCK_TIME_NONE;
data->is_live |= (gst_element_set_state (data->pipeline, data->target_state) == GST_STATE_CHANGE_NO_PREROLL); data->is_live |=
(gst_element_set_state (data->pipeline,
data->target_state) == GST_STATE_CHANGE_NO_PREROLL);
} }
/* Set pipeline to PLAYING state */ /* Set pipeline to PLAYING state */
static void gst_native_play (JNIEnv* env, jobject thiz) { static void
gst_native_play (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Setting state to PLAYING"); GST_DEBUG ("Setting state to PLAYING");
data->target_state = GST_STATE_PLAYING; data->target_state = GST_STATE_PLAYING;
data->is_live |= (gst_element_set_state (data->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL); data->is_live |=
(gst_element_set_state (data->pipeline,
GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL);
} }
/* Set pipeline to PAUSED state */ /* Set pipeline to PAUSED state */
static void gst_native_pause (JNIEnv* env, jobject thiz) { static void
gst_native_pause (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Setting state to PAUSED"); GST_DEBUG ("Setting state to PAUSED");
data->target_state = GST_STATE_PAUSED; data->target_state = GST_STATE_PAUSED;
data->is_live |= (gst_element_set_state (data->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); data->is_live |=
(gst_element_set_state (data->pipeline,
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
} }
/* Instruct the pipeline to seek to a different position */ /* Instruct the pipeline to seek to a different position */
void gst_native_set_position (JNIEnv* env, jobject thiz, int milliseconds) { void
gst_native_set_position (JNIEnv * env, jobject thiz, int milliseconds)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
gint64 desired_position = (gint64)(milliseconds * GST_MSECOND); return;
gint64 desired_position = (gint64) (milliseconds * GST_MSECOND);
if (data->state >= GST_STATE_PAUSED) { if (data->state >= GST_STATE_PAUSED) {
execute_seek(desired_position, data); execute_seek (desired_position, data);
} else { } else {
GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later", GST_TIME_ARGS (desired_position)); GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later",
GST_TIME_ARGS (desired_position));
data->desired_position = desired_position; data->desired_position = desired_position;
} }
} }
/* Static class initializer: retrieve method and field IDs */ /* Static class initializer: retrieve method and field IDs */
static jboolean gst_native_class_init (JNIEnv* env, jclass klass) { static jboolean
custom_data_field_id = (*env)->GetFieldID (env, klass, "native_custom_data", "J"); gst_native_class_init (JNIEnv * env, jclass klass)
set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); {
set_current_position_method_id = (*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V"); custom_data_field_id =
on_gstreamer_initialized_method_id = (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); (*env)->GetFieldID (env, klass, "native_custom_data", "J");
on_media_size_changed_method_id = (*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V"); set_message_method_id =
(*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
set_current_position_method_id =
(*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V");
on_gstreamer_initialized_method_id =
(*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
on_media_size_changed_method_id =
(*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V");
if (!custom_data_field_id || !set_message_method_id || !on_gstreamer_initialized_method_id || if (!custom_data_field_id || !set_message_method_id
!on_media_size_changed_method_id || !set_current_position_method_id) { || !on_gstreamer_initialized_method_id || !on_media_size_changed_method_id
|| !set_current_position_method_id) {
/* We emit this message through the Android log instead of the GStreamer log because the later /* We emit this message through the Android log instead of the GStreamer log because the later
* has not been initialized yet. * has not been initialized yet.
*/ */
__android_log_print (ANDROID_LOG_ERROR, "tutorial-4", "The calling class does not implement all necessary interface methods"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-4",
"The calling class does not implement all necessary interface methods");
return JNI_FALSE; return JNI_FALSE;
} }
return JNI_TRUE; return JNI_TRUE;
} }
static void gst_native_surface_init (JNIEnv *env, jobject thiz, jobject surface) { static void
gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface); return;
GST_DEBUG ("Received surface %p (native window %p)", surface, new_native_window); ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface);
GST_DEBUG ("Received surface %p (native window %p)", surface,
new_native_window);
if (data->native_window) { if (data->native_window) {
ANativeWindow_release (data->native_window); ANativeWindow_release (data->native_window);
if (data->native_window == new_native_window) { if (data->native_window == new_native_window) {
GST_DEBUG ("New native window is the same as the previous one %p", data->native_window); GST_DEBUG ("New native window is the same as the previous one %p",
data->native_window);
if (data->pipeline) { if (data->pipeline) {
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline)); gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline));
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline)); gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline));
} }
return; return;
} else { } else {
@ -523,13 +626,17 @@ static void gst_native_surface_init (JNIEnv *env, jobject thiz, jobject surface)
check_initialization_complete (data); check_initialization_complete (data);
} }
static void gst_native_surface_finalize (JNIEnv *env, jobject thiz) { static void
gst_native_surface_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data) return; if (!data)
return;
GST_DEBUG ("Releasing Native Window %p", data->native_window); GST_DEBUG ("Releasing Native Window %p", data->native_window);
if (data->pipeline) { if (data->pipeline) {
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), (guintptr)NULL); gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline),
(guintptr) NULL);
gst_element_set_state (data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
} }
@ -540,29 +647,35 @@ static void gst_native_surface_finalize (JNIEnv *env, jobject thiz) {
/* List of implemented native methods */ /* List of implemented native methods */
static JNINativeMethod native_methods[] = { static JNINativeMethod native_methods[] = {
{ "nativeInit", "()V", (void *) gst_native_init}, {"nativeInit", "()V", (void *) gst_native_init},
{ "nativeFinalize", "()V", (void *) gst_native_finalize}, {"nativeFinalize", "()V", (void *) gst_native_finalize},
{ "nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri}, {"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
{ "nativePlay", "()V", (void *) gst_native_play}, {"nativePlay", "()V", (void *) gst_native_play},
{ "nativePause", "()V", (void *) gst_native_pause}, {"nativePause", "()V", (void *) gst_native_pause},
{ "nativeSetPosition", "(I)V", (void*) gst_native_set_position}, {"nativeSetPosition", "(I)V", (void *) gst_native_set_position},
{ "nativeSurfaceInit", "(Ljava/lang/Object;)V", (void *) gst_native_surface_init}, {"nativeSurfaceInit", "(Ljava/lang/Object;)V",
{ "nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize}, (void *) gst_native_surface_init},
{ "nativeClassInit", "()Z", (void *) gst_native_class_init} {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
{"nativeClassInit", "()Z", (void *) gst_native_class_init}
}; };
/* Library initializer */ /* Library initializer */
jint JNI_OnLoad(JavaVM *vm, void *reserved) { jint
JNI_OnLoad (JavaVM * vm, void *reserved)
{
JNIEnv *env = NULL; JNIEnv *env = NULL;
java_vm = vm; java_vm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print (ANDROID_LOG_ERROR, "tutorial-5", "Could not retrieve JNIEnv"); __android_log_print (ANDROID_LOG_ERROR, "tutorial-5",
"Could not retrieve JNIEnv");
return 0; return 0;
} }
jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_5/Tutorial5"); jclass klass = (*env)->FindClass (env,
(*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods)); "org/freedesktop/gstreamer/tutorials/tutorial_5/Tutorial5");
(*env)->RegisterNatives (env, klass, native_methods,
G_N_ELEMENTS (native_methods));
pthread_key_create (&current_jni_env, detach_current_thread); pthread_key_create (&current_jni_env, detach_current_thread);

View file

@ -1,6 +1,8 @@
#include <gst/gst.h> #include <gst/gst.h>
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
GstElement *pipeline; GstElement *pipeline;
GstBus *bus; GstBus *bus;
GstMessage *msg; GstMessage *msg;
@ -9,14 +11,19 @@ int main(int argc, char *argv[]) {
gst_init (&argc, &argv); gst_init (&argc, &argv);
/* Build the pipeline */ /* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Start playing */ /* Start playing */
gst_element_set_state (pipeline, GST_STATE_PLAYING); gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* Wait until error or EOS */ /* Wait until error or EOS */
bus = gst_element_get_bus (pipeline); bus = gst_element_get_bus (pipeline);
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS); msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Free resources */ /* Free resources */
if (msg != NULL) if (msg != NULL)

View file

@ -1,16 +1,19 @@
#include <gst/gst.h> #include <gst/gst.h>
#include <string.h> #include <string.h>
typedef struct _CustomData { typedef struct _CustomData
{
gboolean is_live; gboolean is_live;
GstElement *pipeline; GstElement *pipeline;
GMainLoop *loop; GMainLoop *loop;
} CustomData; } CustomData;
static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) { static void
cb_message (GstBus * bus, GstMessage * msg, CustomData * data)
{
switch (GST_MESSAGE_TYPE (msg)) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: { case GST_MESSAGE_ERROR:{
GError *err; GError *err;
gchar *debug; gchar *debug;
@ -28,11 +31,12 @@ static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {
gst_element_set_state (data->pipeline, GST_STATE_READY); gst_element_set_state (data->pipeline, GST_STATE_READY);
g_main_loop_quit (data->loop); g_main_loop_quit (data->loop);
break; break;
case GST_MESSAGE_BUFFERING: { case GST_MESSAGE_BUFFERING:{
gint percent = 0; gint percent = 0;
/* If the stream is live, we do not care about buffering. */ /* If the stream is live, we do not care about buffering. */
if (data->is_live) break; if (data->is_live)
break;
gst_message_parse_buffering (msg, &percent); gst_message_parse_buffering (msg, &percent);
g_print ("Buffering (%3d%%)\r", percent); g_print ("Buffering (%3d%%)\r", percent);
@ -51,10 +55,12 @@ static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {
default: default:
/* Unhandled message */ /* Unhandled message */
break; break;
} }
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
GstElement *pipeline; GstElement *pipeline;
GstBus *bus; GstBus *bus;
GstStateChangeReturn ret; GstStateChangeReturn ret;
@ -68,7 +74,10 @@ int main(int argc, char *argv[]) {
memset (&data, 0, sizeof (data)); memset (&data, 0, sizeof (data));
/* Build the pipeline */ /* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
bus = gst_element_get_bus (pipeline); bus = gst_element_get_bus (pipeline);
/* Start playing */ /* Start playing */

View file

@ -2,17 +2,20 @@
#include <stdio.h> #include <stdio.h>
#include <gst/gst.h> #include <gst/gst.h>
typedef struct _CustomData { typedef struct _CustomData
{
GstElement *pipeline; GstElement *pipeline;
GstElement *video_sink; GstElement *video_sink;
GMainLoop *loop; GMainLoop *loop;
gboolean playing; /* Playing or Paused */ gboolean playing; /* Playing or Paused */
gdouble rate; /* Current playback rate (can be negative) */ gdouble rate; /* Current playback rate (can be negative) */
} CustomData; } CustomData;
/* Send seek event to change rate */ /* Send seek event to change rate */
static void send_seek_event (CustomData *data) { static void
send_seek_event (CustomData * data)
{
gint64 position; gint64 position;
GstEvent *seek_event; GstEvent *seek_event;
@ -24,11 +27,15 @@ static void send_seek_event (CustomData *data) {
/* Create the seek event */ /* Create the seek event */
if (data->rate > 0) { if (data->rate > 0) {
seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, seek_event =
GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET, -1); gst_event_new_seek (data->rate, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
position, GST_SEEK_TYPE_SET, -1);
} else { } else {
seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, seek_event =
GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, position); gst_event_new_seek (data->rate, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, 0,
GST_SEEK_TYPE_SET, position);
} }
if (data->video_sink == NULL) { if (data->video_sink == NULL) {
@ -43,46 +50,51 @@ static void send_seek_event (CustomData *data) {
} }
/* Process keyboard input */ /* Process keyboard input */
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) { static gboolean
handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
{
gchar *str = NULL; gchar *str = NULL;
if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) { if (g_io_channel_read_line (source, &str, NULL, NULL,
NULL) != G_IO_STATUS_NORMAL) {
return TRUE; return TRUE;
} }
switch (g_ascii_tolower (str[0])) { switch (g_ascii_tolower (str[0])) {
case 'p': case 'p':
data->playing = !data->playing; data->playing = !data->playing;
gst_element_set_state (data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED); gst_element_set_state (data->pipeline,
g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE"); data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
break; g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
case 's': break;
if (g_ascii_isupper (str[0])) { case 's':
data->rate *= 2.0; if (g_ascii_isupper (str[0])) {
} else { data->rate *= 2.0;
data->rate /= 2.0; } else {
} data->rate /= 2.0;
send_seek_event (data); }
break; send_seek_event (data);
case 'd': break;
data->rate *= -1.0; case 'd':
send_seek_event (data); data->rate *= -1.0;
break; send_seek_event (data);
case 'n': break;
if (data->video_sink == NULL) { case 'n':
/* If we have not done so, obtain the sink through which we will send the step events */ if (data->video_sink == NULL) {
g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL); /* If we have not done so, obtain the sink through which we will send the step events */
} g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
}
gst_element_send_event (data->video_sink, gst_element_send_event (data->video_sink,
gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE, FALSE)); gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE,
g_print ("Stepping one frame\n"); FALSE));
break; g_print ("Stepping one frame\n");
case 'q': break;
g_main_loop_quit (data->loop); case 'q':
break; g_main_loop_quit (data->loop);
default: break;
break; default:
break;
} }
g_free (str); g_free (str);
@ -90,7 +102,9 @@ static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomDa
return TRUE; return TRUE;
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstStateChangeReturn ret; GstStateChangeReturn ret;
GIOChannel *io_stdin; GIOChannel *io_stdin;
@ -102,16 +116,18 @@ int main(int argc, char *argv[]) {
memset (&data, 0, sizeof (data)); memset (&data, 0, sizeof (data));
/* Print usage map */ /* Print usage map */
g_print ( g_print ("USAGE: Choose one of the following options, then press enter:\n"
"USAGE: Choose one of the following options, then press enter:\n" " 'P' to toggle between PAUSE and PLAY\n"
" 'P' to toggle between PAUSE and PLAY\n" " 'S' to increase playback speed, 's' to decrease playback speed\n"
" 'S' to increase playback speed, 's' to decrease playback speed\n" " 'D' to toggle playback direction\n"
" 'D' to toggle playback direction\n" " 'N' to move to next frame (in the current direction, better in PAUSE)\n"
" 'N' to move to next frame (in the current direction, better in PAUSE)\n" " 'Q' to quit\n");
" 'Q' to quit\n");
/* Build the pipeline */ /* Build the pipeline */
data.pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); data.pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Add a keyboard watch so we get notified of keystrokes */ /* Add a keyboard watch so we get notified of keystrokes */
#ifdef G_OS_WIN32 #ifdef G_OS_WIN32
@ -119,7 +135,7 @@ int main(int argc, char *argv[]) {
#else #else
io_stdin = g_io_channel_unix_new (fileno (stdin)); io_stdin = g_io_channel_unix_new (fileno (stdin));
#endif #endif
g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data); g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);
/* Start playing */ /* Start playing */
ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING); ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);

View file

@ -1,7 +1,10 @@
#include <clutter-gst/clutter-gst.h> #include <clutter-gst/clutter-gst.h>
/* Setup the video texture once its size is known */ /* Setup the video texture once its size is known */
void size_change (ClutterActor *texture, gint width, gint height, gpointer user_data) { void
size_change (ClutterActor * texture, gint width, gint height,
gpointer user_data)
{
ClutterActor *stage; ClutterActor *stage;
gfloat new_x, new_y, new_width, new_height; gfloat new_x, new_y, new_width, new_height;
gfloat stage_width, stage_height; gfloat stage_width, stage_height;
@ -21,7 +24,7 @@ void size_change (ClutterActor *texture, gint width, gint height, gpointer user_
new_x = 0; new_x = 0;
new_y = (stage_height - new_height) / 2; new_y = (stage_height - new_height) / 2;
} else { } else {
new_width = (width * stage_height) / height; new_width = (width * stage_height) / height;
new_height = stage_height; new_height = stage_height;
new_x = (stage_width - new_width) / 2; new_x = (stage_width - new_width) / 2;
@ -29,13 +32,18 @@ void size_change (ClutterActor *texture, gint width, gint height, gpointer user_
} }
clutter_actor_set_position (texture, new_x, new_y); clutter_actor_set_position (texture, new_x, new_y);
clutter_actor_set_size (texture, new_width, new_height); clutter_actor_set_size (texture, new_width, new_height);
clutter_actor_set_rotation (texture, CLUTTER_Y_AXIS, 0.0, stage_width / 2, 0, 0); clutter_actor_set_rotation (texture, CLUTTER_Y_AXIS, 0.0, stage_width / 2, 0,
0);
/* Animate it */ /* Animate it */
animation = clutter_actor_animate (texture, CLUTTER_LINEAR, 10000, "rotation-angle-y", 360.0, NULL); animation =
clutter_actor_animate (texture, CLUTTER_LINEAR, 10000, "rotation-angle-y",
360.0, NULL);
clutter_animation_set_loop (animation, TRUE); clutter_animation_set_loop (animation, TRUE);
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
GstElement *pipeline, *sink; GstElement *pipeline, *sink;
ClutterTimeline *timeline; ClutterTimeline *timeline;
ClutterActor *stage, *texture; ClutterActor *stage, *texture;
@ -50,14 +58,19 @@ int main(int argc, char *argv[]) {
/* Make a timeline */ /* Make a timeline */
timeline = clutter_timeline_new (1000); timeline = clutter_timeline_new (1000);
g_object_set(timeline, "loop", TRUE, NULL); g_object_set (timeline, "loop", TRUE, NULL);
/* Create new texture and disable slicing so the video is properly mapped onto it */ /* Create new texture and disable slicing so the video is properly mapped onto it */
texture = CLUTTER_ACTOR (g_object_new (CLUTTER_TYPE_TEXTURE, "disable-slicing", TRUE, NULL)); texture =
CLUTTER_ACTOR (g_object_new (CLUTTER_TYPE_TEXTURE, "disable-slicing",
TRUE, NULL));
g_signal_connect (texture, "size-change", G_CALLBACK (size_change), NULL); g_signal_connect (texture, "size-change", G_CALLBACK (size_change), NULL);
/* Build the GStreamer pipeline */ /* Build the GStreamer pipeline */
pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Instantiate the Clutter sink */ /* Instantiate the Clutter sink */
sink = gst_element_factory_make ("autocluttersink", NULL); sink = gst_element_factory_make ("autocluttersink", NULL);
@ -70,7 +83,7 @@ int main(int argc, char *argv[]) {
return -1; return -1;
} }
/* Link GStreamer with Clutter by passing the Clutter texture to the Clutter sink*/ /* Link GStreamer with Clutter by passing the Clutter texture to the Clutter sink */
g_object_set (sink, "texture", texture, NULL); g_object_set (sink, "texture", texture, NULL);
/* Add the Clutter sink to the pipeline */ /* Add the Clutter sink to the pipeline */
@ -86,7 +99,7 @@ int main(int argc, char *argv[]) {
clutter_group_add (CLUTTER_GROUP (stage), texture); clutter_group_add (CLUTTER_GROUP (stage), texture);
clutter_actor_show_all (stage); clutter_actor_show_all (stage);
clutter_main(); clutter_main ();
/* Free resources */ /* Free resources */
gst_element_set_state (pipeline, GST_STATE_NULL); gst_element_set_state (pipeline, GST_STATE_NULL);

View file

@ -1,6 +1,8 @@
#include <gst/gst.h> #include <gst/gst.h>
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
GstElement *pipeline, *source, *sink; GstElement *pipeline, *source, *sink;
GstBus *bus; GstBus *bus;
GstMessage *msg; GstMessage *msg;
@ -42,7 +44,9 @@ int main(int argc, char *argv[]) {
/* Wait until error or EOS */ /* Wait until error or EOS */
bus = gst_element_get_bus (pipeline); bus = gst_element_get_bus (pipeline);
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS); msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Parse message */ /* Parse message */
if (msg != NULL) { if (msg != NULL) {
@ -52,8 +56,10 @@ int main(int argc, char *argv[]) {
switch (GST_MESSAGE_TYPE (msg)) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
break; break;
@ -73,4 +79,4 @@ int main(int argc, char *argv[]) {
gst_element_set_state (pipeline, GST_STATE_NULL); gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline); gst_object_unref (pipeline);
return 0; return 0;
} }

View file

@ -1,7 +1,8 @@
#include <gst/gst.h> #include <gst/gst.h>
/* Structure to contain all our information, so we can pass it to callbacks */ /* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData { typedef struct _CustomData
{
GstElement *pipeline; GstElement *pipeline;
GstElement *source; GstElement *source;
GstElement *convert; GstElement *convert;
@ -9,9 +10,12 @@ typedef struct _CustomData {
} CustomData; } CustomData;
/* Handler for the pad-added signal */ /* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data); static void pad_added_handler (GstElement * src, GstPad * pad,
CustomData * data);
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstBus *bus; GstBus *bus;
GstMessage *msg; GstMessage *msg;
@ -36,7 +40,8 @@ int main(int argc, char *argv[]) {
/* Build the pipeline. Note that we are NOT linking the source at this /* Build the pipeline. Note that we are NOT linking the source at this
* point. We will do it later. */ * point. We will do it later. */
gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert , data.sink, NULL); gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert,
data.sink, NULL);
if (!gst_element_link (data.convert, data.sink)) { if (!gst_element_link (data.convert, data.sink)) {
g_printerr ("Elements could not be linked.\n"); g_printerr ("Elements could not be linked.\n");
gst_object_unref (data.pipeline); gst_object_unref (data.pipeline);
@ -44,10 +49,13 @@ int main(int argc, char *argv[]) {
} }
/* Set the URI to play */ /* Set the URI to play */
g_object_set (data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); g_object_set (data.source, "uri",
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Connect to the pad-added signal */ /* Connect to the pad-added signal */
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data); g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler),
&data);
/* Start playing */ /* Start playing */
ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING); ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
@ -71,8 +79,10 @@ int main(int argc, char *argv[]) {
switch (GST_MESSAGE_TYPE (msg)) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
terminate = TRUE; terminate = TRUE;
@ -85,9 +95,11 @@ int main(int argc, char *argv[]) {
/* We are only interested in state-changed messages from the pipeline */ /* We are only interested in state-changed messages from the pipeline */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state,
&pending_state);
g_print ("Pipeline state changed from %s to %s:\n", g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state));
} }
break; break;
default: default:
@ -107,14 +119,17 @@ int main(int argc, char *argv[]) {
} }
/* This function will be called by the pad-added signal */ /* This function will be called by the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) { static void
pad_added_handler (GstElement * src, GstPad * new_pad, CustomData * data)
{
GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink"); GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
GstPadLinkReturn ret; GstPadLinkReturn ret;
GstCaps *new_pad_caps = NULL; GstCaps *new_pad_caps = NULL;
GstStructure *new_pad_struct = NULL; GstStructure *new_pad_struct = NULL;
const gchar *new_pad_type = NULL; const gchar *new_pad_type = NULL;
g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src)); g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad),
GST_ELEMENT_NAME (src));
/* If our converter is already linked, we have nothing to do here */ /* If our converter is already linked, we have nothing to do here */
if (gst_pad_is_linked (sink_pad)) { if (gst_pad_is_linked (sink_pad)) {
@ -127,7 +142,8 @@ static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *dat
new_pad_struct = gst_caps_get_structure (new_pad_caps, 0); new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
new_pad_type = gst_structure_get_name (new_pad_struct); new_pad_type = gst_structure_get_name (new_pad_struct);
if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) { if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type); g_print ("It has type '%s' which is not raw audio. Ignoring.\n",
new_pad_type);
goto exit; goto exit;
} }

View file

@ -1,19 +1,22 @@
#include <gst/gst.h> #include <gst/gst.h>
/* Structure to contain all our information, so we can pass it around */ /* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData { typedef struct _CustomData
GstElement *playbin; /* Our one and only element */ {
gboolean playing; /* Are we in the PLAYING state? */ GstElement *playbin; /* Our one and only element */
gboolean terminate; /* Should we terminate execution? */ gboolean playing; /* Are we in the PLAYING state? */
gboolean seek_enabled; /* Is seeking enabled for this media? */ gboolean terminate; /* Should we terminate execution? */
gboolean seek_done; /* Have we performed the seek already? */ gboolean seek_enabled; /* Is seeking enabled for this media? */
gint64 duration; /* How long does this media last, in nanoseconds */ gboolean seek_done; /* Have we performed the seek already? */
gint64 duration; /* How long does this media last, in nanoseconds */
} CustomData; } CustomData;
/* Forward definition of the message processing function */ /* Forward definition of the message processing function */
static void handle_message (CustomData *data, GstMessage *msg); static void handle_message (CustomData * data, GstMessage * msg);
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstBus *bus; GstBus *bus;
GstMessage *msg; GstMessage *msg;
@ -37,7 +40,9 @@ int main(int argc, char *argv[]) {
} }
/* Set the URI to play */ /* Set the URI to play */
g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); g_object_set (data.playbin, "uri",
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Start playing */ /* Start playing */
ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING); ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
@ -51,7 +56,8 @@ int main(int argc, char *argv[]) {
bus = gst_element_get_bus (data.playbin); bus = gst_element_get_bus (data.playbin);
do { do {
msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND, msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION); GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS |
GST_MESSAGE_DURATION);
/* Parse message */ /* Parse message */
if (msg != NULL) { if (msg != NULL) {
@ -62,13 +68,15 @@ int main(int argc, char *argv[]) {
gint64 current = -1; gint64 current = -1;
/* Query the current position of the stream */ /* Query the current position of the stream */
if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME, &current)) { if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME,
&current)) {
g_printerr ("Could not query current position.\n"); g_printerr ("Could not query current position.\n");
} }
/* If we didn't know it yet, query the stream duration */ /* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data.duration)) { if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME, &data.duration)) { if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME,
&data.duration)) {
g_printerr ("Could not query current duration.\n"); g_printerr ("Could not query current duration.\n");
} }
} }
@ -95,15 +103,19 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} }
static void handle_message (CustomData *data, GstMessage *msg) { static void
handle_message (CustomData * data, GstMessage * msg)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
data->terminate = TRUE; data->terminate = TRUE;
@ -116,12 +128,14 @@ static void handle_message (CustomData *data, GstMessage *msg) {
/* The duration has changed, mark the current one as invalid */ /* The duration has changed, mark the current one as invalid */
data->duration = GST_CLOCK_TIME_NONE; data->duration = GST_CLOCK_TIME_NONE;
break; break;
case GST_MESSAGE_STATE_CHANGED: { case GST_MESSAGE_STATE_CHANGED:{
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state,
&pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
g_print ("Pipeline state changed from %s to %s:\n", g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state));
/* Remember whether we are in the PLAYING state or not */ /* Remember whether we are in the PLAYING state or not */
data->playing = (new_state == GST_STATE_PLAYING); data->playing = (new_state == GST_STATE_PLAYING);
@ -132,21 +146,23 @@ static void handle_message (CustomData *data, GstMessage *msg) {
gint64 start, end; gint64 start, end;
query = gst_query_new_seeking (GST_FORMAT_TIME); query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (data->playbin, query)) { if (gst_element_query (data->playbin, query)) {
gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end); gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start,
&end);
if (data->seek_enabled) { if (data->seek_enabled) {
g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n", g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %"
GST_TIME_ARGS (start), GST_TIME_ARGS (end)); GST_TIME_FORMAT "\n", GST_TIME_ARGS (start),
GST_TIME_ARGS (end));
} else { } else {
g_print ("Seeking is DISABLED for this stream.\n"); g_print ("Seeking is DISABLED for this stream.\n");
} }
} } else {
else {
g_printerr ("Seeking query failed."); g_printerr ("Seeking query failed.");
} }
gst_query_unref (query); gst_query_unref (query);
} }
} }
} break; }
break;
default: default:
/* We should not reach here */ /* We should not reach here */
g_printerr ("Unexpected message received.\n"); g_printerr ("Unexpected message received.\n");

View file

@ -14,21 +14,24 @@
#endif #endif
/* Structure to contain all our information, so we can pass it around */ /* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData { typedef struct _CustomData
GstElement *playbin; /* Our one and only pipeline */ {
GstElement *playbin; /* Our one and only pipeline */
GtkWidget *slider; /* Slider widget to keep track of current position */ GtkWidget *slider; /* Slider widget to keep track of current position */
GtkWidget *streams_list; /* Text widget to display info about the streams */ GtkWidget *streams_list; /* Text widget to display info about the streams */
gulong slider_update_signal_id; /* Signal ID for the slider update signal */ gulong slider_update_signal_id; /* Signal ID for the slider update signal */
GstState state; /* Current state of the pipeline */ GstState state; /* Current state of the pipeline */
gint64 duration; /* Duration of the clip, in nanoseconds */ gint64 duration; /* Duration of the clip, in nanoseconds */
} CustomData; } CustomData;
/* This function is called when the GUI toolkit creates the physical window that will hold the video. /* This function is called when the GUI toolkit creates the physical window that will hold the video.
* At this point we can retrieve its handler (which has a different meaning depending on the windowing system) * At this point we can retrieve its handler (which has a different meaning depending on the windowing system)
* and pass it to GStreamer through the XOverlay interface. */ * and pass it to GStreamer through the XOverlay interface. */
static void realize_cb (GtkWidget *widget, CustomData *data) { static void
realize_cb (GtkWidget * widget, CustomData * data)
{
GdkWindow *window = gtk_widget_get_window (widget); GdkWindow *window = gtk_widget_get_window (widget);
guintptr window_handle; guintptr window_handle;
@ -37,33 +40,42 @@ static void realize_cb (GtkWidget *widget, CustomData *data) {
/* Retrieve window handler from GDK */ /* Retrieve window handler from GDK */
#if defined (GDK_WINDOWING_WIN32) #if defined (GDK_WINDOWING_WIN32)
window_handle = (guintptr)GDK_WINDOW_HWND (window); window_handle = (guintptr) GDK_WINDOW_HWND (window);
#elif defined (GDK_WINDOWING_QUARTZ) #elif defined (GDK_WINDOWING_QUARTZ)
window_handle = gdk_quartz_window_get_nsview (window); window_handle = gdk_quartz_window_get_nsview (window);
#elif defined (GDK_WINDOWING_X11) #elif defined (GDK_WINDOWING_X11)
window_handle = GDK_WINDOW_XID (window); window_handle = GDK_WINDOW_XID (window);
#endif #endif
/* Pass it to playbin, which implements XOverlay and will forward it to the video sink */ /* Pass it to playbin, which implements XOverlay and will forward it to the video sink */
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->playbin), window_handle); gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->playbin),
window_handle);
} }
/* This function is called when the PLAY button is clicked */ /* This function is called when the PLAY button is clicked */
static void play_cb (GtkButton *button, CustomData *data) { static void
play_cb (GtkButton * button, CustomData * data)
{
gst_element_set_state (data->playbin, GST_STATE_PLAYING); gst_element_set_state (data->playbin, GST_STATE_PLAYING);
} }
/* This function is called when the PAUSE button is clicked */ /* This function is called when the PAUSE button is clicked */
static void pause_cb (GtkButton *button, CustomData *data) { static void
pause_cb (GtkButton * button, CustomData * data)
{
gst_element_set_state (data->playbin, GST_STATE_PAUSED); gst_element_set_state (data->playbin, GST_STATE_PAUSED);
} }
/* This function is called when the STOP button is clicked */ /* This function is called when the STOP button is clicked */
static void stop_cb (GtkButton *button, CustomData *data) { static void
stop_cb (GtkButton * button, CustomData * data)
{
gst_element_set_state (data->playbin, GST_STATE_READY); gst_element_set_state (data->playbin, GST_STATE_READY);
} }
/* This function is called when the main window is closed */ /* This function is called when the main window is closed */
static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) { static void
delete_event_cb (GtkWidget * widget, GdkEvent * event, CustomData * data)
{
stop_cb (NULL, data); stop_cb (NULL, data);
gtk_main_quit (); gtk_main_quit ();
} }
@ -71,7 +83,9 @@ static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *dat
/* This function is called everytime the video window needs to be redrawn (due to damage/exposure, /* This function is called everytime the video window needs to be redrawn (due to damage/exposure,
* rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise, * rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise,
* we simply draw a black rectangle to avoid garbage showing up. */ * we simply draw a black rectangle to avoid garbage showing up. */
static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, CustomData *data) { static gboolean
draw_cb (GtkWidget * widget, cairo_t * cr, CustomData * data)
{
if (data->state < GST_STATE_PAUSED) { if (data->state < GST_STATE_PAUSED) {
GtkAllocation allocation; GtkAllocation allocation;
@ -88,41 +102,59 @@ static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, CustomData *data) {
/* This function is called when the slider changes its position. We perform a seek to the /* This function is called when the slider changes its position. We perform a seek to the
* new position here. */ * new position here. */
static void slider_cb (GtkRange *range, CustomData *data) { static void
slider_cb (GtkRange * range, CustomData * data)
{
gdouble value = gtk_range_get_value (GTK_RANGE (data->slider)); gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
gst_element_seek_simple (data->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, gst_element_seek_simple (data->playbin, GST_FORMAT_TIME,
(gint64)(value * GST_SECOND)); GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
(gint64) (value * GST_SECOND));
} }
/* This creates all the GTK+ widgets that compose our application, and registers the callbacks */ /* This creates all the GTK+ widgets that compose our application, and registers the callbacks */
static void create_ui (CustomData *data) { static void
GtkWidget *main_window; /* The uppermost window, containing all other windows */ create_ui (CustomData * data)
GtkWidget *video_window; /* The drawing area where the video will be shown */ {
GtkWidget *main_box; /* VBox to hold main_hbox and the controls */ GtkWidget *main_window; /* The uppermost window, containing all other windows */
GtkWidget *main_hbox; /* HBox to hold the video_window and the stream info text widget */ GtkWidget *video_window; /* The drawing area where the video will be shown */
GtkWidget *controls; /* HBox to hold the buttons and the slider */ GtkWidget *main_box; /* VBox to hold main_hbox and the controls */
GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */ GtkWidget *main_hbox; /* HBox to hold the video_window and the stream info text widget */
GtkWidget *controls; /* HBox to hold the buttons and the slider */
GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */
main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data); g_signal_connect (G_OBJECT (main_window), "delete-event",
G_CALLBACK (delete_event_cb), data);
video_window = gtk_drawing_area_new (); video_window = gtk_drawing_area_new ();
gtk_widget_set_double_buffered (video_window, FALSE); gtk_widget_set_double_buffered (video_window, FALSE);
g_signal_connect (video_window, "realize", G_CALLBACK (realize_cb), data); g_signal_connect (video_window, "realize", G_CALLBACK (realize_cb), data);
g_signal_connect (video_window, "draw", G_CALLBACK (draw_cb), data); g_signal_connect (video_window, "draw", G_CALLBACK (draw_cb), data);
play_button = gtk_button_new_from_icon_name ("media-playback-start", GTK_ICON_SIZE_SMALL_TOOLBAR); play_button =
g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), data); gtk_button_new_from_icon_name ("media-playback-start",
GTK_ICON_SIZE_SMALL_TOOLBAR);
g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb),
data);
pause_button = gtk_button_new_from_icon_name ("media-playback-pause", GTK_ICON_SIZE_SMALL_TOOLBAR); pause_button =
g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), data); gtk_button_new_from_icon_name ("media-playback-pause",
GTK_ICON_SIZE_SMALL_TOOLBAR);
g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb),
data);
stop_button = gtk_button_new_from_icon_name ("media-playback-stop", GTK_ICON_SIZE_SMALL_TOOLBAR); stop_button =
g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), data); gtk_button_new_from_icon_name ("media-playback-stop",
GTK_ICON_SIZE_SMALL_TOOLBAR);
g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb),
data);
data->slider = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1); data->slider =
gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1);
gtk_scale_set_draw_value (GTK_SCALE (data->slider), 0); gtk_scale_set_draw_value (GTK_SCALE (data->slider), 0);
data->slider_update_signal_id = g_signal_connect (G_OBJECT (data->slider), "value-changed", G_CALLBACK (slider_cb), data); data->slider_update_signal_id =
g_signal_connect (G_OBJECT (data->slider), "value-changed",
G_CALLBACK (slider_cb), data);
data->streams_list = gtk_text_view_new (); data->streams_list = gtk_text_view_new ();
gtk_text_view_set_editable (GTK_TEXT_VIEW (data->streams_list), FALSE); gtk_text_view_set_editable (GTK_TEXT_VIEW (data->streams_list), FALSE);
@ -147,7 +179,9 @@ static void create_ui (CustomData *data) {
} }
/* This function is called periodically to refresh the GUI */ /* This function is called periodically to refresh the GUI */
static gboolean refresh_ui (CustomData *data) { static gboolean
refresh_ui (CustomData * data)
{
gint64 current = -1; gint64 current = -1;
/* We do not want to update anything unless we are in the PAUSED or PLAYING states */ /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
@ -156,11 +190,13 @@ static gboolean refresh_ui (CustomData *data) {
/* If we didn't know it yet, query the stream duration */ /* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) { if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
if (!gst_element_query_duration (data->playbin, GST_FORMAT_TIME, &data->duration)) { if (!gst_element_query_duration (data->playbin, GST_FORMAT_TIME,
&data->duration)) {
g_printerr ("Could not query current duration.\n"); g_printerr ("Could not query current duration.\n");
} else { } else {
/* Set the range of the slider to the clip duration, in SECONDS */ /* Set the range of the slider to the clip duration, in SECONDS */
gtk_range_set_range (GTK_RANGE (data->slider), 0, (gdouble)data->duration / GST_SECOND); gtk_range_set_range (GTK_RANGE (data->slider), 0,
(gdouble) data->duration / GST_SECOND);
} }
} }
@ -169,7 +205,8 @@ static gboolean refresh_ui (CustomData *data) {
* (which would trigger a seek the user has not requested) */ * (which would trigger a seek the user has not requested) */
g_signal_handler_block (data->slider, data->slider_update_signal_id); g_signal_handler_block (data->slider, data->slider_update_signal_id);
/* Set the position of the slider to the current pipeline positoin, in SECONDS */ /* Set the position of the slider to the current pipeline positoin, in SECONDS */
gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND); gtk_range_set_value (GTK_RANGE (data->slider),
(gdouble) current / GST_SECOND);
/* Re-enable the signal */ /* Re-enable the signal */
g_signal_handler_unblock (data->slider, data->slider_update_signal_id); g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
} }
@ -177,22 +214,27 @@ static gboolean refresh_ui (CustomData *data) {
} }
/* This function is called when new metadata is discovered in the stream */ /* This function is called when new metadata is discovered in the stream */
static void tags_cb (GstElement *playbin, gint stream, CustomData *data) { static void
tags_cb (GstElement * playbin, gint stream, CustomData * data)
{
/* We are possibly in a GStreamer working thread, so we notify the main /* We are possibly in a GStreamer working thread, so we notify the main
* thread of this event through a message in the bus */ * thread of this event through a message in the bus */
gst_element_post_message (playbin, gst_element_post_message (playbin,
gst_message_new_application (GST_OBJECT (playbin), gst_message_new_application (GST_OBJECT (playbin),
gst_structure_new_empty ("tags-changed"))); gst_structure_new_empty ("tags-changed")));
} }
/* This function is called when an error message is posted on the bus */ /* This function is called when an error message is posted on the bus */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
/* Print error details on the screen */ /* Print error details on the screen */
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
@ -203,14 +245,18 @@ static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
/* This function is called when an End-Of-Stream message is posted on the bus. /* This function is called when an End-Of-Stream message is posted on the bus.
* We just set the pipeline to READY (which stops playback) */ * We just set the pipeline to READY (which stops playback) */
static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
eos_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
g_print ("End-Of-Stream reached.\n"); g_print ("End-Of-Stream reached.\n");
gst_element_set_state (data->playbin, GST_STATE_READY); gst_element_set_state (data->playbin, GST_STATE_READY);
} }
/* This function is called when the pipeline changes states. We use it to /* This function is called when the pipeline changes states. We use it to
* keep track of the current state. */ * keep track of the current state. */
static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
@ -224,7 +270,9 @@ static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
} }
/* Extract metadata from all the streams and write it to the text widget in the GUI */ /* Extract metadata from all the streams and write it to the text widget in the GUI */
static void analyze_streams (CustomData *data) { static void
analyze_streams (CustomData * data)
{
gint i; gint i;
GstTagList *tags; GstTagList *tags;
gchar *str, *total_str; gchar *str, *total_str;
@ -308,15 +356,20 @@ static void analyze_streams (CustomData *data) {
/* This function is called when an "application" message is posted on the bus. /* This function is called when an "application" message is posted on the bus.
* Here we retrieve the message posted by the tags_cb callback */ * Here we retrieve the message posted by the tags_cb callback */
static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
if (g_strcmp0 (gst_structure_get_name (gst_message_get_structure (msg)), "tags-changed") == 0) { application_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
if (g_strcmp0 (gst_structure_get_name (gst_message_get_structure (msg)),
"tags-changed") == 0) {
/* If the message is the "tags-changed" (only one we are currently issuing), update /* If the message is the "tags-changed" (only one we are currently issuing), update
* the stream info GUI */ * the stream info GUI */
analyze_streams (data); analyze_streams (data);
} }
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstStateChangeReturn ret; GstStateChangeReturn ret;
GstBus *bus; GstBus *bus;
@ -340,12 +393,17 @@ int main(int argc, char *argv[]) {
} }
/* Set the URI to play */ /* Set the URI to play */
g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); g_object_set (data.playbin, "uri",
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Connect to interesting signals in playbin */ /* Connect to interesting signals in playbin */
g_signal_connect (G_OBJECT (data.playbin), "video-tags-changed", (GCallback) tags_cb, &data); g_signal_connect (G_OBJECT (data.playbin), "video-tags-changed",
g_signal_connect (G_OBJECT (data.playbin), "audio-tags-changed", (GCallback) tags_cb, &data); (GCallback) tags_cb, &data);
g_signal_connect (G_OBJECT (data.playbin), "text-tags-changed", (GCallback) tags_cb, &data); g_signal_connect (G_OBJECT (data.playbin), "audio-tags-changed",
(GCallback) tags_cb, &data);
g_signal_connect (G_OBJECT (data.playbin), "text-tags-changed",
(GCallback) tags_cb, &data);
/* Create the GUI */ /* Create the GUI */
create_ui (&data); create_ui (&data);
@ -353,10 +411,13 @@ int main(int argc, char *argv[]) {
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data.playbin); bus = gst_element_get_bus (data.playbin);
gst_bus_add_signal_watch (bus); gst_bus_add_signal_watch (bus);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, &data); &data);
g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, &data); g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb, &data);
g_signal_connect (G_OBJECT (bus), "message::application", (GCallback)application_cb, &data); g_signal_connect (G_OBJECT (bus), "message::state-changed",
(GCallback) state_changed_cb, &data);
g_signal_connect (G_OBJECT (bus), "message::application",
(GCallback) application_cb, &data);
gst_object_unref (bus); gst_object_unref (bus);
/* Start playing */ /* Start playing */
@ -368,7 +429,7 @@ int main(int argc, char *argv[]) {
} }
/* Register a function that GLib will call every second */ /* Register a function that GLib will call every second */
g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data); g_timeout_add_seconds (1, (GSourceFunc) refresh_ui, &data);
/* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */ /* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */
gtk_main (); gtk_main ();

View file

@ -1,7 +1,9 @@
#include <gst/gst.h> #include <gst/gst.h>
/* Functions below print the Capabilities in a human-friendly format */ /* Functions below print the Capabilities in a human-friendly format */
static gboolean print_field (GQuark field, const GValue * value, gpointer pfx) { static gboolean
print_field (GQuark field, const GValue * value, gpointer pfx)
{
gchar *str = gst_value_serialize (value); gchar *str = gst_value_serialize (value);
g_print ("%s %15s: %s\n", (gchar *) pfx, g_quark_to_string (field), str); g_print ("%s %15s: %s\n", (gchar *) pfx, g_quark_to_string (field), str);
@ -9,7 +11,9 @@ static gboolean print_field (GQuark field, const GValue * value, gpointer pfx) {
return TRUE; return TRUE;
} }
static void print_caps (const GstCaps * caps, const gchar * pfx) { static void
print_caps (const GstCaps * caps, const gchar * pfx)
{
guint i; guint i;
g_return_if_fail (caps != NULL); g_return_if_fail (caps != NULL);
@ -32,11 +36,14 @@ static void print_caps (const GstCaps * caps, const gchar * pfx) {
} }
/* Prints information about a Pad Template, including its Capabilities */ /* Prints information about a Pad Template, including its Capabilities */
static void print_pad_templates_information (GstElementFactory * factory) { static void
print_pad_templates_information (GstElementFactory * factory)
{
const GList *pads; const GList *pads;
GstStaticPadTemplate *padtemplate; GstStaticPadTemplate *padtemplate;
g_print ("Pad Templates for %s:\n", gst_element_factory_get_longname (factory)); g_print ("Pad Templates for %s:\n",
gst_element_factory_get_longname (factory));
if (!gst_element_factory_get_num_pad_templates (factory)) { if (!gst_element_factory_get_num_pad_templates (factory)) {
g_print (" none\n"); g_print (" none\n");
return; return;
@ -77,7 +84,9 @@ static void print_pad_templates_information (GstElementFactory * factory) {
} }
/* Shows the CURRENT capabilities of the requested pad in the given element */ /* Shows the CURRENT capabilities of the requested pad in the given element */
static void print_pad_capabilities (GstElement *element, gchar *pad_name) { static void
print_pad_capabilities (GstElement * element, gchar * pad_name)
{
GstPad *pad = NULL; GstPad *pad = NULL;
GstCaps *caps = NULL; GstCaps *caps = NULL;
@ -100,7 +109,9 @@ static void print_pad_capabilities (GstElement *element, gchar *pad_name) {
gst_object_unref (pad); gst_object_unref (pad);
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
GstElement *pipeline, *source, *sink; GstElement *pipeline, *source, *sink;
GstElementFactory *source_factory, *sink_factory; GstElementFactory *source_factory, *sink_factory;
GstBus *bus; GstBus *bus;
@ -150,14 +161,16 @@ int main(int argc, char *argv[]) {
/* Start playing */ /* Start playing */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) { if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr ("Unable to set the pipeline to the playing state (check the bus for error messages).\n"); g_printerr
("Unable to set the pipeline to the playing state (check the bus for error messages).\n");
} }
/* Wait until error, EOS or State Change */ /* Wait until error, EOS or State Change */
bus = gst_element_get_bus (pipeline); bus = gst_element_get_bus (pipeline);
do { do {
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS | msg =
GST_MESSAGE_STATE_CHANGED); gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_STATE_CHANGED);
/* Parse message */ /* Parse message */
if (msg != NULL) { if (msg != NULL) {
@ -167,8 +180,10 @@ int main(int argc, char *argv[]) {
switch (GST_MESSAGE_TYPE (msg)) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
terminate = TRUE; terminate = TRUE;
@ -181,9 +196,11 @@ int main(int argc, char *argv[]) {
/* We are only interested in state-changed messages from the pipeline */ /* We are only interested in state-changed messages from the pipeline */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (pipeline)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (pipeline)) {
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state,
&pending_state);
g_print ("\nPipeline state changed from %s to %s:\n", g_print ("\nPipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state));
/* Print the current capabilities of the sink element */ /* Print the current capabilities of the sink element */
print_pad_capabilities (sink, "sink"); print_pad_capabilities (sink, "sink");
} }

View file

@ -1,7 +1,10 @@
#include <gst/gst.h> #include <gst/gst.h>
int main(int argc, char *argv[]) { int
GstElement *pipeline, *audio_source, *tee, *audio_queue, *audio_convert, *audio_resample, *audio_sink; main (int argc, char *argv[])
{
GstElement *pipeline, *audio_source, *tee, *audio_queue, *audio_convert,
*audio_resample, *audio_sink;
GstElement *video_queue, *visual, *video_convert, *video_sink; GstElement *video_queue, *visual, *video_convert, *video_sink;
GstBus *bus; GstBus *bus;
GstMessage *msg; GstMessage *msg;
@ -26,8 +29,9 @@ int main(int argc, char *argv[]) {
/* Create the empty pipeline */ /* Create the empty pipeline */
pipeline = gst_pipeline_new ("test-pipeline"); pipeline = gst_pipeline_new ("test-pipeline");
if (!pipeline || !audio_source || !tee || !audio_queue || !audio_convert || !audio_resample || !audio_sink || if (!pipeline || !audio_source || !tee || !audio_queue || !audio_convert
!video_queue || !visual || !video_convert || !video_sink) { || !audio_resample || !audio_sink || !video_queue || !visual
|| !video_convert || !video_sink) {
g_printerr ("Not all elements could be created.\n"); g_printerr ("Not all elements could be created.\n");
return -1; return -1;
} }
@ -37,11 +41,14 @@ int main(int argc, char *argv[]) {
g_object_set (visual, "shader", 0, "style", 1, NULL); g_object_set (visual, "shader", 0, "style", 1, NULL);
/* Link all elements that can be automatically linked because they have "Always" pads */ /* Link all elements that can be automatically linked because they have "Always" pads */
gst_bin_add_many (GST_BIN (pipeline), audio_source, tee, audio_queue, audio_convert, audio_resample, audio_sink, gst_bin_add_many (GST_BIN (pipeline), audio_source, tee, audio_queue,
video_queue, visual, video_convert, video_sink, NULL); audio_convert, audio_resample, audio_sink, video_queue, visual,
if (gst_element_link_many (audio_source, tee, NULL) != TRUE || video_convert, video_sink, NULL);
gst_element_link_many (audio_queue, audio_convert, audio_resample, audio_sink, NULL) != TRUE || if (gst_element_link_many (audio_source, tee, NULL) != TRUE
gst_element_link_many (video_queue, visual, video_convert, video_sink, NULL) != TRUE) { || gst_element_link_many (audio_queue, audio_convert, audio_resample,
audio_sink, NULL) != TRUE
|| gst_element_link_many (video_queue, visual, video_convert, video_sink,
NULL) != TRUE) {
g_printerr ("Elements could not be linked.\n"); g_printerr ("Elements could not be linked.\n");
gst_object_unref (pipeline); gst_object_unref (pipeline);
return -1; return -1;
@ -49,10 +56,12 @@ int main(int argc, char *argv[]) {
/* Manually link the Tee, which has "Request" pads */ /* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_get_request_pad (tee, "src_%u"); tee_audio_pad = gst_element_get_request_pad (tee, "src_%u");
g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad)); g_print ("Obtained request pad %s for audio branch.\n",
gst_pad_get_name (tee_audio_pad));
queue_audio_pad = gst_element_get_static_pad (audio_queue, "sink"); queue_audio_pad = gst_element_get_static_pad (audio_queue, "sink");
tee_video_pad = gst_element_get_request_pad (tee, "src_%u"); tee_video_pad = gst_element_get_request_pad (tee, "src_%u");
g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad)); g_print ("Obtained request pad %s for video branch.\n",
gst_pad_get_name (tee_video_pad));
queue_video_pad = gst_element_get_static_pad (video_queue, "sink"); queue_video_pad = gst_element_get_static_pad (video_queue, "sink");
if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK || if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK) { gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK) {
@ -68,7 +77,9 @@ int main(int argc, char *argv[]) {
/* Wait until error or EOS */ /* Wait until error or EOS */
bus = gst_element_get_bus (pipeline); bus = gst_element_get_bus (pipeline);
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS); msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Release the request pads from the Tee, and unref them */ /* Release the request pads from the Tee, and unref them */
gst_element_release_request_pad (tee, tee_audio_pad); gst_element_release_request_pad (tee, tee_audio_pad);

View file

@ -2,53 +2,60 @@
#include <gst/audio/audio.h> #include <gst/audio/audio.h>
#include <string.h> #include <string.h>
#define CHUNK_SIZE 1024 /* Amount of bytes we are sending in each buffer */ #define CHUNK_SIZE 1024 /* Amount of bytes we are sending in each buffer */
#define SAMPLE_RATE 44100 /* Samples per second we are sending */ #define SAMPLE_RATE 44100 /* Samples per second we are sending */
/* Structure to contain all our information, so we can pass it to callbacks */ /* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData { typedef struct _CustomData
GstElement *pipeline, *app_source, *tee, *audio_queue, *audio_convert1, *audio_resample, *audio_sink; {
GstElement *video_queue, *audio_convert2, *visual, *video_convert, *video_sink; GstElement *pipeline, *app_source, *tee, *audio_queue, *audio_convert1,
*audio_resample, *audio_sink;
GstElement *video_queue, *audio_convert2, *visual, *video_convert,
*video_sink;
GstElement *app_queue, *app_sink; GstElement *app_queue, *app_sink;
guint64 num_samples; /* Number of samples generated so far (for timestamp generation) */ guint64 num_samples; /* Number of samples generated so far (for timestamp generation) */
gfloat a, b, c, d; /* For waveform generation */ gfloat a, b, c, d; /* For waveform generation */
guint sourceid; /* To control the GSource */ guint sourceid; /* To control the GSource */
GMainLoop *main_loop; /* GLib's Main Loop */ GMainLoop *main_loop; /* GLib's Main Loop */
} CustomData; } CustomData;
/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc. /* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
* The idle handler is added to the mainloop when appsrc requests us to start sending data (need-data signal) * The idle 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). * and is removed when appsrc has enough data (enough-data signal).
*/ */
static gboolean push_data (CustomData *data) { static gboolean
push_data (CustomData * data)
{
GstBuffer *buffer; GstBuffer *buffer;
GstFlowReturn ret; GstFlowReturn ret;
int i; int i;
GstMapInfo map; GstMapInfo map;
gint16 *raw; gint16 *raw;
gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */ gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
gfloat freq; gfloat freq;
/* Create a new empty buffer */ /* Create a new empty buffer */
buffer = gst_buffer_new_and_alloc (CHUNK_SIZE); buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);
/* Set its timestamp and duration */ /* Set its timestamp and duration */
GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE); GST_BUFFER_TIMESTAMP (buffer) =
GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE); gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
GST_BUFFER_DURATION (buffer) =
gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);
/* Generate some psychodelic waveforms */ /* Generate some psychodelic waveforms */
gst_buffer_map (buffer, &map, GST_MAP_WRITE); gst_buffer_map (buffer, &map, GST_MAP_WRITE);
raw = (gint16 *)map.data; raw = (gint16 *) map.data;
data->c += data->d; data->c += data->d;
data->d -= data->c / 1000; data->d -= data->c / 1000;
freq = 1100 + 1000 * data->d; freq = 1100 + 1000 * data->d;
for (i = 0; i < num_samples; i++) { for (i = 0; i < num_samples; i++) {
data->a += data->b; data->a += data->b;
data->b -= data->a / freq; data->b -= data->a / freq;
raw[i] = (gint16)(500 * data->a); raw[i] = (gint16) (500 * data->a);
} }
gst_buffer_unmap (buffer, &map); gst_buffer_unmap (buffer, &map);
data->num_samples += num_samples; data->num_samples += num_samples;
@ -69,7 +76,9 @@ static gboolean push_data (CustomData *data) {
/* This signal callback triggers when appsrc needs data. Here, we add an idle handler /* This signal callback triggers when appsrc needs data. Here, we add an idle handler
* to the mainloop to start pushing data into the appsrc */ * to the mainloop to start pushing data into the appsrc */
static void start_feed (GstElement *source, guint size, CustomData *data) { static void
start_feed (GstElement * source, guint size, CustomData * data)
{
if (data->sourceid == 0) { if (data->sourceid == 0) {
g_print ("Start feeding\n"); g_print ("Start feeding\n");
data->sourceid = g_idle_add ((GSourceFunc) push_data, data); data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
@ -78,7 +87,9 @@ static void start_feed (GstElement *source, guint size, CustomData *data) {
/* This callback triggers when appsrc has enough data and we can stop sending. /* This callback triggers when appsrc has enough data and we can stop sending.
* We remove the idle handler from the mainloop */ * We remove the idle handler from the mainloop */
static void stop_feed (GstElement *source, CustomData *data) { static void
stop_feed (GstElement * source, CustomData * data)
{
if (data->sourceid != 0) { if (data->sourceid != 0) {
g_print ("Stop feeding\n"); g_print ("Stop feeding\n");
g_source_remove (data->sourceid); g_source_remove (data->sourceid);
@ -87,7 +98,9 @@ static void stop_feed (GstElement *source, CustomData *data) {
} }
/* The appsink has received a buffer */ /* The appsink has received a buffer */
static GstFlowReturn new_sample (GstElement *sink, CustomData *data) { static GstFlowReturn
new_sample (GstElement * sink, CustomData * data)
{
GstSample *sample; GstSample *sample;
/* Retrieve the buffer */ /* Retrieve the buffer */
@ -103,13 +116,16 @@ static GstFlowReturn new_sample (GstElement *sink, CustomData *data) {
} }
/* This function is called when an error message is posted on the bus */ /* This function is called when an error message is posted on the bus */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
/* Print error details on the screen */ /* Print error details on the screen */
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
@ -117,7 +133,9 @@ static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstPad *tee_audio_pad, *tee_video_pad, *tee_app_pad; GstPad *tee_audio_pad, *tee_video_pad, *tee_app_pad;
GstPad *queue_audio_pad, *queue_video_pad, *queue_app_pad; GstPad *queue_audio_pad, *queue_video_pad, *queue_app_pad;
@ -127,7 +145,7 @@ int main(int argc, char *argv[]) {
/* Initialize cumstom data structure */ /* Initialize cumstom data structure */
memset (&data, 0, sizeof (data)); memset (&data, 0, sizeof (data));
data.b = 1; /* For waveform generation */ data.b = 1; /* For waveform generation */
data.d = 1; data.d = 1;
/* Initialize GStreamer */ /* Initialize GStreamer */
@ -137,13 +155,17 @@ int main(int argc, char *argv[]) {
data.app_source = gst_element_factory_make ("appsrc", "audio_source"); data.app_source = gst_element_factory_make ("appsrc", "audio_source");
data.tee = gst_element_factory_make ("tee", "tee"); data.tee = gst_element_factory_make ("tee", "tee");
data.audio_queue = gst_element_factory_make ("queue", "audio_queue"); data.audio_queue = gst_element_factory_make ("queue", "audio_queue");
data.audio_convert1 = gst_element_factory_make ("audioconvert", "audio_convert1"); data.audio_convert1 =
data.audio_resample = gst_element_factory_make ("audioresample", "audio_resample"); gst_element_factory_make ("audioconvert", "audio_convert1");
data.audio_resample =
gst_element_factory_make ("audioresample", "audio_resample");
data.audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink"); data.audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
data.video_queue = gst_element_factory_make ("queue", "video_queue"); data.video_queue = gst_element_factory_make ("queue", "video_queue");
data.audio_convert2 = gst_element_factory_make ("audioconvert", "audio_convert2"); data.audio_convert2 =
gst_element_factory_make ("audioconvert", "audio_convert2");
data.visual = gst_element_factory_make ("wavescope", "visual"); data.visual = gst_element_factory_make ("wavescope", "visual");
data.video_convert = gst_element_factory_make ("videoconvert", "video_convert"); data.video_convert =
gst_element_factory_make ("videoconvert", "video_convert");
data.video_sink = gst_element_factory_make ("autovideosink", "video_sink"); data.video_sink = gst_element_factory_make ("autovideosink", "video_sink");
data.app_queue = gst_element_factory_make ("queue", "app_queue"); data.app_queue = gst_element_factory_make ("queue", "app_queue");
data.app_sink = gst_element_factory_make ("appsink", "app_sink"); data.app_sink = gst_element_factory_make ("appsink", "app_sink");
@ -151,9 +173,11 @@ int main(int argc, char *argv[]) {
/* Create the empty pipeline */ /* Create the empty pipeline */
data.pipeline = gst_pipeline_new ("test-pipeline"); data.pipeline = gst_pipeline_new ("test-pipeline");
if (!data.pipeline || !data.app_source || !data.tee || !data.audio_queue || !data.audio_convert1 || if (!data.pipeline || !data.app_source || !data.tee || !data.audio_queue
!data.audio_resample || !data.audio_sink || !data.video_queue || !data.audio_convert2 || !data.visual || || !data.audio_convert1 || !data.audio_resample || !data.audio_sink
!data.video_convert || !data.video_sink || !data.app_queue || !data.app_sink) { || !data.video_queue || !data.audio_convert2 || !data.visual
|| !data.video_convert || !data.video_sink || !data.app_queue
|| !data.app_sink) {
g_printerr ("Not all elements could be created.\n"); g_printerr ("Not all elements could be created.\n");
return -1; return -1;
} }
@ -164,23 +188,30 @@ int main(int argc, char *argv[]) {
/* Configure appsrc */ /* Configure appsrc */
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL); gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL);
audio_caps = gst_audio_info_to_caps (&info); audio_caps = gst_audio_info_to_caps (&info);
g_object_set (data.app_source, "caps", audio_caps, "format", GST_FORMAT_TIME, NULL); g_object_set (data.app_source, "caps", audio_caps, "format", GST_FORMAT_TIME,
g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed), &data); NULL);
g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed), &data); g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed),
&data);
g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed),
&data);
/* Configure appsink */ /* Configure appsink */
g_object_set (data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL); g_object_set (data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL);
g_signal_connect (data.app_sink, "new-sample", G_CALLBACK (new_sample), &data); g_signal_connect (data.app_sink, "new-sample", G_CALLBACK (new_sample),
&data);
gst_caps_unref (audio_caps); gst_caps_unref (audio_caps);
/* Link all elements that can be automatically linked because they have "Always" pads */ /* Link all elements that can be automatically linked because they have "Always" pads */
gst_bin_add_many (GST_BIN (data.pipeline), data.app_source, data.tee, data.audio_queue, data.audio_convert1, data.audio_resample, gst_bin_add_many (GST_BIN (data.pipeline), data.app_source, data.tee,
data.audio_sink, data.video_queue, data.audio_convert2, data.visual, data.video_convert, data.video_sink, data.app_queue, data.audio_queue, data.audio_convert1, data.audio_resample,
data.app_sink, NULL); data.audio_sink, data.video_queue, data.audio_convert2, data.visual,
if (gst_element_link_many (data.app_source, data.tee, NULL) != TRUE || data.video_convert, data.video_sink, data.app_queue, data.app_sink, NULL);
gst_element_link_many (data.audio_queue, data.audio_convert1, data.audio_resample, data.audio_sink, NULL) != TRUE || if (gst_element_link_many (data.app_source, data.tee, NULL) != TRUE
gst_element_link_many (data.video_queue, data.audio_convert2, data.visual, data.video_convert, data.video_sink, NULL) != TRUE || || gst_element_link_many (data.audio_queue, data.audio_convert1,
gst_element_link_many (data.app_queue, data.app_sink, NULL) != TRUE) { data.audio_resample, data.audio_sink, NULL) != TRUE
|| gst_element_link_many (data.video_queue, data.audio_convert2,
data.visual, data.video_convert, data.video_sink, NULL) != TRUE
|| gst_element_link_many (data.app_queue, data.app_sink, NULL) != TRUE) {
g_printerr ("Elements could not be linked.\n"); g_printerr ("Elements could not be linked.\n");
gst_object_unref (data.pipeline); gst_object_unref (data.pipeline);
return -1; return -1;
@ -188,13 +219,16 @@ int main(int argc, char *argv[]) {
/* Manually link the Tee, which has "Request" pads */ /* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_get_request_pad (data.tee, "src_%u"); tee_audio_pad = gst_element_get_request_pad (data.tee, "src_%u");
g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad)); g_print ("Obtained request pad %s for audio branch.\n",
gst_pad_get_name (tee_audio_pad));
queue_audio_pad = gst_element_get_static_pad (data.audio_queue, "sink"); queue_audio_pad = gst_element_get_static_pad (data.audio_queue, "sink");
tee_video_pad = gst_element_get_request_pad (data.tee, "src_%u"); tee_video_pad = gst_element_get_request_pad (data.tee, "src_%u");
g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad)); g_print ("Obtained request pad %s for video branch.\n",
gst_pad_get_name (tee_video_pad));
queue_video_pad = gst_element_get_static_pad (data.video_queue, "sink"); queue_video_pad = gst_element_get_static_pad (data.video_queue, "sink");
tee_app_pad = gst_element_get_request_pad (data.tee, "src_%u"); tee_app_pad = gst_element_get_request_pad (data.tee, "src_%u");
g_print ("Obtained request pad %s for app branch.\n", gst_pad_get_name (tee_app_pad)); g_print ("Obtained request pad %s for app branch.\n",
gst_pad_get_name (tee_app_pad));
queue_app_pad = gst_element_get_static_pad (data.app_queue, "sink"); queue_app_pad = gst_element_get_static_pad (data.app_queue, "sink");
if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK || if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK || gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK ||
@ -210,7 +244,8 @@ int main(int argc, char *argv[]) {
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data.pipeline); bus = gst_element_get_bus (data.pipeline);
gst_bus_add_signal_watch (bus); gst_bus_add_signal_watch (bus);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
&data);
gst_object_unref (bus); gst_object_unref (bus);
/* Start playing the pipeline */ /* Start playing the pipeline */

View file

@ -3,13 +3,17 @@
#include <gst/pbutils/pbutils.h> #include <gst/pbutils/pbutils.h>
/* Structure to contain all our information, so we can pass it around */ /* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData { typedef struct _CustomData
{
GstDiscoverer *discoverer; GstDiscoverer *discoverer;
GMainLoop *loop; GMainLoop *loop;
} CustomData; } CustomData;
/* Print a tag in a human-readable format (name: value) */ /* Print a tag in a human-readable format (name: value) */
static void print_tag_foreach (const GstTagList *tags, const gchar *tag, gpointer user_data) { static void
print_tag_foreach (const GstTagList * tags, const gchar * tag,
gpointer user_data)
{
GValue val = { 0, }; GValue val = { 0, };
gchar *str; gchar *str;
gint depth = GPOINTER_TO_INT (user_data); gint depth = GPOINTER_TO_INT (user_data);
@ -28,7 +32,9 @@ static void print_tag_foreach (const GstTagList *tags, const gchar *tag, gpointe
} }
/* Print information regarding a stream */ /* Print information regarding a stream */
static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) { static void
print_stream_info (GstDiscovererStreamInfo * info, gint depth)
{
gchar *desc = NULL; gchar *desc = NULL;
GstCaps *caps; GstCaps *caps;
const GstTagList *tags; const GstTagList *tags;
@ -43,7 +49,9 @@ static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) {
gst_caps_unref (caps); gst_caps_unref (caps);
} }
g_print ("%*s%s: %s\n", 2 * depth, " ", gst_discoverer_stream_info_get_stream_type_nick (info), (desc ? desc : "")); g_print ("%*s%s: %s\n", 2 * depth, " ",
gst_discoverer_stream_info_get_stream_type_nick (info),
(desc ? desc : ""));
if (desc) { if (desc) {
g_free (desc); g_free (desc);
@ -58,7 +66,9 @@ static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) {
} }
/* Print information regarding a stream and its substreams, if any */ /* Print information regarding a stream and its substreams, if any */
static void print_topology (GstDiscovererStreamInfo *info, gint depth) { static void
print_topology (GstDiscovererStreamInfo * info, gint depth)
{
GstDiscovererStreamInfo *next; GstDiscovererStreamInfo *next;
if (!info) if (!info)
@ -73,7 +83,9 @@ static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
} else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) { } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
GList *tmp, *streams; GList *tmp, *streams;
streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info)); streams =
gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO
(info));
for (tmp = streams; tmp; tmp = tmp->next) { for (tmp = streams; tmp; tmp = tmp->next) {
GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data; GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
print_topology (tmpinf, depth + 1); print_topology (tmpinf, depth + 1);
@ -84,7 +96,10 @@ static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
/* This function is called every time the discoverer has information regarding /* This function is called every time the discoverer has information regarding
* one of the URIs we provided.*/ * one of the URIs we provided.*/
static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) { static void
on_discovered_cb (GstDiscoverer * discoverer, GstDiscovererInfo * info,
GError * err, CustomData * data)
{
GstDiscovererResult result; GstDiscovererResult result;
const gchar *uri; const gchar *uri;
const GstTagList *tags; const GstTagList *tags;
@ -128,7 +143,8 @@ static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info
/* If we got no error, show the retrieved information */ /* If we got no error, show the retrieved information */
g_print ("\nDuration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_discoverer_info_get_duration (info))); g_print ("\nDuration: %" GST_TIME_FORMAT "\n",
GST_TIME_ARGS (gst_discoverer_info_get_duration (info)));
tags = gst_discoverer_info_get_tags (info); tags = gst_discoverer_info_get_tags (info);
if (tags) { if (tags) {
@ -136,7 +152,8 @@ static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info
gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1)); gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
} }
g_print ("Seekable: %s\n", (gst_discoverer_info_get_seekable (info) ? "yes" : "no")); g_print ("Seekable: %s\n",
(gst_discoverer_info_get_seekable (info) ? "yes" : "no"));
g_print ("\n"); g_print ("\n");
@ -155,16 +172,21 @@ static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info
/* This function is called when the discoverer has finished examining /* This function is called when the discoverer has finished examining
* all the URIs we provided.*/ * all the URIs we provided.*/
static void on_finished_cb (GstDiscoverer *discoverer, CustomData *data) { static void
on_finished_cb (GstDiscoverer * discoverer, CustomData * data)
{
g_print ("Finished discovering\n"); g_print ("Finished discovering\n");
g_main_loop_quit (data->loop); g_main_loop_quit (data->loop);
} }
int main (int argc, char **argv) { int
main (int argc, char **argv)
{
CustomData data; CustomData data;
GError *err = NULL; GError *err = NULL;
gchar *uri = "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm"; gchar *uri =
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm";
/* if a URI was provided, use it instead of the default one */ /* if a URI was provided, use it instead of the default one */
if (argc > 1) { if (argc > 1) {
@ -188,8 +210,10 @@ int main (int argc, char **argv) {
} }
/* Connect to the interesting signals */ /* Connect to the interesting signals */
g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data); g_signal_connect (data.discoverer, "discovered",
g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data); G_CALLBACK (on_discovered_cb), &data);
g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb),
&data);
/* Start the discoverer process (nothing to do yet) */ /* Start the discoverer process (nothing to do yet) */
gst_discoverer_start (data.discoverer); gst_discoverer_start (data.discoverer);
@ -213,4 +237,4 @@ int main (int argc, char **argv) {
g_main_loop_unref (data.loop); g_main_loop_unref (data.loop);
return 0; return 0;
} }

View file

@ -2,32 +2,38 @@
#include <gst/gst.h> #include <gst/gst.h>
/* Structure to contain all our information, so we can pass it around */ /* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData { typedef struct _CustomData
GstElement *playbin; /* Our one and only element */ {
GstElement *playbin; /* Our one and only element */
gint n_video; /* Number of embedded video streams */ gint n_video; /* Number of embedded video streams */
gint n_audio; /* Number of embedded audio streams */ gint n_audio; /* Number of embedded audio streams */
gint n_text; /* Number of embedded subtitle streams */ gint n_text; /* Number of embedded subtitle streams */
gint current_video; /* Currently playing video stream */ gint current_video; /* Currently playing video stream */
gint current_audio; /* Currently playing audio stream */ gint current_audio; /* Currently playing audio stream */
gint current_text; /* Currently playing subtitle stream */ gint current_text; /* Currently playing subtitle stream */
GMainLoop *main_loop; /* GLib's Main Loop */ GMainLoop *main_loop; /* GLib's Main Loop */
} CustomData; } CustomData;
/* playbin flags */ /* playbin flags */
typedef enum { typedef enum
GST_PLAY_FLAG_VIDEO = (1 << 0), /* We want video output */ {
GST_PLAY_FLAG_AUDIO = (1 << 1), /* We want audio output */ GST_PLAY_FLAG_VIDEO = (1 << 0), /* We want video output */
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */ GST_PLAY_FLAG_AUDIO = (1 << 1), /* We want audio output */
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */
} GstPlayFlags; } GstPlayFlags;
/* Forward definition for the message and keyboard processing functions */ /* Forward definition for the message and keyboard processing functions */
static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data); static gboolean handle_message (GstBus * bus, GstMessage * msg,
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data); CustomData * data);
static gboolean handle_keyboard (GIOChannel * source, GIOCondition cond,
CustomData * data);
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstBus *bus; GstBus *bus;
GstStateChangeReturn ret; GstStateChangeReturn ret;
@ -46,7 +52,9 @@ int main(int argc, char *argv[]) {
} }
/* Set the URI to play */ /* Set the URI to play */
g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_cropped_multilingual.webm", NULL); g_object_set (data.playbin, "uri",
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_cropped_multilingual.webm",
NULL);
/* Set flags to show Audio and Video but ignore Subtitles */ /* Set flags to show Audio and Video but ignore Subtitles */
g_object_get (data.playbin, "flags", &flags, NULL); g_object_get (data.playbin, "flags", &flags, NULL);
@ -59,7 +67,7 @@ int main(int argc, char *argv[]) {
/* Add a bus watch, so we get notified when a message arrives */ /* Add a bus watch, so we get notified when a message arrives */
bus = gst_element_get_bus (data.playbin); bus = gst_element_get_bus (data.playbin);
gst_bus_add_watch (bus, (GstBusFunc)handle_message, &data); gst_bus_add_watch (bus, (GstBusFunc) handle_message, &data);
/* Add a keyboard watch so we get notified of keystrokes */ /* Add a keyboard watch so we get notified of keystrokes */
#ifdef G_OS_WIN32 #ifdef G_OS_WIN32
@ -67,7 +75,7 @@ int main(int argc, char *argv[]) {
#else #else
io_stdin = g_io_channel_unix_new (fileno (stdin)); io_stdin = g_io_channel_unix_new (fileno (stdin));
#endif #endif
g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data); g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);
/* Start playing */ /* Start playing */
ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING); ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
@ -91,7 +99,9 @@ int main(int argc, char *argv[]) {
} }
/* Extract some metadata from the streams and print it on the screen */ /* Extract some metadata from the streams and print it on the screen */
static void analyze_streams (CustomData *data) { static void
analyze_streams (CustomData * data)
{
gint i; gint i;
GstTagList *tags; GstTagList *tags;
gchar *str; gchar *str;
@ -103,7 +113,7 @@ static void analyze_streams (CustomData *data) {
g_object_get (data->playbin, "n-text", &data->n_text, NULL); g_object_get (data->playbin, "n-text", &data->n_text, NULL);
g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n", g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n",
data->n_video, data->n_audio, data->n_text); data->n_video, data->n_audio, data->n_text);
g_print ("\n"); g_print ("\n");
for (i = 0; i < data->n_video; i++) { for (i = 0; i < data->n_video; i++) {
@ -161,21 +171,27 @@ static void analyze_streams (CustomData *data) {
g_object_get (data->playbin, "current-text", &data->current_text, NULL); g_object_get (data->playbin, "current-text", &data->current_text, NULL);
g_print ("\n"); g_print ("\n");
g_print ("Currently playing video stream %d, audio stream %d and text stream %d\n", g_print
data->current_video, data->current_audio, data->current_text); ("Currently playing video stream %d, audio stream %d and text stream %d\n",
g_print ("Type any number and hit ENTER to select a different audio stream\n"); data->current_video, data->current_audio, data->current_text);
g_print
("Type any number and hit ENTER to select a different audio stream\n");
} }
/* Process messages from GStreamer */ /* Process messages from GStreamer */
static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data) { static gboolean
handle_message (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
@ -184,16 +200,18 @@ static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data)
g_print ("End-Of-Stream reached.\n"); g_print ("End-Of-Stream reached.\n");
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
break; break;
case GST_MESSAGE_STATE_CHANGED: { case GST_MESSAGE_STATE_CHANGED:{
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state,
&pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
if (new_state == GST_STATE_PLAYING) { if (new_state == GST_STATE_PLAYING) {
/* Once we are in the playing state, analyze the streams */ /* Once we are in the playing state, analyze the streams */
analyze_streams (data); analyze_streams (data);
} }
} }
} break; }
break;
default: default:
break; break;
} }
@ -203,10 +221,13 @@ static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data)
} }
/* Process keyboard input */ /* Process keyboard input */
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) { static gboolean
handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
{
gchar *str = NULL; gchar *str = NULL;
if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) { if (g_io_channel_read_line (source, &str, NULL, NULL,
NULL) == G_IO_STATUS_NORMAL) {
int index = g_ascii_strtoull (str, NULL, 0); int index = g_ascii_strtoull (str, NULL, 0);
if (index < 0 || index >= data->n_audio) { if (index < 0 || index >= data->n_audio) {
g_printerr ("Index out of bounds\n"); g_printerr ("Index out of bounds\n");

View file

@ -2,32 +2,38 @@
#include <gst/gst.h> #include <gst/gst.h>
/* Structure to contain all our information, so we can pass it around */ /* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData { typedef struct _CustomData
GstElement *playbin; /* Our one and only element */ {
GstElement *playbin; /* Our one and only element */
gint n_video; /* Number of embedded video streams */ gint n_video; /* Number of embedded video streams */
gint n_audio; /* Number of embedded audio streams */ gint n_audio; /* Number of embedded audio streams */
gint n_text; /* Number of embedded subtitle streams */ gint n_text; /* Number of embedded subtitle streams */
gint current_video; /* Currently playing video stream */ gint current_video; /* Currently playing video stream */
gint current_audio; /* Currently playing audio stream */ gint current_audio; /* Currently playing audio stream */
gint current_text; /* Currently playing subtitle stream */ gint current_text; /* Currently playing subtitle stream */
GMainLoop *main_loop; /* GLib's Main Loop */ GMainLoop *main_loop; /* GLib's Main Loop */
} CustomData; } CustomData;
/* playbin flags */ /* playbin flags */
typedef enum { typedef enum
GST_PLAY_FLAG_VIDEO = (1 << 0), /* We want video output */ {
GST_PLAY_FLAG_AUDIO = (1 << 1), /* We want audio output */ GST_PLAY_FLAG_VIDEO = (1 << 0), /* We want video output */
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */ GST_PLAY_FLAG_AUDIO = (1 << 1), /* We want audio output */
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */
} GstPlayFlags; } GstPlayFlags;
/* Forward definition for the message and keyboard processing functions */ /* Forward definition for the message and keyboard processing functions */
static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data); static gboolean handle_message (GstBus * bus, GstMessage * msg,
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data); CustomData * data);
static gboolean handle_keyboard (GIOChannel * source, GIOCondition cond,
CustomData * data);
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstBus *bus; GstBus *bus;
GstStateChangeReturn ret; GstStateChangeReturn ret;
@ -46,10 +52,14 @@ int main(int argc, char *argv[]) {
} }
/* Set the URI to play */ /* Set the URI to play */
g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.ogv", NULL); g_object_set (data.playbin, "uri",
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.ogv",
NULL);
/* Set the subtitle URI to play and some font description */ /* Set the subtitle URI to play and some font description */
g_object_set (data.playbin, "suburi", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer_gr.srt", NULL); g_object_set (data.playbin, "suburi",
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer_gr.srt",
NULL);
g_object_set (data.playbin, "subtitle-font-desc", "Sans, 18", NULL); g_object_set (data.playbin, "subtitle-font-desc", "Sans, 18", NULL);
/* Set flags to show Audio, Video and Subtitles */ /* Set flags to show Audio, Video and Subtitles */
@ -59,7 +69,7 @@ int main(int argc, char *argv[]) {
/* Add a bus watch, so we get notified when a message arrives */ /* Add a bus watch, so we get notified when a message arrives */
bus = gst_element_get_bus (data.playbin); bus = gst_element_get_bus (data.playbin);
gst_bus_add_watch (bus, (GstBusFunc)handle_message, &data); gst_bus_add_watch (bus, (GstBusFunc) handle_message, &data);
/* Add a keyboard watch so we get notified of keystrokes */ /* Add a keyboard watch so we get notified of keystrokes */
#ifdef G_OS_WIN32 #ifdef G_OS_WIN32
@ -67,7 +77,7 @@ int main(int argc, char *argv[]) {
#else #else
io_stdin = g_io_channel_unix_new (fileno (stdin)); io_stdin = g_io_channel_unix_new (fileno (stdin));
#endif #endif
g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data); g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);
/* Start playing */ /* Start playing */
ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING); ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
@ -91,7 +101,9 @@ int main(int argc, char *argv[]) {
} }
/* Extract some metadata from the streams and print it on the screen */ /* Extract some metadata from the streams and print it on the screen */
static void analyze_streams (CustomData *data) { static void
analyze_streams (CustomData * data)
{
gint i; gint i;
GstTagList *tags; GstTagList *tags;
gchar *str; gchar *str;
@ -103,7 +115,7 @@ static void analyze_streams (CustomData *data) {
g_object_get (data->playbin, "n-text", &data->n_text, NULL); g_object_get (data->playbin, "n-text", &data->n_text, NULL);
g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n", g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n",
data->n_video, data->n_audio, data->n_text); data->n_video, data->n_audio, data->n_text);
g_print ("\n"); g_print ("\n");
for (i = 0; i < data->n_video; i++) { for (i = 0; i < data->n_video; i++) {
@ -163,21 +175,27 @@ static void analyze_streams (CustomData *data) {
g_object_get (data->playbin, "current-text", &data->current_text, NULL); g_object_get (data->playbin, "current-text", &data->current_text, NULL);
g_print ("\n"); g_print ("\n");
g_print ("Currently playing video stream %d, audio stream %d and subtitle stream %d\n", g_print
("Currently playing video stream %d, audio stream %d and subtitle stream %d\n",
data->current_video, data->current_audio, data->current_text); data->current_video, data->current_audio, data->current_text);
g_print ("Type any number and hit ENTER to select a different subtitle stream\n"); g_print
("Type any number and hit ENTER to select a different subtitle stream\n");
} }
/* Process messages from GStreamer */ /* Process messages from GStreamer */
static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data) { static gboolean
handle_message (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
@ -186,16 +204,18 @@ static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data)
g_print ("End-Of-Stream reached.\n"); g_print ("End-Of-Stream reached.\n");
g_main_loop_quit (data->main_loop); g_main_loop_quit (data->main_loop);
break; break;
case GST_MESSAGE_STATE_CHANGED: { case GST_MESSAGE_STATE_CHANGED:{
GstState old_state, new_state, pending_state; GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); gst_message_parse_state_changed (msg, &old_state, &new_state,
&pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) { if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
if (new_state == GST_STATE_PLAYING) { if (new_state == GST_STATE_PLAYING) {
/* Once we are in the playing state, analyze the streams */ /* Once we are in the playing state, analyze the streams */
analyze_streams (data); analyze_streams (data);
} }
} }
} break; }
break;
default: default:
break; break;
} }
@ -205,10 +225,13 @@ static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data)
} }
/* Process keyboard input */ /* Process keyboard input */
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) { static gboolean
handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
{
gchar *str = NULL; gchar *str = NULL;
if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) { if (g_io_channel_read_line (source, &str, NULL, NULL,
NULL) == G_IO_STATUS_NORMAL) {
int index = g_ascii_strtoull (str, NULL, 0); int index = g_ascii_strtoull (str, NULL, 0);
if (index < 0 || index >= data->n_text) { if (index < 0 || index >= data->n_text) {
g_printerr ("Index out of bounds\n"); g_printerr ("Index out of bounds\n");

View file

@ -2,52 +2,57 @@
#include <gst/audio/audio.h> #include <gst/audio/audio.h>
#include <string.h> #include <string.h>
#define CHUNK_SIZE 1024 /* Amount of bytes we are sending in each buffer */ #define CHUNK_SIZE 1024 /* Amount of bytes we are sending in each buffer */
#define SAMPLE_RATE 44100 /* Samples per second we are sending */ #define SAMPLE_RATE 44100 /* Samples per second we are sending */
/* Structure to contain all our information, so we can pass it to callbacks */ /* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData { typedef struct _CustomData
{
GstElement *pipeline; GstElement *pipeline;
GstElement *app_source; GstElement *app_source;
guint64 num_samples; /* Number of samples generated so far (for timestamp generation) */ guint64 num_samples; /* Number of samples generated so far (for timestamp generation) */
gfloat a, b, c, d; /* For waveform generation */ gfloat a, b, c, d; /* For waveform generation */
guint sourceid; /* To control the GSource */ guint sourceid; /* To control the GSource */
GMainLoop *main_loop; /* GLib's Main Loop */ GMainLoop *main_loop; /* GLib's Main Loop */
} CustomData; } CustomData;
/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc. /* This method is called by the idle GSource in the mainloop, to 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) * 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). * and is removed when appsrc has enough data (enough-data signal).
*/ */
static gboolean push_data (CustomData *data) { static gboolean
push_data (CustomData * data)
{
GstBuffer *buffer; GstBuffer *buffer;
GstFlowReturn ret; GstFlowReturn ret;
int i; int i;
GstMapInfo map; GstMapInfo map;
gint16 *raw; gint16 *raw;
gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */ gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
gfloat freq; gfloat freq;
/* Create a new empty buffer */ /* Create a new empty buffer */
buffer = gst_buffer_new_and_alloc (CHUNK_SIZE); buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);
/* Set its timestamp and duration */ /* Set its timestamp and duration */
GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE); GST_BUFFER_TIMESTAMP (buffer) =
GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE); gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
GST_BUFFER_DURATION (buffer) =
gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);
/* Generate some psychodelic waveforms */ /* Generate some psychodelic waveforms */
gst_buffer_map (buffer, &map, GST_MAP_WRITE); gst_buffer_map (buffer, &map, GST_MAP_WRITE);
raw = (gint16 *)map.data; raw = (gint16 *) map.data;
data->c += data->d; data->c += data->d;
data->d -= data->c / 1000; data->d -= data->c / 1000;
freq = 1100 + 1000 * data->d; freq = 1100 + 1000 * data->d;
for (i = 0; i < num_samples; i++) { for (i = 0; i < num_samples; i++) {
data->a += data->b; data->a += data->b;
data->b -= data->a / freq; data->b -= data->a / freq;
raw[i] = (gint16)(500 * data->a); raw[i] = (gint16) (500 * data->a);
} }
gst_buffer_unmap (buffer, &map); gst_buffer_unmap (buffer, &map);
data->num_samples += num_samples; data->num_samples += num_samples;
@ -68,7 +73,9 @@ static gboolean push_data (CustomData *data) {
/* This signal callback triggers when appsrc needs data. Here, we add an idle handler /* This signal callback triggers when appsrc needs data. Here, we add an idle handler
* to the mainloop to start pushing data into the appsrc */ * to the mainloop to start pushing data into the appsrc */
static void start_feed (GstElement *source, guint size, CustomData *data) { static void
start_feed (GstElement * source, guint size, CustomData * data)
{
if (data->sourceid == 0) { if (data->sourceid == 0) {
g_print ("Start feeding\n"); g_print ("Start feeding\n");
data->sourceid = g_idle_add ((GSourceFunc) push_data, data); data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
@ -77,7 +84,9 @@ static void start_feed (GstElement *source, guint size, CustomData *data) {
/* This callback triggers when appsrc has enough data and we can stop sending. /* This callback triggers when appsrc has enough data and we can stop sending.
* We remove the idle handler from the mainloop */ * We remove the idle handler from the mainloop */
static void stop_feed (GstElement *source, CustomData *data) { static void
stop_feed (GstElement * source, CustomData * data)
{
if (data->sourceid != 0) { if (data->sourceid != 0) {
g_print ("Stop feeding\n"); g_print ("Stop feeding\n");
g_source_remove (data->sourceid); g_source_remove (data->sourceid);
@ -86,13 +95,16 @@ static void stop_feed (GstElement *source, CustomData *data) {
} }
/* This function is called when an error message is posted on the bus */ /* This function is called when an error message is posted on the bus */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) { static void
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err; GError *err;
gchar *debug_info; gchar *debug_info;
/* Print error details on the screen */ /* Print error details on the screen */
gst_message_parse_error (msg, &err, &debug_info); gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Error received from element %s: %s\n",
GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
g_clear_error (&err); g_clear_error (&err);
g_free (debug_info); g_free (debug_info);
@ -102,7 +114,9 @@ static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
/* This function is called when playbin has created the appsrc element, so we have /* This function is called when playbin has created the appsrc element, so we have
* a chance to configure it. */ * a chance to configure it. */
static void source_setup (GstElement *pipeline, GstElement *source, CustomData *data) { static void
source_setup (GstElement * pipeline, GstElement * source, CustomData * data)
{
GstAudioInfo info; GstAudioInfo info;
GstCaps *audio_caps; GstCaps *audio_caps;
@ -118,13 +132,15 @@ static void source_setup (GstElement *pipeline, GstElement *source, CustomData *
gst_caps_unref (audio_caps); gst_caps_unref (audio_caps);
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstBus *bus; GstBus *bus;
/* Initialize cumstom data structure */ /* Initialize cumstom data structure */
memset (&data, 0, sizeof (data)); memset (&data, 0, sizeof (data));
data.b = 1; /* For waveform generation */ data.b = 1; /* For waveform generation */
data.d = 1; data.d = 1;
/* Initialize GStreamer */ /* Initialize GStreamer */
@ -132,12 +148,14 @@ int main(int argc, char *argv[]) {
/* Create the playbin element */ /* Create the playbin element */
data.pipeline = gst_parse_launch ("playbin uri=appsrc://", NULL); data.pipeline = gst_parse_launch ("playbin uri=appsrc://", NULL);
g_signal_connect (data.pipeline, "source-setup", G_CALLBACK (source_setup), &data); g_signal_connect (data.pipeline, "source-setup", G_CALLBACK (source_setup),
&data);
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data.pipeline); bus = gst_element_get_bus (data.pipeline);
gst_bus_add_signal_watch (bus); gst_bus_add_signal_watch (bus);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
&data);
gst_object_unref (bus); gst_object_unref (bus);
/* Start playing the pipeline */ /* Start playing the pipeline */

View file

@ -4,18 +4,23 @@
#define GRAPH_LENGTH 78 #define GRAPH_LENGTH 78
/* playbin flags */ /* playbin flags */
typedef enum { typedef enum
GST_PLAY_FLAG_DOWNLOAD = (1 << 7) /* Enable progressive download (on selected formats) */ {
GST_PLAY_FLAG_DOWNLOAD = (1 << 7) /* Enable progressive download (on selected formats) */
} GstPlayFlags; } GstPlayFlags;
typedef struct _CustomData { typedef struct _CustomData
{
gboolean is_live; gboolean is_live;
GstElement *pipeline; GstElement *pipeline;
GMainLoop *loop; GMainLoop *loop;
gint buffering_level; gint buffering_level;
} CustomData; } CustomData;
static void got_location (GstObject *gstobject, GstObject *prop_object, GParamSpec *prop, gpointer data) { static void
got_location (GstObject * gstobject, GstObject * prop_object, GParamSpec * prop,
gpointer data)
{
gchar *location; gchar *location;
g_object_get (G_OBJECT (prop_object), "temp-location", &location, NULL); g_object_get (G_OBJECT (prop_object), "temp-location", &location, NULL);
g_print ("Temporary file: %s\n", location); g_print ("Temporary file: %s\n", location);
@ -24,10 +29,12 @@ static void got_location (GstObject *gstobject, GstObject *prop_object, GParamSp
/* g_object_set (G_OBJECT (prop_object), "temp-remove", FALSE, NULL); */ /* g_object_set (G_OBJECT (prop_object), "temp-remove", FALSE, NULL); */
} }
static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) { static void
cb_message (GstBus * bus, GstMessage * msg, CustomData * data)
{
switch (GST_MESSAGE_TYPE (msg)) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: { case GST_MESSAGE_ERROR:{
GError *err; GError *err;
gchar *debug; gchar *debug;
@ -47,7 +54,8 @@ static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {
break; break;
case GST_MESSAGE_BUFFERING: case GST_MESSAGE_BUFFERING:
/* If the stream is live, we do not care about buffering. */ /* If the stream is live, we do not care about buffering. */
if (data->is_live) break; if (data->is_live)
break;
gst_message_parse_buffering (msg, &data->buffering_level); gst_message_parse_buffering (msg, &data->buffering_level);
@ -65,10 +73,12 @@ static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {
default: default:
/* Unhandled message */ /* Unhandled message */
break; break;
} }
} }
static gboolean refresh_ui (CustomData *data) { static gboolean
refresh_ui (CustomData * data)
{
GstQuery *query; GstQuery *query;
gboolean result; gboolean result;
@ -88,15 +98,15 @@ static gboolean refresh_ui (CustomData *data) {
gst_query_parse_nth_buffering_range (query, range, &start, &stop); gst_query_parse_nth_buffering_range (query, range, &start, &stop);
start = start * GRAPH_LENGTH / (stop - start); start = start * GRAPH_LENGTH / (stop - start);
stop = stop * GRAPH_LENGTH / (stop - start); stop = stop * GRAPH_LENGTH / (stop - start);
for (i = (gint)start; i < stop; i++) for (i = (gint) start; i < stop; i++)
graph [i] = '-'; graph[i] = '-';
} }
if (gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position) && if (gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)
GST_CLOCK_TIME_IS_VALID (position) && && GST_CLOCK_TIME_IS_VALID (position)
gst_element_query_duration (data->pipeline, GST_FORMAT_TIME, &duration) && && gst_element_query_duration (data->pipeline, GST_FORMAT_TIME,
GST_CLOCK_TIME_IS_VALID (duration)) { &duration) && GST_CLOCK_TIME_IS_VALID (duration)) {
i = (gint)(GRAPH_LENGTH * (double)position / (double)(duration + 1)); i = (gint) (GRAPH_LENGTH * (double) position / (double) (duration + 1));
graph [i] = data->buffering_level < 100 ? 'X' : '>'; graph[i] = data->buffering_level < 100 ? 'X' : '>';
} }
g_print ("[%s]", graph); g_print ("[%s]", graph);
if (data->buffering_level < 100) { if (data->buffering_level < 100) {
@ -111,7 +121,9 @@ static gboolean refresh_ui (CustomData *data) {
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
GstElement *pipeline; GstElement *pipeline;
GstBus *bus; GstBus *bus;
GstStateChangeReturn ret; GstStateChangeReturn ret;
@ -127,7 +139,10 @@ int main(int argc, char *argv[]) {
data.buffering_level = 100; data.buffering_level = 100;
/* Build the pipeline */ /* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
bus = gst_element_get_bus (pipeline); bus = gst_element_get_bus (pipeline);
/* Set the download flag */ /* Set the download flag */
@ -154,10 +169,11 @@ int main(int argc, char *argv[]) {
gst_bus_add_signal_watch (bus); gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data); g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data);
g_signal_connect (pipeline, "deep-notify::temp-location", G_CALLBACK (got_location), NULL); g_signal_connect (pipeline, "deep-notify::temp-location",
G_CALLBACK (got_location), NULL);
/* Register a function that GLib will call every second */ /* Register a function that GLib will call every second */
g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data); g_timeout_add_seconds (1, (GSourceFunc) refresh_ui, &data);
g_main_loop_run (main_loop); g_main_loop_run (main_loop);

View file

@ -3,13 +3,17 @@
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/video/colorbalance.h> #include <gst/video/colorbalance.h>
typedef struct _CustomData { typedef struct _CustomData
{
GstElement *pipeline; GstElement *pipeline;
GMainLoop *loop; GMainLoop *loop;
} CustomData; } CustomData;
/* Process a color balance command */ /* Process a color balance command */
static void update_color_channel (const gchar *channel_name, gboolean increase, GstColorBalance *cb) { static void
update_color_channel (const gchar * channel_name, gboolean increase,
GstColorBalance * cb)
{
gdouble step; gdouble step;
gint value; gint value;
GstColorBalanceChannel *channel = NULL; GstColorBalanceChannel *channel = NULL;
@ -18,7 +22,7 @@ static void update_color_channel (const gchar *channel_name, gboolean increase,
/* Retrieve the list of channels and locate the requested one */ /* Retrieve the list of channels and locate the requested one */
channels = gst_color_balance_list_channels (cb); channels = gst_color_balance_list_channels (cb);
for (l = channels; l != NULL; l = l->next) { for (l = channels; l != NULL; l = l->next) {
GstColorBalanceChannel *tmp = (GstColorBalanceChannel *)l->data; GstColorBalanceChannel *tmp = (GstColorBalanceChannel *) l->data;
if (g_strrstr (tmp->label, channel_name)) { if (g_strrstr (tmp->label, channel_name)) {
channel = tmp; channel = tmp;
@ -32,11 +36,11 @@ static void update_color_channel (const gchar *channel_name, gboolean increase,
step = 0.1 * (channel->max_value - channel->min_value); step = 0.1 * (channel->max_value - channel->min_value);
value = gst_color_balance_get_value (cb, channel); value = gst_color_balance_get_value (cb, channel);
if (increase) { if (increase) {
value = (gint)(value + step); value = (gint) (value + step);
if (value > channel->max_value) if (value > channel->max_value)
value = channel->max_value; value = channel->max_value;
} else { } else {
value = (gint)(value - step); value = (gint) (value - step);
if (value < channel->min_value) if (value < channel->min_value)
value = channel->min_value; value = channel->min_value;
} }
@ -44,46 +48,57 @@ static void update_color_channel (const gchar *channel_name, gboolean increase,
} }
/* Output the current values of all Color Balance channels */ /* Output the current values of all Color Balance channels */
static void print_current_values (GstElement *pipeline) { static void
print_current_values (GstElement * pipeline)
{
const GList *channels, *l; const GList *channels, *l;
/* Output Color Balance values */ /* Output Color Balance values */
channels = gst_color_balance_list_channels (GST_COLOR_BALANCE (pipeline)); channels = gst_color_balance_list_channels (GST_COLOR_BALANCE (pipeline));
for (l = channels; l != NULL; l = l->next) { for (l = channels; l != NULL; l = l->next) {
GstColorBalanceChannel *channel = (GstColorBalanceChannel *)l->data; GstColorBalanceChannel *channel = (GstColorBalanceChannel *) l->data;
gint value = gst_color_balance_get_value (GST_COLOR_BALANCE (pipeline), channel); gint value =
gst_color_balance_get_value (GST_COLOR_BALANCE (pipeline), channel);
g_print ("%s: %3d%% ", channel->label, g_print ("%s: %3d%% ", channel->label,
100 * (value - channel->min_value) / (channel->max_value - channel->min_value)); 100 * (value - channel->min_value) / (channel->max_value -
channel->min_value));
} }
g_print ("\n"); g_print ("\n");
} }
/* Process keyboard input */ /* Process keyboard input */
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) { static gboolean
handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
{
gchar *str = NULL; gchar *str = NULL;
if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) { if (g_io_channel_read_line (source, &str, NULL, NULL,
NULL) != G_IO_STATUS_NORMAL) {
return TRUE; return TRUE;
} }
switch (g_ascii_tolower (str[0])) { switch (g_ascii_tolower (str[0])) {
case 'c': case 'c':
update_color_channel ("CONTRAST", g_ascii_isupper (str[0]), GST_COLOR_BALANCE (data->pipeline)); update_color_channel ("CONTRAST", g_ascii_isupper (str[0]),
break; GST_COLOR_BALANCE (data->pipeline));
case 'b': break;
update_color_channel ("BRIGHTNESS", g_ascii_isupper (str[0]), GST_COLOR_BALANCE (data->pipeline)); case 'b':
break; update_color_channel ("BRIGHTNESS", g_ascii_isupper (str[0]),
case 'h': GST_COLOR_BALANCE (data->pipeline));
update_color_channel ("HUE", g_ascii_isupper (str[0]), GST_COLOR_BALANCE (data->pipeline)); break;
break; case 'h':
case 's': update_color_channel ("HUE", g_ascii_isupper (str[0]),
update_color_channel ("SATURATION", g_ascii_isupper (str[0]), GST_COLOR_BALANCE (data->pipeline)); GST_COLOR_BALANCE (data->pipeline));
break; break;
case 'q': case 's':
g_main_loop_quit (data->loop); update_color_channel ("SATURATION", g_ascii_isupper (str[0]),
break; GST_COLOR_BALANCE (data->pipeline));
default: break;
break; case 'q':
g_main_loop_quit (data->loop);
break;
default:
break;
} }
g_free (str); g_free (str);
@ -93,7 +108,9 @@ static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomDa
return TRUE; return TRUE;
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
CustomData data; CustomData data;
GstStateChangeReturn ret; GstStateChangeReturn ret;
GIOChannel *io_stdin; GIOChannel *io_stdin;
@ -105,16 +122,18 @@ int main(int argc, char *argv[]) {
memset (&data, 0, sizeof (data)); memset (&data, 0, sizeof (data));
/* Print usage map */ /* Print usage map */
g_print ( g_print ("USAGE: Choose one of the following options, then press enter:\n"
"USAGE: Choose one of the following options, then press enter:\n" " 'C' to increase contrast, 'c' to decrease contrast\n"
" 'C' to increase contrast, 'c' to decrease contrast\n" " 'B' to increase brightness, 'b' to decrease brightness\n"
" 'B' to increase brightness, 'b' to decrease brightness\n" " 'H' to increase hue, 'h' to decrease hue\n"
" 'H' to increase hue, 'h' to decrease hue\n" " 'S' to increase saturation, 's' to decrease saturation\n"
" 'S' to increase saturation, 's' to decrease saturation\n" " 'Q' to quit\n");
" 'Q' to quit\n");
/* Build the pipeline */ /* Build the pipeline */
data.pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); data.pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Add a keyboard watch so we get notified of keystrokes */ /* Add a keyboard watch so we get notified of keystrokes */
#ifdef G_OS_WIN32 #ifdef G_OS_WIN32
@ -122,7 +141,7 @@ int main(int argc, char *argv[]) {
#else #else
io_stdin = g_io_channel_unix_new (fileno (stdin)); io_stdin = g_io_channel_unix_new (fileno (stdin));
#endif #endif
g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data); g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);
/* Start playing */ /* Start playing */
ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING); ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);

View file

@ -1,12 +1,15 @@
#include <gst/gst.h> #include <gst/gst.h>
/* playbin2 flags */ /* playbin2 flags */
typedef enum { typedef enum
GST_PLAY_FLAG_VIS = (1 << 3) /* Enable rendering of visualizations when there is no video stream. */ {
GST_PLAY_FLAG_VIS = (1 << 3) /* Enable rendering of visualizations when there is no video stream. */
} GstPlayFlags; } GstPlayFlags;
/* Return TRUE if this is a Visualization element */ /* Return TRUE if this is a Visualization element */
static gboolean filter_vis_features (GstPluginFeature *feature, gpointer data) { static gboolean
filter_vis_features (GstPluginFeature * feature, gpointer data)
{
GstElementFactory *factory; GstElementFactory *factory;
if (!GST_IS_ELEMENT_FACTORY (feature)) if (!GST_IS_ELEMENT_FACTORY (feature))
@ -18,7 +21,9 @@ static gboolean filter_vis_features (GstPluginFeature *feature, gpointer data) {
return TRUE; return TRUE;
} }
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
GstElement *pipeline, *vis_plugin; GstElement *pipeline, *vis_plugin;
GstBus *bus; GstBus *bus;
GstMessage *msg; GstMessage *msg;
@ -30,17 +35,19 @@ int main(int argc, char *argv[]) {
gst_init (&argc, &argv); gst_init (&argc, &argv);
/* Get a list of all visualization plugins */ /* Get a list of all visualization plugins */
list = gst_registry_feature_filter (gst_registry_get (), filter_vis_features, FALSE, NULL); list =
gst_registry_feature_filter (gst_registry_get (), filter_vis_features,
FALSE, NULL);
/* Print their names */ /* Print their names */
g_print("Available visualization plugins:\n"); g_print ("Available visualization plugins:\n");
for (walk = list; walk != NULL; walk = g_list_next (walk)) { for (walk = list; walk != NULL; walk = g_list_next (walk)) {
const gchar *name; const gchar *name;
GstElementFactory *factory; GstElementFactory *factory;
factory = GST_ELEMENT_FACTORY (walk->data); factory = GST_ELEMENT_FACTORY (walk->data);
name = gst_element_factory_get_longname (factory); name = gst_element_factory_get_longname (factory);
g_print(" %s\n", name); g_print (" %s\n", name);
if (selected_factory == NULL || g_str_has_prefix (name, "GOOM")) { if (selected_factory == NULL || g_str_has_prefix (name, "GOOM")) {
selected_factory = factory; selected_factory = factory;
@ -55,13 +62,16 @@ int main(int argc, char *argv[]) {
} }
/* We have now selected a factory for the visualization element */ /* We have now selected a factory for the visualization element */
g_print ("Selected '%s'\n", gst_element_factory_get_longname (selected_factory)); g_print ("Selected '%s'\n",
gst_element_factory_get_longname (selected_factory));
vis_plugin = gst_element_factory_create (selected_factory, NULL); vis_plugin = gst_element_factory_create (selected_factory, NULL);
if (!vis_plugin) if (!vis_plugin)
return -1; return -1;
/* Build the pipeline */ /* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=http://radio.hbr1.com:19800/ambient.ogg", NULL); pipeline =
gst_parse_launch ("playbin uri=http://radio.hbr1.com:19800/ambient.ogg",
NULL);
/* Set the visualization flag */ /* Set the visualization flag */
g_object_get (pipeline, "flags", &flags, NULL); g_object_get (pipeline, "flags", &flags, NULL);
@ -76,7 +86,9 @@ int main(int argc, char *argv[]) {
/* Wait until error or EOS */ /* Wait until error or EOS */
bus = gst_element_get_bus (pipeline); bus = gst_element_get_bus (pipeline);
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS); msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Free resources */ /* Free resources */
if (msg != NULL) if (msg != NULL)

View file

@ -1,6 +1,8 @@
#include <gst/gst.h> #include <gst/gst.h>
int main(int argc, char *argv[]) { int
main (int argc, char *argv[])
{
GstElement *pipeline, *bin, *equalizer, *convert, *sink; GstElement *pipeline, *bin, *equalizer, *convert, *sink;
GstPad *pad, *ghost_pad; GstPad *pad, *ghost_pad;
GstBus *bus; GstBus *bus;
@ -10,7 +12,10 @@ int main(int argc, char *argv[]) {
gst_init (&argc, &argv); gst_init (&argc, &argv);
/* Build the pipeline */ /* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Create the elements inside the sink bin */ /* Create the elements inside the sink bin */
equalizer = gst_element_factory_make ("equalizer-3bands", "equalizer"); equalizer = gst_element_factory_make ("equalizer-3bands", "equalizer");
@ -32,8 +37,8 @@ int main(int argc, char *argv[]) {
gst_object_unref (pad); gst_object_unref (pad);
/* Configure the equalizer */ /* Configure the equalizer */
g_object_set (G_OBJECT (equalizer), "band1", (gdouble)-24.0, NULL); g_object_set (G_OBJECT (equalizer), "band1", (gdouble) - 24.0, NULL);
g_object_set (G_OBJECT (equalizer), "band2", (gdouble)-24.0, NULL); g_object_set (G_OBJECT (equalizer), "band2", (gdouble) - 24.0, NULL);
/* Set playbin2's audio sink to be our sink bin */ /* Set playbin2's audio sink to be our sink bin */
g_object_set (GST_OBJECT (pipeline), "audio-sink", bin, NULL); g_object_set (GST_OBJECT (pipeline), "audio-sink", bin, NULL);
@ -43,7 +48,9 @@ int main(int argc, char *argv[]) {
/* Wait until error or EOS */ /* Wait until error or EOS */
bus = gst_element_get_bus (pipeline); bus = gst_element_get_bus (pipeline);
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS); msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Free resources */ /* Free resources */
if (msg != NULL) if (msg != NULL)