0 目标演示

在APP中显示一个GStreamer版本。

这第一篇 Android 教程非常简单:它只是检索 GStreamer 版本并将其显示在屏幕上。演示如何从Java访问GStreamer C代码,并验证了没有任何链接问题。

1 Hello GStreamer (Java 代码)

教程代码在tutorials/android-tutorial-1子目录下的gst-docs中。这个目录包含通常的Android NDK结构:一个src文件夹用于Java代码,一个jni文件夹用于C代码,一个res文件夹用于UI资源。
我们建议你在Eclipse中打开这个项目(正如在安装Android开发中所解释的那样),这样你就可以很容易地看到所有的部分是如何结合在一起的。
让我们先介绍一下Java代码,然后是C代码,最后是允许GStreamer集成的makefile。

//src/org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1.java
package org.freedesktop.gstreamer.tutorials.tutorial_1;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;

import org.freedesktop.gstreamer.GStreamer;

public class Tutorial1 extends Activity {
    private native String nativeGetGStreamerInfo();

    // Called when the activity is first created.
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        try {
            GStreamer.init(this);
        } catch (Exception e) {
            Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
            finish();
            return;
        }

        setContentView(R.layout.main);

        TextView tv = (TextView)findViewById(R.id.textview_info);
        tv.setText("Welcome to " + nativeGetGStreamerInfo() + " !");
    }

    static {
        System.loadLibrary("gstreamer_android");
        System.loadLibrary("tutorial-1");
    }

}

上面的代码中演示了声明一个从Java中调用C本地方法,就是这个方法

private native String nativeGetGStreamerInfo();

表示,告诉Java程序该方法已经存在,编译器可以继续编译,并且保证在运行时可调用。具体的实现会在后面说明。
第一个被实际执行的代码是类的静态初始化类。

static {
    System.loadLibrary("gstreamer_android");
    System.loadLibrary("tutorial-1");
}

此处声明表示加载libgstreamer_android.so和libtutorial-1.so两个库,前者包含GStreamer的全部方法,后者是本示例中所用到的方法,后面会说明。
在加载时,这些库的JNI_OnLoad()方法都会被执行。它基本上注册了这些库所公开的本地方法。GStreamer库只暴露了一个init()方法,它初始化了GStreamer,并注册了所有的插件(教程库将在后面解释)。

try {
    GStreamer.init(this);
} catch (Exception e) {
    Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
    finish();
    return;
}

接下来,在Activity的OnCreate()方法中,我们实际上是通过调用GStreamer.init()来初始化GStreamer。这个方法需要一个Context,所以不能从静态初始化器中调用,但是多次调用它并没有危险,因为除了第一次之外,所有的调用都会被忽略。
如果初始化失败,init()方法将抛出一个由GStreamer库提供异常详情的Exception。

TextView tv = (TextView)findViewById(R.id.textview_info);
tv.setText("Welcome to " + nativeGetGStreamerInfo() + " !");

然后,调用本地方法nativeGetGStreamerInfo(),并获取一个字符串,用来格式化UI中TextView的内容。
本教程的UI部分到此结束。我们来看看C代码。

2 Hello Gstreamer (C代码)

//jni/tutorial-1.c

#include <string.h>
#include <jni.h>
#include <android/log.h>
#include <gst/gst.h>

/*
 * Java Bindings
 */
static jstring gst_native_get_gstreamer_info (JNIEnv* env, jobject thiz) {
  char *version_utf8 = gst_version_string();
  jstring *version_jstring = (*env)->NewStringUTF(env, version_utf8);
  g_free (version_utf8);
  return version_jstring;
}

static JNINativeMethod native_methods[] = {
  { "nativeGetGStreamerInfo", "()Ljava/lang/String;", (void *) gst_native_get_gstreamer_info}
};

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
  JNIEnv *env = NULL;

  if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    __android_log_print (ANDROID_LOG_ERROR, "tutorial-1", "Could not retrieve JNIEnv");
    return 0;
  }
  jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1");
  (*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods));

  return JNI_VERSION_1_4;
}

2.1 获取JNI交互环境
JNI_ONLoad()方法会在Java虚拟机(JVM)加载的时候执行。
在这里,我们获取与Java交互的调用所需的JNI环境。

JNIEnv *env = NULL;

if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
  __android_log_print (ANDROID_LOG_ERROR, "tutorial-1", "Could not retrieve JNIEnv");
  return 0;
}

然后使用FindClass()定位包含本教程UI部分的类。

jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1");

最后,我们用RegisterNatives()注册我们的native方法,这就是我们用native关键字提供我们在Java中声明的的方法的代码。

(*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods));

2.2 本地方法声明暴露
native_methods数组列出了每个要注册的方法(本教程中只有一个)。对于每个方法,它提供了它的Java名称、类型签名和一个指向实现它的C函数的指针。

static JNINativeMethod native_methods[] = {
  { "nativeGetGStreamerInfo", "()Ljava/lang/String;", (void *) gst_native_get_gstreamer_info}
};

2.3 C中本地方法定义
本教程中唯一使用的本地方法是nativeGetGStreamerInfo()。

jstring gst_native_get_gstreamer_info (JNIEnv* env, jobject thiz) {
  char *version_utf8 = gst_version_string();
  jstring *version_jstring = (*env)->NewStringUTF(env, version_utf8);
  g_free (version_utf8);
  return version_jstring;
}

它只是简单地调用gst_version_string()来获取一个描述这个版本的GStreamer的字符串。这个修改后的UTF8字符串会按照Java的要求被NewStringUTF()转换为UTF16并返回。Java将负责释放新的UTF16字符串所使用的内存,但我们必须释放gst_version_string()返回的char *。

3 Hello GStreamer (Android.mk 声明)

jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := tutorial-1
LOCAL_SRC_FILES := tutorial-1.c
LOCAL_SHARED_LIBRARIES := gstreamer_android
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)

ifndef GSTREAMER_ROOT
ifndef GSTREAMER_ROOT_ANDROID
$(error GSTREAMER_ROOT_ANDROID is not defined!)
endif
GSTREAMER_ROOT        := $(GSTREAMER_ROOT_ANDROID)
endif
GSTREAMER_NDK_BUILD_PATH  := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/
GSTREAMER_PLUGINS         := coreelements
include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk

这是一个支持GStreamer的项目的makefile。它简单地说明了它依赖于libgstreamer_android.so库(第7行),并且需要coreelements插件(第18行)。更复杂的应用可能会在Android.mk中添加更多的库和插件。

4 总结

第一篇Android教程到此结束。它表明,除了Java和C(遵守标准的JNI程序)之间的互连之外,在Android应用程序中添加GStreamer支持并不比在桌面应用程序中添加它复杂。

下面的教程详细介绍了专门为Android平台开发时必须注意的几个地方。

Tags: android, GStreamer

Related Posts:

Leave a Comment