使用gstreamer动态库搭建安卓环境播放器

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(&current_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;
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值