本篇介绍
本篇介绍下如何在macos上编译android的ffmpeg,并在android工程中使用。
编译ffmpeg
ffmpeg代码下载:
代码语言:javascript复制git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
对configure做如下修改:
代码语言:javascript复制diff --git a/configure b/configure
index f9fdf58bc3..d874b762f9 100755
--- a/configure
b/configure
@@ -2511,6 2511,7 @@ CMDLINE_SET="
objcc
cpu
cross_prefix
cross_prefix_clang
custom_allocator
cxx
dep_cc
@@ -4352,8 4353,8 @@ if test "$target_os" = android; then
fi
ar_default="${cross_prefix}${ar_default}"
-cc_default="${cross_prefix}${cc_default}"
-cxx_default="${cross_prefix}${cxx_default}"
cc_default="${cross_prefix_clang}${cc_default}"
cxx_default="${cross_prefix_clang}${cxx_default}"
nm_default="${cross_prefix}${nm_default}"
新建一个build_android.sh,内容如下:
代码语言:javascript复制#!/bin/bash
set -x
# 目标Android版本
API=29
ARCH=arm64
CPU=armv8-a
TOOL_CPU_NAME=aarch64
#so库输出目录
OUTPUT=./android/$CPU
# NDK的路径,根据自己的NDK位置进行设置
NDK=/Users/shanks/Workspace/tools/android-ndk-r22b/
# 编译工具链路径
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64
# 编译环境
SYSROOT=$TOOLCHAIN/sysroot
TOOL_PREFIX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-android"
CC="$TOOL_PREFIX$API-clang"
CXX="$TOOL_PREFIX$API-clang "
OPTIMIZE_CFLAGS="-march=$CPU"
function build
{
./configure
--prefix=$OUTPUT
--target-os=android
--arch=$ARCH
--cpu=$CPU
--disable-asm
--enable-neon
--enable-cross-compile
--enable-shared
--disable-static
--disable-doc
--disable-ffplay
--disable-ffprobe
--disable-symver
--disable-ffmpeg
--cc=$CC
--cxx=$CXX
--sysroot=$SYSROOT
--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS"
make clean all
# 这里是定义用几个CPU编译
make -j8
make install
}
build
把里面NDK的路径换成自己的路径。 执行这个脚本,就可以看到库编译出来了。
ffmpeg库
在android中使用
新建一个Native C 工程,然后在src/main下建一个jniLibs目录,并把编译的库拷贝进去。结构如下:
加载库
在src/main/cpp下新建一个ffmpeg目录,把编译的头文件也拷贝过来,结构如下:
拷贝头文件
修改CMakelists.txt,内容如下:
代码语言:javascript复制# 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.4.1)
# 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.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu 11")
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR})
include_directories(${ffmpeg_head_dir}/ffmpeg)
include_directories(${ffmpeg_head_dir}/include)
add_library( avutil
SHARED
IMPORTED )
set_target_properties( avutil
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavutil.so )
add_library( swresample
SHARED
IMPORTED )
set_target_properties( swresample
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libswresample.so )
add_library( avcodec
SHARED
IMPORTED )
set_target_properties( avcodec
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavcodec.so )
add_library( avfilter
SHARED
IMPORTED)
set_target_properties( avfilter
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavfilter.so )
add_library( swscale
SHARED
IMPORTED)
set_target_properties( swscale
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libswscale.so )
add_library( avformat
SHARED
IMPORTED)
set_target_properties( avformat
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavformat.so )
add_library( avdevice
SHARED
IMPORTED)
set_target_properties( avdevice
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavdevice.so )
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp )
# 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 )
# 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.
native-lib
avutil
swresample
avcodec
avfilter
swscale
avformat
avdevice
# Links the target library to the log library
# included in the NDK.
${log-lib} )
再在gradle里面加载该库:
代码语言:javascript复制externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
defaultConfig {
ndk {
abiFilters 'arm64-v8a'
}
}
sourceSets {
main {
jniLibs.srcDirs = ["src/main/jniLibs"]
}
}
}
接下来就是写代码调用了,从官网(https://www.ffmpeg.org/doxygen/4.1/avio_reading_8c-example.html)copy一个example, 放到native-lib.cpp中,完整如下:
代码语言:javascript复制#include <jni.h>
#include <string>
#include "android/log.h"
#include <cstdio>
#include <iostream>
extern "C" {
#include "ffmpeg/libavformat/avformat.h"
#include "ffmpeg/libavcodec/avcodec.h"
#include "ffmpeg/libavutil/macros.h"
#include "ffmpeg/libavutil/error.h"
#include "ffmpeg/libavutil/file.h"
}
struct buffer_data {
uint8_t *ptr;
size_t size; ///< size left in the buffer
};
static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{
struct buffer_data *bd = (struct buffer_data *)opaque;
buf_size = FFMIN(buf_size, bd->size);
if (!buf_size)
return AVERROR_EOF;
__android_log_print(ANDROID_LOG_INFO, "lhr","ptr:%p size:%zun", bd->ptr, bd->size);
/* copy internal buffer data to buf */
memcpy(buf, bd->ptr, buf_size);
bd->ptr = buf_size;
bd->size -= buf_size;
return buf_size;
}
void StartPlay(const std::string &path) {
AVFormatContext *fmt_ctx = nullptr;
AVIOContext *avio_ctx = nullptr;
uint8_t *buffer = nullptr, *avio_ctx_buffer = nullptr;
size_t buffer_size, avio_ctx_buffer_size = 4096;
char *input_filename = nullptr;
int ret = 0;
struct buffer_data bd = { 0 };
input_filename = const_cast<char *>(path.data());
ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
if (ret < 0) {
goto end;
}
bd.ptr = buffer;
bd.size = buffer_size;
if (!(fmt_ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
goto end;
}
avio_ctx_buffer = static_cast<uint8_t *>(av_malloc(avio_ctx_buffer_size));
if (!avio_ctx_buffer) {
ret = AVERROR(ENOMEM);
goto end;
}
avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,
0, &bd, &read_packet, NULL, NULL);
if (!avio_ctx) {
ret = AVERROR(ENOMEM);
goto end;
}
fmt_ctx->pb = avio_ctx;
ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
if (ret < 0) {
__android_log_print(ANDROID_LOG_INFO, "lhr", "Could not open inputn");
goto end;
}
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
__android_log_print(ANDROID_LOG_INFO, "lhr","Could not find stream informationn");
goto end;
}
av_dump_format(fmt_ctx, 0, input_filename, 0);
__android_log_print(ANDROID_LOG_INFO, "lhr","av_dump_format finish");
end:
avformat_close_input(&fmt_ctx);
/* note: the internal buffer could have changed, and be != avio_ctx_buffer */
if (avio_ctx) {
av_freep(&avio_ctx->buffer);
av_freep(&avio_ctx);
}
av_file_unmap(buffer, buffer_size);
if (ret < 0) {
return;
}
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ffmpegdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C ";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT void JNICALL
Java_com_example_ffmpegdemo_MainActivity_startPlayJNI(
JNIEnv* env,
jobject thiz, jstring str) {
const jsize len = env->GetStringUTFLength(str);
const char* strChars = env->GetStringUTFChars(str, (jboolean *)0);
std::string path(strChars, len);
StartPlay(path);
env->ReleaseStringUTFChars(str, strChars);
}
然后再在java侧传递一个path,这样就可以调用了。需要注意的是av_file_map在Android 30上会报权限问题,暂时的修改方法是把compileSdkVersion和targetSdkVersion修改成28或以下即可。