A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】06 / 56
■ 应用:AirBrake(from HandBrake)(AirBrake)
■ 定位:HandBrake 移植,视频格式转码
■ 上游:HandBrake | 路线:GTK4 4.14.5 | 版本:1.3.0
■ 开源: https://gitcode.com/Andy88/Harmony-HandBrake
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917615706846835445
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-HandBrake
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirBrake
视频转码是桌面计算里少见的"重负载 + 长时间"场景:一个视频从 H.264 压到 H.265 或 AV1,动辄占用全部核心运行数分钟。HandBrake 是这一领域最知名的开源工具,其 libhb 转码核心与 GTK4 前端汇聚了 x264、x265、SVT-AV1、dav1d、libass、ffmpeg 等一整套编解码生态。把它带到鸿蒙 PC 与平板上,意味着鸿蒙设备第一次拥有完整的本地视频重编码能力,也为检验 OHOS NDK 对大型 C 语言工程的兼容性提供了一个高标准的试金石。
工程代号 AirBrake,上游取自 HandBrake 1.12.0 之后的开发版(GTK4 前端 + libhb 核心)。路线选择由上游约束决定:gtk 前端强依赖 gtk4 ≥ 4.6,无法走 GTK3 通道,因此采用 GTK4 OHOS GDK 后端方案,与姊妹工程 AirScape 同血统。应用包名 com.airbrake.app,版本 1.0.0,deviceTypes 覆盖 tablet、2in1 与 phone,SDK 为 HarmonyOS API 23+。PORTING.md 记录了依赖矩阵、P 系列补丁清单、风险登记册与 12 条测试用例,ai_log 目录下 10 篇过程记录保存了每一轮真机回归的证据。
▍二、先划清边界:不把编译通过当成适配完成
HandBrake 的 linux/GTK 前端假设运行在完整桌面上:GtkApplication 主循环、原生菜单栏、GtkFileDialog、Nvidia/Intel 硬件编码器各走各的平台白名单。仅把 libhb 交叉编译出静态库,得到的是一个不能交互的转码函数集合;只有把窗口、菜单、文件选择、弹窗这些桌面外壳逐一接入鸿蒙系统能力,产品才成立。下表是当前工程划定的对应关系。
层次 原项目实现 鸿蒙侧当前方案
应用入口 main.c + GtkApplication 主循环 Stage 模型 EntryAbility + g_application_run 前预绑定时序
窗口承载 GTK4 Wayland/X11 表面 XComponent(TEXTURE) + GDK OHOS 后端 cairo 整帧 blit
界面层 gtk/src 全部 GTK 控件 GTK4 原生渲染,除菜单栏外零 ArkTS 手绘 UI
菜单栏 gtk_application_set_menubar 原生显示 GMenuModel→JSON 导出,ArkUI Menu 渲染后回注 GAction
文件选择 GtkFileDialog / GhbFileButton 全部桥接鸿蒙 DocumentViewPicker(P2 补丁)
弹窗 GDK surface 直接呈现 dialog ops 四层链映射为 ArkTS SubWindowManager 真子窗口
转码核心 libhb + contrib 编解码器 libhb.a 与 ffmpeg 9.0.1 等静态闭包进 libghb.so
边界中有明确的裁剪:--disable-libdovi(Rust 无 OHOS target)、fdk-aac、nvenc/qsv/vce/mf/vaapi 全部关闭,OHOS 侧没有对应的 GPU 编码 API 对接;x265 因承担 H.265 核心功能而保留。这些取舍在 PORTING.md §2.2 有逐项记载,不把裁剪掉的能力伪装成已支持。
▍三、鸿蒙版的整体架构
工程是 Air 双层架构的又一次实践:ArkTS 壳只负责 Ability 生命周期、部署遮罩、沉浸式、系统 Picker、原生菜单栏与子窗口管理;NAPI 模块 libbrakebridge.so 是只链系统库的薄壳;OnSurfaceCreated 时按拓扑序 dlopen 全栈(RTLD_GLOBAL),再 dlsym core API 进入 native 世界。转码、解码、字幕渲染全部发生在 native 宿主线程,JS 侧绝不同步等待 native,这是风险登记册 R8 对 appfreeze 的结构性防御。
text
EntryAbility(ArkTS 壳:splash 部署遮罩 / 沉浸式 / Picker / ArkUI 菜单栏 / 子窗口)
│ NAPI:libbrakebridge.so(薄壳,只链系统库)
▼ OnSurfaceCreated 按拓扑序 dlopen(RTLD_GLOBAL)→ dlsym core API
libairbrake_core.so(宿主线程 + env/gettext/日志/SIGSEGV handler/picker 桥/dialog ops 桥)
├── libgtk-4.so 4.14.5(含 OHOS GDK 后端,弹窗多实例 dialog_ops 版)
├── libghb.so(gtk 前端源码排除 main.c,meson 产物)
├── libhb.a(libhb 核心,静态链入 libghb.so)
└── ffmpeg 9.0.1 + x264 + x265 + SVT-AV1 + dav1d + libass(静态闭包进 libghb.so)
主要目录如下:
text
AirBrake/
├── PORTING.md # 架构/依赖矩阵/补丁清单/风险登记册/测试用例
├── ai_log/ # 逐日真机回归与根因记录
├── native/
│ ├── patches/ # P 系列补丁权威副本(ghb-ohos/gtk4-ohos/theme)
│ ├── scripts/ # env.sh、build_gtk4/build_libhb/build_ffmpeg/
│ │ # build_ghb、deploy_runtime_libs 等流水线
│ ├── install/ # 复用 AirScape 预编译栈(hardlink,只读)
│ └── sources/ # gtk4-src 与 handbrake 上游源码
└── entry/src/main/
├── ets/ # EntryAbility/Index/DialogPage/SubWindowManager
│ # /ImmersiveManager/FileManager
└── cpp/ # napi_init、airbrake_core、ELF 五件套
# (elf_edit/fix_verdef/fix_elf_align/patch_libs)
▍四、核心适配过程
### 1. GtkApplication 主循环与鸿蒙表面的预绑定时序
GTK 应用默认自己拉起主循环并创建表面,而鸿蒙侧必须先有 XComponent 表面才有东西可画。工程按风险登记册 R5 的对策实现时序:gtk_init_check 先行完成 GTK 初始化,再把 NativeWindow 交给 GDK 后端(set_native_window)、通知初始尺寸(notify_resize),最后才进入 g_application_run。这样 GTK 首帧绘制时表面已经存在,避免主循环空转等待一个不存在的窗口。
### 2. 菜单栏从 GMenuModel 到 ArkUI Menu 的往返桥
ghb 在 application.c:895 调用 gtk_application_set_menubar 构建菜单模型。补丁 P1 用环境变量门控跳过原生 menubar 显示(模型保留供导出),core 层把 gtk_application_get_menubar 返回的 GMenuModel 序列化为 JSON,ArkTS 侧用 ArkUI 原生 Menu 渲染中文菜单栏;用户点击后经 activateMenuAction 回注为 GAction 激活,命令语义完全由 GTK 侧裁决。这条 AirScape 范式的通道在 AirBrake 上零改动适用,是"原生引擎画界面、系统能力管外壳"的典型分工。
### 3. 弹窗多实例:dialog ops 四层链
GTK4 的对话框默认是单一表面的合成结果,而鸿蒙需要真子窗口才能独立拖动与关闭。补丁 P4 把 dialog ops 四层链(classify/create/geometry/hide/destroy + bind_surface + local_to_root + 子窗口合成)从 AirPS 的 GTK3 后端移植到 GDK4 OHOS 后端,每个对话框 surface 经 ArkTS SubWindowManager 落成独立子窗口。PORTING.md 踩坑记录里围绕它修复了三个真问题:隐藏 surface 吞点击(hit_test 补 !GDK_SURFACE_IS_MAPPED 过滤)、present 时刻自然尺寸测量过早导致按钮被裁剪(present 后 150/300ms 延迟重算两轮)、双发 create op 重 claim 覆盖导致关弹窗误关主窗(claim 幂等 + 主窗确定性识别)。
### 4. 文件选择与字幕渲染桥接系统
HandBrake 的源选择、目标保存、字幕 SRT 导入原来都走 GTK 文件对话框。补丁 P2 把 ghb-file-button.c 等处的文件选择统一改道 airbrake_ohos_pick_file,经 dlsym 惰性解析桥到鸿蒙 DocumentViewPicker;选择结果回填 GTK 数据结构,ghb 上层代码无感知。字幕轨由 libass 渲染,其依赖的 freetype/fontconfig/harfbuzz/fribidi 全部来自 AirScape 预编译栈,hardlink 拷入 native/install 只读使用,避免重复构建。
### 5. 转码核心的静态闭包与中文化
x264、x265 三精度合并、SVT-AV1、dav1d、zimg、蓝光/DVD 读取族与 ffmpeg 9.0.1 全部以静态库形式链进 libghb.so,libhb 亦然;动态依赖收敛到 GTK 栈,HAP 内需要管理的 .so 数量大幅减少。中文化走 airlibintl 真 .mo 替身加 LANGUAGE=zh_CN,ghb(gtk/po/zh_CN.po 编译)、gtk40、glib20 三个域一并生效,界面与菜单全中文。
▍五、适配中遇到的几个难点
### 难点1:注入坐标系是物理像素,不是逻辑密度
PC 全功能实测首轮出现"选项卡不翻页、预览按钮无响应"的全面失灵。定位结果是测试注入点击按 density 1.9 换算了坐标,而 2in1(3120×2080)的注入坐标系就是物理像素,全部点击落空。这一教训被写进 ai_log 作为固定纪律:任何真机自动化前先确认目标设备的坐标契约,误报的"功能失效"可能只是测量工具用错了比例尺。
### 难点2:g_clear_pointer 包装二级指针导致静默内存破坏
关闭预览窗稳定复现 SIGSEGV,且野指针地址跨进程恒定。真根因是 ffstream.c 用 glib 的 g_clear_pointer 包装 avcodec_free_context 等二级指针 API——该宏会把 *pp 的值传给销毁函数,于是静态的 av_codec_context_class 被当成 AVCodecContext 关闭,guard 全在静态内存上读"字段"恒通过,最终在野 mutex 上崩溃。所谓"毒化地址"实为 .text 两条指令的小端 qword。修复是直接传 &member,回归 11 次关窗零崩溃。这个案例说明移植中每引入一个包装宏都要核对被包装函数的参数契约。
### 难点3:修复已上线,HAP 里还是旧库
meson 重编 libghb 后,hvigor 增量构建不会重新 staging native 库,修复从编译到上设备相差近一小时,期间"修复无效"的结论全是假象。工程固化了完整链路:改 native 源码后必须 touch entry 的 CMakeLists、清理三处 intermediates、先跑 deploy_runtime_libs.sh 刷新 stage_libs.cmake 的快照再重建,最后用 unzip 加 strings 验收 HAP 内的 libghb。打包链的另一个同类坑是重编 gtk4 后主栈同步会跳过 install 符号链接,必须手工拷贝实体文件再走 patch_libs。
### 难点4:GDK 帧时钟的自旋与死亡超时
预览播放偶发冻结,六轮插桩把根因锁定在 GdkFrameClockIdle 的双时钟交替:一个时钟正常 60fps,另一个以 BEFORE_PAINT+RESUME 相位 0ms 间隔自旋(约 357Hz),paint 相位被饿死。后续又在熄屏 SUSPEND-恢复循环中确认了死亡 paint timeout 的变体——挂起的 timeout 永不触发,request_phase 全部被挡死。修复是在 maybe_start_idle 里加入挂起超时检测,超过 200ms 强制移除重排并 g_warning 留痕,触摸与重绘因此自愈。
### 难点5:IME 桥的挂接时序
新增预设对话框无法输入文字。第一层根因是 IME 桥的 set_ui_notify 永不挂接——注册回调时 core 句柄尚未加载,此前的"键盘弹出实证"来自热重载轮,冷启动必坏;修复是补 bridge_ime_handler_if_ready() 双时序幂等挂接。第二层根因是对话框子窗二次触发 create op,claim 被覆盖成主窗,按关闭键等于 gtk_window_close(主窗),应用直接冻死;修复加入 claim 幂等复用与主窗确定性识别(hb_window 是注册进 GtkApplication 的窗口,ghb 弹窗全 transient-for 不进列表)。四层 IME 桥本身(后端 touch hook → core gtk_widget_pick + pango 布局 → TSFN → 隐形 TextArea 差分)在 09-05 已落地,冷启动时序是它最后的缺口。
▍六、编译、安装与启动
Native 侧先于 ArkTS 构建。按 native/scripts/env.sh 配置交叉 SDK(OHOS NDK clang 15,musl aarch64),流水线顺序为:download_all.sh 拉取 tarball 缓存到 native/dl,build_gtk4.sh 从官方 4.14.5 tarball 重放 ohos-backend-files.txt 的 5 处集成点,build_small_libs.sh/build_codec_libs.sh/build_ffmpeg.sh 产出静态编解码闭包,build_libhb.sh 以 make 替代脚本自产 project.h 与 preset_builtin.h,build_ghb.sh 用 meson 生成 libghb.so,最后 deploy_runtime_libs.sh 把运行库同步到 entry。
ArkTS 打包使用标准 hvigor 流程:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
签名产物位于 entry/build/default/outputs/default/entry-default-signed.hap。安装启动命令使用真实包名:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airbrake.app
依据 PORTING.md §5 与 ai_log 记载,交付包在 MatePad 11.5 上完成全新安装终验:T1 冷启动、T2 菜单栏鸿蒙化、T4 源扫描、T5 预览播放(时间码严格递增)、T10 队列窗口、T12 崩溃巡检(faultlog 恒空)全部通过;Mini 上另验证了 T3 汉化、T6 x264、T7 x265/AV1 编码产物可播。
▍七、当前功能边界
当前版本已覆盖的能力:
- 完整转码链:视频源扫描(标题/章节)、x264/x265/SVT-AV1 编码、队列多任务、内置预设加载与自定义保存;
- 原生 GTK4 界面:全控件原生渲染、中文本地化(ghb/gtk40/glib20 三域)、工具栏真实图标;
- 桌面窗口体系:ArkUI 菜单栏、弹窗真子窗口多实例、DocumentViewPicker 文件选择、字幕 SRT 导入与 libass 压制渲染;
- 内置预览播放:基于 ffmpeg 解码的时间码驱动播放,EOF 停末帧,连续重播稳定;
- 稳定性基线:SIGSEGV handler、日志落 .txt(airbrake_trace.txt)、faultlog 零记录的巡检用例。
尚未覆盖或有待处理的能力(均出自 ai_log 遗留清单):键盘弹出时描述域被遮挡(确认按钮在顶部,工程选择接受现状)、新建预设重开时类别下拉残留上次输入(上游不重置行为)、tooltip 偶发滞留、MatePad Mini 上的预览冻结已定位修复但待设备回连复验、GPU 编码(nvenc/qsv 等)因平台无对接 API 而未纳入。IME 中文输入在桌面 GTK 输入框的链路已经打通,但自动化注入无法验证该通道,以真手复测为准。
因此,当前版本更准确的定位是"HandBrake 鸿蒙可用交付版":视频转码主流程、界面体系与稳定性巡检构成完整闭环,剩余项集中在上游交互细节与特定设备的长尾问题。
▍八、总结
AirBrake 的适配把 Air 系架构从图形工具推到了重计算场景:GUI 层继续由 GTK4 OHOS 后端原生渲染,ArkTS 壳收敛到菜单、弹窗与文件系统能力,转码负载整体沉入 native 宿主线程。工程证明 libhb 与整套 contrib 编解码生态可以在 OHOS NDK clang 15 下完成静态闭包,也暴露了大型 C 工程移植中包装宏契约、构建缓存、帧时钟时序这类不写在任何 API 文档里的问题——PORTING.md 的踩坑映射与 ai_log 的逐日证据是这些经验的主要载体。
后续工作的重心在收尾而非开疆:复验 Mini 预览冻结的修复、清理上游交互残留(tooltip、类别下拉)、以及视需求评估硬编码器的替代路径。每一步仍以 PORTING.md 的测试用例表为准,真机 faultlog 恒空是任何变更合入前的底线。
■ 应用:AirBrake(from HandBrake)(AirBrake)
■ 定位:HandBrake 移植,视频格式转码
■ 上游:HandBrake | 路线:GTK4 4.14.5 | 版本:1.3.0
■ 开源: https://gitcode.com/Andy88/Harmony-HandBrake
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917615706846835445
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-HandBrake
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirBrake
视频转码是桌面计算里少见的"重负载 + 长时间"场景:一个视频从 H.264 压到 H.265 或 AV1,动辄占用全部核心运行数分钟。HandBrake 是这一领域最知名的开源工具,其 libhb 转码核心与 GTK4 前端汇聚了 x264、x265、SVT-AV1、dav1d、libass、ffmpeg 等一整套编解码生态。把它带到鸿蒙 PC 与平板上,意味着鸿蒙设备第一次拥有完整的本地视频重编码能力,也为检验 OHOS NDK 对大型 C 语言工程的兼容性提供了一个高标准的试金石。
工程代号 AirBrake,上游取自 HandBrake 1.12.0 之后的开发版(GTK4 前端 + libhb 核心)。路线选择由上游约束决定:gtk 前端强依赖 gtk4 ≥ 4.6,无法走 GTK3 通道,因此采用 GTK4 OHOS GDK 后端方案,与姊妹工程 AirScape 同血统。应用包名 com.airbrake.app,版本 1.0.0,deviceTypes 覆盖 tablet、2in1 与 phone,SDK 为 HarmonyOS API 23+。PORTING.md 记录了依赖矩阵、P 系列补丁清单、风险登记册与 12 条测试用例,ai_log 目录下 10 篇过程记录保存了每一轮真机回归的证据。
▍二、先划清边界:不把编译通过当成适配完成
HandBrake 的 linux/GTK 前端假设运行在完整桌面上:GtkApplication 主循环、原生菜单栏、GtkFileDialog、Nvidia/Intel 硬件编码器各走各的平台白名单。仅把 libhb 交叉编译出静态库,得到的是一个不能交互的转码函数集合;只有把窗口、菜单、文件选择、弹窗这些桌面外壳逐一接入鸿蒙系统能力,产品才成立。下表是当前工程划定的对应关系。
层次 原项目实现 鸿蒙侧当前方案
应用入口 main.c + GtkApplication 主循环 Stage 模型 EntryAbility + g_application_run 前预绑定时序
窗口承载 GTK4 Wayland/X11 表面 XComponent(TEXTURE) + GDK OHOS 后端 cairo 整帧 blit
界面层 gtk/src 全部 GTK 控件 GTK4 原生渲染,除菜单栏外零 ArkTS 手绘 UI
菜单栏 gtk_application_set_menubar 原生显示 GMenuModel→JSON 导出,ArkUI Menu 渲染后回注 GAction
文件选择 GtkFileDialog / GhbFileButton 全部桥接鸿蒙 DocumentViewPicker(P2 补丁)
弹窗 GDK surface 直接呈现 dialog ops 四层链映射为 ArkTS SubWindowManager 真子窗口
转码核心 libhb + contrib 编解码器 libhb.a 与 ffmpeg 9.0.1 等静态闭包进 libghb.so
边界中有明确的裁剪:--disable-libdovi(Rust 无 OHOS target)、fdk-aac、nvenc/qsv/vce/mf/vaapi 全部关闭,OHOS 侧没有对应的 GPU 编码 API 对接;x265 因承担 H.265 核心功能而保留。这些取舍在 PORTING.md §2.2 有逐项记载,不把裁剪掉的能力伪装成已支持。
▍三、鸿蒙版的整体架构
工程是 Air 双层架构的又一次实践:ArkTS 壳只负责 Ability 生命周期、部署遮罩、沉浸式、系统 Picker、原生菜单栏与子窗口管理;NAPI 模块 libbrakebridge.so 是只链系统库的薄壳;OnSurfaceCreated 时按拓扑序 dlopen 全栈(RTLD_GLOBAL),再 dlsym core API 进入 native 世界。转码、解码、字幕渲染全部发生在 native 宿主线程,JS 侧绝不同步等待 native,这是风险登记册 R8 对 appfreeze 的结构性防御。
text
EntryAbility(ArkTS 壳:splash 部署遮罩 / 沉浸式 / Picker / ArkUI 菜单栏 / 子窗口)
│ NAPI:libbrakebridge.so(薄壳,只链系统库)
▼ OnSurfaceCreated 按拓扑序 dlopen(RTLD_GLOBAL)→ dlsym core API
libairbrake_core.so(宿主线程 + env/gettext/日志/SIGSEGV handler/picker 桥/dialog ops 桥)
├── libgtk-4.so 4.14.5(含 OHOS GDK 后端,弹窗多实例 dialog_ops 版)
├── libghb.so(gtk 前端源码排除 main.c,meson 产物)
├── libhb.a(libhb 核心,静态链入 libghb.so)
└── ffmpeg 9.0.1 + x264 + x265 + SVT-AV1 + dav1d + libass(静态闭包进 libghb.so)
主要目录如下:
text
AirBrake/
├── PORTING.md # 架构/依赖矩阵/补丁清单/风险登记册/测试用例
├── ai_log/ # 逐日真机回归与根因记录
├── native/
│ ├── patches/ # P 系列补丁权威副本(ghb-ohos/gtk4-ohos/theme)
│ ├── scripts/ # env.sh、build_gtk4/build_libhb/build_ffmpeg/
│ │ # build_ghb、deploy_runtime_libs 等流水线
│ ├── install/ # 复用 AirScape 预编译栈(hardlink,只读)
│ └── sources/ # gtk4-src 与 handbrake 上游源码
└── entry/src/main/
├── ets/ # EntryAbility/Index/DialogPage/SubWindowManager
│ # /ImmersiveManager/FileManager
└── cpp/ # napi_init、airbrake_core、ELF 五件套
# (elf_edit/fix_verdef/fix_elf_align/patch_libs)
▍四、核心适配过程
### 1. GtkApplication 主循环与鸿蒙表面的预绑定时序
GTK 应用默认自己拉起主循环并创建表面,而鸿蒙侧必须先有 XComponent 表面才有东西可画。工程按风险登记册 R5 的对策实现时序:gtk_init_check 先行完成 GTK 初始化,再把 NativeWindow 交给 GDK 后端(set_native_window)、通知初始尺寸(notify_resize),最后才进入 g_application_run。这样 GTK 首帧绘制时表面已经存在,避免主循环空转等待一个不存在的窗口。
### 2. 菜单栏从 GMenuModel 到 ArkUI Menu 的往返桥
ghb 在 application.c:895 调用 gtk_application_set_menubar 构建菜单模型。补丁 P1 用环境变量门控跳过原生 menubar 显示(模型保留供导出),core 层把 gtk_application_get_menubar 返回的 GMenuModel 序列化为 JSON,ArkTS 侧用 ArkUI 原生 Menu 渲染中文菜单栏;用户点击后经 activateMenuAction 回注为 GAction 激活,命令语义完全由 GTK 侧裁决。这条 AirScape 范式的通道在 AirBrake 上零改动适用,是"原生引擎画界面、系统能力管外壳"的典型分工。
### 3. 弹窗多实例:dialog ops 四层链
GTK4 的对话框默认是单一表面的合成结果,而鸿蒙需要真子窗口才能独立拖动与关闭。补丁 P4 把 dialog ops 四层链(classify/create/geometry/hide/destroy + bind_surface + local_to_root + 子窗口合成)从 AirPS 的 GTK3 后端移植到 GDK4 OHOS 后端,每个对话框 surface 经 ArkTS SubWindowManager 落成独立子窗口。PORTING.md 踩坑记录里围绕它修复了三个真问题:隐藏 surface 吞点击(hit_test 补 !GDK_SURFACE_IS_MAPPED 过滤)、present 时刻自然尺寸测量过早导致按钮被裁剪(present 后 150/300ms 延迟重算两轮)、双发 create op 重 claim 覆盖导致关弹窗误关主窗(claim 幂等 + 主窗确定性识别)。
### 4. 文件选择与字幕渲染桥接系统
HandBrake 的源选择、目标保存、字幕 SRT 导入原来都走 GTK 文件对话框。补丁 P2 把 ghb-file-button.c 等处的文件选择统一改道 airbrake_ohos_pick_file,经 dlsym 惰性解析桥到鸿蒙 DocumentViewPicker;选择结果回填 GTK 数据结构,ghb 上层代码无感知。字幕轨由 libass 渲染,其依赖的 freetype/fontconfig/harfbuzz/fribidi 全部来自 AirScape 预编译栈,hardlink 拷入 native/install 只读使用,避免重复构建。
### 5. 转码核心的静态闭包与中文化
x264、x265 三精度合并、SVT-AV1、dav1d、zimg、蓝光/DVD 读取族与 ffmpeg 9.0.1 全部以静态库形式链进 libghb.so,libhb 亦然;动态依赖收敛到 GTK 栈,HAP 内需要管理的 .so 数量大幅减少。中文化走 airlibintl 真 .mo 替身加 LANGUAGE=zh_CN,ghb(gtk/po/zh_CN.po 编译)、gtk40、glib20 三个域一并生效,界面与菜单全中文。
▍五、适配中遇到的几个难点
### 难点1:注入坐标系是物理像素,不是逻辑密度
PC 全功能实测首轮出现"选项卡不翻页、预览按钮无响应"的全面失灵。定位结果是测试注入点击按 density 1.9 换算了坐标,而 2in1(3120×2080)的注入坐标系就是物理像素,全部点击落空。这一教训被写进 ai_log 作为固定纪律:任何真机自动化前先确认目标设备的坐标契约,误报的"功能失效"可能只是测量工具用错了比例尺。
### 难点2:g_clear_pointer 包装二级指针导致静默内存破坏
关闭预览窗稳定复现 SIGSEGV,且野指针地址跨进程恒定。真根因是 ffstream.c 用 glib 的 g_clear_pointer 包装 avcodec_free_context 等二级指针 API——该宏会把 *pp 的值传给销毁函数,于是静态的 av_codec_context_class 被当成 AVCodecContext 关闭,guard 全在静态内存上读"字段"恒通过,最终在野 mutex 上崩溃。所谓"毒化地址"实为 .text 两条指令的小端 qword。修复是直接传 &member,回归 11 次关窗零崩溃。这个案例说明移植中每引入一个包装宏都要核对被包装函数的参数契约。
### 难点3:修复已上线,HAP 里还是旧库
meson 重编 libghb 后,hvigor 增量构建不会重新 staging native 库,修复从编译到上设备相差近一小时,期间"修复无效"的结论全是假象。工程固化了完整链路:改 native 源码后必须 touch entry 的 CMakeLists、清理三处 intermediates、先跑 deploy_runtime_libs.sh 刷新 stage_libs.cmake 的快照再重建,最后用 unzip 加 strings 验收 HAP 内的 libghb。打包链的另一个同类坑是重编 gtk4 后主栈同步会跳过 install 符号链接,必须手工拷贝实体文件再走 patch_libs。
### 难点4:GDK 帧时钟的自旋与死亡超时
预览播放偶发冻结,六轮插桩把根因锁定在 GdkFrameClockIdle 的双时钟交替:一个时钟正常 60fps,另一个以 BEFORE_PAINT+RESUME 相位 0ms 间隔自旋(约 357Hz),paint 相位被饿死。后续又在熄屏 SUSPEND-恢复循环中确认了死亡 paint timeout 的变体——挂起的 timeout 永不触发,request_phase 全部被挡死。修复是在 maybe_start_idle 里加入挂起超时检测,超过 200ms 强制移除重排并 g_warning 留痕,触摸与重绘因此自愈。
### 难点5:IME 桥的挂接时序
新增预设对话框无法输入文字。第一层根因是 IME 桥的 set_ui_notify 永不挂接——注册回调时 core 句柄尚未加载,此前的"键盘弹出实证"来自热重载轮,冷启动必坏;修复是补 bridge_ime_handler_if_ready() 双时序幂等挂接。第二层根因是对话框子窗二次触发 create op,claim 被覆盖成主窗,按关闭键等于 gtk_window_close(主窗),应用直接冻死;修复加入 claim 幂等复用与主窗确定性识别(hb_window 是注册进 GtkApplication 的窗口,ghb 弹窗全 transient-for 不进列表)。四层 IME 桥本身(后端 touch hook → core gtk_widget_pick + pango 布局 → TSFN → 隐形 TextArea 差分)在 09-05 已落地,冷启动时序是它最后的缺口。
▍六、编译、安装与启动
Native 侧先于 ArkTS 构建。按 native/scripts/env.sh 配置交叉 SDK(OHOS NDK clang 15,musl aarch64),流水线顺序为:download_all.sh 拉取 tarball 缓存到 native/dl,build_gtk4.sh 从官方 4.14.5 tarball 重放 ohos-backend-files.txt 的 5 处集成点,build_small_libs.sh/build_codec_libs.sh/build_ffmpeg.sh 产出静态编解码闭包,build_libhb.sh 以 make 替代脚本自产 project.h 与 preset_builtin.h,build_ghb.sh 用 meson 生成 libghb.so,最后 deploy_runtime_libs.sh 把运行库同步到 entry。
ArkTS 打包使用标准 hvigor 流程:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
签名产物位于 entry/build/default/outputs/default/entry-default-signed.hap。安装启动命令使用真实包名:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airbrake.app
依据 PORTING.md §5 与 ai_log 记载,交付包在 MatePad 11.5 上完成全新安装终验:T1 冷启动、T2 菜单栏鸿蒙化、T4 源扫描、T5 预览播放(时间码严格递增)、T10 队列窗口、T12 崩溃巡检(faultlog 恒空)全部通过;Mini 上另验证了 T3 汉化、T6 x264、T7 x265/AV1 编码产物可播。
▍七、当前功能边界
当前版本已覆盖的能力:
- 完整转码链:视频源扫描(标题/章节)、x264/x265/SVT-AV1 编码、队列多任务、内置预设加载与自定义保存;
- 原生 GTK4 界面:全控件原生渲染、中文本地化(ghb/gtk40/glib20 三域)、工具栏真实图标;
- 桌面窗口体系:ArkUI 菜单栏、弹窗真子窗口多实例、DocumentViewPicker 文件选择、字幕 SRT 导入与 libass 压制渲染;
- 内置预览播放:基于 ffmpeg 解码的时间码驱动播放,EOF 停末帧,连续重播稳定;
- 稳定性基线:SIGSEGV handler、日志落 .txt(airbrake_trace.txt)、faultlog 零记录的巡检用例。
尚未覆盖或有待处理的能力(均出自 ai_log 遗留清单):键盘弹出时描述域被遮挡(确认按钮在顶部,工程选择接受现状)、新建预设重开时类别下拉残留上次输入(上游不重置行为)、tooltip 偶发滞留、MatePad Mini 上的预览冻结已定位修复但待设备回连复验、GPU 编码(nvenc/qsv 等)因平台无对接 API 而未纳入。IME 中文输入在桌面 GTK 输入框的链路已经打通,但自动化注入无法验证该通道,以真手复测为准。
因此,当前版本更准确的定位是"HandBrake 鸿蒙可用交付版":视频转码主流程、界面体系与稳定性巡检构成完整闭环,剩余项集中在上游交互细节与特定设备的长尾问题。
▍八、总结
AirBrake 的适配把 Air 系架构从图形工具推到了重计算场景:GUI 层继续由 GTK4 OHOS 后端原生渲染,ArkTS 壳收敛到菜单、弹窗与文件系统能力,转码负载整体沉入 native 宿主线程。工程证明 libhb 与整套 contrib 编解码生态可以在 OHOS NDK clang 15 下完成静态闭包,也暴露了大型 C 工程移植中包装宏契约、构建缓存、帧时钟时序这类不写在任何 API 文档里的问题——PORTING.md 的踩坑映射与 ai_log 的逐日证据是这些经验的主要载体。
后续工作的重心在收尾而非开疆:复验 Mini 预览冻结的修复、清理上游交互残留(tooltip、类别下拉)、以及视需求评估硬编码器的替代路径。每一步仍以 PORTING.md 的测试用例表为准,真机 faultlog 恒空是任何变更合入前的底线。
楼主
8 天前