1.下载gst源码工程cerbero
2. 编译gstreamer版本包
./cerbero-uninstalled -c config/cross-android-arm64.cbc bootstrap
./cerbero-uninstalled -c config/cross-android-arm64.cbc package gstreamer-1.0
3.版本包有两种:
(1)gstreamer-1.0-android-arm64-1.20.6.1.tar.xz
(2)gstreamer-1.0-android-arm64-1.20.6.1-runtime.tar.xz
方式1:静态库包,
包含用于ndk的源码gst-android(arm64\share\gst-android\ndk-build),根据教程tutorial 5 比较容易搭建一个简易apk。
官网tutorial:
tutorial代码:git clone https://gitlab.freedesktop.org/gstreamer/gst-docs
方式2:动态库包
4. 播放器应用我们复用tutorial 5中静态包的java等代码,只需要重写jni,修改为动态编译依赖so的形式。
5. build.gradle设置
(1)build.gradle
plugins {
id 'com.android.application'
}
android {
compileSdk 33
defaultConfig {
applicationId "com.example.gstdemo"
minSdk 26
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ''
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
}
}
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.18.1'
}
}
buildFeatures {
viewBinding true
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
resources.srcDirs = ['src']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
}
}
ndkVersion '21.3.6528147'
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
//noinspection GradleCompatible
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
6. 头文件/依赖库/插件库/pkg-config文件拷贝
a)将编译的版本包里面的include(静态版本包中有)直接拷贝到arm64-v8a下:(实际用不到这么多的头文件,只是为了省力)
b)将动态版本包gstreamer-1.0-android-arm64-1.20.6.1-runtime.tar\lib 下的so拷贝到arm64-v8a下,
lib\gio\modules\libgioopenssl.so也拷贝到arm64-v8a下
c)将静态版本包gstreamer-1.0-android-arm64-1.20.6.1.tar\arm64\lib\pkgconfig下的*pc拷贝到
arm64-v8a/pkgconfig下
d) 将静态版本包\gstreamer-1.0-android-arm64-1.20.6.1.tar\arm64\share\gst-android\ndk-build\tools\windows\pkg-config.exe 拷贝到arm64-v8a下
e)将动态版本包gstreamer-1.0-android-arm64-1.20.6.1.tar\arm64\lib\gstreamer-1.0下的插件库拷贝到assets 下;gstreamer-1.0-android-arm64-1.20.6.1-runtime.tar\libexec\gstreamer-1.0\gst-plugin-scanner拷贝到assets 下;(插件so如下拷贝到对应的plugins/arm64-v8a下)
备注:pkg-config.exe 所在的路径就是${prefix},所以对应的头文件和so都放到了这里,还需要修改下*.pc里面的路径,需要去掉libdir=${prefix}/lib后面的lib。
prefix=/home/jan/devel/gstreamer/cerbero/build/dist/android_universal/x86_64
libdir=${prefix}
includedir=${prefix}/include
exec_prefix=${prefix}
toolsdir=${exec_prefix}/bin
pluginsdir=${libdir}/gstreamer-1.0
datarootdir=${prefix}/share
datadir=${datarootdir}
girdir=${datadir}/gir-1.0
typelibdir=${libdir}/girepository-1.0
libexecdir=${prefix}/libexec
pluginscannerdir=${libexecdir}/gstreamer-1.0
Name: gstreamer-base-1.0
Description: Base classes for GStreamer elements
Version: 1.19.3
Requires: gstreamer-1.0
Requires.private: gobject-2.0, glib-2.0 >= 2.56.0, gmodule-2.0
Libs: -L${libdir} -lgstbase-1.0
Cflags: -I${includedir}/gstreamer-1.0
f) font和 certs也要拷贝过来
7 cmakelists的配置
这里也会有2个so,对应静态demo中的gstreamer_android.c(gst-android) 和 tutorial-5.c(gst-jni)
这里将原来的ndk方式改成了camke。
cmakelists:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
ADD_SUBDIRECTORY(gst_jni)
ADD_SUBDIRECTORY(gst_and)
gst-jni:
cmakelists:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("gstjni")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
gstjni
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
gst_jni.c)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
set(gst_lib_path ${CMAKE_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/lib)
set(ENV{PKG_CONFIG_PATH} ${gst_lib_path}/pkgconfig:$ENV{PKG_CONFIG_PATH})
set(ENV{PKG_CONFIG} "${CMAKE_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/pkg-config.exe")
#set(CMAKE_BUILD_TYPE "Debug")
find_package(PkgConfig REQUIRED)
pkg_check_modules (TEST REQUIRED gstreamer-1.0 NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH)
pkg_search_module(TEST REQUIRED gstreamer-1.0 NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH)
pkg_check_modules (GST_VIDEO REQUIRED gstreamer-video-1.0 NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH)
pkg_search_module(GST_VIDEO REQUIRED gstreamer-video-1.0 NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH)
string(REGEX REPLACE "\;" " " TEST_LDFLAGS "${TEST_LDFLAGS}")
string(REGEX REPLACE "\;" " " TEST_CFLAGS "${TEST_CFLAGS}")
string(REGEX REPLACE "\;" " " GST_VIDEO_LDFLAGS "${GST_VIDEO_LDFLAGS}")
string(REGEX REPLACE "\;" " " GST_VIDEO_CFLAGS "${GST_VIDEO_CFLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} ${TEST_LDFLAGS} ${TEST_CFLAGS} ${GST_VIDEO_LDFLAGS} ${GST_VIDEO_CFLAGS} -landroid")
set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} ${TEST_LDFLAGS} ${TEST_CFLAGS} ${GST_VIDEO_LDFLAGS} ${GST_VIDEO_CFLAGS} -landroid")
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${TEST_LDFLAGS} ${TEST_CFLAGS}")
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} ${TEST_LDFLAGS} ${TEST_CFLAGS}")
#[[
message("****************** _PREFIX dirs:" ${TEST_PREFIX})
message("****************** _INCLUDEDIR:" ${TEST_INCLUDEDIR})
message("****************** _LIBDIR:" ${TEST_LIBDIR})
message("****************** _CFLAGS:" ${TEST_CFLAGS})
message("***************** _LIBRARIES:" ${TEST_LIBRARIES})
message("****************** _VERSION:" ${TEST_VERSION})
message("****************** _LIBRARY_DIRS:" ${TEST_LIBRARY_DIRS})
message("****************** _LDFLAGS:" ${TEST_LDFLAGS})
message("****************** _INCLUDE_DIRS:" ${TEST_INCLUDE_DIRS})
message("******************- CMAKE_CXX_FLAGS_DEBUG:" ${CMAKE_CXX_FLAGS_DEBUG})
]]
#[[
C/C++: debug|x86_64 :****************** _PREFIX dirs:C:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64
C/C++: debug|x86_64 :****************** _INCLUDEDIR:C:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/include
C/C++: debug|x86_64 :****************** _LIBDIR:C:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/lib
C/C++: debug|x86_64 :****************** _CFLAGS:-pthread-IC:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/include/gstreamer-1.0-IC:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/include-IC:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/include/glib-2.0-IC:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/lib/glib-2.0/include
C/C++: debug|x86_64 :***************** _LIBRARIES:gstreamer-1.0gobject-2.0glib-2.0intl
C/C++: debug|x86_64 :****************** _VERSION:1.19.3
C/C++: debug|x86_64 :****************** _LIBRARY_DIRS:C:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/lib
C/C++: debug|x86_64 :****************** _LDFLAGS:-LC:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/lib-lgstreamer-1.0-lgobject-2.0-lglib-2.0-lintl
C/C++: debug|x86_64 :****************** _INCLUDE_DIRS:C:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/include/gstreamer-1.0C:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/includeC:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/include/glib-2.0C:/Users/AndroidStudioProjects/gstdemo/app/libs/x86_64/lib/glib-2.0/include
]]
#EXECUTE_PROCESS(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable pc_path pkg-config
# TIMEOUT 5
# OUTPUT_VARIABLE ret
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
#message("**************************************************pc_path dirs:" ${ret})
#EXECUTE_PROCESS(COMMAND ${PKG_CONFIG_EXECUTABLE} --cflags --libs flac
# TIMEOUT 5
# OUTPUT_VARIABLE ret
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
#message("**************************************************flac dirs:" ${ret})
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
gstjni
# Links the target library to the log library
# included in the NDK.
${log-lib})
gst-jin.c
#include <string.h>
#include <stdint.h>
#include <jni.h>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/video/videooverlay.h>
#include <pthread.h>
GST_DEBUG_CATEGORY_STATIC (debug_category);
#define GST_CAT_DEFAULT debug_category
/*
* These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into
* a jlong, which is always 64 bits, without warnings.
*/
#if GLIB_SIZEOF_VOID_P == 8
# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID)
# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data)
#else
# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID)
# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data)
#endif
/* Do not allow seeks to be performed closer than this distance. It is visually useless, and will probably
* confuse some demuxers. */
#define SEEK_MIN_DELAY (500 * GST_MSECOND)
/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
jobject app; /* Application instance, used to call its methods. A global reference is kept. */
GstElement *pipeline; /* The running pipeline */
GMainContext *context; /* GLib context used to run the main loop */
GMainLoop *main_loop; /* GLib main loop */
gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
ANativeWindow *native_window; /* The Android native window where video will be rendered */
GstState state; /* Current pipeline state */
GstState target_state; /* Desired pipeline state, to be set once buffering is complete */
gint64 duration; /* Cached clip duration */
gint64 desired_position; /* Position to seek to, once the pipeline is running */
GstClockTime last_seek_time; /* For seeking overflow prevention (throttling) */
gboolean is_live; /* Live streams do not use buffering */
} CustomData;
/* playbin2 flags */
typedef enum {
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */
} GstPlayFlags;
/* These global variables cache values which are not changing during execution */
static pthread_t gst_app_thread;
static pthread_key_t current_jni_env;
static JavaVM *java_vm;
static jfieldID custom_data_field_id;
static jmethodID set_message_method_id;
static jmethodID set_current_position_method_id;
static jmethodID on_gstreamer_initialized_method_id;
static jmethodID on_media_size_changed_method_id;
/*
* Private methods
*/
/* Register this thread with the VM */
static JNIEnv *
attach_current_thread(void) {
JNIEnv *env;
JavaVMAttachArgs args;
GST_DEBUG ("Attaching thread %p", g_thread_self());
args.version = JNI_VERSION_1_4;
args.name = NULL;
args.group = NULL;
if ((*java_vm)->AttachCurrentThread(java_vm, &env, &args) < 0) {
GST_ERROR ("Failed to attach current thread");
return NULL;
}
return env;
}
/* Unregister this thread from the VM */
static void
detach_current_thread(void *env) {
GST_DEBUG ("Detaching thread %p", g_thread_self());
(*java_vm)->DetachCurrentThread(java_vm);
}
/* Retrieve the JNI environment for this thread */
static JNIEnv *
get_jni_env(void) {
JNIEnv *env;
if ((env = pthread_getspecific(current_jni_env)) == NULL) {
env = attach_current_thread();
pthread_setspecific(current_jni_env, env);
}
return env;
}
/* Change the content of the UI's TextView */
static void
set_ui_message(const gchar *message, CustomData *data) {
JNIEnv *env = get_jni_env();
//GST_DEBUG ("Setting message to: %s", message);
jstring jmessage = (*env)->NewStringUTF(env, message);
(*env)->CallVoidMethod(env, data->app, set_message_method_id, jmessage);
if ((*env)->ExceptionCheck(env)) {
GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear(env);
}
(*env)->DeleteLocalRef(env, jmessage);
}
/* Tell the application what is the current position and clip duration */
static void
set_current_ui_position(gint position, gint duration, CustomData *data) {
JNIEnv *env = get_jni_env();
(*env)->CallVoidMethod(env, data->app, set_current_position_method_id,
position, duration);
if ((*env)->ExceptionCheck(env)) {
GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear(env);
}
}
/* If we have pipeline and it is running, query the current position and clip duration and inform
* the application */
static gboolean
refresh_ui(CustomData *data) {
gint64 current = -1;
gint64 position;
/* We do not want to update anything unless we have a working pipeline in the PAUSED or PLAYING state */
if (!data || !data->pipeline || data->state < GST_STATE_PAUSED)
return TRUE;
/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
if (!gst_element_query_duration(data->pipeline, GST_FORMAT_TIME,
&data->duration)) {
GST_WARNING
("Could not query current duration (normal for still pictures)");
data->duration = 0;
}
}
if (!gst_element_query_position(data->pipeline, GST_FORMAT_TIME, &position)) {
GST_WARNING
("Could not query current position (normal for still pictures)");
position = 0;
}
/* Java expects these values in milliseconds, and GStreamer provides nanoseconds */
set_current_ui_position(position / GST_MSECOND, data->duration / GST_MSECOND,
data);
return TRUE;
}
/* Forward declaration for the delayed seek callback */
static gboolean delayed_seek_cb(CustomData *data);
/* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for
* some time in the future. */
static void
execute_seek(gint64 desired_position, CustomData *data) {
gint64 diff;
if (desired_position == GST_CLOCK_TIME_NONE)
return;
diff = gst_util_get_timestamp() - data->last_seek_time;
if (GST_CLOCK_TIME_IS_VALID (data->last_seek_time) && diff < SEEK_MIN_DELAY) {
/* The previous seek was too close, delay this one */
GSource *timeout_source;
if (data->desired_position == GST_CLOCK_TIME_NONE) {
/* 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);
g_source_set_callback(timeout_source, (GSourceFunc) delayed_seek_cb,
data, NULL);
g_source_attach(timeout_source, data->context);
g_source_unref(timeout_source);
}
/* Update the desired seek position. If multiple petitions are received before it is time
* to perform a seek, only the last one is remembered. */
data->desired_position = desired_position;
GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %"
GST_TIME_FORMAT, GST_TIME_ARGS(desired_position),
GST_TIME_ARGS(SEEK_MIN_DELAY - diff));
} else {
/* Perform the seek now */
GST_DEBUG ("Seeking to %" GST_TIME_FORMAT,
GST_TIME_ARGS(desired_position));
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);
data->desired_position = GST_CLOCK_TIME_NONE;
}
}
/* Delayed seek callback. This gets called by the timer setup in the above function. */
static gboolean
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);
return FALSE;
}
/* Retrieve errors from the bus and show them on the UI */
static void
error_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
GError *err;
gchar *debug_info;
gchar *message_string;
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);
g_clear_error(&err);
g_free(debug_info);
set_ui_message(message_string, data);
g_free(message_string);
data->target_state = GST_STATE_NULL;
gst_element_set_state(data->pipeline, GST_STATE_NULL);
}
/* 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) {
data->target_state = GST_STATE_PAUSED;
data->is_live |=
(gst_element_set_state(data->pipeline,
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
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. */
static void
duration_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
data->duration = GST_CLOCK_TIME_NONE;
}
/* 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. */
static void
buffering_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
gint percent;
if (data->is_live)
return;
gst_message_parse_buffering(msg, &percent);
if (percent < 100 && data->target_state >= GST_STATE_PAUSED) {
gchar *message_string = g_strdup_printf("Buffering %d%%", percent);
gst_element_set_state(data->pipeline, GST_STATE_PAUSED);
set_ui_message(message_string, data);
g_free(message_string);
} else if (data->target_state >= GST_STATE_PLAYING) {
gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
} else if (data->target_state >= GST_STATE_PAUSED) {
set_ui_message("Buffering complete", data);
}
}
/* Called when the clock is lost */
static void
clock_lost_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
if (data->target_state >= GST_STATE_PLAYING) {
gst_element_set_state(data->pipeline, GST_STATE_PAUSED);
gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
}
}
/* Retrieve the video sink's Caps and tell the application about the media size */
static void
check_media_size(CustomData *data) {
JNIEnv *env = get_jni_env();
GstElement *video_sink;
GstPad *video_sink_pad;
GstCaps *caps;
GstVideoInfo info;
/* Retrieve the Caps at the entrance of the video sink */
g_object_get(data->pipeline, "video-sink", &video_sink, NULL);
video_sink_pad = gst_element_get_static_pad(video_sink, "sink");
caps = gst_pad_get_current_caps(video_sink_pad);
if (gst_video_info_from_caps(&info, caps)) {
info.width = info.width * info.par_n / info.par_d;
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);
if ((*env)->ExceptionCheck(env)) {
GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear(env);
}
}
gst_caps_unref(caps);
gst_object_unref(video_sink_pad);
gst_object_unref(video_sink);
}
/* Notify UI about pipeline state changes */
static void
state_changed_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
GstState 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 */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
data->state = new_state;
gchar *message = g_strdup_printf("State changed to %s",
gst_element_state_get_name(new_state));
set_ui_message(message, data);
g_free(message);
if (new_state == GST_STATE_NULL || new_state == GST_STATE_READY)
data->is_live = FALSE;
/* The Ready to Paused state change is particularly interesting: */
if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
/* By now the sink already knows the media size */
check_media_size(data);
/* 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))
execute_seek(data->desired_position, data);
}
}
}
/* Check if all conditions are met to report GStreamer as initialized.
* These conditions will change depending on the application */
static void
check_initialization_complete(CustomData *data) {
JNIEnv *env = get_jni_env();
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);
/* 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);
(*env)->CallVoidMethod(env, data->app, on_gstreamer_initialized_method_id);
if ((*env)->ExceptionCheck(env)) {
GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear(env);
}
data->initialized = TRUE;
}
}
/* Main method for the native code. This is executed on its own thread. */
static void *
app_function(void *userdata) {
JavaVMAttachArgs args;
GstBus *bus;
CustomData *data = (CustomData *) userdata;
GSource *timeout_source;
GSource *bus_source;
GError *error = NULL;
guint flags;
/* Create our own GLib Main Context and make it the default one */
data->context = g_main_context_new();
g_main_context_push_thread_default(data->context);
/* Build pipeline */
data->pipeline = gst_parse_launch("playbin", &error);
if (error) {
gchar *message =
g_strdup_printf("Unable to build pipeline: %s", error->message);
g_clear_error(&error);
set_ui_message(message, data);
g_free(message);
return NULL;
}
/* Disable subtitles */
g_object_get(data->pipeline, "flags", &flags, NULL);
flags &= ~GST_PLAY_FLAG_TEXT;
g_object_set(data->pipeline, "flags", flags, NULL);
/* Set the pipeline to READY, so it can already accept a window handle, if we have one */
data->target_state = 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 */
bus = gst_element_get_bus(data->pipeline);
bus_source = gst_bus_create_watch(bus);
g_source_set_callback(bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach(bus_source, data->context);
g_source_unref(bus_source);
g_signal_connect (G_OBJECT(bus), "message::error", (GCallback) error_cb,
data);
g_signal_connect (G_OBJECT(bus), "message::eos", (GCallback) eos_cb, data);
g_signal_connect (G_OBJECT(bus), "message::state-changed",
(GCallback) state_changed_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);
/* Register a function that GLib will call 4 times per second */
timeout_source = g_timeout_source_new(250);
g_source_set_callback(timeout_source, (GSourceFunc) refresh_ui, data, NULL);
g_source_attach(timeout_source, data->context);
g_source_unref(timeout_source);
/* Create a GLib Main Loop and set it to run */
data->main_loop = g_main_loop_new(data->context, FALSE);
check_initialization_complete(data);
const gchar *priv_gst_dump_dot_dir = g_getenv("GST_DEBUG_DUMP_DOT_DIR");
const gchar *debuig = g_getenv("GST_DEBUG");
g_main_loop_run(data->main_loop);
GST_DEBUG ("Exited main loop");
g_main_loop_unref(data->main_loop);
data->main_loop = NULL;
/* Free resources */
g_main_context_pop_thread_default(data->context);
g_main_context_unref(data->context);
data->target_state = GST_STATE_NULL;
gst_element_set_state(data->pipeline, GST_STATE_NULL);
gst_object_unref(data->pipeline);
return NULL;
}
/*
* Java Bindings
*/
/* Instruct the native code to create its internal data structure, pipeline and thread */
static void
gst_native_init(JNIEnv *env, jobject thiz) {
CustomData *data = g_new0 (CustomData, 1);
data->desired_position = GST_CLOCK_TIME_NONE;
data->last_seek_time = GST_CLOCK_TIME_NONE;
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-5", 0,
"Android tutorial 5");
gst_debug_set_threshold_for_name("tutorial-5", GST_LEVEL_DEBUG);
GST_DEBUG ("Created CustomData at %p", data);
data->app = (*env)->NewGlobalRef(env, thiz);
GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
pthread_create(&gst_app_thread, NULL, &app_function, data);
}
/* Quit the main loop, remove the native thread and free resources */
static void
gst_native_finalize(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Quitting main loop...");
g_main_loop_quit(data->main_loop);
GST_DEBUG ("Waiting for thread to finish...");
pthread_join(gst_app_thread, NULL);
GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
(*env)->DeleteGlobalRef(env, data->app);
GST_DEBUG ("Freeing CustomData at %p", data);
g_free(data);
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
GST_DEBUG ("Done finalizing");
}
/* Set playbin2's URI */
void
gst_native_set_uri(JNIEnv *env, jobject thiz, jstring uri) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data || !data->pipeline)
return;
const gchar *char_uri = (*env)->GetStringUTFChars(env, uri, NULL);
GST_DEBUG ("Setting URI to %s", char_uri);
if (data->target_state >= GST_STATE_READY)
gst_element_set_state(data->pipeline, GST_STATE_READY);
g_object_set(data->pipeline, "uri", char_uri, NULL);
(*env)->ReleaseStringUTFChars(env, uri, char_uri);
data->duration = GST_CLOCK_TIME_NONE;
data->is_live |=
(gst_element_set_state(data->pipeline,
data->target_state) == GST_STATE_CHANGE_NO_PREROLL);
}
/* Set pipeline to PLAYING state */
static void
gst_native_play(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Setting state to PLAYING");
data->target_state = GST_STATE_PLAYING;
data->is_live |=
(gst_element_set_state(data->pipeline,
GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL);
}
/* Set pipeline to PAUSED state */
static void
gst_native_pause(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Setting state to PAUSED");
const gchar *priv_gst_dump_dot_dir = g_getenv("GST_DEBUG_DUMP_DOT_DIR");
gst_debug_bin_to_dot_file(GST_BIN(data->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");
data->target_state = GST_STATE_PAUSED;
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 */
void
gst_native_set_position(JNIEnv *env, jobject thiz, int milliseconds) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
gint64 desired_position = (gint64) (milliseconds * GST_MSECOND);
if (data->state >= GST_STATE_PAUSED) {
execute_seek(desired_position, data);
} else {
GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later",
GST_TIME_ARGS(desired_position));
data->desired_position = desired_position;
}
}
/* Static class initializer: retrieve method and field IDs */
static jboolean
gst_native_class_init(JNIEnv *env, jclass klass) {
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");
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 || !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
* has not been initialized yet.
*/
__android_log_print(ANDROID_LOG_ERROR, "tutorial-4",
"The calling class does not implement all necessary interface methods");
return JNI_FALSE;
}
return JNI_TRUE;
}
static void
gst_native_surface_init(JNIEnv *env, jobject thiz, jobject surface) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface);
GST_DEBUG ("Received surface %p (native window %p)", surface,
new_native_window);
if (data->native_window) {
ANativeWindow_release(data->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);
if (data->pipeline) {
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
}
return;
} else {
GST_DEBUG ("Released previous native window %p", data->native_window);
data->initialized = FALSE;
}
}
data->native_window = new_native_window;
check_initialization_complete(data);
}
static void
gst_native_surface_finalize(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Releasing Native Window %p", data->native_window);
if (data->pipeline) {
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->pipeline),
(guintptr) NULL);
gst_element_set_state(data->pipeline, GST_STATE_READY);
}
ANativeWindow_release(data->native_window);
data->native_window = NULL;
data->initialized = FALSE;
}
/* List of implemented native methods */
static JNINativeMethod native_methods[] = {
{"nativeInit", "()V", (void *) gst_native_init},
{"nativeFinalize", "()V", (void *) gst_native_finalize},
{"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
{"nativePlay", "()V", (void *) gst_native_play},
{"nativePause", "()V", (void *) gst_native_pause},
{"nativeSetPosition", "(I)V", (void *) gst_native_set_position},
{"nativeSurfaceInit", "(Ljava/lang/Object;)V",
(void *) gst_native_surface_init},
{"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
{"nativeClassInit", "()Z", (void *) gst_native_class_init}
};
/* Library initializer */
jint
JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
java_vm = vm;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, "tutorial-5",
"Could not retrieve JNIEnv");
return 0;
}
jclass klass = (*env)->FindClass(env,
"org/freedesktop/gstreamer/tutorial_5/Tutorial5");
(*env)->RegisterNatives(env, klass, native_methods,
G_N_ELEMENTS (native_methods));
pthread_key_create(¤t_jni_env, detach_current_thread);
return JNI_VERSION_1_4;
}
gst-android:
camkelists:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("gstandroid")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
gstand
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
gst_android.c)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
set(gst_lib_path ${CMAKE_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/lib)
set(ENV{PKG_CONFIG_PATH} ${gst_lib_path}/pkgconfig:$ENV{PKG_CONFIG_PATH})
set(ENV{PKG_CONFIG} "${CMAKE_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/pkg-config.exe")
#set(CMAKE_BUILD_TYPE "Debug")
find_package(PkgConfig REQUIRED)
pkg_check_modules (GST_DEP REQUIRED gstreamer-1.0 gio-unix-2.0 gioopenssl gmodule-2.0 NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH)
pkg_search_module(GST_DEP REQUIRED gstreamer-1.0 gio-unix-2.0 gioopenssl gmodule-2.0 NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH)
string(REGEX REPLACE "\;" " " GST_DEP_LDFLAGS "${GST_DEP_LDFLAGS}")
string(REGEX REPLACE "\;" " " GST_DEP_CFLAGS "${GST_DEP_CFLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} ${GST_DEP_LDFLAGS} ${GST_DEP_CFLAGS} -landroid")
set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} ${GST_DEP_LDFLAGS} ${GST_DEP_CFLAGS} -landroid")
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${TEST_LDFLAGS} ${TEST_CFLAGS}")
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} ${TEST_LDFLAGS} ${TEST_CFLAGS}")
message("****************** _PREFIX dirs:" ${GST_DEP_PREFIX})
message("****************** _INCLUDEDIR:" ${GST_DEP_INCLUDEDIR})
message("****************** _LIBDIR:" ${GST_DEP_LIBDIR})
message("****************** _CFLAGS:" ${GST_DEP_CFLAGS})
message("***************** _LIBRARIES:" ${GST_DEP_LIBRARIES})
message("****************** _VERSION:" ${GST_DEP_VERSION})
message("****************** _LIBRARY_DIRS:" ${GST_DEP_LIBRARY_DIRS})
message("****************** _LDFLAGS:" ${GST_DEP_LDFLAGS})
message("****************** _INCLUDE_DIRS:" ${GST_DEP_INCLUDE_DIRS})
message("******************- CMAKE_CXX_FLAGS_DEBUG:" ${CMAKE_C_FLAGS})
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
gstand
# Links the target library to the log library
# included in the NDK.
${log-lib})
gst_android.c
说明:
这里设置了GST_PLUGIN_SCANNER/GST_PLUGIN_PATH的路径
#include <jni.h>
#include <gst/gst.h>
#include <gio/gio.h>
#include <android/log.h>
#include <string.h>
/* XXX: Workaround for Android <21 making signal() an inline function
* around bsd_signal(), and Android >= 21 not having any bsd_signal()
* symbol but only signal().
* See https://bugzilla.gnome.org/show_bug.cgi?id=766235
*/
static gpointer
load_real_signal (gpointer data)
{
GModule *module;
gpointer ret = NULL;
module = g_module_open ("libc.so", G_MODULE_BIND_LOCAL);
g_module_symbol (module, "signal", &ret);
/* As fallback, let's try bsd_signal */
if (ret == NULL) {
g_warning ("Can't find signal(3) in libc.so!");
g_module_symbol (module, "bsd_signal", &ret);
}
g_module_close (module);
return ret;
}
__sighandler_t bsd_signal(int signum, __sighandler_t handler) __attribute__((weak));
__sighandler_t bsd_signal(int signum, __sighandler_t handler)
{
static GOnce gonce = G_ONCE_INIT;
__sighandler_t (*real_signal) (int signum, __sighandler_t handler);
g_once (&gonce, load_real_signal, NULL);
real_signal = gonce.retval;
g_assert (real_signal != NULL);
return real_signal(signum, handler);
}
static jobject _context = NULL;
static jobject _class_loader = NULL;
static JavaVM *_java_vm = NULL;
static GstClockTime _priv_gst_info_start_time;
#define GST_G_IO_MODULE_DECLARE(name) \
extern void G_PASTE(g_io_, G_PASTE(name, _load)) (gpointer data)
#define GST_G_IO_MODULE_LOAD(name) \
G_PASTE(g_io_, G_PASTE(name, _load)) (NULL)
/* Call this function to load GIO modules */
void
gst_android_load_gio_modules (void)
{
GTlsBackend *backend;
const gchar *ca_certs;
GST_G_IO_MODULE_LOAD(openssl);
ca_certs = g_getenv ("CA_CERTIFICATES");
backend = g_tls_backend_get_default ();
if (backend && ca_certs) {
GTlsDatabase *db;
GError *error = NULL;
db = g_tls_file_database_new (ca_certs, &error);
if (db) {
g_tls_backend_set_default_database (backend, db);
g_object_unref (db);
} else {
g_warning ("Failed to create a database from file: %s",
error ? error->message : "Unknown");
}
}
}
static void
glib_print_handler (const gchar * string)
{
__android_log_print (ANDROID_LOG_INFO, "GLib+stdout", "%s", string);
}
static void
glib_printerr_handler (const gchar * string)
{
__android_log_print (ANDROID_LOG_ERROR, "GLib+stderr", "%s", string);
}
/* Based on GLib's default handler */
#define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \
(wc == 0x7f) || \
(wc >= 0x80 && wc < 0xa0)))
#define FORMAT_UNSIGNED_BUFSIZE ((GLIB_SIZEOF_LONG * 3) + 3)
#define STRING_BUFFER_SIZE (FORMAT_UNSIGNED_BUFSIZE + 32)
#define ALERT_LEVELS (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)
#define DEFAULT_LEVELS (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE)
#define INFO_LEVELS (G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG)
static void
escape_string (GString * string)
{
const char *p = string->str;
gunichar wc;
while (p < string->str + string->len) {
gboolean safe;
wc = g_utf8_get_char_validated (p, -1);
if (wc == (gunichar) - 1 || wc == (gunichar) - 2) {
gchar *tmp;
guint pos;
pos = p - string->str;
/* Emit invalid UTF-8 as hex escapes
*/
tmp = g_strdup_printf ("\\x%02x", (guint) (guchar) * p);
g_string_erase (string, pos, 1);
g_string_insert (string, pos, tmp);
p = string->str + (pos + 4); /* Skip over escape sequence */
g_free (tmp);
continue;
}
if (wc == '\r') {
safe = *(p + 1) == '\n';
} else {
safe = CHAR_IS_SAFE (wc);
}
if (!safe) {
gchar *tmp;
guint pos;
pos = p - string->str;
/* Largest char we escape is 0x0a, so we don't have to worry
* about 8-digit \Uxxxxyyyy
*/
tmp = g_strdup_printf ("\\u%04x", wc);
g_string_erase (string, pos, g_utf8_next_char (p) - p);
g_string_insert (string, pos, tmp);
g_free (tmp);
p = string->str + (pos + 6); /* Skip over escape sequence */
} else
p = g_utf8_next_char (p);
}
}
static void
glib_log_handler (const gchar * log_domain, GLogLevelFlags log_level,
const gchar * message, gpointer user_data)
{
gchar *string;
GString *gstring;
const gchar *domains;
gint android_log_level;
gchar *tag;
if ((log_level & DEFAULT_LEVELS) || (log_level >> G_LOG_LEVEL_USER_SHIFT))
goto emit;
domains = g_getenv ("G_MESSAGES_DEBUG");
if (((log_level & INFO_LEVELS) == 0) ||
domains == NULL ||
(strcmp (domains, "all") != 0 && (!log_domain
|| !strstr (domains, log_domain))))
return;
emit:
if (log_domain)
tag = g_strdup_printf ("GLib+%s", log_domain);
else
tag = g_strdup ("GLib");
switch (log_level & G_LOG_LEVEL_MASK) {
case G_LOG_LEVEL_ERROR:
android_log_level = ANDROID_LOG_ERROR;
break;
case G_LOG_LEVEL_CRITICAL:
android_log_level = ANDROID_LOG_ERROR;
break;
case G_LOG_LEVEL_WARNING:
android_log_level = ANDROID_LOG_WARN;
break;
case G_LOG_LEVEL_MESSAGE:
android_log_level = ANDROID_LOG_INFO;
break;
case G_LOG_LEVEL_INFO:
android_log_level = ANDROID_LOG_INFO;
break;
case G_LOG_LEVEL_DEBUG:
android_log_level = ANDROID_LOG_DEBUG;
break;
default:
android_log_level = ANDROID_LOG_INFO;
break;
}
gstring = g_string_new (NULL);
if (!message) {
g_string_append (gstring, "(NULL) message");
} else {
GString * msg = g_string_new (message);
escape_string (msg);
g_string_append (gstring, msg->str);
g_string_free (msg, TRUE);
}
string = g_string_free (gstring, FALSE);
__android_log_print (android_log_level, tag, "%s", string);
g_free (string);
g_free (tag);
}
static void
gst_debug_logcat (GstDebugCategory * category, GstDebugLevel level,
const gchar * file, const gchar * function, gint line,
GObject * object, GstDebugMessage * message, gpointer unused)
{
GstClockTime elapsed;
gint android_log_level;
gchar *tag;
if (level > gst_debug_category_get_threshold (category))
return;
elapsed = GST_CLOCK_DIFF (_priv_gst_info_start_time,
gst_util_get_timestamp ());
switch (level) {
case GST_LEVEL_ERROR:
android_log_level = ANDROID_LOG_ERROR;
break;
case GST_LEVEL_WARNING:
android_log_level = ANDROID_LOG_WARN;
break;
case GST_LEVEL_INFO:
android_log_level = ANDROID_LOG_INFO;
break;
case GST_LEVEL_DEBUG:
android_log_level = ANDROID_LOG_DEBUG;
break;
default:
android_log_level = ANDROID_LOG_VERBOSE;
break;
}
tag = g_strdup_printf ("GStreamer+%s",
gst_debug_category_get_name (category));
if (object) {
gchar *obj;
if (GST_IS_PAD (object) && GST_OBJECT_NAME (object)) {
obj = g_strdup_printf ("<%s:%s>", GST_DEBUG_PAD_NAME (object));
} else if (GST_IS_OBJECT (object) && GST_OBJECT_NAME (object)) {
obj = g_strdup_printf ("<%s>", GST_OBJECT_NAME (object));
} else if (G_IS_OBJECT (object)) {
obj = g_strdup_printf ("<%s@%p>", G_OBJECT_TYPE_NAME (object), object);
} else {
obj = g_strdup_printf ("<%p>", object);
}
__android_log_print (android_log_level, tag,
"%" GST_TIME_FORMAT " %p %s:%d:%s:%s %s\n",
GST_TIME_ARGS (elapsed), g_thread_self (),
file, line, function, obj, gst_debug_message_get (message));
g_free (obj);
} else {
__android_log_print (android_log_level, tag,
"%" GST_TIME_FORMAT " %p %s:%d:%s %s\n",
GST_TIME_ARGS (elapsed), g_thread_self (),
file, line, function, gst_debug_message_get (message));
}
g_free (tag);
}
static gboolean
get_application_dirs (JNIEnv * env, jobject context, gchar ** cache_dir,
gchar ** files_dir)
{
jclass context_class;
jmethodID get_cache_dir_id, get_files_dir_id;
jclass file_class;
jmethodID get_absolute_path_id;
jobject dir;
jstring abs_path;
const gchar *abs_path_str;
*cache_dir = *files_dir = NULL;
context_class = (*env)->GetObjectClass (env, context);
if (!context_class) {
return FALSE;
}
get_cache_dir_id =
(*env)->GetMethodID (env, context_class, "getCacheDir",
"()Ljava/io/File;");
get_files_dir_id =
(*env)->GetMethodID (env, context_class, "getFilesDir",
"()Ljava/io/File;");
if (!get_cache_dir_id || !get_files_dir_id) {
(*env)->DeleteLocalRef (env, context_class);
return FALSE;
}
file_class = (*env)->FindClass (env, "java/io/File");
if (!file_class) {
(*env)->DeleteLocalRef (env, context_class);
return FALSE;
}
get_absolute_path_id =
(*env)->GetMethodID (env, file_class, "getAbsolutePath",
"()Ljava/lang/String;");
if (!get_absolute_path_id) {
(*env)->DeleteLocalRef (env, context_class);
(*env)->DeleteLocalRef (env, file_class);
return FALSE;
}
dir = (*env)->CallObjectMethod (env, context, get_cache_dir_id);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
(*env)->DeleteLocalRef (env, context_class);
(*env)->DeleteLocalRef (env, file_class);
return FALSE;
}
if (dir) {
abs_path = (*env)->CallObjectMethod (env, dir, get_absolute_path_id);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
(*env)->DeleteLocalRef (env, dir);
(*env)->DeleteLocalRef (env, context_class);
(*env)->DeleteLocalRef (env, file_class);
return FALSE;
}
abs_path_str = (*env)->GetStringUTFChars (env, abs_path, NULL);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
(*env)->DeleteLocalRef (env, abs_path);
(*env)->DeleteLocalRef (env, dir);
(*env)->DeleteLocalRef (env, context_class);
(*env)->DeleteLocalRef (env, file_class);
return FALSE;
}
*cache_dir = abs_path ? g_strdup (abs_path_str) : NULL;
(*env)->ReleaseStringUTFChars (env, abs_path, abs_path_str);
(*env)->DeleteLocalRef (env, abs_path);
(*env)->DeleteLocalRef (env, dir);
}
dir = (*env)->CallObjectMethod (env, context, get_files_dir_id);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
(*env)->DeleteLocalRef (env, context_class);
(*env)->DeleteLocalRef (env, file_class);
return FALSE;
}
if (dir) {
abs_path = (*env)->CallObjectMethod (env, dir, get_absolute_path_id);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
(*env)->DeleteLocalRef (env, dir);
(*env)->DeleteLocalRef (env, context_class);
(*env)->DeleteLocalRef (env, file_class);
return FALSE;
}
abs_path_str = (*env)->GetStringUTFChars (env, abs_path, NULL);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
(*env)->DeleteLocalRef (env, abs_path);
(*env)->DeleteLocalRef (env, dir);
(*env)->DeleteLocalRef (env, context_class);
(*env)->DeleteLocalRef (env, file_class);
return FALSE;
}
*files_dir = files_dir ? g_strdup (abs_path_str) : NULL;
(*env)->ReleaseStringUTFChars (env, abs_path, abs_path_str);
(*env)->DeleteLocalRef (env, abs_path);
(*env)->DeleteLocalRef (env, dir);
}
(*env)->DeleteLocalRef (env, file_class);
(*env)->DeleteLocalRef (env, context_class);
return TRUE;
}
jobject
gst_android_get_application_context (void)
{
return _context;
}
jobject
gst_android_get_application_class_loader (void)
{
return _class_loader;
}
JavaVM *
gst_android_get_java_vm (void)
{
return _java_vm;
}
static gboolean
init (JNIEnv *env, jobject context)
{
jclass context_cls = NULL;
jmethodID get_class_loader_id = 0;
jobject class_loader = NULL;
context_cls = (*env)->GetObjectClass (env, context);
if (!context_cls) {
return FALSE;
}
get_class_loader_id = (*env)->GetMethodID (env, context_cls,
"getClassLoader", "()Ljava/lang/ClassLoader;");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
return FALSE;
}
class_loader = (*env)->CallObjectMethod (env, context, get_class_loader_id);
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
return FALSE;
}
if (_context) {
(*env)->DeleteGlobalRef (env, _context);
}
_context = (*env)->NewGlobalRef (env, context);
if (_class_loader) {
(*env)->DeleteGlobalRef (env, _class_loader);
}
_class_loader = (*env)->NewGlobalRef (env, class_loader);
return TRUE;
}
void
gst_android_init (JNIEnv * env, jobject context)
{
gchar *cache_dir;
gchar *files_dir;
gchar *registry;
GError *error = NULL;
if (!init (env, context)) {
__android_log_print (ANDROID_LOG_INFO, "GStreamer",
"GStreamer failed to initialize");
}
if (gst_is_initialized ()) {
__android_log_print (ANDROID_LOG_INFO, "GStreamer",
"GStreamer already initialized");
return;
}
if (!get_application_dirs (env, context, &cache_dir, &files_dir)) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Failed to get application dirs");
}
if (cache_dir) {
g_setenv ("TMP", cache_dir, TRUE);
g_setenv ("TEMP", cache_dir, TRUE);
g_setenv ("TMPDIR", cache_dir, TRUE);
g_setenv ("XDG_RUNTIME_DIR", cache_dir, TRUE);
g_setenv ("XDG_CACHE_HOME", cache_dir, TRUE);
registry = g_build_filename (cache_dir, "registry.bin", NULL);
g_setenv ("GST_REGISTRY", registry, TRUE);
g_free (registry);
//g_setenv ("GST_REGISTRY_REUSE_PLUGIN_SCANNER", "no", TRUE);
/* TODO: Should probably also set GST_PLUGIN_SCANNER and GST_PLUGIN_SYSTEM_PATH */
}
g_setenv ("GST_PLUGIN_SCANNER", "/data/data/com.example.gstdemo/files/exe/gst-plugin-scanner", TRUE);
g_setenv ("GST_PLUGIN_PATH", "/data/data/com.example.gstdemo/files/plugins", TRUE);
if (files_dir) {
gchar *fontconfig, *certs;
g_setenv ("HOME", files_dir, TRUE);
g_setenv ("XDG_DATA_DIRS", files_dir, TRUE);
g_setenv ("XDG_CONFIG_DIRS", files_dir, TRUE);
g_setenv ("XDG_CONFIG_HOME", files_dir, TRUE);
g_setenv ("XDG_DATA_HOME", files_dir, TRUE);
fontconfig = g_build_filename (files_dir, "fontconfig", NULL);
g_setenv ("FONTCONFIG_PATH", fontconfig, TRUE);
g_free (fontconfig);
certs = g_build_filename (files_dir, "ssl", "certs", "ca-certificates.crt", NULL);
g_setenv ("CA_CERTIFICATES", certs, TRUE);
g_free (certs);
}
g_free (cache_dir);
g_free (files_dir);
/* Set GLib print handlers */
g_set_print_handler (glib_print_handler);
g_set_printerr_handler (glib_printerr_handler);
g_log_set_default_handler (glib_log_handler, NULL);
/* Disable this for releases if performance is important
* or increase the threshold to get more information */
gst_debug_set_active (TRUE);
gst_debug_set_default_threshold (9);
g_setenv("G_MESSAGES_DEBUG","all",TRUE);
gst_debug_remove_log_function (gst_debug_log_default);
gst_debug_add_log_function ((GstLogFunction) gst_debug_logcat, NULL, NULL);
/* get time we started for debugging messages */
_priv_gst_info_start_time = gst_util_get_timestamp ();
if (!gst_init_check (NULL, NULL, &error)) {
gchar *message = g_strdup_printf ("GStreamer initialization failed: %s",
error && error->message ? error->message : "(no message)");
jclass exception_class = (*env)->FindClass (env, "java/lang/Exception");
__android_log_print (ANDROID_LOG_ERROR, "GStreamer", "%s", message);
(*env)->ThrowNew (env, exception_class, message);
g_free (message);
return;
}
gst_android_load_gio_modules ();
__android_log_print (ANDROID_LOG_INFO, "GStreamer",
"GStreamer initialization complete");
}
static void
gst_android_init_jni (JNIEnv * env, jobject gstreamer, jobject context)
{
gst_android_init (env, context);
}
static JNINativeMethod native_methods[] = {
{"nativeInit", "(Landroid/content/Context;)V", (void *) gst_android_init_jni}
};
jint
JNI_OnLoad (JavaVM * vm, void * reserved)
{
JNIEnv *env = NULL;
GModule *module;
if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Could not retrieve JNIEnv");
return 0;
}
jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/GStreamer");
if (!klass) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Could not retrieve class org.freedesktop.gstreamer.GStreamer");
return 0;
}
if ((*env)->RegisterNatives (env, klass, native_methods,
G_N_ELEMENTS (native_methods))) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Could not register native methods for org.freedesktop.gstreamer.GStreamer");
return 0;
}
/* Remember Java VM */
_java_vm = vm;
/* Tell the androidmedia plugin about the Java VM if we can */
module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
if (module) {
void (*set_java_vm) (JavaVM *) = NULL;
if (g_module_symbol (module, "gst_amc_jni_set_java_vm",
(gpointer *) & set_java_vm) && set_java_vm) {
set_java_vm (vm);
}
g_module_close (module);
}
return JNI_VERSION_1_4;
}
void
JNI_OnUnload (JavaVM * vm, void * reversed)
{
JNIEnv *env = NULL;
if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Could not retrieve JNIEnv");
return;
}
if (_context) {
(*env)->DeleteGlobalRef (env, _context);
_context = NULL;
}
if (_class_loader) {
(*env)->DeleteGlobalRef (env, _class_loader);
_class_loader = NULL;
}
_java_vm = NULL;
}
8 loadLibrary动态so
static {
System.loadLibrary("gstand");
System.loadLibrary("gstjni");
nativeClassInit();
}
9.从assets下拷贝字体/certs/插件so到程序安装目录files下
package org.freedesktop.gstreamer;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Properties;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Build;
import android.util.Log;
public class GStreamer {
private static native void nativeInit(Context context) throws Exception;
public static void init(Context context) throws Exception {
copyCaCertificates(context);
copyFonts(context);
copyplugins(context);
copyexe(context);
nativeInit(context);
}
private static void copyFonts(Context context) {
AssetManager assetManager = context.getAssets();
File filesDir = context.getFilesDir();
File fontsFCDir = new File (filesDir, "fontconfig");
File fontsDir = new File (fontsFCDir, "fonts");
File fontsCfg = new File (fontsFCDir, "fonts.conf");
fontsDir.mkdirs();
try {
/* Copy the config file */
copyFile (assetManager, "fontconfig/fonts.conf", fontsCfg);
/* Copy the fonts */
for(String filename : assetManager.list("fontconfig/fonts/truetype")) {
File font = new File(fontsDir, filename);
copyFile (assetManager, "fontconfig/fonts/truetype/" + filename, font);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static String execCmd(String cmd) {
DataOutputStream dos = null;
String result = "";
String lastline = " ";
try {
Process process = Runtime.getRuntime().exec(cmd);// 经过Root处理的android系统即有su命令
//get the err line
InputStream stderr = process.getErrorStream();
InputStreamReader isrerr = new InputStreamReader(stderr);
BufferedReader brerr = new BufferedReader(isrerr);
//get the output line
InputStream outs = process.getInputStream();
InputStreamReader isrout = new InputStreamReader(outs);
BufferedReader brout = new BufferedReader(isrout);
String errline = null;
// get the whole error message
String line = "";
while ( (line = brerr.readLine()) != null)
{
result += line;
result += "/n";
}
if( result != "" )
{
// put the result string on the screen
Log.i("GStreamer"," the str error message " + result.toString());
}
// get the whole standard output string
while ( (line = brout.readLine()) != null)
{
lastline = line;
result += line;
result += "/n";
}
if( result != "" )
{
// put the result string on the screen
Log.i("GStreamer"," the standard output message " + lastline.toString());
}
}catch(Throwable t)
{
t.printStackTrace();
}
return lastline.toString();
}
private static void copyplugins(Context context) {
AssetManager assetManager = context.getAssets();
File filesDir = context.getFilesDir();
File pluginsDir = new File (filesDir, "plugins");
//Properties properties = System.getProperties();
// String arch = properties.getProperty("os.arch");
// case"arm64-v8a": return"aarch64";
// case"armeabi-v7a": return"arm";
// case"x86_64": return"x86_64";
// case"x86": return"i686"
//Log.i ("GStreamer", "arch:" + Build.CPU_ABI); //模拟读取的值不对
String cpu_abi = execCmd("getprop ro.product.cpu.abi");
if(pluginsDir.exists()){
Log.i ("GStreamer", "plugins already exists");
return;
}
pluginsDir.mkdirs();
String plugpath = new String("plugins") + "/" + cpu_abi;
Log.i ("GStreamer", "arch:" +cpu_abi);
try {
/* Copy the sos */
for(String filename : assetManager.list(plugpath)) {
// Log.i ("GStreamer", "filename:" + filename);
File so = new File(pluginsDir, filename);
copyFile (assetManager, plugpath + "/" + filename, so);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void copyexe(Context context) {
AssetManager assetManager = context.getAssets();
File filesDir = context.getFilesDir();
File exeDir = new File (filesDir, "exe");
File exe = new File (exeDir, "gst-plugin-scanner");
exeDir.mkdirs();
try {
/* Copy the exe */
copyFile (assetManager, "exe/gst-plugin-scanner", exe);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void copyCaCertificates(Context context) {
AssetManager assetManager = context.getAssets();
File filesDir = context.getFilesDir();
File sslDir = new File (filesDir, "ssl");
File certsDir = new File (sslDir, "certs");
File certs = new File (certsDir, "ca-certificates.crt");
certsDir.mkdirs();
try {
/* Copy the certificates file */
copyFile (assetManager, "ssl/certs/ca-certificates.crt", certs);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void copyFile(AssetManager assetManager, String assetPath, File outFile) throws IOException {
InputStream in = null;
OutputStream out = null;
IOException exception = null;
if (outFile.exists())
outFile.delete();
try {
in = assetManager.open(assetPath);
out = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.flush();
} catch (IOException e) {
exception = e;
} finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
if (exception == null)
exception = e;
}
if (out != null)
try {
out.close();
} catch (IOException e) {
if (exception == null)
exception = e;
}
if (exception != null)
throw exception;
}
}
}