Summary 1.

To do a good job, must first sharpen his device in this paper on the basis of the application of explain reason, so we all around “pragmatism”, the NDK development is a very basic in the development of audio and video, tools, light can Java development is to learn the ears of the audio and video, into the nature of pictures and audio, all need to understand its structure and reason, There are audio and video core libraries are basically C, C++ development, so cross compilation so that we must master.

If you want to use these libraries for Android or iOS, you must learn how to cross-compile them. Then master the CMAKE compilation process before you can integrate them into your project. Learn this article, you will understand:

  • What is cross-compilation?
  • CMake runs the process
  • What are the key points in JNI development
  • How to analyze a native crash
  • How do I cross-compile for large projects

2. Cross-compile

What is cross-compilation? For those of you who haven’t done embedded development, it may seem strange that some Android development, if it doesn’t involve much in the JNI aspect, is not clear what cross-compilation is. In general, cross-compilation is the generation of code on one platform that can be executed on another platform. For example, the executable file on Windows is.exe, but the.exe file will not run on Android, so if I want to compile a library file that will be loaded on the Android platform, the compilation process is cross-compilation.

Is cross-compilation really that important for audio and video developers? To be clear, it is very important, because the core development logic of audio and video is in the Native layer, and the Java layer is only an interface API and simple packaging, so the interaction between JNI and Native is inevitable, and some popular libraries are used in a large number of audio and video. Libraries such as FFmpeg, VLC, iJkPlayer, Gstreamer, OpenSSL, libx264, etc. need to be cross-compiled in order to generate libraries that can be loaded on the Android platform. How are these libraries cross-compiled? This will be covered later in this chapter.

2.1 Cross-compilation

For those of you who have done JNI development, you know that JNI code is compiled using the NDK tool chain, which includes the cross-compilation tool chain.The cross-compilation toolchain we’re talking about is in the ToolChains folder here, so you can take a closer look:These directories represent compilation toolchains for different CPU architectures, such as ARM-Linux-Androideabi-4.9 for arm architecture and x86-4.9 for x86 architecture.

  • Arm – Linux – androideabi – 4.9
  • Aarch64 – Linux – android 4.9
  • Mipsel – Linux – android 4.9
  • Mips64el – Linux – android 4.9
  • X86-4.9
  • X86_64-4.9 –

In fact, this is targeted at different CPU architecture platforms. We are familiar with arm platform. Android phones are basically based on ARM platform, while x86 is mainly PC. MIPS is an acronym for Original without Interlocked Piped Stages Architecture, which is a processor architecture with condensed instruction sets mainly used in personal entertainment devices. The three instruction sets above have their own advantages and disadvantages.

2.1.1 ARM instruction set

ARM is fully Advanced RISC Machine, it is a compact instruction set, ARM processor features are:

  • Small size, low power consumption, low cost and high performance, ARM is also the most widely used chip architecture in embedded devices
  • With extensive use of registers, instruction execution is much faster, and most of its data operations are performed in registers
  • The addressing method is flexible and simple with high execution efficiency
  • Fixed instruction length
  • Pipelined processing

2.1.2 X86 instruction set

X86 is an instruction architecture of microprocessor architecture designed by Intel. X86 architecture dominates PC. Different from ARM, X86 uses CISC architecture, which is complex instruction set computer. The operations in each instruction are also sequential.

Sequential execution has the advantages of simple control, low utilization efficiency and slow execution speed.

2.1.3 MIPS instruction set

MIPS is a fully developed RISC instruction set architecture developed by MIPS with the following features:

  • Contains a large number of registers, instruction sets, and characters
  • Visual pipeline delay handling
  • The energy consumption is very low.

But unfortunately, recently I heard that MIPS has begun to embrace ARM, and there are fewer choices in the market. I have to say it is sad.

2.2 Cross-compile tool chain

Now that we have covered the differences between different architectures, we can look at the specific cross-compilation tools available on the ARM platform.We introduce a few common tools:

  • arm-linux-androideabi-gcc : /urs/include/stdio.h. /urs/include/stdio.h. /urs/include/stdio.h. /urs/include/stdio.h.
  • Arm-linux-androideabi-g ++ : cross-compiler to compile CPP files
  • Arm-linux-androideabi-addr2line: a tool to reverse solve the stack of Native Crash on Android using addr2line
  • Arm-linux-androideabi-ld: cross-linker that links compiled files to files running on the ARM platform
  • Arm-linux-androideabi-readelf: a tool to check. Elf files. The compiler does not run because the processor size side is corresponding to the compiled program size side.
  • Arm-linux-androideabi-objdump: Disassembles the executable file and saves the input to the text. You can view the underlying assembly code.
  • Arm-linux-androideabi-ar: Can archive multiple relocated target modules as a function library file.

Cross-compilation has a complete process:From the point of view of the cross-compilation project, it is no different from the normal compilation, but there are two points:

  • Cross-compilation uses cross-compilation tools
  • The library or header file to cross-compile the link must be explicitly specified

For example, during normal GCC compilation, some library functions are already specified in the system’s PATH PATH. We do not need to specify them separately, but we must specify them explicitly when cross-compiling.

To understand cross-compilation, use an example:

A very simple C program

#include <stdio.h>

int main(int argc, char** argv) {
	printf("Hello, jeffmony\n");
	return 0;
}
Copy the code

The output is as follows:

jeffli@admindeMacBook-Pro 02-files % gcc hello.c -o hello
jeffli@admindeMacBook-Pro 02-files % ./hello 
Hello, jeffmony
Copy the code

GCC: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c: hello.c

$ANDROID_NDK toolchains/arm - Linux - androideabi - 4.9 / prebuilt/Darwin - x86_64 / bin/arm - Linux - androideabi - GCC \ --sysroot=$ANDROID_NDK/platforms/android-24/arch-arm hello.c -o hello-androidCopy the code

Arm-linux-androideabi-gcc, arm-linux-androideabi-gcc, arm-linux-androideabi-gcc, arm-linux-androideabi-gcc, arm-linux-androideabi-gcc, arm-linux-androideabi-gcc, arm-linux-androideabi-gccIt contains include and Lib folders, which represent libraries of headers for the Android platform, and any files we compile may reference the libraries under this file rack. This link cannot be lost. Hello-android is compiled to run on Android phones.

2.3 Independent Tool chain

Standalone toolchains are rarely talked about these days, but for large projects, standalone toolchains have their own advantages because they are really flexible. The NDK provides the make_standalone_toolchain-py script so that you can perform a custom toolchain installation from the command line. The script is in the $ANDROID_NDK/build/tools/ directory:

Create a separate toolchain:

$ANDROID_NDK/build/tools/make_standalone_toolchain.py \
    --arch arm --api 21 --install-dir /tmp/my-android-toolchain

Copy the code

This command creates a directory called/TMP/my-Android-toolchain/that contains a copy of Android-21 / ARCH-arm sysroot, as well as a copy of toolchain binaries for 32-bit ARM targets.

Note that the toolchain binaries do not depend on or contain host-specific paths. In other words, you can install it anywhere, or even change its location as needed.

Why is the standalone tool chain mentioned specifically? Because many of the larger projects we’re familiar with, such as iJkPlayer, use separate toolchains with very flexible controls. To be honest, I don’t recommend using a separate tool chain, because it generates a lot of extra files, and it’s not very easy to link, and you write a lot of extra code, which is confusing, but it gives you an option.

3.CMake

If you have done NDK programming on Android5.0, you will need to configure Android. Mk and Application. Mk. It’s not quite the same as CMake compilation today. The current CMake architecture looks simpler overall, lowering the threshold for NDK development.

What is CMake?

CMake is an open source, cross-platform family of tools designed to build, test, and package software. CMake is used to control the software compilation process using simple platform – and compiler-independent configuration files and to generate native Makefiles and workspaces that can be used in the compiler environment of your choice. The CMake tool suite was created by Kitware in response to the need for a powerful cross-platform build environment for open source projects such as ITK and VTK.

CMake is actually a set of project organizing tools that can integrate the entire compilation process. Android also introduced this mechanism, which is very useful.

To create a project that includes native code, focus on these two constructs:

In the main directory, CPP and Java folders are created. CPP is to write native code, and Java is the upper code. There is a cmakelists. TXT file under CPP folder, which is a tool to organize CPP files.

Here is an example of cmakelists.txt:

# Sets the minimum version of CMake required to build the native
# library.
cmake_minimum_required(VERSION 3.4.1)

# Creates the project's shared lib: libnative-lib.so.
# The lib is loaded by this project's Java code in MainActivity.java:
#     System.loadLibrary("native-lib");
# The lib name in both places must match.
add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

find_library(log-lib
             log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

Copy the code

Add_library contains all the native codes in our CPP, and some header files need not be attached. Native-lib is the name of our target library, and the compiled target library is libnative-lib.so. The target_link_libraries parameter specifies which libraries to link to compile libnative-lib.so. Large audio and video projects may link to additional libraries, which will be understood as we develop audio and video projects.

Build. gradle is the core hub for organizing cmakelists.txt into your project.

// Sets up parameters for both jni build and cmake.
// For a complete list of parameters, see
// developer.android.com/ndk/guides/cmake.html#variables
externalNativeBuild {
   cmake {
       // cppFlags are configured according to your selection
       // of "Customize C++ Support", in this codelab's
       //    "Create a Sample App with the C++ Template",
       //    step 6
       cppFlags "-std=c++17"
   }
......

// Specifies the location of the top level CMakeLists.txt
// The path is relative to the hosting directory
// of this build.gradle file
externalNativeBuild {
   cmake {
       path "src/main/cpp/CMakeLists.txt"
       version "3.10.2"

   }
}

Copy the code

The cmakelists. TXT file can be placed anywhere, as long as it is specified in build.gradle.

Also don’t forget the System.loadLibrary library in the Java layer.

After we compile, the corresponding SO will be generated in the build directory

4. Comprehensive analysis of JNI

JNI is the first part of the knowledge we need to understand when we contact audio and video development. This section will explain the relevant knowledge of JNI from the simple to the deep to help you quickly understand the relevant core knowledge of JNI. JNI is the Java Native Interface provided by Android. It is a way for Java and Native code (C and C++) to interact and connect.

4.1 Data types in JNI

4.1.1 Basic Types

In Java type Type in the Native placeholder
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

Some special variables are also defined in jni.h:

#define JNI_FALSE 0 #define JNI_TRUE 1 #define JNI_VERSION_1_1 0x00010001 #define JNI_VERSION_1_2 0x00010002 #define JNI_VERSION_1_4 0x00010004 #define JNI_VERSION_1_6 0x00010006 #define JNI_OK (0) /* no error */ #define JNI_ERR (-1) /* generic error */ #define JNI_EDETACHED (-2) /* thread detached from the VM */ #define JNI_EVERSION (-3) /* JNI version error */ #define JNI_ENOMEM (-4) /* Out of memory */ #define JNI_EEXIST (-5) /* VM already created */ #define JNI_EINVAL  (-6) /* Invalid argument */ #define JNI_COMMIT 1 /* copy content, do not free buffer */ #define JNI_ABORT 2 /* free buffer w/o copying back */Copy the code

4.1.2 Reference Types

Reference types in Java are well known, and JNI, as the Java and Native interaction layer, must have similar reference types.

JNI in name Corresponding Java name
jobject Java.lang.object Object
jclass Java. Lang. Class object
jstring Java. Lang. String object
jthrowable Java. Lang. Throwable object

Above are the generic reference types, as well as some array reference types

JNI in name Corresponding Java name
jarray Generic array reference types
jobjectArray The object array
jbooleanArray Boolean array
jbyteArray Byte array
jcharArray Char array
jshortArray Short array
jintArray An array of int
jlongArray Long array
jfloatArray A float array
jdoubleArray A double array
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};

typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;
typedef _jthrowable*    jthrowable;
typedef _jobject*       jweak;
Copy the code

Jclass, jmethodID, and jfieldID To access the fields referenced by Java objects in native code, perform the following operations:

  • Use FindClass to get a class object reference for a class
  • Use GetFieldID to get the field ID of the field
  • Use the appropriate function to get the contents of the field, such as GetIntFieldID

If you need to call methods of class object, have a class methods and instance methods, for instance method, you first need to get the class object reference, and then obtaining method ID, method ID is just internal pointer to the runtime data structure, usually by string search method to find the corresponding method, find, call method can very quickly.

auto array_list_class = env->FindClass("java/util/ArrayList"); auto array_list_init_id = env->GetMethodID(array_list_class, "<init>", "()V"); auto array_list_obj = env->NewObject(array_list_class, array_list_init_id); auto array_list_add_method_id = env->GetMethodID(array_list_class, "add", "(Ljava/lang/Object;) Z");Copy the code

4.1.3 Type Signature

A type signature is a type identifier for interaction between JNI and the upper layer. Different characters identify different types.

Type signatures The corresponding Java type
Z boolean
B byte
C char
S short
I int
J long
F float
D double

For class types, the structure is: “L class name;” , for example, Ljava/lang/String; That means String

Function methods are represented as: (argument type) return type, for example, for an int getResult(int argv, String argv) can be written as: (ILjava/lang/String;) I

VideoInfo we define a class, for example, in the package name. Com jeffmony. The video below, that the corresponding type identification in the JNI Lcom/jeffmony/video/VideoInfo;

4.1.4 JavaVM and JNIEnv

JNI defines two key data structures, JavaVM and JNIEnv, both of which are essentially second-level Pointers to function tables. JavaVM is highly process-dependent. Android applications can only have one JavaVM object per process. The important thing to keep in mind here is that JavaVM has one value in a process, which is important, and JNI multithreading needs this as a foundation.

struct _JavaVM {
    const struct JNIInvokeInterface* functions;

#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
Copy the code

JNIEnv provides most of the JNI functions. Your native functions all receive JNIEnv as their first argument. JNIEnv = JNIEnv = JNIEnv = JNIEnv = JNIEnv = JNIEnv = JNIEnv = JNIEnv The answer is to get the current thread’s JNIEnv from JavaVM’s GetEnv, which is explained in more detail below. You can understand it here. JNIEnv below is the source code, more, we remember commonly used on the line, other need to use the time to query the corresponding API.

struct _JNIEnv { /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions; #if defined(__cplusplus) jclass GetObjectClass(jobject obj) { return functions->GetObjectClass(this, obj); } jobject NewGlobalRef(jobject obj) { return functions->NewGlobalRef(this, obj); } void DeleteGlobalRef(jobject globalRef) { functions->DeleteGlobalRef(this, globalRef); } void DeleteLocalRef(jobject localRef) { functions->DeleteLocalRef(this, localRef); } jboolean IsSameObject(jobject ref1, jobject ref2) { return functions->IsSameObject(this, ref1, ref2); } #endif /*__cplusplus*/};Copy the code

4.2 What are Static and dynamic Registration

We know that libraries loaded in Android are dynamic libraries that need to be loaded in Java layer code system.loadLibrary (“native-lib”); Libnative-lib. so will be generated to initialize and load the dynamic library. There are generally two registration methods for native functions, dynamic registration and static registration. One of the most frequently asked questions in the interview was the difference between Android static and dynamic registration. Dynamic registration is to register JNI classes and methods at run time. Static registration is to locate JNI functions by specific method names without explicit registration.

4.2.1 Dynamic Registration

Dynamic registration is the runtime method of loading libraries. There are two methods of loading libraries:

  • RegisterNatives displays the native method registration
  • The runtime uses DLSYM for dynamic lookup

RegisterNatives have the advantage of pre-checking for symbols and obtaining a smaller and faster shared library with the JNI_OnLoad callback method.

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);

Copy the code

JNI provides a callback method to register SO, that is JNI_OnLoad. In the JNI_OnLoad callback, you can use RegisterNatives to register all native methods.

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) ! = JNI_OK) { return JNI_ERR; } // Find your class. JNI_OnLoad is called from the correct class loader context for this to work. jclass c = env->FindClass("com/example/app/package/MyClass"); if (c == nullptr) return JNI_ERR; // Register your class' native methods. static const JNINativeMethod methods[] = { {"nativeFoo", "()V", reinterpret_cast<void*>(nativeFoo)}, {"nativeBar", "(Ljava/lang/String; I)Z", reinterpret_cast<void*>(nativeBar)}, }; int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod)); if (rc ! = JNI_OK) return rc; return JNI_VERSION_1_6; }Copy the code

In audio and video development, it is recommended to use the method of dynamic registration, because many signature problems of static registration may need to be found when calling, which is not conducive to finding problems. Or dynamic registration is better, the code is simple, and the registration efficiency is high.

4.2.2 Static Registration

To register statically, native functions need to be named according to a specific naming convention. Let’s use an example to explore this naming convention in detail:

package com.jeffmony.media; 


class Test { 


  private native int func1(double d);

  private static native int func2(double d); 

} 
Copy the code

The corresponding method name generated in JNI is:

JNIEXPORT jint JNICALL Java_com_jeffmony_media_Test_func1(JNIEnv* env, jobject object, jdouble d);

JNIEXPORT jint JNICALL Java_com_jeffmony_media_Test_func2(JNIEnv* env, jclass object, jdouble d);

Copy the code

We can see the specific difference, because we have recorded the path of the current native method in the method name, so we can directly find the corresponding native method according to the method name. But what if the method names are the same? If the method name is the same, it is impossible for the parameter type or parameter number to be the same, so mark the difference in the signature method name. Generally, the type of the parameter is added to the method name. If we add another function to the above class:


class Test { 


  private native int func1(double d);

  private static native int func2(double d); 

  private static native int func2(int n);

} 
Copy the code

The two func2 will be written as:

JNIEXPORT jint JNICALL Java_com_jeffmony_media_Test_func2D(JNIEnv* env, jclass object, jdouble d);

JNIEXPORT jint JNICALL Java_com_jeffmony_media_Test_func2I(JNIEnv* env, jclass object, jint n);

Copy the code

Can you see the difference? C language does not have function overloading, can not appear the same function name of the function, so by the function name with different function parameters to distinguish.

4.3 Global and local References in JNI

4.3.1 Local Reference

Every argument passed to a native method, and almost every object returned by a JNI function, is a “local reference.” This means that a local reference is valid for the duration of the current native method running in the current thread. After the native method returns, the reference is invalid even if the object itself continues to exist.

This applies to all subclasses of Jobject, including JClass, JString, and jarray. It’s kind of like what we call local variables.

4.3.2 Global Reference

If you want to keep a reference for a long time, you must use a global reference. The method of obtaining global reference is through NewGlobalRef and NewWeakGlobalRef functions. In the development of JNI, we use local reference as parameter to get global reference by calling NewGlobalRef. When you’re done, remember to call DeleteGlobalRef to remove the global reference.

jclass localClass = env->FindClass("MyClass"); jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass)); **/ env->DeleteGlobalRef(localClass);Copy the code

Don’t get global references wrong. Multiple calls to NewGlobalRef may yield different values for a global reference. Use IsSameObject to determine if the same object is referenced. Never use == to judge. JNI development is different from Java development, you must remember to release variables, do not have memory leakage problems.

Global references have their own application scenarios, and it is best not to use global references unless they are objects that need to be shared globally, because forgetting to release them can cause serious problems.

4.4 How to implement multithreading in JNI

One scenario we often encounter is that a thread might be opened in JNI, how to call back to the Java layer? JavaVM is shared between processes, so we need to save the JavaVM of the current process during JNI_OnLoad. JNIEnv is thread bound. Each thread has a different JNIEnv, but you can get an instance of the current thread’s JNIEnv from JavaVM.

(1) First obtain the JNIEnv of the current thread by calling javaVM->AttachCurrentThread

if((javaVM->AttachCurrentThread(&env,NULL))! =JNI_OK) { LOGI("%s AttachCurrentThread error failed ",__FUNCTION__); return NULL; }Copy the code

JNIEnv is the object associated with the current thread. We can then call JNI methods from JNIEnv objects.

JavaVM ->DetachCurrentThread unbind JNIEnv

if((javaVM->DetachCurrentThread())! =JNI_OK) { LOGI("%s DetachCurrentThread error failed ",__FUNCTION__); }Copy the code

You can understand this meaning by trying it a few times in practice.

4.5 Analysis of JNI multithreading problems

In audio and video development, many Native Crash problems will be encountered. Here is a very common JNI exception problem, and the specific stack is as follows:

#00 pc 0000000000089908 /apex/com.android.runtime/lib64/bionic/libc.so (abort+168) #01 pc 0000000000552abc /apex/com.android.art/lib64/libart.so (_ZN3art7Runtime5AbortEPKc+2260) #02 pc 0000000000013990 /system/lib64/libbase.so (_ZZN7android4base10SetAborterEONSt3__18functionIFvPKcEEEEN3$_38__invokeES4_+76) #03 pc 0000000000012fb4 /system/lib64/libbase.so (_ZN7android4base10LogMessageD1Ev+320) #04 pc 00000000002f8044 /apex/com.android.art/lib64/libart.so (_ZN3art22IndirectReferenceTable17AbortIfNoCheckJNIERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE+2 24) #05 pc 0000000000389d70 /apex/com.android.art/lib64/libart.so (_ZNK3art22IndirectReferenceTable10GetCheckedEPv+444) #06 pc 0000000000385324 /apex/com.android.art/lib64/libart.so (_ZN3art9JavaVMExt12DecodeGlobalEPv+24) #07 pc 00000000005a6f68 /apex/com.android.art/lib64/libart.so (_ZNK3art6Thread13DecodeJObjectEP8_jobject+144) #08 pc 000000000039ac7c /apex/com.android.art/lib64/libart.so (_ZN3art3JNIILb0EE14GetObjectClassEP7_JNIEnvP8_jobject+612) #09 pc 00000000000d7a74 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so #10 pc 00000000000ed098 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so #11 pc 00000000000f0c04 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so #12 pc 00000000000d7118 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so #13 pc 00000000000d56e4 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so #14 pc 00000000000d3360 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so #15 pc 00000000000d32b4 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so #16 pc 00000000000d3008 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so #17 pc 00000000000eb504 /apex/com.android.runtime/lib64/bionic/libc.so (_ZL15__pthread_startPv+64) #18 pc 000000000008bb0c /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)Copy the code

Abort message is:

JNI ERROR (app bug): attempt to use stale Global 0x3a6a (should be 0x3a62)
Copy the code

This is a very typical problem. The following stack is reported in our own libav_media.so, which is hung in the system. It is not easy to say that it is hung in the system, but it needs to be analyzed carefully.

The first step, of course, is to unstack, using the addr2line introduced above to start unstack, unstack as follows:

_ZN7_JNIEnv14GetObjectClassEP8_jobject / Users/jeffli/Library/Android/SDK/the NDK / 21.1.6352462 toolchains/LLVM prebuilt/Darwin - x86_64 / sysroot/usr/include/jni. H: 585 _ZN6effect6OpenGL12ProcessImageEjPKfS2_Pf /Users/jeffli/sources/OpenGL/OpenGL/gl/opengl.cc:197 _ZN6effect11FrameBuffer12ProcessImageEjiiPKfS2_Pfl /Users/jeffli/sources/OpenGL/OpenGL/gl/frame_buffer.cc:96 _ZN5media11VideoRecord11OnTakePhotoEjii /Users/jeffli/sources/androidvideoeditor/library/src/main/cpp/record/video_record.cc:362 _ZN5media11VideoRecord11OnDrawFrameEv /Users/jeffli/sources/androidvideoeditor/library/src/main/cpp/record/video_record.cc:454 _ZN5media7Message7ExecuteEv /Users/jeffli/sources/androidvideoeditor/library/src/main/cpp/message/message_queue.cc:31 _ZN5media7Handler14ProcessMessageEv /Users/jeffli/sources/androidvideoeditor/library/src/main/cpp/message/handler.cc:83 _ZN5media7Handler18MessageQueueThreadEPv /Users/jeffli/sources/androidvideoeditor/library/src/main/cpp/message/handler.cc:94Copy the code

Let’s first look at what line jni.h–>585 is.

    jclass GetObjectClass(jobject obj)
    { return functions->GetObjectClass(this, obj); }
Copy the code

This function ->GetObjectClass is called directly to libart.so, and we can use this call to determine exactly where in our own code it is called. Following this line of reasoning, the last place we call jni.h is in the next 740 lines

This is where the photo callback in the AUDIO and video SDK comes in. The take_photo_listener_ is jobject, an interface object passed in by the Java layer. Normally we would think of two points.

  • Take_photo_listener_ is a null pointer
  • Take_photo_listener_ Does the multithreaded call exist

A nullpointer is not always possible because the Abort message is not used for nullpointer. A nullpointer must specify a Null Pointer directly. That leaves the latter possibility, which is multi-threaded invocation. Now verify our guess as to when take_photo_listener_ was set.

Setting and calling places are not in the same thread, so there must be multithreading problems in theory.

But light such conjecture is no good, still must have the theory support. It is not difficult to come to this conclusion by analyzing the origin of the Crash. Recommend an Android source query site cs.android.com, the following analysis of the GetObjectClass call link. The jNI loading code is in the Art/Runtime /jni directory of the Android source code:

  • art/runtime/jni/jni_internal.cc—> GetObjectClass
  • art/runtime/scoped_thread_state_changeinl.h—> ScopedObjectAccessAlreadyRunnable::Decode
  • art/runtime/thread.cc—> Thread::DecodeJObject
  • art/runtime/jni/java_vm_ext.cc—> JavaVMExt::DecodeGlobal
  • art/runtime/indirect_reference_table.h—> SynchronizedGet
  • art/runtime/indirect_reference_table.h—> IndirectReferenceTable::Get
  • art/runtime/indirect_reference_table.h—> IndirectReferenceTable::CheckEntry

Why did it end up wrong? Jobject has not been defined as a GlobalObject yet. This is a GlobalObject that has not yet been defined as a GlobalObject. Env ->NewGlobalRef(take_photo_LISTENER) code has not been executed, so the corresponding data cannot be found in the index table.

According to the above analysis, as long as the global ref do a good job of thread safety protection.

How to ensure thread safety in CPP:

  • Use pthread_mux_lock to achieve the mutex lock, to ensure that the same code block or variable mutually exclusive access, there will not be multithreading problems. For example, the solution above could take this approach

5. How to unstack automatically

The above analysis of the complete analysis process of JNI exceptions, for beginners of audio and video development, unstack is a necessary skill, but including official documents and technical articles have a certain threshold, I directly put the tool to unstack, to help you into the state in a second. What tools are needed to unstack? The first thing you need is the addr2line tool, which is in the NDK, so if you go to the section on cross-compilation you can see how addr2line works. For time to erode, so is the dynamic library we compiled. There are two in view. One is stripped SO, which is equivalent to the library file with the symbol table removed after compression. The most important thing to view is the collapse stack. The structure of the collapse stack is as follows:

#16 pc 00000000000d3008 /data/app/~~yvshfyvSUZ46EK1lhAhiTQ==/com.jeffmony.media-aLxfQNnnePl3Xieaq6E1uQ==/lib/arm64/libav_media.so
Copy the code

There is a PC address, as well as the specific crash SO address, which is the core information of the crash stack. As for the crash stack, I recommend you to check out the Google-BreakPad open source library, which is posted here for those interested in learning about it: github.com/google/brea… The core idea is that Linux terminals are sent to the system through signal. When the system receives the interrupt signal of a crash, it knows that there is an irreversible problem and begins to collect stack information.

The PC address and the unstripped so containing the symbol table are provided. We will use addr2line in NDK to unstack. I will paste the automatic script directly, we can use it directly.

A shell script addr2line_tools.sh

# !/bin/sh

python parse_crash.py crash

Copy the code

Parse_crash. Py is as follows:

# -*- coding:UTF-8 -*- # Author : [email protected] # Date : 28/07/21 import sys import os import re NDK_HOME = '/Users/jeffli/tools/android-ndk-r16b' ADDRLINE = '/ toolchains/aarch64 - Linux - android 4.9 / prebuilt/Darwin - x86_64 / bin/aarch64 - Linux - android - addr2line' ADDRLINE_PATH = NDK_HOME + ADDRLINE SO_PATH = 'XXXX' file_name = sys.argv[1] file_object = open(file_name, 'rU') file_info = '' try: for line in file_object: stack = '' so_name = '' tempStr = line.strip('\n') start = tempStr.find('pc') tempStr = tempStr[start + 3:] tempStr = re.sub(' +',' ', Stack = tempStr[:end] tempStr = tempstr.find (' ') stack = tempStr[:end] tempStr = tempStr[:end + 1:] end = tempstr.find (' ') if end ! = 1: Rfind ('/') + 1:] so_path = so_path + '/' + so_name result = os.popen(ADDRLINE_PATH + ' -f -e ' + so_path + ' ' + stack).read() print result finally: file_object.close()Copy the code

You also need to create a crash file in the current directory and write the corresponding crash stack into the crash file. Run sh addr2line_tools.sh to unstack the file.