This article is the first in a series on reading the source code of the IjkPlayer. I remember compiling IjkPlayer in previous work, and to facilitate further reading of its source code, I will briefly summarize some basic knowledge of JNI in the following text. The main content of this article is as follows:
- IjkPlayer Compilation
- IjkPlayer Source Code Directory
- Introduction to NDK
- Basic Knowledge of JNI
- Summary
IjkPlayer Compilation#
I previously wrote a separate article on compiling IjkPlayer, which is quite detailed. Please refer to the following:
IjkPlayer Source Code Directory#
Introduction to the IjkPlayer source code directory:
├── android // android related directory
│ ├── compile-ijk.sh
│ ├── contrib // ffmpeg compilation directory
│ │ ├── compile-ffmpeg.sh // ffmpeg compilation script
│ │ ├── compile-libsoxr.sh // libsoxr compilation script
│ │ ├── compile-openssl.sh // openssl compilation script
│ │ ├── ffmpeg-arm64
│ │ ├── ffmpeg-armv5
│ │ ├── ffmpeg-armv7a
│ │ ├── ffmpeg-x86
│ │ ├── ffmpeg-x86_64
│ ├── ijk-addr2line.sh
│ ├── ijk-ndk-stack.sh
│ ├── ijkplayer // android ijkPlayer source code directory
│ │ ├── ijkplayer-arm64
│ │ ├── ijkplayer-armv5
│ │ ├── ijkplayer-armv7a
│ │ ├── ijkplayer-example // ijkPlayer usage examples
│ │ ├── ijkplayer-exo
│ │ ├── ijkplayer-java
│ │ ├── ijkplayer-x86
│ │ ├── ijkplayer-x86_64
├── compile-android-j4a.sh
├── config // ffmpeg compilation script configuration directory
│ ├── module-default.sh // ffmpeg default configuration script file
│ ├── module-lite-hevc.sh // ffmpeg minimal configuration adding hevc functionality script file
│ ├── module-lite.sh // ffmpeg minimal configuration script file
│ └── module.sh // ffmpeg current compilation configuration script file
├── doc
│ └── preflight_checklist.md
├── extra // directory for downloading open source libraries used by ijkPlayer
│ ├── ffmpeg // ffmpeg
│ ├── libyuv // yuv image processing library
│ └── soundtouch // audio processing library, mainly for speed and pitch changes
├── ijkmedia // core code of ijkPlayer native layer
│ ├── ijkj4a // interface layer for native and java layer callbacks, generated by open source project jni4android
│ ├── ijkplayer // ijkPlayer native layer code
│ ├── ijksdl // ijkPlayer audio and video rendering SDL library
│ ├── ijksoundtouch // soundtouch library wrapped by ijk
│ └── ijkyuv // yuv image processing library
├── ijkprof // performance debugging library of ijkplayer
├── init-android-exo.sh // initialize exoPlayer script
├── init-android-j4a.sh // initialize j4a script
├── init-android-libsoxr.sh // initialize soxr script
├── init-android-libyuv.sh // initialize yuv script
├── init-android-openssl.sh // initialize openssl script
├── init-android-prof.sh // initialize android-ndk-profile script
├── init-android.sh // initialize android platform script, mainly to pull ffmpeg, third-party libraries, etc.
├── init-android-soundtouch.sh
├── init-config.sh // ffmpeg script file configuration script
├── init-ios-openssl.sh
├── init-ios.sh
├── ios // IOS related directory
Introduction to NDK#
Most application developers may not come into contact with the NDK, but if hardware operations are involved, the NDK must be used. Another reason for using the NDK is that C/C++ is more efficient, so we can implement some time-consuming operations in the NDK.
NDK stands for Native Development Kit, which is a toolset that inherits the Android cross-compilation environment and provides a convenient MakeFile to help developers quickly develop C/C++ dynamic libraries and automatically package the so and java programs into an apk to run in Android.
Basic Knowledge of JNI#
JNI stands for Java Native Interface, which allows Java code to interact with code written in other languages. Since Java 1.1, the JNI standard has become part of the Java platform.
JavaVM and JNIEnv#
JavaVM represents the Java Virtual Machine, defined in jni.h. Each process can have multiple JavaJVMs, but Android only allows one. The corresponding javaJVM object can be shared among threads in the process. When using it, a global JavaVM variable can be saved for sharing, and the common way to obtain it is as follows:
- First method:
static JavaVM* g_jvm;
// First method
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
// Assign value to JavaVM pointer
g_jvm = vm;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
// ...
return JNI_VERSION_1_4;
}
- Second method:
static JavaVM* g_jvm;
JNIEXPORT jint JNICALL Java_manu_com_iptvsamples_ndk_NDKSampleActivity_sum
(JNIEnv * env, jobject obj, jint addend1, jint addend2){
// Assign value to JavaVM pointer
env->GetJavaVM(&g_jvm);
return addend1 + addend2;
}
Additionally, a JavaVM can also be created using the JNI function JNI_CreateJavaVM
.
JNIEnv provides most JNI functions, and Native functions receive JNIEnv as the first parameter. JNIEnv is used for thread-local storage and cannot be shared between threads. If a piece of code has no other way to obtain its JNIEnv, it can use the shared JavaVM to get the thread's JNIEnv through GetEnv.
JNI Registration Methods#
There are mainly two ways to register JNI functions: static registration and dynamic registration. A typical example is the open-source audio and video project ijkPlayer, which uses dynamic registration. Further analysis will be provided later in the text.
- Static Registration
Static registration is mainly done by defining a .java file that contains native methods and generating the corresponding .h header file using the javah command.
Define the native method in the Activity as follows:
public native int sum(int addend1, int addend2);
To facilitate this, switch the directory to the project's java directory and use the following command to generate the header file for C/C++ usage:
javah -jni com.manu.ndksamples.MainActivity
If an exception occurs indicating that the class file cannot be found, try adding the -classpath
parameter to generate the corresponding header file. The code for the header file generated from the above native method is as follows:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class manu_com_iptvsamples_ndk_NDKSampleActivity */
#ifndef _Included_com_manu_ndksamples_MainActivity
#define _Included_com_manu_ndksamples_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_manu_ndksamples_MainActivity
* Method: sum
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_manu_ndksamples_MainActivity_sum
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
The corresponding file name is the package name + class name: com_manu_ndksamples_MainActivity.h
. Import the header file and implement the native method defined in Java using C/C++, as shown below:
#include "com_manu_ndksamples_MainActivity.h"
/*
* Class: com_manu_ndksamples_MainActivity
* Method: sum
* Signature: (II)I
*/
extern "C" JNIEXPORT jint JNICALL Java_com_manu_ndksamples_MainActivity_sum
(JNIEnv * env, jobject obj, jint addend1, jint addend2){
return addend1 + addend2;
}
- Dynamic Registration
Dynamic registration is done through the JNINativeMethod
structure in JNI, which saves the one-to-one correspondence between native functions and JNI functions. This structure is defined as follows:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
The static registered sum
method can also be registered dynamically as follows:
#include <jni.h>
#include <cassert>
#include <iostream>
using namespace std;
#define JNI_CLASS "com/manu/ndksamples/MainActivity"
static JavaVM *g_jvm;
static jint sample_sum(JNIEnv *env, jobject thiz, jint add1, jint add2) {
cout << "sample_sum" << endl;
return add1 + add2;
}
static JNINativeMethod g_methods[] = {
{"sum", "(II)I", (void *) sample_sum}
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
g_jvm = vm;
if ((*vm).GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != nullptr);
jclass clazz = (*env).FindClass(JNI_CLASS);
// Register function correspondence
(*env).RegisterNatives(clazz, g_methods, sizeof(g_methods) / sizeof((g_methods)[0]));
return JNI_VERSION_1_4;
}
JNIEXPORT void JNI_OnUnload(JavaVM *jvm, void *reserved) {
// JNI_OnUnload
}
In the above code, the function sample_sum
corresponds to the native method sum
in Java. This correspondence is registered through the RegisterNatives
function. The basic process is that when calling System.loadLibrary
to load the library, it will look for the JNI_OnLoad
function, and then register in the callback of that function. Similarly, the destruction operation is performed in JNI_OnUnload
.
Summary#
This article mainly introduced the IjkPlayer source code directory, the compilation of IjkPlayer, and some essential basic knowledge related to JNI. The next article will officially begin reading the IjkPlayer source code.