ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ffmpeg_cli鸿蒙化适配:交叉编译与Flutter插件桥接全解析

ffmpeg_cli鸿蒙化适配:交叉编译与Flutter插件桥接全解析 1. 项目背景与适配前的核心需求拆解1.1 先搞清楚 ffmpeg_cli 到底是个什么东西如果你在 Flutter 生态里做过音视频相关的功能大概率听说过flutter_ffmpeg或者它的继任者ffmpeg_kit_flutter。而ffmpeg_cli本质上是一条更轻的路线它把命令行的 ffmpeg 能力直接包装成 Flutter 插件让你在 Dart 代码里写类似FFmpegKit.execute(-i input.mp4 -vf scale1280:720 output.mp4)这样的调用来完成视频转码、视频截帧、音频提取、画质压缩这些重活。相比逐个封装 C API命令行方案的好处是不需要为每个 ffmpeg 滤镜写 Dart 封装ffmpeg 那几百个滤镜全部保留等于把整个多媒体工具箱都搬过来了。但这里有个关键前提flutter_ffmpeg / ffmpeg_kit 官方一直以 Android、iOS、macOS 为主战场鸿蒙原生HarmonyOS NEXT出来后原来基于 Android AAR 的集成方式直接失效了。鸿蒙不再兼容 Android APK你没法把 java 层的包装硬塞进去只能重新给鸿蒙编译出可用的 native 动态库再走 Flutter 的通道把命令透传过去。所以这个项目标题里说的“鸿蒙化适配”不是简单地把 Android 的 so 拷过去而是要完成四件事用 OpenHarmony 工具链重新交叉编译 ffmpeg 的完整动态库在鸿蒙侧实现一个能够执行命令行命令的桥接层接管原来 Android 的 ShellExecutor / CommandExecutor 逻辑在 Dart 侧通过dart:ffi或 Platform Channel 把“执行 ffmpeg 命令”这条路打通处理好 ABI 差异、动态库打包、线程回调和文件路径这几个最容易翻车的点。这篇文章就是把这四件事完整走一遍。适合谁看你正在做 Flutter 应用向鸿蒙迁移或者你手头有个音视频 App 想在鸿蒙元服务里塞一个“视频压缩/格式转换”能力再或者你只是好奇 OpenHarmony 的交叉编译环境到底怎么搭——这篇文章都能给你省下少说两周的踩坑时间。1.2 鸿蒙化适配真正要解决的问题做鸿蒙适配最容易犯的错误是一上来就搜“ffmpeg 鸿蒙编译”拿到别人贴的 config 脚本就复制编译出一堆 so 后发现 Flutter 侧还是调不通。说到底鸿蒙化适配有三层问题缺一层都会卡壳。第一层是编译层。HarmonyOS NEXT 的 native 库虽然也是 ELF 格式的 .so但它用的是 OpenHarmony SDK 自带的一套 sysroot和 Android NDK 的 sysroot 并不完全一致。你如果用 Android 的 NDK clang 去编编译可能会通过但运行时会因为某些 libc 符号解析失败直接崩溃。更隐蔽的是OpenHarmony 的 libc 是 musl 而不是 bionic这是关键差异所以 configure 的时候--target-os和--cc必须指向 OHOS 的工具链。这部分网上资料很少踩坑只能靠实战。第二层是桥接层。ffmpeg_cli 内部依赖的命令行执行能力在 Android 上是通过Runtime.exec()或者 ffmpeg_kit 的 CommandExecutor 实现的。鸿蒙上没有Runtime.exec()这个说法至少 HarmonyOS NEXT 的 API 上没有直接对应物你得自己用ohos.process.ChildProcess或者干脆绕开 shell直接解析参数后传给 ffmpeg 的avformat_open_input那一套 C API。我个人建议优先走后者理由后面会讲。第三层是分发层。Flutter 插件在鸿蒙上不是走 pub.dev 的自动注册也不是看GeneratedPluginRegistrant而是要在鸿蒙工程的EntryAbility里手动把插件绑定到 FlutterEngine 上。这一层不处理好插件代码写再多也不会被调用而且不会报编译错只会静默失败——这是最气人的。1.3 选型对比直接移植、Platform Channel 桥接、还是做成完整插件动手之前先想清楚你要做到哪一档。我整理一下三种路线路线工作量运行效率维护成本适合场景路线 A直接移植 Android 插件源码仅替换 ffmpeg so小差高临时验证不建议上线路线 BPlatform Channel 桥接Dart 发命令鸿蒙侧执行中中中单功能场景例如“视频压缩”路线 C完整插件化 dart:ffi 直调 C API 封装大高中需要复用滤镜能力和多场景调用如果你只做一个“把视频压缩到 10MB 以内再上传”的功能路线 B 足够代码量大概就 500 行。但如果你想保住 ffmpeg_cli 的灵活性让产品经理随手就能提“加一个倍速播放”“加一个人声分离”那我建议直接上路线 C因为命令行的所有参数都在 Dart 侧拼好字符串就可以了不需要为每一种新功能重新编译插件这才是 ffmpeg_cli 存在的意义。2. 环境准备与技术难点分析2.1 工具清单比想象中多一个 OHOS SDK先列一下我实际用到的环境实测下来这一套是最稳的开发机Ubuntu 22.04x86_64内存 16G 以上磁盘至少预留 20G鸿蒙开发工具DevEco Studio 5.0 以上带 HarmonyOS SDK 5.0.0(12)OpenHarmony SDK从 DevEco 的Sdk/openharmony目录取重点用到native/llvm/bin下的 clang 和native/sysrootFlutter 版本Flutter 3.22并且开启鸿蒙分支或者用 OpenHarmony 官方维护的 flutter_flutter 仓库真机HarmonyOS NEXT 开发者预览版设备开 USB 调试这里我要特别强调很多人以为装了 DevEco Studio 就够了实际上真正编译 ffmpeg 需要的是它里面自带的 native SDK。你在 DevEco 的安装目录下找类似Sdk/openharmony/的结构里面有native/llvm/bin/clang这样一条路。最好把它加进 PATH之后所有编译命令都靠它。环境变量建议这样配export OHOS_SDK/path/to/DevEcoStudio/Sdk/openharmony export OHOS_NATIVE_TOOLCHAIN$OHOS_SDK/native/llvm/bin export PATH$OHOS_NATIVE_TOOLCHAIN:$PATH export CC$OHOS_NATIVE_TOOLCHAIN/clang export CXX$OHOS_NATIVE_TOOLCHAIN/clang注意这里别用 Android NDK 的 clang 顶替后文会说为什么。2.2 为什么不能用 Android NDK 的编译器这是我在项目里踩过的最深的一个坑值得单开一节讲清楚。Android 的 native 库跑在bionic libc上而 OpenHarmony 的 native 库跑在musl libc上。两者虽然都提供 pthread、malloc 这类标准接口但具体实现的符号版本、链接行为有差异。ffmpeg 这种重度依赖系统调用的库在 configure 阶段就会探测getaddrinfo、pthread_setname_np这类函数的存在与否。如果你用 Android NDK 的 clang 编译链接器会把一些 bionic 才有的符号标记进来最终产物在鸿蒙设备上加载时很典型的报错是dlopen failed: cannot locate symbol XXX referenced by libavcodec.so而且这类问题不一定在启动时爆发有时是跑到某个解码器分支才触发排查难度极高。所以我给你的第一条忠告先确认 clang 版本来自 OHOS SDK再谈编译。检查方法很简单clang --version如果你在里面看到OpenHarmony或者HarmonyOS字样就对了如果看到的是 Android NDK 那串描述哪怕其他环境都一样也先停下来去换工具链。2.3 configure 参数详解照着这份抄作业ffmpeg 的 configure 是整个移植里最核心的环节。参数不能照搬 Linux 桌面版的也不能照搬 Android 版的需要针对 OHOS 做调整。我整理了一份实测可用的配置目标是编译出裁剪后的、只保留常用编解码器的动态库./configure \ --target-oslinux \ --archaarch64 \ --enable-cross-compile \ --cc$OHOS_NATIVE_TOOLCHAIN/clang \ --cxx$OHOS_NATIVE_TOOLCHAIN/clang \ --nm$OHOS_NATIVE_TOOLCHAIN/llvm-nm \ --ar$OHOS_NATIVE_TOOLCHAIN/llvm-ar \ --strip$OHOS_NATIVE_TOOLCHAIN/llvm-strip \ --sysroot$OHOS_SDK/native/sysroot \ --enable-shared \ --disable-static \ --disable-programs \ --disable-doc \ --disable-avdevice \ --disable-postproc \ --enable-small \ --enable-gpl \ --enable-libx264 \ --enable-libmp3lame \ --enable-libopus \ --enable-libvpx \ --enable-encoderaac,libx264,mp3,libopus,libvpx_vp8,libvpx_vp9 \ --enable-decoderaac,h264,hevc,mp3,vp8,vp9,opus,pcm_s16le \ --enable-parseraac,h264,hevc,mpegaudio,opus,vp8,vp9 \ --enable-muxermp4,mov,matroska,mp3,ogg,opus \ --enable-demuxermov,matroska,mp3,ogg,opus \ --enable-protocolfile,pipe \ --enable-filterscale,crop,trim,concat,aresample,volume,overlay几个参数的解释和取舍逻辑--target-oslinux在 ffmpeg 眼里OpenHarmony 接近 Linux 内核但又不能用--target-osandroid那个会启用 bionic 相关逻辑。实测linux是最稳的。--disable-avdevice你如果在 Flutter App 里做视频处理基本不会去操作摄像头采集设备这个东西体积不小去掉能省不少空间。--disable-programs不生成 ffmpeg 可执行文件我们只要 so。编码器、解码器、封装器不要贪多每多加一个滤镜或者解码器都会让 so 体积肉眼可见地膨胀。我的建议是先用最小集通一遍流程跑通之后再按需加回来。configure 完之后就是 makemake -j$(nproc)编译过程大概 5 到 15 分钟取决于机器性能。最后检查产物ls -lh libavcodec/libavcodec.so libavformat/libavformat.so \ libavutil/libavutil.so libswscale/libswscale.so \ libswresample/libswresample.so libavfilter/libavfilter.so如果 6 个库里有一个没生成八成是 configure 阶段某个 enable 选项对应的依赖没找到回头看输出日志里的警告。另外鸿蒙手机是 aarch64别只编这一个架构就满足了后面调试的时候如果跑在模拟器x86_64上会哑火所以最佳做法是两个架构各编一遍把产物分别放到libs/arm64-v8a和libs/x86_64目录。3. 核心实操交叉编译与插件桥接3.1 第一步把编译产物整理进鸿蒙工程交叉编译完成只是万里长征第一步。接下来要做的是把 ffmpeg 的 so 文件送进鸿蒙 native 工程里。鸿蒙工程里动态库的默认搜索位置是libs/arm64-v8a这种结构你可以在 DevEco Studio 的模块目录下手动建一个entry/src/main/cpp/libs/arm64-v8a/ libavcodec.so libavformat.so libavutil.so libswscale.so libswresample.so libavfilter.so然后在entry/oh-package.json5或者 CMakeLists.txt 里声明链接位置。比如 CMake 可以这样加set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}) add_library(ffmpeg_wrapper SHARED ffmpeg_wrapper.cpp) target_link_libraries(ffmpeg_wrapper PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libavcodec.so ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libavformat.so ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libavutil.so ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libswscale.so ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libswresample.so ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libavfilter.so )注意CMake 里链接的是带完整路径的 so而不是用-l:libavcodec.so简写避免链接器去系统路径里找同名库把鸿蒙自带的一些多媒体库给误链了这个问题我后面在问题清单里还会再提。3.2 第二步在鸿蒙侧封装 CommandExecutorffmpeg_cli 最初的实现依赖“执行命令行”这个能力。Android 那边用Runtime.exec(command)把参数丢给 shell鸿蒙上没有这个现成接口但可以通过ohos.process.ChildProcess来做。我先给你看一个最直白的鸿蒙侧封装import { process } from kit.BasicServicesKit; export class CommandExecutor { static execute(command: string): Promisestring { return new Promise((resolve, reject) { const child process.ChildProcess.exec(${command} 21); child.on(message, (data: string) { // 处理实时日志 }); child.on(exit, (code: number) { if (code 0) { resolve(ok); } else { reject(new Error(exit code: ${code})); } }); }); } }这段代码在简单场景下可以跑但你很快会碰到一个问题ffmpeg 的输出日志量非常大如果你用命令行的方式跑大量的 stdout/stderr 回传会把 Channel 挤爆Dart 侧 UI 卡顿是小事回调乱序甚至崩溃都有可能。所以我的实际建议是命令行的方案只用来做调试验证上线版本应该直接跳过 shell 这层在鸿蒙侧解析参数后调用 ffmpeg 的 C API。ffmpeg 本身提供了avformat_open_input、avcodec_send_packet、avcodec_receive_frame这套解码流程你把它包成一个FFmpegNativeBridge对外只暴露三个方法executeWithArgs(ListString args)、cancel()、getLogs()。这样既不依赖 shell 的解析差异又能精确控制进度回调。3.3 第三步Dart 侧 dynamic library 加载与 ffi 封装Dart 侧需要先解决动态库加载的问题。在 Flutter 里通常用dart:ffi的DynamicLibrary.open()但在鸿蒙上路径要写对import dart:ffi; import dart:io; final DynamicLibrary ffmpegBridge Platform.isAndroid ? DynamicLibrary.open(libffmpeg_wrapper.so) : DynamicLibrary.open(libffmpeg_wrapper.so); // 鸿蒙上同样使用 .so 后缀注意一个细节鸿蒙和 Android 的动态库加载逻辑不同。Android 上 Flutter 会从 APK 的 lib/ 目录自动找而鸿蒙上 Flutter 的 dynamic library 默认搜索路径不一定覆盖到你放在entry/src/main/cpp/libs下的库。如果你发现DynamicLibrary.open报library not found先检查一下 so 是否被正确打进了 HAP 包解包看一眼libs/arm64-v8a下有没有对应文件。开发阶段可以临时把 so 放到entry/src/main/resources/rawfile通过路径去打开但上线前一定要归位到 libs 目录。接下来定义一个简单的 FFI 接口typedef ExecuteNative Int32 Function(PointerUtf8 args); typedef ExecuteDart int Function(PointerUtf8 args); final ExecuteDart _execute ffmpegBridge .lookupNativeFunctionExecuteNative(ffmpeg_wrapper_execute) .asFunction();这里如果你熟悉package:ffi的套路就知道字符串要转成PointerUtf8。但有个坑ffmpeg 参数里有空格、引号、中文路径直接拼成字符串传过去鸿蒙侧的ChildProcess.exec很可能因为转义不对而失败。更稳的做法是封装成“参数数组”final result _executeArguments( [-i, inputPath, -vf, scale1280:720, outputPath], );底层 C 函数直接接收char** argv和int argc从根上避开命令行转义问题。这个思路也正好规避了之前提到的 shell 解析差异。3.4 第四步插件注册与 MethodChannel 对接现在到了 Flutter 插件机制的关键部分。鸿蒙上的插件注册方式跟 Android 差异很大你没法依赖flutter pub get之后生成的GeneratedPluginRegistrant。正确做法是在鸿蒙工程里找到主 Ability 的.ets文件手动挂插件// EntryAbility.ets import { FlutterAbility } from ohos/flutter_ability; import { FlutterEngine } from ohos/flutter_engine; import { FfmpegCliPlugin } from ./FfmpegCliPlugin; export default class EntryAbility extends FlutterAbility { onWindowStageCreate(windowStage: window.WindowStage): void { super.onWindowStageCreate(windowStage); const engine: FlutterEngine this.flutterEngine; engine.getPluginRegistry().register(new FfmpegCliPlugin()); } }FfmpegCliPlugin自己实现 MethodChannel 的 handlerimport { MethodChannel } from ohos/flutter_plugin; export class FfmpegCliPlugin { private channel: MethodChannel; constructor() { this.channel new MethodChannel(com.example.ffmpeg_cli/method); this.channel.setMethodCallHandler((call, result) { switch (call.method) { case execute: this.execute(call.arguments as Arraystring, result); break; case cancel: this.cancel(); result.success(true); break; default: result.notImplemented(); } }); } }注意一个细节MethodChannel 的 handler 回调线程不是 UI 线程所以在鸿蒙侧执行耗时 ffmpeg 任务时不要在回调里直接更新 UI 状态。Dart 侧收到 result 之后再用 Provider 或者 ValueNotifier 通知界面刷新进度别跨层拿 context。如果你对 Flutter 组件通信和状态管理比较熟可以在 Dart 侧这样设计插件只负责“执行命令、回传结果”用一个MediaTaskController extends ChangeNotifier维护任务状态然后借助provider包在页面里监听。鸿蒙侧的 Channel 只管透传字符串和二进制数据不要把 UI 逻辑写进插件里否则后面想复用这个能力给鸿蒙元服务时就麻烦了。4. 常见问题与排查技巧实录4.1 问题速查表我把这个项目里遇到的高频问题整理成一张表方便你直接定位现象可能原因解决方案编译时找不到sysroot里的头文件OHOS_SDK 路径配错检查$OHOS_SDK/native/sysroot/usr/include是否存在so 加载失败报 missing symbol用了 Android NDK 或者 bionic libc 工具链换成 OHOS SDK 自带的 clang 重新编译HAP 包解出来没有 so 文件CMake 输出目录不对在 CMake 中显式设置LIBRARY_OUTPUT_DIRECTORY指定到 libsffmpeg 执行后日志乱码中文编码问题统一用 UTF-8Dart 侧把字符串转成PointerUtf8进程执行后卡死没有任何回调子进程没有正确关闭 stdin调用child.closeStdin()或者用参数数组模式规避DynamicLibrary.open抛异常so 没打进 HAP查看libs/arm64-v8a确认产物在正确目录鸿蒙设备上 Hello World 插件能跑ffmpeg 命令不生效插件注册代码没执行在EntryAbility的 onWindowStageCreate 里手动注册4.2 逐个复盘从库找不到聊到线程回调逐个说一下最典型的几个场景。“库找不到”是我遇到最多的反馈。很多人把 so 放进entry/src/main/cpp/libs了但不知道 DevEco 的构建系统默认只打包entry/libs或者entry/src/main/cpp/libs下与 ABI 匹配的目录。如果你同时开了arm64-v8a和x86_64但构建产物只出了 x86_64那真机arm64上必然加载失败。解决方法是检查build-profile.json5里abiFilters是否配置了arm64-v8a。这个不是 Kotlin/Gradle 那套了别惯性思维去找 build.gradle。“回调不触发”是另一个重灾区。ffmpeg 执行时间长如果鸿蒙侧的 CommandExecutor 是 async 的但 Dart 侧的 MethodChannel 调用设置了超时或者 Flutter 里用了await但事件一直没回最常见的原因是你的 native 函数在 UI 线程上同步阻塞了。鸿蒙的 flutter 插件运行环境里MethodChannel 的 handler 默认跑在平台的 UI 线程上。你把 ffmpeg 的转码循环直接放在 handler 里等于把 UI 线程卡住此时任何 UI 刷新和 Channel 回传都会被挂起。我说得难听一点这是新手最容易犯的错。正确做法是鸿蒙侧的 handler 里 new 一个 TaskPool 或者 Worker把 execute 丢进去完成后通过taskpool.Task回到主线程再调result.success()。“非华为电脑连接鸿蒙手机调试”。这个话题很多人问。其实不必非得是华为电脑只要你的开发机装了 DevEco Studio 并启用了 adb/haradb 工具鸿蒙手机开启开发者模式后连 USB 就能被识别。我用的是一台普通的 Windows 笔记本在cmd里执行haradb devices能看到设备就行。关键是别用 Flutter 默认的flutter devices去检测鸿蒙设备要配合 OpenHarmony 的 Flutter SDK 来用。“flutter run 在鸿蒙设备上跑不起来”。如果你是从 Flutter 3.x 的标准分支拉的代码直接对鸿蒙设备flutter run大概率报错。因为需要先切换到支持鸿蒙的分支或使用 OpenHarmony 仓维护的 Flutter SDK比如flutter_flutter的 harmony 分支。这个分支对flutter_aar这类懒加载组件支持更全。另一个办法是只把 Flutter 作为 Android/iOS 跨端代码鸿蒙侧用 DevEco 的原生页面承载非要复用 Flutter UI 的话再考虑完整分支。4.3 性能与体积能阉割就阉割多媒体库最容易膨胀。ffmpeg 全量编译后 so 体积超过 100MB 是家常便饭这在移动端是不可接受的。我给你的建议是从“最小可用集”开始加需求而不是从全集开始减。以视频压缩为例你真正需要的解码器可能只有h264、hevc编码器可能只需要libx264封装/解封装只需要mp4。那么 configure 里就只保留这几个。哪怕后面要加音频也是加aac和mp3而不是把所有编解码器全勾上。另一个体积杀手是--enable-debug。我见过有人为了排查问题编译了 debug 版 ffmpegso 直接翻三四倍代码里还没有条件编译开关连上线也没有切回 release。反面教材引以为戒。建议 configure 时保留--disable-debug需要日志时通过av_log_set_level在运行时控制级别。性能方面软编软解在鸿蒙上默认是吃 CPU 的。一味堆参数解决不了问题后续可以接鸿蒙的 MediaCodec 硬解让 ffmpeg 只做滤镜拼接和容器封装这样能省不少电。进阶的做法是给 ffmpeg 加mediacodec的 hwaccel 后端不过那部分工作量大很多不在本文范围但方向是对的。5. 后续扩展鸿蒙元服务与多场景玩法ffmpeg_cli 在鸿蒙上跑通之后能玩的花样比想象中多。第一个方向是把能力嵌到鸿蒙元服务。鸿蒙的元服务天生轻量化不太可能直接塞一个 50MB 的 ffmpeg但如果你只编译libavcodeclibavutil这两个库裁剪后可以压到几 MB 级别实现一个“一键压缩视频”的小卡片服务在微信里收到大视频后直接拉起处理体验会有代差感。这也是鸿蒙生态里比较新的机会点早入场的人有优势。第二个方向是做音频层面的精细化处理。比如你在音频通话场景里做降噪、回声消除、音量均衡ffmpeg 的aresample和volume滤镜足够用再往上还可以接 G.722、Opus 等编码器。之前有开发者聊到鸿蒙通话蓝牙噪声的问题这类系统级现象通常需要结合硬件策略但 ffmpeg 侧的音频前处理能缓解一部分。第三个方向是把 flutter 的 impeller 渲染引擎跟 ffmpeg 的画面处理结合。比如从视频里抠出一帧做图片超分、人脸检测后再送进 flutter 侧渲染。ffmpeg 负责视频解码和帧提取flutter 侧负责视觉效果两边各干各擅长的架构上清晰也不容易崩。最后再聊一点逆向工程的杂谈flutter逆向工具箱里处理 Flutter 插件的逻辑很大一部分依赖对 MethodChannel 名字和 init 入口的识别。你在鸿蒙上做适配时把 channel 名字定义得规范一些、不要一股脑全塞进一个“万能 method”里将来对自己调试、对第三方工具识别都会友好很多。这不是为了规避什么纯粹是工程洁癖带来的一系列好处。6. 实操经验补充我是怎么把这个项目真正收尾的如果只把 so 编译出来、插件能跑这个项目只能算完成 60%。真正的收尾工作其实是把使用体验打磨到跟 Android/iOS 版本一致。我做了三件事你可以直接抄第一统一日志输出。ffmpeg 的日志通过av_log_set_callback回传Android 上很多人直接丢给 Logcat鸿蒙上则需要转发到hilog。我在鸿蒙侧封装了一个logCallback把 AV_LOG_LEVEL 映射到 hilog 的级别比如AV_LOG_ERROR对应HiLogErrorAV_LOG_DEBUG对应HiLogDebug。这样真机调试时hilog | grep Ffmpeg就能看到完整链路不用再猜哪一步断了。第二做一个任务取消机制。ffmpeg 的长任务如果无法取消用户界面会很被动。ffmpeg 提供了av_interrupt_callback你可以在鸿蒙侧维护一个全局 AtomicBoolean当 Dart 侧调用cancel()时置为 trueffmpeg 在下一帧处理前检查到这个标记就会中断退出。这个机制加不加体验差别非常大。没有它用户点了个“取消”界面却要等转码跑完那感觉就像让用户等着看进度条走完再认输。第三把进度回调做成流式事件。ffmpeg 的转码进度可以从编码器的AV_TIME_BASE换算出来但我更推荐直接读av_read_frame的解码位置百分比。在鸿蒙侧的 native 层每隔一段时间抛出一个 progress 事件通过 EventChannel 持续推送到 Dart 侧。这里要说的是Dart 侧收到进度后不要每次都 setState 刷新整个页面用 ValueNotifier 或者 StreamBuilder 精准更新进度条避免 Flutter 重绘风暴这个细节在小内存设备上尤其重要。我个人的体会是鸿蒙化适配这种工作本质上是把一个成熟生态里的成熟能力迁移到一个还在快速演进的平台上。ffmpeg 本身不复杂复杂的是它周围那一圈链路工具链、ABI、插件注册、线程模型。你把这些链路逐一打通之后会发现鸿蒙开发并没有想象中那么陌生它只是换了一套编译器和一套系统接口底层逻辑还是那些底层逻辑。下一次你再去适配其他 C/C 库时把本文里这套“确认工具链 - 交叉编译 - 整理产物 - 桥接通道 - 流式回调 - 任务控制”的流程复制过去速度会快非常多。
返回列表