【FFmpeg 系列】如何在 Android 下使用(八)

1、第一个 JNI 程序
  • Java 层:
package com.open.firstjni;

import android.os.Bundle;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    // 第一步:应用程序启动时加载 "native-lib" 库
    static {
        System.loadLibrary("native-lib");
    }

    // 第二步:由 "native-lib" 库实现的方法,它与此应用程序打包在一起
    public native String stringFromJNI();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 第三步:调用 JNI 方法示例
        TextView textView = (TextView) findViewById(R.id.sample_text);
        textView.setText(stringFromJNI());
    }
}
  • Kotlin 层:
package com.open.firstjni

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    companion object {
        // 第一步:应用程序启动时加载 "native-lib" 库
        init {
            System.loadLibrary("native-lib")
        }
    }

    // 第二步:由 "native-lib" 库实现的方法,它与此应用程序打包在一起
    external fun stringFromJNI(): String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 第三步:调用 JNI 方法示例
        findViewById<TextView>(R.id.sample_text).text = stringFromJNI()
    }
}
  • JNI 层:
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_open_firstjni_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
2、Java 调用 C/C++
  • JNI 基本概念
// Java 的本地化环境
JNIEnv
// Java 的虚拟机
JavaVM
// 线程
  • (1)Java 调用 C/C++ 方式一:
第一步:在 Java 层定义 native 关键字函数
第二步:在 C/C++ 层创建 Java_packname_classname_methodname 函数

## 示例如:第一个 JNI 程序所示
  • 代码示例如:第一个 JNI 程序所示
  • (2)Java 调用 C/C++ 方式二:
// 注册 Native 方法,手动在 C/C++ 层手动映射
RegisterNative
映射表对应如下:
	typedef struct{
		const char* name;
		const char* signature;
		void* fnPtr;
	}JNINativeMethod;
	
// 注册 Native 方法的最佳时机,动态库被加载的时候会触发
jint JNI_OnLoad(JavaVM *vm, void *reserved)

// 动态库被卸载的时候会触发
jint JNI_UnLoad(JavaVM *vm, void *reserved)
  • Kotlin 层:
package com.open.firstjni

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    companion object {
        // 第一步:应用程序启动时加载 "native-lib" 库
        init {
            System.loadLibrary("native-lib")
        }
    }

    // 第二步:由 "native-lib" 库实现的方法,它与此应用程序打包在一起
    external fun stringFromJNI2(): String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 第三步:调用 JNI 方法示例
        findViewById<TextView>(R.id.sample_text).text = stringFromJNI2()
    }
}
  • JNI 层:
#include <jni.h>
#include <string>

#define JNI_CLASS_PATH "com/open/firstjni/MainActivity"

extern "C"
JNIEXPORT jstring JNICALL
stringFromJNI2(JNIEnv *env, jobject thiz) {
    return env->NewStringUTF("1111");
}

static JNINativeMethod g_method[] = {
        {"stringFromJNI2", "()Ljava/lang/String;", (void *) stringFromJNI2},
};

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    vm->GetEnv((void **) &env, JNI_VERSION_1_6);
    jclass clazz = env->FindClass(JNI_CLASS_PATH);
    env->RegisterNatives(clazz, g_method, sizeof(g_method) / sizeof(g_method[0]));

    return JNI_VERSION_1_6;
}
3、JNI 中的 signature
  • Java 与 C/C++ 相互调用时,表示函数参数的描述符
  • 输入参数放在括号()内,输出参数放在括号()外
  • 多个参数之间顺序存放,并用 “;” 分割
  • 原始类型的 signature
    Java 类型 | 符号 | Java 类型 | 符号
    —|--- |
    boolean | Z | byte | B
    char | C | short | S
    int | I | long | L
    float | F | double | D
    void | V
  • Java 对象参数:“L包路径/类名”
  • Java 数组:"["
  • 示例:
示例一:([Student;)[LStudent;
=Student() Xxx(Student[] st)
示例二:([Ljava/lang/String;)[Ljava/lang/Object;
=》Object[] Xxx(String[] s)
4、C 调用 Java
  • API:
// 在 C/C++ 层找到 Java 层的类
FindClass
// 获取 Java 类中的所有方法
GetMethodID
// 获取 Java 类中的所有属性
GetFieldID
// 代表在内存中 Java 的对象
NewObject
// 执行 Java 类中的这个方法
Call<TYPE> Method/[G/S]et<Type>Field
  • 示例:
<!-- Java 层代码 -->
package com.open.firstjni

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Example of a call to a native method
        findViewById<TextView>(R.id.sample_text).text = stringFromJNI()
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    external fun stringFromJNI(): String

    companion object {
        // Used to load the 'native-lib' library on application startup.
        init {
            System.loadLibrary("native-lib")
        }
    }
}

package com.open.firstjni;

public class Student {

    private int year;

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }
}

<!-- C/C++ 层代码 -->
#include <jni.h>
#include <string>

#define STUDENT_CLASS_PATH "com/open/firstjni/Student"

extern "C" JNIEXPORT jstring JNICALL
Java_com_open_firstjni_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
    // 第一步:
    jclass clazz = env->FindClass(STUDENT_CLASS_PATH);
    // 第二步:
    jmethodID method_init_year = env->GetMethodID(clazz, "<init>", "()V");
    jmethodID method_set_year = env->GetMethodID(clazz, "setYear", "(I)V");
    jmethodID method_get_year = env->GetMethodID(clazz, "getYear", "()I");
    // 第三步:
    jobject obj = env->NewObject(clazz, method_init_year);
    // 第四步:
    env->CallVoidMethod(obj, method_set_year, 18);
    int year = env->CallIntMethod(obj, method_get_year);

    char tmp[256];
    sprintf(tmp, "%d", year);

    std::string hello = "Hello from C/C++, year = ";
    hello.append(tmp);

    return env->NewStringUTF(hello.c_str());
}
  • 运行结果:
显示:Hello from C/C++, year = 18
5、交叉编译
  • 什么是交叉编译?
通俗理解就是,就是在某个系统下编译生成另外一个系统的库,即为交叉编译。
举例:
在 Mac 系统下编译成功 Android 系统下使用的 FFmpeg 库。
  • 以 NDK 为 android-ndk-r21e 示例:
  • (1)生成交叉编译工具链方式:
build/tools/make-standalone-toolchain.sh
// 参数
--toolchain=arm-linux-androideabi-4.9
--platform=android-21
--install-dir=../toolchain

// 示例:
// 第一步:进行 android-ndk-r21e 目录
cd android-ndk-r21e 目录
// 第二步:执行命令生成交叉编译工具链
build/tools/make-standalone-toolchain.sh --toolchain=arm-linux-androideabi-4.9 --platform=android-21 --install-dir=../toolchain
  • (2)不生成交叉编译工具链方式:
// 设置你自己的 NDK 目录
export NDK=/Users/Xxx/Library/Android/ndk/android-ndk-r20b
// 设置你自己的 NDK 目录所对应的交叉编译目录
export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin
  • 编写编译脚本(build_ffmpeg_android.sh):
// 第一步:下载并解压 FFmpeg 源码
// 第二步:进入 FFmpeg 源码目录
cd ffmpeg
// 第三步:创建并编写 build_ffmpeg_android.sh shell 脚本
#!/bin/bash

# 清除缓存
make clean
# 设置编译出错后马上退出
set -e
# 指定cpu架构是32位还是64位
archbit=32

if [ $archbit -eq 64 ];then
ARCH=aarch64
# 设置支持 Android CPU 架构
CPU=armv8-a
# 最低支持的 Android SDK 版本
API=21
PLATFORM=aarch64
ANDROID=android
CFLAGS=""
LDFLAGS=""
echo "Compiling FFmpeg build for 64bit and $CPU"

else
ARCH=arm
# 设置支持 Android CPU 架构
CPU=armv7-a
# 最低支持的 Android SDK 版本
API=16
PLATFORM=armv7a
ANDROID=androideabi
CFLAGS="-mfloat-abi=softfp -march=$CPU"
LDFLAGS="-Wl,--fix-cortex-a8"
echo "Compiling FFmpeg build for 32bit and $CPU"
fi

# 设置你自己的 NDK 目录
export NDK=/Users/Xxx/Library/Android/ndk/android-ndk-r20b
# 设置你自己的 NDK 目录所对应的交叉编译目录(二者选其一)
# 方式一:生成交叉编译工具链方式:
export TOOLCHAIN=/Users/Xxx/Library/Android/ndk/toolchain/bin
# 方式二:不生成交叉编译工具链方式:
# export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin

export SYSROOT=$NDK/toolchains/llvm/prebuilt/darwin-x86_64/sysroot
export CROSS_PREFIX=$TOOLCHAIN/$ARCH-linux-$ANDROID-
export CC=$TOOLCHAIN/$PLATFORM-linux-$ANDROID$API-clang
export CXX=$TOOLCHAIN/$PLATFORM-linux-$ANDROID$API-clang++
export PREFIX=$(pwd)/android-build/$CPU

# 函数里面的 enable 代表开启,disable 代表关闭,
# 也就是对 ffmpeg 进行剪裁,根据我们需要的功能进行 enable。
function build_android {
    ./configure \
    --prefix=$PREFIX \
    --cross-prefix=$CROSS_PREFIX \
    --target-os=android \
    --arch=$ARCH \
    --cpu=$CPU \
    --cc=$CC \
    --cxx=$CXX \
    --nm=$TOOLCHAIN/$ARCH-linux-$ANDROID-nm \
    --strip=$TOOLCHAIN/$ARCH-linux-$ANDROID-strip \
    --enable-cross-compile \
    --sysroot=$SYSROOT \
    --extra-cflags="$CFLAGS" \
    --extra-ldflags="$LDFLAGS" \
    --extra-ldexeflags=-pie \
    --enable-runtime-cpudetect \
    --disable-static \
    --enable-shared \
    --disable-ffprobe \
    --disable-ffplay \
    --disable-ffmpeg \
    --disable-debug \
    --disable-doc \
    --enable-avfilter \
    --enable-swresample \
    --enable-decoders \
    $ADDITIONAL_CONFIGURE_FLAG

    # 执行编译,并开启8个线程
    make -j8
    # 执行安装
    make install
    # 打印输出日志
    echo "The Compilation of FFmpeg for $CPU is completed and generated in $PREFIX directory"
}
# 执行函数
build_android
6、Android 下的 FFmpeg 播放器
  • github项目地址:https://github.com/xing-tang/FFmpeg-master
7、Linux 系统为什么无法编译出 FFplay?
  • Linux 系统必须有显示系统
  • 必须安装了 SDL 库
  • 需设置了 DISPLAY 环境变量
  • 你的 Linux 系统必须是桌面版,而不能是服务版,这样它才会有显示系统,建议使用 Ubuntu系统
  • 安装 SDL 库, apt install libsdl2-dev
  • 在 ~/.bashrc 中设备 DISPLAY=0.0 环境变量
8、错误分析
  • 编译时提示 ERROR: libfdk_aac not found 这是什么原因?
- 这是由于你使用的 FFmpeg 在编译时没有加 libfdk_aac 库。使用源码方式编译 FFmpeg,在执行 configure 时将 –enable-libfdk-aac 选项加上,并且在编译之前先将 libfdk-aac 库装上。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值