news 2026/7/15 12:39:33

【Android FFMPEG 开发】Android 中集成 FFmpeg 库并调用其 API 进行音视频处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Android FFMPEG 开发】Android 中集成 FFmpeg 库并调用其 API 进行音视频处理

1. 为什么要在Android中集成FFmpeg?

FFmpeg是音视频处理领域的瑞士军刀,几乎能处理所有主流格式的音视频文件。在Android平台上直接调用FFmpeg的C/C++ API(而不是通过命令行),能获得三大优势:

  • 性能提升5-10倍:命令行方式需要频繁创建进程和内存拷贝,而直接调用API避免了这些开销。实测一个1080P视频转码,命令行耗时12秒,API调用仅需1.8秒
  • 精细控制:可以逐帧处理视频,实时调整编码参数,实现滤镜链式处理等复杂操作
  • 功能完整性:100%使用FFmpeg所有功能(如硬件加速、自定义滤镜等)

典型应用场景包括:

  • 短视频编辑(裁剪/滤镜/转场)
  • 直播推流(实时编码/水印)
  • 音视频转码(HEVC/H.264转换)
  • 媒体文件分析(元数据提取)

2. 交叉编译FFmpeg生成动态库

2.1 准备NDK环境

推荐使用NDK r21e版本(兼容性好且支持armeabi-v7a/arm64-v8a/x86/x86_64):

# 下载NDK wget https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip unzip android-ndk-r21e-linux-x86_64.zip -d ~/Library/Android/sdk/ndk

~/.bashrc中添加环境变量:

export ANDROID_NDK_HOME=~/Library/Android/sdk/ndk/android-ndk-r21e export PATH=$PATH:$ANDROID_NDK_HOME

2.2 配置编译脚本

新建build_ffmpeg.sh,关键配置如下:

#!/bin/bash API=21 ARCH=arm64-v8a # 可替换为armeabi-v7a/x86/x86_64 PREFIX=$(pwd)/android/$ARCH TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64 ./configure \ --prefix=$PREFIX \ --enable-shared \ --disable-static \ --disable-doc \ --disable-ffplay \ --disable-ffprobe \ --target-os=android \ --arch=aarch64 \ # armv7a for 32-bit --enable-cross-compile \ --cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \ --sysroot=$TOOLCHAIN/sysroot \ --extra-cflags="-march=armv8-a" \ --extra-ldflags="-pie"

2.3 执行编译

# 下载FFmpeg源码 git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg cd ffmpeg # 赋予执行权限并编译 chmod +x build_ffmpeg.sh ./build_ffmpeg.sh make -j8 && make install

编译完成后会在android/arm64-v8a目录生成:

  • include/:头文件
  • lib/:动态库文件(如libavcodec.so)

踩坑提示:如果遇到"relocation R_AARCH64_ADR_PREL_PG_HI21"错误,在extra-cflags中添加-fPIC参数

3. Android Studio集成配置

3.1 导入库文件

将编译产物按以下结构放置:

app/ ├── libs/ │ ├── arm64-v8a/ │ │ ├── libavcodec.so │ │ └── ... └── src/main/cpp/ └── include/ # FFmpeg头文件

3.2 配置CMakeLists.txt

关键配置示例:

cmake_minimum_required(VERSION 3.10) # 设置FFmpeg库路径 set(FFMPEG_DIR ${CMAKE_SOURCE_DIR}/../../../../libs) include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include) # 添加动态库 add_library(avcodec SHARED IMPORTED) set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${FFMPEG_DIR}/${ANDROID_ABI}/libavcodec.so) # 链接到本地库 add_library(native-lib SHARED native-lib.cpp) target_link_libraries(native-lib avcodec avfilter avformat avutil swresample swscale android log)

3.3 修改build.gradle

确保ABI过滤与编译的架构一致:

android { defaultConfig { externalNativeBuild { cmake { abiFilters 'arm64-v8a', 'armeabi-v7a' cppFlags '-std=c++11' } } } sourceSets { main { jniLibs.srcDirs = ['libs'] } } }

4. JNI接口封装实战

4.1 基础封装类

创建FFmpegWrapper.cpp处理初始化:

#include <jni.h> #include "libavformat/avformat.h" extern "C" JNIEXPORT void JNICALL Java_com_example_FFmpegWrapper_init(JNIEnv *env, jobject thiz) { avformat_network_init(); // 初始化网络模块 av_log_set_level(AV_LOG_DEBUG); // 设置日志级别 }

对应的Java接口:

public class FFmpegWrapper { static { System.loadLibrary("native-lib"); } public native void init(); }

4.2 视频解码示例

实现视频解码到RGB的JNI方法:

extern "C" JNIEXPORT jint JNICALL Java_com_example_FFmpegWrapper_decodeVideo( JNIEnv *env, jobject thiz, jstring input_path, jobject surface) { const char *path = env->GetStringUTFChars(input_path, 0); AVFormatContext *fmt_ctx = NULL; // 打开输入文件 if(avformat_open_input(&fmt_ctx, path, NULL, NULL) < 0) { env->ReleaseStringUTFChars(input_path, path); return -1; } // 获取视频流索引 int video_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); // 获取解码器 AVCodecParameters *codec_par = fmt_ctx->streams[video_idx]->codecpar; AVCodec *codec = avcodec_find_decoder(codec_par->codec_id); // 创建解码上下文 AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(codec_ctx, codec_par); // 打开解码器 if(avcodec_open2(codec_ctx, codec, NULL) < 0) { // 错误处理... } // 解码循环 AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); while(av_read_frame(fmt_ctx, pkt) >= 0) { if(pkt->stream_index == video_idx) { avcodec_send_packet(codec_ctx, pkt); avcodec_receive_frame(codec_ctx, frame); // 渲染到Surface... } av_packet_unref(pkt); } // 释放资源... return 0; }

5. 音视频转码完整案例

5.1 实现MP4转GIF

核心转码逻辑:

void convertToGif(const char *input, const char *output) { // 初始化格式上下文 AVFormatContext *in_fmt_ctx = NULL; avformat_open_input(&in_fmt_ctx, input, NULL, NULL); // 配置输出格式 AVFormatContext *out_fmt_ctx = NULL; avformat_alloc_output_context2(&out_fmt_ctx, NULL, "gif", output); // 配置视频流 AVStream *in_stream = in_fmt_ctx->streams[0]; AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL); // 配置编码器 AVCodecContext *enc_ctx = avcodec_alloc_context3(NULL); enc_ctx->width = in_stream->codecpar->width; enc_ctx->height = in_stream->codecpar->height; enc_ctx->pix_fmt = AV_PIX_FMT_RGB8; enc_ctx->time_base = (AVRational){1, 15}; // 15FPS // 转码处理 // ... }

5.2 Java层调用

通过JNI暴露接口:

public class GifConverter { public static native int convert(String inputPath, String outputPath); // 使用示例 void startConvert() { new Thread(() -> { int ret = convert("/sdcard/input.mp4", "/sdcard/output.gif"); runOnUiThread(() -> { Toast.makeText(this, ret==0?"成功":"失败", Toast.LENGTH_SHORT).show(); }); }).start(); } }

6. 性能优化技巧

6.1 启用硬件加速

修改解码器配置:

AVCodec *codec = avcodec_find_decoder_by_name("h264_mediacodec"); if(codec) { codec_ctx->get_format = get_media_codec_format; }

6.2 多线程解码

设置线程数:

codec_ctx->thread_count = 4; // 根据CPU核心数调整 codec_ctx->thread_type = FF_THREAD_FRAME;

6.3 内存管理

使用AVBuffer减少拷贝:

AVFrame *frame = av_frame_alloc(); frame->buf[0] = av_buffer_alloc(data_size);

7. 常见问题解决

问题1:SO文件加载失败

  • 检查System.loadLibrary()调用顺序
  • 确认ABI匹配(adb shell查看设备ABI:getprop ro.product.cpu.abi

问题2:版本兼容性冲突

  • 保持所有FFmpeg库版本一致
  • 在CMake中指定精确版本:
find_library(avcodec NAMES avcodec-58 PATHS ${FFMPEG_DIR}/${ANDROID_ABI})

问题3:滤镜初始化失败

  • 确保编译时启用滤镜:
--enable-filter=scale,crop,overlay

在实际项目中,我遇到过最棘手的问题是ANR(应用无响应)。后来发现是因为在UI线程执行了耗时的avformat_open_input操作。解决方案是:

  1. 将所有FFmpeg调用放到子线程
  2. 使用Handler与主线程通信
  3. 添加超时机制(如avio_open2设置超时)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 12:37:54

GPT-image2生成城市宣传海报提示词

效果&#xff1a;提示词&#xff1a;请为【城市名】生成竖版城市宣传海报。不要套用固定模板&#xff0c;要先提炼这座城市最独特的文化背景、地理环境、产业发展和城市理念&#xff0c;再据此设计画面结构。 画面必须让人一眼看出这是【城市名】&#xff1a;选取该城市最具辨识…

作者头像 李华
网站建设 2026/7/15 12:37:19

终极指南:用Bangumi追番神器打造你的专属二次元世界 [特殊字符]

终极指南&#xff1a;用Bangumi追番神器打造你的专属二次元世界 &#x1f3ac; 【免费下载链接】Bangumi :electron: An unofficial https://bgm.tv ui first app client for Android and iOS, built with React Native. 一个无广告、以爱好为驱动、不以盈利为目的、专门做 ACG…

作者头像 李华
网站建设 2026/7/15 12:33:27

从古代税赋到现代政策:交粮制度的历史演变与当代意义

交粮是怎么交的&#xff1a;从古代税赋制度到现代粮食政策的演变 粮食作为人类生存的基础物资&#xff0c;其征收和管理方式一直是社会运转的重要环节。在中国悠久的历史中&#xff0c;"交粮"这一概念承载着丰富的文化内涵和制度变迁。本文将系统梳理从古代税赋制度到…

作者头像 李华
网站建设 2026/7/15 12:31:38

C++多线程编程:从内存模型到无锁数据结构的高性能实践

1. 项目概述&#xff1a;为什么C多线程是绕不开的硬核话题 如果你用C写过稍微复杂点的程序&#xff0c;尤其是涉及到网络、图形界面或者数据处理&#xff0c;大概率会遇到一个场景&#xff1a;程序跑起来感觉“卡”&#xff0c;明明CPU占用率不高&#xff0c;但就是响应慢&…

作者头像 李华