1 # Android tutorial 2: A running pipeline
7 The tutorials seen in the [Basic](tutorials/basic/index.md) and
8 [Playback](tutorials/playback/index.md) sections are intended for Desktop
9 platforms and, therefore, their main thread is allowed to block (using
10 `gst_bus_pop_filtered()`) or relinquish control to a GLib main loop. On
11 Android this would lead to the application being tagged as
12 non-responsive and probably closed.
14 This tutorial shows how to overcome this problem. In particular, we will
17 - How to move the native code to its own thread
18 - How to allow threads created from C code to communicate with Java
19 - How to access Java code from C
20 - How to allocate a `CustomData` structure from C and have Java host
25 When using a Graphical User Interface (UI), if the application waits for
26 GStreamer calls to complete the user experience will suffer. The usual
27 approach, with the [GTK+ toolkit](http://www.gtk.org) for example, is to
28 relinquish control to a GLib `GMainLoop` and let it control the events
29 coming from the UI or GStreamer.
31 This approach can be very cumbersome when GStreamer and the Android UI
32 communicate through the JNI interface, so we take a cleaner route: We
33 use a GLib main loop, and move it to its own thread, so it does not
34 block the application. This simplifies the GStreamer-Android
35 integration, and we only need to worry about a few inter-process
36 synchronization bits, which are detailed in this tutorial.
38 Additionally, this tutorial shows how to obtain, from any thread, the
40 pointer](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp16696)
41 required to make JNI calls. This is necessary, for example, to call Java
42 code from callbacks in threads spawned deep within GStreamer, which
43 never received this pointer directly.
45 Finally, this tutorial explains how to call Java methods from native C
46 code, which involves locating the desired method’s ID in the class.
47 These IDs never change, so they are cached as global variables in the C
48 code and obtained in the static initializer of the class.
50 The code below builds a pipeline with an `audiotestsrc` and an
51 `autoaudiosink` (it plays an audible tone). Two buttons in the UI allow
52 setting the pipeline to PLAYING or PAUSED. A TextView in the UI shows
53 messages sent from the C code (for errors and state changes).
55 ### A pipeline on Android \[Java code\]
57 **src/org/freedesktop/gstreamer/tutorials/tutorial\_2/Tutorial2.java**
60 package org.freedesktop.gstreamer.tutorials.tutorial_2;
62 import android.app.Activity;
63 import android.os.Bundle;
64 import android.util.Log;
65 import android.view.View;
66 import android.view.View.OnClickListener;
67 import android.widget.ImageButton;
68 import android.widget.TextView;
69 import android.widget.Toast;
71 import org.freedesktop.gstreamer.GStreamer;
73 public class Tutorial2 extends Activity {
74 private native void nativeInit(); // Initialize native code, build pipeline, etc
75 private native void nativeFinalize(); // Destroy pipeline and shutdown native code
76 private native void nativePlay(); // Set pipeline to PLAYING
77 private native void nativePause(); // Set pipeline to PAUSED
78 private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
79 private long native_custom_data; // Native code will use this to keep private data
81 private boolean is_playing_desired; // Whether the user asked to go to PLAYING
83 // Called when the activity is first created.
85 public void onCreate(Bundle savedInstanceState)
87 super.onCreate(savedInstanceState);
89 // Initialize GStreamer and warn if it fails
92 } catch (Exception e) {
93 Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
98 setContentView(R.layout.main);
100 ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
101 play.setOnClickListener(new OnClickListener() {
102 public void onClick(View v) {
103 is_playing_desired = true;
108 ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop);
109 pause.setOnClickListener(new OnClickListener() {
110 public void onClick(View v) {
111 is_playing_desired = false;
116 if (savedInstanceState != null) {
117 is_playing_desired = savedInstanceState.getBoolean("playing");
118 Log.i ("GStreamer", "Activity created. Saved state is playing:" + is_playing_desired);
120 is_playing_desired = false;
121 Log.i ("GStreamer", "Activity created. There is no saved state, playing: false");
124 // Start with disabled buttons, until native code is initialized
125 this.findViewById(R.id.button_play).setEnabled(false);
126 this.findViewById(R.id.button_stop).setEnabled(false);
131 protected void onSaveInstanceState (Bundle outState) {
132 Log.d ("GStreamer", "Saving state, playing:" + is_playing_desired);
133 outState.putBoolean("playing", is_playing_desired);
136 protected void onDestroy() {
141 // Called from native code. This sets the content of the TextView from the UI thread.
142 private void setMessage(final String message) {
143 final TextView tv = (TextView) this.findViewById(R.id.textview_message);
144 runOnUiThread (new Runnable() {
151 // Called from native code. Native code calls this once it has created its pipeline and
152 // the main loop is running, so it is ready to accept commands.
153 private void onGStreamerInitialized () {
154 Log.i ("GStreamer", "Gst initialized. Restoring state, playing:" + is_playing_desired);
155 // Restore previous playing state
156 if (is_playing_desired) {
162 // Re-enable buttons, now that GStreamer is initialized
163 final Activity activity = this;
164 runOnUiThread(new Runnable() {
166 activity.findViewById(R.id.button_play).setEnabled(true);
167 activity.findViewById(R.id.button_stop).setEnabled(true);
173 System.loadLibrary("gstreamer_android");
174 System.loadLibrary("tutorial-2");
181 As usual, the first bit that gets executed is the static initializer of
186 System.loadLibrary("gstreamer_android");
187 System.loadLibrary("tutorial-2");
192 As explained in the previous tutorial, the two native libraries are
193 loaded and their `JNI_OnLoad()` methods are executed. Here, we also call
194 the native method `nativeClassInit()`, previously declared with the
195 `native` keyword in line 19. We will later see what its purpose is
197 In the `onCreate()` method GStreamer is initialized as in the previous
198 tutorial with `GStreamer.init(this)`, and then the layout is inflated
199 and listeners are setup for the two UI buttons:
202 ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
203 play.setOnClickListener(new OnClickListener() {
204 public void onClick(View v) {
205 is_playing_desired = true;
209 ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop);
210 pause.setOnClickListener(new OnClickListener() {
211 public void onClick(View v) {
212 is_playing_desired = false;
218 Each button instructs the native code to set the pipeline to the desired
219 state, and also remembers this state in the
220 `is_playing_desired` variable. This is required so, when the
221 application is restarted (for example, due to an orientation change), it
222 can set the pipeline again to the desired state. This approach is easier
223 and safer than tracking the actual pipeline state, because orientation
224 changes can happen before the pipeline has moved to the desired state,
228 if (savedInstanceState != null) {
229 is_playing_desired = savedInstanceState.getBoolean("playing");
230 Log.i ("GStreamer", "Activity created. Saved state is playing:" + is_playing_desired);
232 is_playing_desired = false;
233 Log.i ("GStreamer", "Activity created. There is no saved state, playing: false");
237 Restore the previous playing state (if any) from `savedInstanceState`.
238 We will first build the GStreamer pipeline (below) and only when the
239 native code reports itself as initialized we will use
240 `is_playing_desired`.
246 As will be shown in the C code, `nativeInit()` creates a dedicated
247 thread, a GStreamer pipeline, a GLib main loop, and, right before
248 calling `g_main_loop_run()` and going to sleep, it warns the Java code
249 that the native code is initialized and ready to accept commands.
251 This finishes the `onCreate()` method and the Java initialization. The
252 UI buttons are disabled, so nothing will happen until native code is
253 ready and `onGStreamerInitialized()` is called:
256 private void onGStreamerInitialized () {
257 Log.i ("GStreamer", "Gst initialized. Restoring state, playing:" + is_playing_desired);
260 This is called by the native code when its main loop is finally running.
261 We first retrieve the desired playing state from `is_playing_desired`,
262 and then set that state:
265 // Restore previous playing state
266 if (is_playing_desired) {
273 Here comes the first caveat, when re-enabling the UI buttons:
276 // Re-enable buttons, now that GStreamer is initialized
277 final Activity activity = this;
278 runOnUiThread(new Runnable() {
280 activity.findViewById(R.id.button_play).setEnabled(true);
281 activity.findViewById(R.id.button_stop).setEnabled(true);
286 This method is being called from the thread that the native code created
287 to run its main loop, and is not allowed to issue UI-altering commands:
288 Only the UI thread can do that. The solution is easy though: Android
289 Activities have a handy
290 [runOnUiThread()](http://developer.android.com/reference/android/app/Activity.html#runOnUiThread\(java.lang.Runnable\))
291 method which lets bits of code to be executed from the correct thread. A
292 [Runnable](http://developer.android.com/reference/java/lang/Runnable.html)
293 instance has to be constructed and any parameter can be passed either by
295 [Runnable](http://developer.android.com/reference/java/lang/Runnable.html)
296 and adding a dedicated constructor, or by using the `final` modifier, as
297 shown in the above snippet.
299 The same problem exists when the native code wants to output a string in
300 our TextView using the `setMessage()` method: it has to be done from the
301 UI thread. The solution is the same:
304 private void setMessage(final String message) {
305 final TextView tv = (TextView) this.findViewById(R.id.textview_message);
306 runOnUiThread (new Runnable() {
314 Finally, a few remaining bits:
317 protected void onSaveInstanceState (Bundle outState) {
318 Log.d ("GStreamer", "Saving state, playing:" + is_playing_desired);
319 outState.putBoolean("playing", is_playing_desired);
323 This method stores the currently desired playing state when Android is
324 about to shut us down, so next time it restarts (after an orientation
325 change, for example), it can restore the same state.
328 protected void onDestroy() {
334 And this is called before Android destroys our application. We call the
335 `nativeFinalize()`method to exit the main loop, destroy its thread and
336 all allocated resources.
338 This concludes the UI part of the tutorial.
340 ### A pipeline on Android \[C code\]
347 #include <android/log.h>
351 GST_DEBUG_CATEGORY_STATIC (debug_category);
352 #define GST_CAT_DEFAULT debug_category
355 * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into
356 * a jlong, which is always 64 bits, without warnings.
358 #if GLIB_SIZEOF_VOID_P == 8
359 ## define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID)
360 ## define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data)
362 ## define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID)
363 ## define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data)
366 /* Structure to contain all our information, so we can pass it to callbacks */
367 typedef struct _CustomData {
368 jobject app; /* Application instance, used to call its methods. A global reference is kept. */
369 GstElement *pipeline; /* The running pipeline */
370 GMainContext *context; /* GLib context used to run the main loop */
371 GMainLoop *main_loop; /* GLib main loop */
372 gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
375 /* These global variables cache values which are not changing during execution */
376 static pthread_t gst_app_thread;
377 static pthread_key_t current_jni_env;
378 static JavaVM *java_vm;
379 static jfieldID custom_data_field_id;
380 static jmethodID set_message_method_id;
381 static jmethodID on_gstreamer_initialized_method_id;
387 /* Register this thread with the VM */
388 static JNIEnv *attach_current_thread (void) {
390 JavaVMAttachArgs args;
392 GST_DEBUG ("Attaching thread %p", g_thread_self ());
393 args.version = JNI_VERSION_1_4;
397 if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
398 GST_ERROR ("Failed to attach current thread");
405 /* Unregister this thread from the VM */
406 static void detach_current_thread (void *env) {
407 GST_DEBUG ("Detaching thread %p", g_thread_self ());
408 (*java_vm)->DetachCurrentThread (java_vm);
411 /* Retrieve the JNI environment for this thread */
412 static JNIEnv *get_jni_env (void) {
415 if ((env = pthread_getspecific (current_jni_env)) == NULL) {
416 env = attach_current_thread ();
417 pthread_setspecific (current_jni_env, env);
423 /* Change the content of the UI's TextView */
424 static void set_ui_message (const gchar *message, CustomData *data) {
425 JNIEnv *env = get_jni_env ();
426 GST_DEBUG ("Setting message to: %s", message);
427 jstring jmessage = (*env)->NewStringUTF(env, message);
428 (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
429 if ((*env)->ExceptionCheck (env)) {
430 GST_ERROR ("Failed to call Java method");
431 (*env)->ExceptionClear (env);
433 (*env)->DeleteLocalRef (env, jmessage);
436 /* Retrieve errors from the bus and show them on the UI */
437 static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
440 gchar *message_string;
442 gst_message_parse_error (msg, &err, &debug_info);
443 message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message);
444 g_clear_error (&err);
446 set_ui_message (message_string, data);
447 g_free (message_string);
448 gst_element_set_state (data->pipeline, GST_STATE_NULL);
451 /* Notify UI about pipeline state changes */
452 static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
453 GstState old_state, new_state, pending_state;
454 gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
455 /* Only pay attention to messages coming from the pipeline, not its children */
456 if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
457 gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state));
458 set_ui_message(message, data);
463 /* Check if all conditions are met to report GStreamer as initialized.
464 * These conditions will change depending on the application */
465 static void check_initialization_complete (CustomData *data) {
466 JNIEnv *env = get_jni_env ();
467 if (!data->initialized && data->main_loop) {
468 GST_DEBUG ("Initialization complete, notifying application. main_loop:%p", data->main_loop);
469 (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
470 if ((*env)->ExceptionCheck (env)) {
471 GST_ERROR ("Failed to call Java method");
472 (*env)->ExceptionClear (env);
474 data->initialized = TRUE;
478 /* Main method for the native code. This is executed on its own thread. */
479 static void *app_function (void *userdata) {
480 JavaVMAttachArgs args;
482 CustomData *data = (CustomData *)userdata;
484 GError *error = NULL;
486 GST_DEBUG ("Creating pipeline in CustomData at %p", data);
488 /* Create our own GLib Main Context and make it the default one */
489 data->context = g_main_context_new ();
490 g_main_context_push_thread_default(data->context);
493 data->pipeline = gst_parse_launch("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error);
495 gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);
496 g_clear_error (&error);
497 set_ui_message(message, data);
502 /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
503 bus = gst_element_get_bus (data->pipeline);
504 bus_source = gst_bus_create_watch (bus);
505 g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL);
506 g_source_attach (bus_source, data->context);
507 g_source_unref (bus_source);
508 g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data);
509 g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data);
510 gst_object_unref (bus);
512 /* Create a GLib Main Loop and set it to run */
513 GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
514 data->main_loop = g_main_loop_new (data->context, FALSE);
515 check_initialization_complete (data);
516 g_main_loop_run (data->main_loop);
517 GST_DEBUG ("Exited main loop");
518 g_main_loop_unref (data->main_loop);
519 data->main_loop = NULL;
522 g_main_context_pop_thread_default(data->context);
523 g_main_context_unref (data->context);
524 gst_element_set_state (data->pipeline, GST_STATE_NULL);
525 gst_object_unref (data->pipeline);
534 /* Instruct the native code to create its internal data structure, pipeline and thread */
535 static void gst_native_init (JNIEnv* env, jobject thiz) {
536 CustomData *data = g_new0 (CustomData, 1);
537 SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
538 GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2", 0, "Android tutorial 2");
539 gst_debug_set_threshold_for_name("tutorial-2", GST_LEVEL_DEBUG);
540 GST_DEBUG ("Created CustomData at %p", data);
541 data->app = (*env)->NewGlobalRef (env, thiz);
542 GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
543 pthread_create (&gst_app_thread, NULL, &app_function, data);
546 /* Quit the main loop, remove the native thread and free resources */
547 static void gst_native_finalize (JNIEnv* env, jobject thiz) {
548 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
550 GST_DEBUG ("Quitting main loop...");
551 g_main_loop_quit (data->main_loop);
552 GST_DEBUG ("Waiting for thread to finish...");
553 pthread_join (gst_app_thread, NULL);
554 GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
555 (*env)->DeleteGlobalRef (env, data->app);
556 GST_DEBUG ("Freeing CustomData at %p", data);
558 SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
559 GST_DEBUG ("Done finalizing");
562 /* Set pipeline to PLAYING state */
563 static void gst_native_play (JNIEnv* env, jobject thiz) {
564 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
566 GST_DEBUG ("Setting state to PLAYING");
567 gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
570 /* Set pipeline to PAUSED state */
571 static void gst_native_pause (JNIEnv* env, jobject thiz) {
572 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
574 GST_DEBUG ("Setting state to PAUSED");
575 gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
578 /* Static class initializer: retrieve method and field IDs */
579 static jboolean gst_native_class_init (JNIEnv* env, jclass klass) {
580 custom_data_field_id = (*env)->GetFieldID (env, klass, "native_custom_data", "J");
581 set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
582 on_gstreamer_initialized_method_id = (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
584 if (!custom_data_field_id || !set_message_method_id || !on_gstreamer_initialized_method_id) {
585 /* We emit this message through the Android log instead of the GStreamer log because the later
586 * has not been initialized yet.
588 __android_log_print (ANDROID_LOG_ERROR, "tutorial-2", "The calling class does not implement all necessary interface methods");
594 /* List of implemented native methods */
595 static JNINativeMethod native_methods[] = {
596 { "nativeInit", "()V", (void *) gst_native_init},
597 { "nativeFinalize", "()V", (void *) gst_native_finalize},
598 { "nativePlay", "()V", (void *) gst_native_play},
599 { "nativePause", "()V", (void *) gst_native_pause},
600 { "nativeClassInit", "()Z", (void *) gst_native_class_init}
603 /* Library initializer */
604 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
609 if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
610 __android_log_print (ANDROID_LOG_ERROR, "tutorial-2", "Could not retrieve JNIEnv");
613 jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_2/Tutorial2");
614 (*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods));
616 pthread_key_create (¤t_jni_env, detach_current_thread);
618 return JNI_VERSION_1_4;
622 Let’s start with the `CustomData` structure. We have seen it in most of
623 the basic tutorials, and it is used to hold all our information in one
624 place, so we can easily pass it around to
628 /* Structure to contain all our information, so we can pass it to callbacks */
629 typedef struct _CustomData {
630 jobject app; /* Application instance, used to call its methods. A global reference is kept. */
631 GstElement *pipeline; /* The running pipeline */
632 GMainContext *context; /* GLib context used to run the main loop */
633 GMainLoop *main_loop; /* GLib main loop */
634 gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
638 We will see the meaning of each member as we go. What is interesting now
639 is that `CustomData` belongs to the application, so a pointer is kept in
640 the Tutorial2 Java class in the `private long
641 native_custom_data` attribute. Java only holds this pointer for us; it
642 is completely handled in C code.
644 From C, this pointer can be set and retrieved with the
645 [SetLongField()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html#wp16613)
647 [GetLongField()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html#wp16572)
648 JNI functions, but two convenience macros have been defined,
649 `SET_CUSTOM_DATA` and `GET_CUSTOM_DATA`. These macros are handy because
650 the `long` type used in Java is always 64 bits wide, but the pointer
651 used in C can be either 32 or 64 bits wide. The macros take care of the
652 conversion without warnings.
655 /* Library initializer */
656 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
661 if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
662 __android_log_print (ANDROID_LOG_ERROR, "tutorial-2", "Could not retrieve JNIEnv");
665 jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_2/Tutorial2");
666 (*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods));
668 pthread_key_create (¤t_jni_env, detach_current_thread);
670 return JNI_VERSION_1_4;
674 The `JNI_OnLoad` function is almost the same as the previous tutorial.
675 It registers the list of native methods (which is longer in this
677 uses [pthread\_key\_create()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_create.html)
678 to be able to store per-thread information, which is crucial to properly
679 manage the JNI Environment, as shown later.
682 /* Static class initializer: retrieve method and field IDs */
683 static jboolean gst_native_class_init (JNIEnv* env, jclass klass) {
684 custom_data_field_id = (*env)->GetFieldID (env, klass, "native_custom_data", "J");
685 set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
686 on_gstreamer_initialized_method_id = (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
688 if (!custom_data_field_id || !set_message_method_id || !on_gstreamer_initialized_method_id) {
689 /* We emit this message through the Android log instead of the GStreamer log because the later
690 * has not been initialized yet.
692 __android_log_print (ANDROID_LOG_ERROR, "tutorial-2", "The calling class does not implement all necessary interface methods");
699 This method is called from the static initializer of the Java class,
700 which is passed as a parameter (since this is called from a static
701 method, it receives a class object instead of an instance object). In
702 order for C code to be able to call a Java method, it needs to know the
704 [MethodID](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp1064).
705 This ID is obtained from the method’s name and signature and can be
706 cached. The purpose of the `gst_native_class_init()` function is to
707 obtain the IDs of all the methods and fields that the C code will need.
708 If some ID cannot be retrieved, the calling Java class does not offer
709 the expected interface and execution should halt (which is not currently
710 done for simplicity).
712 Let’s review now the first native method which can be directly called
715 #### `gst_native_init()` (`nativeInit()` from Java)
717 This method is called at the end of Java's `onCreate()`.
720 static void gst_native_init (JNIEnv* env, jobject thiz) {
721 CustomData *data = g_new0 (CustomData, 1);
722 SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
725 It first allocates memory for the `CustomData` structure and passes the
726 pointer to the Java class with `SET_CUSTOM_DATA`, so it is remembered.
729 data->app = (*env)->NewGlobalRef (env, thiz);
732 A pointer to the application class (the `Tutorial2` class) is also kept
733 in `CustomData` (a [Global
734 Reference](http://developer.android.com/guide/practices/jni.html#local_and_global_references)
735 is used) so its methods can be called later.
738 pthread_create (&gst_app_thread, NULL, &app_function, data);
741 Finally, a thread is created and it starts running the
742 `app_function()` method.
744 #### `app_function()`
747 /* Main method for the native code. This is executed on its own thread. */
748 static void *app_function (void *userdata) {
749 JavaVMAttachArgs args;
751 CustomData *data = (CustomData *)userdata;
753 GError *error = NULL;
755 GST_DEBUG ("Creating pipeline in CustomData at %p", data);
757 /* Create our own GLib Main Context and make it the default one */
758 data->context = g_main_context_new ();
759 g_main_context_push_thread_default(data->context);
762 It first creates a GLib context so all `GSource` are kept in the same
763 place. This also helps cleaning after GSources created by other
764 libraries which might not have been properly disposed of. A new context
765 is created with `g_main_context_new()` and then it is made the default
766 one for the thread with
767 `g_main_context_push_thread_default()`.
770 data->pipeline = gst_parse_launch("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error);
772 gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);
773 g_clear_error (&error);
774 set_ui_message(message, data);
780 It then creates a pipeline the easy way, with `gst-parse-launch()`. In
781 this case, it is simply an `audiotestsrc` (which produces a continuous
782 tone) and an `autoaudiosink`, with accompanying adapter elements.
785 bus = gst_element_get_bus (data->pipeline);
786 bus_source = gst_bus_create_watch (bus);
787 g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL);
788 g_source_attach (bus_source, data->context);
789 g_source_unref (bus_source);
790 g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data);
791 g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data);
792 gst_object_unref (bus);
795 These lines create a bus signal watch and connect to some interesting
796 signals, just like we have been doing in the basic tutorials. The
797 creation of the watch is done step by step instead of using
798 `gst_bus_add_signal_watch()` to exemplify how to use a custom GLib
802 GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
803 data->main_loop = g_main_loop_new (data->context, FALSE);
804 check_initialization_complete (data);
805 g_main_loop_run (data->main_loop);
806 GST_DEBUG ("Exited main loop");
807 g_main_loop_unref (data->main_loop);
808 data->main_loop = NULL;
811 Finally, the main loop is created and set to run. When it exits (because
812 somebody else calls `g_main_loop_quit()`) the main loop is disposed of.
813 Before entering the main loop, though,
814 `check_initialization_complete()` is called. This method checks if all
815 conditions are met to consider the native code “ready” to accept
816 commands. Since having a running main loop is one of the conditions,
817 `check_initialization_complete()` is called here. This method is
820 Once the main loop has quit, all resources are freed in lines 178 to
823 #### `check_initialization_complete()`
826 static void check_initialization_complete (CustomData *data) {
827 JNIEnv *env = get_jni_env ();
828 if (!data->initialized && data->main_loop) {
829 GST_DEBUG ("Initialization complete, notifying application. main_loop:%p", data->main_loop);
830 (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
831 if ((*env)->ExceptionCheck (env)) {
832 GST_ERROR ("Failed to call Java method");
833 (*env)->ExceptionClear (env);
835 data->initialized = TRUE;
840 This method does not do much in this tutorial, but it will also be used
841 in the next ones, with progressively more complex functionality. Its
842 purpose is to check if the native code is ready to accept commands, and,
843 if so, notify the UI code.
845 In tutorial 2, the only conditions are 1) the code is not already
846 initialized and 2) the main loop is running. If these two are met, the
847 Java `onGStreamerInitialized()` method is called via the
848 [CallVoidMethod()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html#wp4256)
851 Here comes a tricky bit. JNI calls require a JNI Environment, **which is
852 different for every thread**. C methods called from Java receive a
853 `JNIEnv` pointer as a parameter, but this is not the situation with
854 `check_initialization_complete()`. Here, we are in a thread which has
855 never been called from Java, so we have no `JNIEnv`. We need to use the
856 `JavaVM` pointer (passed to us in the `JNI_OnLoad()` method, and shared
857 among all threads) to attach this thread to the Java Virtual Machine and
858 obtain a `JNIEnv`. This `JNIEnv` is stored in the [Thread-Local
859 Storage](http://en.wikipedia.org/wiki/Thread-local_storage) (TLS) using
860 the pthread key we created in `JNI_OnLoad()`, so we do not need to
861 attach the thread anymore.
863 This behavior is implemented in the `get_jni_env()` method, used for
864 example in `check_initialization_complete()` as we have just seen. Let’s
865 see how it works, step by step:
870 static JNIEnv *get_jni_env (void) {
872 if ((env = pthread_getspecific (current_jni_env)) == NULL) {
873 env = attach_current_thread ();
874 pthread_setspecific (current_jni_env, env);
880 It first retrieves the current `JNIEnv` from the TLS using
881 [pthread\_getspecific()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getspecific.html)
882 and the key we obtained from
883 [pthread\_key\_create()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_create.html).
884 If it returns NULL, we never attached this thread, so we do now with
885 `attach_current_thread()` and then store the new `JNIEnv` into the TLS
887 [pthread\_setspecific()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setspecific.html).
889 #### `attach_current_thread()`
891 This method is simply a convenience wrapper around
892 [AttachCurrentThread()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#attach_current_thread)
893 to deal with its parameters.
895 #### `detach_current_thread()`
897 This method is called by the pthreads library when a TLS key is deleted,
898 meaning that the thread is about to be destroyed. We simply detach the
899 thread from the JavaVM with
900 [DetachCurrentThread()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#detach_current_thread).
902 Let's now review the rest of the native methods accessible from Java:
904 #### `gst_native_finalize()` (`nativeFinalize()` from Java)
907 static void gst_native_finalize (JNIEnv* env, jobject thiz) {
908 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
910 GST_DEBUG ("Quitting main loop...");
911 g_main_loop_quit (data->main_loop);
912 GST_DEBUG ("Waiting for thread to finish...");
913 pthread_join (gst_app_thread, NULL);
914 GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
915 (*env)->DeleteGlobalRef (env, data->app);
916 GST_DEBUG ("Freeing CustomData at %p", data);
918 SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
919 GST_DEBUG ("Done finalizing");
923 This method is called from Java in `onDestroy()`, when the activity is
924 about to be destroyed. Here, we:
926 - Instruct the GLib main loop to quit with `g_main_loop_quit()`. This
927 call returns immediately, and the main loop will terminate at its
928 earliest convenience.
929 - Wait for the thread to finish with
930 [pthread\_join()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_join.html).
931 This call blocks until the `app_function()` method returns, meaning
932 that the main loop has exited, and the thread has been destroyed.
933 - Dispose of the global reference we kept for the Java application
934 class (`Tutorial2`) in `CustomData`.
935 - Free `CustomData` and set the Java pointer inside the
936 `Tutorial2` class to NULL with
939 #### `gst_native_play` and `gst_native_pause()` (`nativePlay` and `nativePause()` from Java)
941 These two simple methods retrieve `CustomData` from the passed-in object
942 with `GET_CUSTOM_DATA()` and set the pipeline found inside `CustomData`
943 to the desired state, returning immediately.
945 Finally, let’s see how the GStreamer callbacks are handled:
947 #### `error_cb` and `state_changed_cb`
949 This tutorial does not do much in these callbacks. They simply parse the
950 error or state changed message and display a message in the UI using the
951 `set_ui_message()` method:
953 #### `set_ui_message()`
956 static void set_ui_message (const gchar *message, CustomData *data) {
957 JNIEnv *env = get_jni_env ();
958 GST_DEBUG ("Setting message to: %s", message);
959 jstring jmessage = (*env)->NewStringUTF(env, message);
960 (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
961 if ((*env)->ExceptionCheck (env)) {
962 GST_ERROR ("Failed to call Java method");
963 (*env)->ExceptionClear (env);
965 (*env)->DeleteLocalRef (env, jmessage);
971 This is the other method (besides `check_initialization_complete()`)
972 that needs to call a Java function from a thread which never received an
973 `JNIEnv` pointer directly. Notice how all the complexities of attaching
974 the thread to the JavaVM and storing the JNI environment in the TLS are
975 hidden in the simple call to `get_jni_env()`.
977 The desired message (received in
978 [ASCII](http://en.wikipedia.org/wiki/ASCII), or modified
979 [UTF8](http://en.wikipedia.org/wiki/Modified_UTF-8#Modified_UTF-8)), is
980 converted to [UTF16](http://en.wikipedia.org/wiki/UTF-16) as required by
982 [NewStringUTF()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html#wp17220)
985 The `setMessage()` Java method is called via the JNI
986 [CallVoidMethod()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html#wp4256)
987 using the global reference to the class we are keeping in
988 `CustomData` (`data->app`) and the `set_message_method_id` we cached in
989 `gst_native_class_init()`.
991 We check for exceptions with the JNI
992 [ExceptionCheck()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html#exception_check)
993 method and free the UTF16 message with
994 [DeleteLocalRef()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html#DeleteLocalRef).
996 ### A pipeline on Android \[Android.mk\]
1001 LOCAL_PATH := $(call my-dir)
1003 include $(CLEAR_VARS)
1005 LOCAL_MODULE := tutorial-2
1006 LOCAL_SRC_FILES := tutorial-2.c
1007 LOCAL_SHARED_LIBRARIES := gstreamer_android
1008 LOCAL_LDLIBS := -llog
1009 include $(BUILD_SHARED_LIBRARY)
1011 ifndef GSTREAMER_ROOT
1012 ifndef GSTREAMER_ROOT_ANDROID
1013 $(error GSTREAMER_ROOT_ANDROID is not defined!)
1015 GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)
1017 GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/
1018 include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk
1019 GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_SYS)
1020 include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk
1023 Notice how the required `GSTREAMER_PLUGINS` are now
1024 `$(GSTREAMER_PLUGINS_CORE)` (For the test source and converter elements)
1025 and `$(GSTREAMER_PLUGINS_SYS)` (for the audio sink).
1027 And this is it\! This has been a rather long tutorial, but we covered a
1028 lot of territory. Building on top of this one, the following ones are
1029 shorter and focus only on the new topics.
1033 This tutorial has shown:
1035 - How to manage multiple threads from C code and have them interact
1037 - How to access Java code from any C thread
1038 using [AttachCurrentThread()](http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#attach_current_thread).
1039 - How to allocate a CustomData structure from C and have Java host it,
1040 so it is available to all threads.
1042 Most of the methods introduced in this tutorial, like `get_jni_env()`,
1043 `check_initialization_complete()`, `app_function()` and the API methods
1044 `gst_native_init()`, `gst_native_finalize()` and
1045 `gst_native_class_init()` will continue to be used in the following
1046 tutorials with minimal modifications, so better get used to them\!
1048 As usual, it has been a pleasure having you here, and see you soon\!
1050 [screenshot]: images/tutorials/android-a-running-pipeline-screenshot.png