查看: 110|回复: 3

OBS鸿蒙迁移:滤镜黑屏三重根因与相机NV12排障

[复制链接]
发表于 半小时前 | 显示全部楼层 |阅读模式
OBS Studio 的鸿蒙迁移在技术实践篇收官时给出过 40% 的对齐度账单,这一天集中还账:滤镜面板与多场景、相机源、窗口 Picker、色准四项同时推进,其中三项落地,窗口采集卡在权限审批。下面按问题场景拆解其中可复用的适配与排障方法。

一、滤镜面板:FILTER 是私有源,不能按普通源创建

桥接层第一版照抄普通源的创建方式,调用 obs_source_create,返回 NULL,日志提示 type 'color_correction_filter' can't be created publically。这里叠了两个坑:一是注册名靠脑补,obs-filters 插件里色彩校正实际注册名是 color_filter,而且并没有 flip_filter,需要从插件源码里 grep '.id = "' 抄真实注册名;二是 libobs 里一切皆 source,但 source 分 INPUT/FILTER/TRANSITION 三类,FILTER 与 TRANSITION 属于私有源,必须依附宿主,创建要用 obs_source_create_private(),再用 obs_source_filter_add 挂到宿主上。
  1. // 错误示范
  2. obs_source_t *filter = obs_source_create("color_correction_filter", ...);
  3. // 返回 NULL,日志:type 'color_correction_filter' can't be created publically
复制代码
  1. // 正确姿势:滤镜是私有源
  2. obs_source_t *filter = obs_source_create_private("color_filter", "color_filter",
  3.     obs_get_source_defaults("color_filter"));
  4. obs_source_filter_add(source, filter);
复制代码

Qt 前端天然围绕这套语义构建滤镜 UI,自建 ArkTS 前端时就必须在第一次真机上把这条隐藏规则撞出来。

二、黑屏第一层:预览画布被布局挤成 43px

滤镜挂上后预览整块变黑。第一反应是 GPU 驱动:glGetError 返回 0x26D72A00(合法 GL 错误码只到 0x0505),EGL_BAD_NATIVE_WINDOW 刷屏,据此判断 Maleoon 驱动被息屏测试搞进了坏状态,重启设备后依旧黑。A/B 验证把前一天还能显示的旧 HAP 装回去,同样黑,排除了当天改动引入回归。真正定案的是一条日志:
  1. XComponent surface created: 2046 x 43
复制代码

预览画布被压成了 43 像素高的一条线。为塞进滤镜面板,侧栏高度从固定 300 调到 400,重启后窗口没有最大化、走了堆叠布局,固定高度侧栏加 TransportBar 和混音器把 layoutWeight(1) 的预览挤没了。修复办法是在 EntryAbility 的 loadContent 之后调用 window.maximize(),让桌面软件在 PC/2in1 上默认最大化,同时给预览加 constraintSize 兜底、把堆叠布局改为可滚动。面板够不着、预览被挤扁、自动化坐标漂移三个问题一并消失。

这段弯路的价值在于归因方式:要按实验证据链走,不能按日志里最吓眼的错误走。那串异常 GL 错误码真实存在,是 surface 尺寸异常引发的连锁反应,但不是根因。项目还曾基于错误归因提交过一次“已修复”,发现后用后续 commit 做了公开更正。

三、黑屏第二层:effect 资源从未进包

布局修好后,单独挂滤镜仍然黑。这次日志很干净:
  1. gs_effect_create_from_file: Null 'file' parameter
复制代码

obs_module_file() 返回 NULL,说明 obs-filters 的 data 目录(含 color_correction_filter.effect)从未被打进 HAP。回溯构建链发现,stage 脚本里“拷 libobs data”那段的条件路径在 OHOS 构建下不存在,等于该逻辑从未执行;插件 .so 进包了,但运行时读取的 effect/shader 资源一个都没进。libobs 的设计里,effect 加载失败不会阻止滤镜对象创建,它只会在画面上黑给你看。

修复分两半:stage 脚本改为遍历同步全部 7 个已加载插件的 data 目录进 rawfile/obs-plugins/<name>/;另外 rawfile 解压按 versionCode 幂等,versionCode 不变就整树跳过,新增资源不会重新解压,因此在 Index 中增加内容戳常量 ASSET_STAMP,资源树变化时递增。结论是资源进包要验货,光改脚本不够,还得让设备真的重新解压。

四、黑屏第三层:settings 传 nullptr 让 opacity 变 0

effect 进包后还是黑。把目光从 GPU 挪回 CPU,看 color_filter 源码里 opacity 怎么读设置:
  1. double opacity = obs_data_get_double(settings, SETTING_OPACITY);
  2. // settings 为 NULL → get_item(NULL) 返回 NULL → opacity = 0
  3. // 0/100 进入色彩矩阵 → 整行乘零 → 每个像素乘成黑
复制代码

桥接层创建滤镜时 settings 传的是 nullptr。libobs 的默认值机制是:创建时不传 settings,不会自动填充插件的 get_defaults,那是 Qt 前端在 UI 层做的事。桥接层应先用 obs_get_source_defaults(typeId) 跑一遍插件 get_defaults,拿到 opacity=100,再传入创建接口。

这类“宽容型”框架的典型故障形态是:滤镜创建成功、effect 加载成功、矩阵全乘零,每一层都不报错,错误只以最终观感呈现。对付它最有效的办法不是读渲染代码,而是先把数据域验证清楚:settings 里到底有没有 opacity、值是多少。

五、相机源:元数据可靠性按生产者分域

harmony-camera 插件首测时表面能用:源创建成功、video output frame start,但预览黑、录制也黑。日志刷的是:
  1. plane 0 stride 1 smaller than linesize 1920
复制代码

拷贝代码从 OH_NativeBuffer_MapPlanes 拿 rowStride,拿到的是 1。同一套代码在屏幕采集插件里跑了两天正常,对 AVScreenCapture 的 buffer,它报的 stride 是真的;对 ImageReceiver 相机 buffer,它是垃圾。正解是数据生产方自己的 API:OH_ImageNative_GetRowStride(image, componentType, &stride),探针验证得到 stride_y=1920。同时还发现结构差异:相机 NV12 的 GetComponentTypes 只返回 1 个组件,是半平面布局,Y 面和交织 CbCr 面在同一个 buffer 里,而代码假设了 2 个组件各一个 buffer,色度起点应为 stride_y × height。

拷贝修正后加内容探针,对 Y 平面按质数步长采样求和,得到 2080/2080 个采样点非零、均值约 91;再加时间戳探针,image 时间戳与 os_gettime_ns 差 -61ms,属于正常直播延迟量级。CPU 数据对、时钟对、拷贝成功、obs_source_output_video 也调了,画面依然黑。此时合成器每帧刷的错误就是全部真相:
  1. glDrawArrays failed, glGetError returned GL_INVALID_FRAMEBUFFER_OPERATION(0x506)
复制代码

追进 libobs 的异步帧上传路径,它把 NV12 拆成 GL_R8 加 GL_RG16 两张纹理上传,采样时靠 swizzle 拼回。Maleoon GLES 驱动不吃这套:上传不报错,采样帧缓冲就是 invalid,每帧渲染失败。旁证是 display-capture 一直只走 packed RGBA 上传路径,从没碰过这个雷。

三个选择:给 libobs 上传路径打补丁、换 EGLImage 外部纹理在 GPU 内转换、CPU 端直接 NV12→RGBA。最终选第三种,1080p30 每帧约 1ms,量级可接受,而且能把帧汇入已验证的 packed 快车道。转换用 BT.709 limited-range 系数乘 256 转整数运算:
  1. int32_t yc = 298 * (yy - 16);
  2. int32_t r = (yc + 459 * cr) >> 8;
  3. int32_t g = (yc - 55 * cb - 137 * cr) >> 8;
  4. int32_t b = (yc + 541 * cb) >> 8;
复制代码

奇数高度底行复制,full_range 标好,避免 libobs 二次展开。验证用三重证据:真机预览画面直出;相机源上再挂色彩校正,画面不黑,顺带证明滤镜链对相机帧同样生效;8 秒录制成片 34MB 拉回抽帧,像素统计 min=5 max=255 mean=166.6 unique=201,是真实曝光分布而非纯色或压缩噪点。

六、窗口 Picker:三条官方机制都接上,卡在受限权限

AVScreenCapture 的 OH_CAPTURE_SPECIFIED_WINDOW 模式要求传 missionIDs。文档说该字段可选,据此用空列表启动,Init/Start 都成功,但没有任何 UI 弹出,文档推断被证伪。随后把 API 26 sysroot 里找到的三条机制逐个接入:SetSelectionCallback(API 20,要求启动前注册,接上后从未触发);CaptureStrategy 加 StrategyForPickerPopUp(true)(API 20,SetCaptureStrategy 返回成功,仍不弹);PresentPicker(API 22,Init 后、Start 前各调一次,均返回 ERR_OPERATE_NOT_PERMIT(2),错误码释义为当前操作未获得必要权限或处于非法状态)。

线索拼起来后,嫌疑锁定 ohos.permission.CUSTOM_SCREEN_RECORDING:system_basic 级受限开放权限,需要 AGC 后台人工审批。没有它,系统在服务侧就把窗口选择能力关了。申请材料已备好,审批通过、签名 profile 加上后,这三行代码不需要改动就能复测。这类“审批依赖”的剩余工作不再是代码,而是流程,移植排期必须给它留位置。

七、色准闭环:一张 20KB 色卡关掉十天争论

账单上的色准精调长期停留在“大体正常”,谁也说不出差多少。方法是造一张可测量的卡:在 mac 上生成 16-patch sRGB 色卡 PNG,含黑、八档灰、白、六个饱和原色,数值精确、无 dither,用 HTTP 服务起来,设备浏览器全屏打开。先用系统截图 snapshot_display 证明“浏览器→合成器→屏幕→抓屏”这条恒等路径 16 项全中,再用 OBS 采集同一屏幕、录制、拉回抽帧比对。

结果很干净:八个非端点灰阶全部落在 sRGB EOTF 曲线上,饱和原色 0/255 端点全部无恙。端点不动正是“解码一次、缺最终编码”的指纹。根因是此前 Maleoon BGRA 退让把所有 GS_BGRA 纹理降成了线性 GL_RGBA8,把渲染目标也一并降了;而 libobs 的色彩管理 pass 写线性值进 canvas,指望的正是 sRGB FBO 的硬件写回编码。修复只有一行判断:GS_BGRA 且是渲染目标时,internal format 用 GL_SRGB8_ALPHA8;被驱动拒的从来只是 BGRA_EXT 那个三元组,(SRGB8_ALPHA8, GL_RGBA, UNSIGNED_BYTE) 是 GLES 3.0 核心组合,可以接受。可采样源纹理保持线性,解码在 shader 里显式做,不能二次解码。验证时重启清掉设备坏态后录 OBS 界面,同坐标 UI 颜色与恒等截图 Δ≤4。

两条教训:观感大体正常不是结论,可证伪的测量才是;驱动退让要退在最小面,退让过界会把别的语义一起埋掉,而且埋得无声无息。

八、可复用的排障结论

buffer 元数据的可靠性按数据生产者分域。同一个 MapPlanes,对 A 生产者报真 stride,对 B 生产者报垃圾,应优先用数据生产方自己的 API 取元数据,中转层 API 只作交叉验证。

“数据对但画面黑”用探针切 CPU/GPU 域。内容探针证明数据真实,时间戳探针证明时钟正常,两个探针各排除一半,剩下的合成器 GL 错误就是全部真相;这比读驱动源码快一个数量级。

错误归因要公开更正。基于“驱动坏状态”提交过错误修复,A/B 实验打脸后用后续 commit 明确标注这是布局回归。以 PR 为轨道的工作方式里,诚实的更正记录本身就是贡献的一部分。

资源进包要验货。改构建脚本只是第一步,还要处理 rawfile 解压的版本戳,让设备真正重新解压新增资源。

当天四项中,滤镜面板、多场景、相机源、色准落地,对齐度从 40% 走到约 60%(功能面口径),窗口 Picker 等 ACL 审批,另有一批反复启停采集会话后偶发 ANR 的独立遗留待专项。项目仓库为 qingkouwei/obs-studio@harmonyos-port,色准测量全记录在 docs/harmonyos-color-measurement.md。
回复

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: OBS鸿蒙迁移:滤镜黑屏三重根因与相机NV12排障

这个排障记录看着太真实了,尤其是滤镜那一层。libobs 里 source 分 INPUT/FILTER/TRANSITION 三类、私有源必须 obs_source_create_private 再 filter_add,这坑基本每个重写前端的都得踩一遍,Qt 那边把这套语义藏得比较好,换了 ArkTS 自己搭 UI 就全暴露出来了。 最认同的是你讲的归因方式。0x26D72A00 这种非法 GL 错误码加上 EGL_BAD_NATIVE_WINDOW 刷屏,很容易让人一头扎进驱动和 GPU 里出不来,结果真凶是 surface 只有 43 像素高。这种“吓人的日志不是根因”的判断,加上 A/B 回滚旧 HAP 来排除当天回归,是最省时间的路径,比一遍遍猜驱动强太多了。 第二节那个布局问题也值得记一下,固定高度侧栏加 TransportBar 和混音器把 layoutWeight 的预览挤没,桌面端默认不最大化就容易出这种堆叠布局的幺蛾子,顺手把 constraintSize 和可滚动布局一起补上算是把根一起治了。 第三、四层连在一起看更有意思:effect 资源没进包是构建链问题,stage 脚本那段条件路径在 OHOS 下根本不存在,属于“逻辑从未执行”的静默失效;而 settings 传 nullptr 导致 opacity=0 是数据域问题,每一层都不报错,最后只表现为黑
回复 支持 反对

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: OBS鸿蒙迁移:滤镜黑屏三重根因与相机NV12排障

这篇复盘很有料,尤其是把黑屏拆成布局挤压、effect 资源未进包、settings 默认值缺失三层,说明排障顺序和证据链比单独修一个点更重要。FILTER 必须用私有源方式创建这个坑很典型,自建 ArkTS 前端时确实容易照普通源抄。预览被挤到 43px 那段也提醒人,遇到异常 GL 错误码不能直接判 GPU,先看 surface 尺寸和窗口布局更稳。资源进包按 versionCode 幂等、再加内容戳的思路很实战,确实得让设备真的重新解压才算验货。settings 传 nullptr 导致 opacity 为 0 这种宽容型故障最隐蔽,建议把 obs_get_source_defaults 的默认值校验和资源内容戳自检做成启动日志或冒烟项,后面回归会省不少事。相机 NV12 stride 那段虽然到关键处截住了,但按生产者分域看元数据的思路很受用。感谢分享,期待窗口 Picker 权限和相机排障的完整收尾。
回复 支持 反对

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: OBS鸿蒙迁移:滤镜黑屏三重根因与相机NV12排障

这篇排障记录很扎实,尤其把黑屏拆成布局、资源进包、settings 三层来查,比一上来盯 GPU 日志更有参考价值。滤镜是私有源、必须走 create_private 再挂宿主这条隐藏规则,ArkTS 前端第一次踩到确实容易懵;预览被挤成 43px 那段也很典型,异常 GL 错误码只是连锁反应,不是根因,这种按证据链归因的方式很值得借鉴。 资源进包和 versionCode 幂等解压的坑也提醒得对,光改 stage 脚本不够,还得让设备真的重新解压,ASSET_STAMP 这种内容戳很实用。settings 传 nullptr 导致 opacity 为 0 那段更是隐蔽,滤镜创建、effect 加载都不报错,最后只黑给你看,确实得先把数据域验清楚。 相机部分看到 rowStride 拿到 1、但屏幕采集插件正常,感觉“元数据可靠性按生产者分域”这个思路很关键,期待后续把 NV12 排障完整展开。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-9 00:50 , Processed in 0.021212 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部