热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于Docker为Android交叉编译FFMpeg动态库

本文将会介绍:如何在Docker下为Android编译FFMpeg动态库。1前言为什么使用DockerDocker相当于一个虚拟机,类似于Vmwar

本文将会介绍:如何在 Docker 下为 Android 编译 FFMpeg 动态库。


1 前言


为什么使用 Docker

Docker 相当于一个虚拟机,类似于 Vmware Workstation。使用 Docker 可以充分保证(容器内)环境的一致性,减少不同环境的干扰。

基础概念


  • 镜像(image):有过装系统经验的应该不难理解,
  • 宿主机(host):运行 Docker engine 的环境,可以理解为你的电脑正在运行的系统(当然还包括硬件)。
  • 容器(container):通过镜像创建的实体,一个镜像可以创建多个容器。
  • 交叉编译(cross compile):通俗点说,是在一个架构的环境下,编译另一个架构下可以运行的目标文件(动态库、静态库、可执行文件等)。

2 环境

为确保之后的编译步骤顺畅进行,在此将我所使用的环境列出来:

  • 镜像:ubuntu:18.04
  • 宿主机:macOS Catalina 10.15.7
  • FFMpeg 源码版本:5.0
  • NDK 版本:23.1.7779620

理论上你应将除宿主机以外的环境跟笔者保持一致。

3 步骤


3.1 宿主机操作


创建容器

docker run -it -d ubuntu:18.04 /bin/bash

这条命令将会自动下载 ubuntu:18.04 镜像(如果本地没有),然后创建并进入该容器。

后续所有步骤/命令,均在容器内进行/执行。


3.2 容器内操作


3.2.1 更新软件源

cd ~ && apt update

进入容器的默认用户身份是 root(默认当前路径是 /),因此执行命令不需要 sudo


3.2.2 安装必要软件包

apt install build-essential curl zip openjdk-8-jdk vim -y

介绍下各软件包的作用:

  • build-essential:Ubuntu 上基础编译软件的工具大集合。
  • curl:这里被用来下载文件。
  • zip:解压 zip 文件用的。
  • openjdk-8-jdk:部分 command line tools 需要 JAVA 环境才能执行。
  • vim:文本编辑。

3.2.3 准备 ffmpeg 源码

# 下载
curl -OL https://www.ffmpeg.org/releases/ffmpeg-5.0.tar.xz
# 解压
tar xvJf ffmpeg-5.0.tar.xz

3.2.4 准备 NDK

# 下载 Command Line Tools
curl -OL https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip
# 解压 Commmand Line Tools
unzip commandlinetools-linux-7583922_latest.zip
# 设置 android sdk 目录
mkdir -pv ~/.local/android
# 配置 Command Line Tools
mkdir -pv ~/.local/android/cmdline-tools/
mv ~/cmdline-tools ~/.local/android/cmdline-tools/latest
# 添加环境变量
echo "export PATH=$HOME/.local/android/cmdline-tools/latest/bin:$PATH" >> ~/.bashrc
# 使环境变量生效
source ~/.bashrc
# 安装 NDK,注意这里需要同意下协议!!
sdkmanager --install "ndk;23.1.7779620"

安装好的 NDK 将会在 $HOME/.local/android/ndk/23.1.7779620/ 路径。

3.2.5 编译配置选项

ffmpeg 功能十分丰富,因而有相当多的配置选项,主要用于配置功能的开关,可以通过 ./configure --help 查看。

如果是线上环境使用,为了商业合规、控制包体积,我们需要根据开源协议、实际所需功能,进行裁剪。

此处主要目的是学习,因此将常用、尽可能多的功能打开。

ffmpeg 主要有以下几大模块:

  • libavcodec:音视频的编解码库。
  • libavdevice:与多媒体设备交互的库。
  • libavfilter:滤波器库。音频的算法处理、视频的滤镜等等。
  • libavformat:多媒体文件的格式和协议的封装、解封库。如 mp4 文件格式,rtmp 网络协议。
  • libavutil:ffmpeg 里面的工具类
  • libpostproc:后处理库。
  • libswresample:重采样库。
  • libswscale:图像缩放、颜色空间和图像格式转换库。

# 切换到 ffmpeg 源码目录
cd ~/ffmpeg-5.0
# 创建编译脚本
vim compile_ffmpeg.sh

注意:自行了解 vim 使用。

以下是配置脚本内容。

#!/bin/bash# filename: compile_ffmpeg.shset -eAPI=29
OS=android
PREFIX="${pwd}/out/"
ARCH=arm64
CPU=armv8-a
CFLAGS="-Os"ANDROID_HOME=$HOME/.local/android
NDK=$ANDROID_HOME/ndk/23.1.7779620/
TOOLCHAINS=$NDK/toolchains/llvm/prebuilt/linux-x86_64
CC=$TOOLCHAINS/bin/aarch64-linux-android$API-clang
CXX=$TOOLCHAINS/bin/aarch64-linux-android$API-clang++
SYSROOT=$TOOLCHAINS/sysroot
CROSS_PREFIX=$TOOLCHAINS/bin/aarch64-linux-android-
NM=$TOOLCHAINS/bin/llvm-nm
STRIP=$TOOLCHAINS/bin/llvm-strip
PKG_CFG=$TOOLCHAINS/bin/llvm-configfunction build_ffmpeg
{
echo "Start build ffmpeg...for $CPU"
SECONDS=0
./configure \--prefix=$PREFIX \--disable-static \--enable-shared \--arch=$ARCH \--cpu=$CPU \--target-os=$OS \--cc=$CC \--cxx=$CXX \--enable-cross-compile \--cross-prefix=$CROSS_PREFIX \--sysroot=$SYSROOT \--nm=$NM \--strip=$STRIP \--pkg-config=$PKG_CFG \--enable-jni \--enable-mediacodec \--enable-pic \--enable-hwaccels \--disable-doc \--extra-cflags=$CFLAGS \--extra-cxxflags=$CXXFLAGS \make -j
make install
duration=$SECONDS
echo "Compile for $CPU success! cost time $(($duration / 60)) mins $(($duration % 60)) seconds"
}# 编译 Arm 64 位架构
ARCH=arm64
CPU=armv8-a
PREFIX=$(pwd)/out/$OS/$CPU
build_ffmpeg# 编译 Arm 32 位架构
#ARCH=arm
#CPU=armv7-a
#PREFIX=$(pwd)/out/$OS/$CPU
#build_ffmpeg

编译脚本将会一直维护更新:build ffmpeg 5.0 with latest NDK on ubuntu 18.04 using Docker

3.2.6 开始编译

# 给编译脚本加上执行权限
chmod u+x compile_ffmpeg.sh
# 开始编译
./compile_ffmpeg.sh

3.2.7 编译产物

编译产物将会在 out/android/${CPU} 目录下。

# 以这里为例,64 位的编译产物将会在下面这个路径
ls -hl ~/ffmpeg-5.0/out/android/armv8-a/

编译产物示例

4 集成


下面将会讲通过 Android Studio 集成 ffmpeg 动态库到 Android 项目中。


4.1 配置环境


4.1.1 创建存放动态库的文件夹

# 创建动态库的文件夹,这里命名为 libs
mkdir -pv $PROJECT_ROOT/app/src/main/libs
# 创建存放特定架构动态库的文件夹,这里创建了存放 arm 64 位的动态库文件夹
mkdir -pv $PROJECT_ROOT/app/src/main/libs/arm64-v8a

注意:

  1. libs 文件夹的名字可以任取,只要不是 jniLibs,否则需要做些特殊配置。
  2. 创建存放特定架构动态库的文件夹名字建议与 ANDROID_ABI 保持一致,方便后续在 CMakeLists.txt 中使用。

4.1.2 复制动态库到项目

# 先想办法把步骤 3.2.7 的编译产物 arm 64 位动态库从容器中弄出来
# 然后复制到上面 4.1.1 创建的文件夹 $PROJECT_ROOT/app/src/main/libs/arm64-v8a 下

这一步骤后,项目工程大概长这样:

项目配置动态库

4.1.3 创建 native 编译文件

# 创建 cpp 文件夹,用于存放 c/c++ 源码
mkdir -pv $PROJECT_ROOT/app/src/main/cpp
# 新建 FFMpegJNI.cpp 文件,用于实现这里的 JNI 接口
# 新建 CMakeLists.txt 文件
touch $PROJECT_ROOT/app/src/main/cpp/CMakeLists.txt

CMakeLists.txt 文件的内容为:

cmake_minimum_required(VERSION 3.18.1)project("ffmpegturtorial")set(CMAKE_CXX_STANDARD 17)# 创建变量,声明了 ffmpeg 动态库的位置,将会根据 abi 有不同的区分
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../libs/${ANDROID_ABI})
# 创建变量,声明了 ffmpeg 头文件的位置
set(ffmpeg_include_dir ${CMAKE_SOURCE_DIR}/ffmpeg)# 添加预构建的 ffmpeg 动态库到项目中
# ref:https://developer.android.com/studio/projects/configure-cmake#add-other-library
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodecPROPERTIES IMPORTED_LOCATION${ffmpeg_lib_dir}/libavcodec.so)add_library(avdevice SHARED IMPORTED)
set_target_properties(avdevicePROPERTIES IMPORTED_LOCATION${ffmpeg_lib_dir}/libavdevice.so)add_library(avfilter SHARED IMPORTED)
set_target_properties(avfilterPROPERTIES IMPORTED_LOCATION${ffmpeg_lib_dir}/libavfilter.so)add_library(avformat SHARED IMPORTED)
set_target_properties(avformatPROPERTIES IMPORTED_LOCATION${ffmpeg_lib_dir}/libavformat.so)add_library(swresample SHARED IMPORTED)
set_target_properties(swresamplePROPERTIES IMPORTED_LOCATION${ffmpeg_lib_dir}/libswresample.so)add_library(swscale SHARED IMPORTED)
set_target_properties(swscalePROPERTIES IMPORTED_LOCATION${ffmpeg_lib_dir}/libswscale.so)add_library(avutil SHARED IMPORTED)
set_target_properties(avutilPROPERTIES IMPORTED_LOCATION${ffmpeg_lib_dir}/libavutil.so)add_library(ffmpegSHAREDFFMpegJNI.cpp)find_library(log-liblog)target_include_directories(ffmpegPRIVATE${ffmpeg_include_dir})target_link_libraries(ffmpeg# 链接到 ffmpeg 动态库avcodecavdeviceavfilteravformatswresampleswscaleavutil${log-lib})

4.1.4 复制 ffmpeg 头文件到项目

头文件在容器内的 ~/ffmpeg-5.0/out/android/armv8-a/include 目录下。

头文件在编译产物中的位置

复制完以后,项目工程大概长这样:
引入头文件后的项目工程

4.1.3 build.gradle 文件配置

主要修改模块build.gradle 文件。

android {// ...defaultConfig {// ...// 配置 native 代码的一些默认参数externalNativeBuild {cmake {cppFlags ''}}ndk {// !!!!!重点注意这里!!!!!// 只构建 arm 64 位的 native 库,因为上面只提供了 arm 64 位的 ffmpeg 库abiFilter "arm64-v8a"}}// native 库的构建方式externalNativeBuild {// 采用 cmake 构建cmake {// CMakeLists.txt 文件的位置path file('src/main/cpp/CMakeLists.txt')// 指定 cmake 的版本,要求不小于 CMakeLists.txt 声明的version '3.18.1'}}
}

4.2 Demo 源码


4.2.1 MainActivity.kt

class MainActivity : AppCompatActivity() {companion object {private const val TAG = "MainActivity"// jni 库的名字跟 CMakeLists.txt 中的保持一致private const val FFMPEG_LIBRARY = "ffmpeg"init {// 注意在 init 语句块中加载 jni 库try {System.loadLibrary(FFMPEG_LIBRARY)Log.i(TAG, "load ffmpeg library success")} catch (e: Exception) {Log.d(TAG, e.message, e)}}}// 定义一个 native 方法// 这个函数的作用是返回 ffmpeg 的一些版本、构建信息private external fun getFFMpegVersion(): String
}

4.2.2 FFMpegJNI.cpp

#include
#include // 尤其注意这里,ffmpeg 是基于 c 构建的,在 include 它的头文件时,也必须以 c 的方式引入,
// 否则链接时会出现符号异常,提示找不到符号。
extern "C" {
#include "libavcodec/version.h"
#include "libavcodec/avcodec.h"
#include "libavfilter/version.h"
#include "libavformat/version.h"
#include "libswscale/version.h"
#include "libswresample/version.h"
}extern "C"
JNIEXPORT jstring JNICALL
Java_me_hjhl_app_ffmpegturtorial_MainActivity_getFFMpegVersion(JNIEnv *env, jobject thiz) {std::string def;def.append("libavcodec: " AV_STRINGIFY(LIBAVCODEC_VERSION) "\n");def.append("libavfilter: " AV_STRINGIFY(LIBAVFILTER_VERSION) "\n");def.append("libavformat: " AV_STRINGIFY(LIBAVFORMAT_VERSION) "\n");def.append("libavutil: " AV_STRINGIFY(LIBAVUTIL_VERSION) "\n");def.append("libswscale: " AV_STRINGIFY(LIBSWSCALE_VERSION) "\n");def.append("libswresample: " AV_STRINGIFY(LIBSWRESAMPLE_VERSION) "\n");def.append("avcodec license: ");def.append(avcodec_license());def.append("\n");def.append("build command: ./configure ");def.append(avcodec_configuration());return env->NewStringUTF(def.c_str());
}

Demo 源码:https://github.com/HJHL/FFMpegTurtorial


推荐阅读
  • systemd-nspawn可以创建最轻量级的容器(ns的意思就是namespace),本文的实验平台是Ubuntu16.04,x86_64机器。本文的目的是:在Ubuntu中用syst ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了在MacOS系统上安装MySQL的步骤,并详细说明了如何设置MySQL服务的开机启动和如何修改MySQL的密码。通过下载MySQL的macos版本并按照提示一步一步安装,在系统偏好设置中可以找到MySQL的图标进行设置。同时,还介绍了通过终端命令来修改MySQL的密码的具体操作步骤。 ... [详细]
  • ZABBIX 3.0 配置监控NGINX性能【OK】
    1.在agent端查看配置:nginx-V查看编辑时是否加入状态监控模块:--with-http_stub_status_module--with-http_gzip_stat ... [详细]
  • docker安装到基本使用
    记录docker概念,安装及入门日常使用Docker安装查看官方文档,在"Debian上安装Docker",其他平台在"这里查 ... [详细]
  • 安装mysqlclient失败解决办法
    本文介绍了在MAC系统中,使用django使用mysql数据库报错的解决办法。通过源码安装mysqlclient或将mysql_config添加到系统环境变量中,可以解决安装mysqlclient失败的问题。同时,还介绍了查看mysql安装路径和使配置文件生效的方法。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 从U ... [详细]
  • python3 nmap函数简介及使用方法
    本文介绍了python3 nmap函数的简介及使用方法,python-nmap是一个使用nmap进行端口扫描的python库,它可以生成nmap扫描报告,并帮助系统管理员进行自动化扫描任务和生成报告。同时,它也支持nmap脚本输出。文章详细介绍了python-nmap的几个py文件的功能和用途,包括__init__.py、nmap.py和test.py。__init__.py主要导入基本信息,nmap.py用于调用nmap的功能进行扫描,test.py用于测试是否可以利用nmap的扫描功能。 ... [详细]
  • 本文介绍了在Android Studio中使用命令行build gradle的方法,并解决了一些常见问题,包括手动配置gradle环境变量和解决External Native Build Issues的方法。同时提供了相关参考文章链接。 ... [详细]
  • MySQL5.6.40在CentOS764下安装过程 ... [详细]
author-avatar
摩擦摩擦魔鬼的步伐
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有