为Pixel 4a开启DeviceAsWebcam功能

Table of contents

反正已经在自行修改编译Android系统了,目的就是围绕自己的需求做功能改进。 功能多多益善嘛。尽管本来只是想实现USB NCM网络共享……

记录 发泄一下头の疼痛……

切入点?几乎是无头苍蝇

一开始我只是想把老手机的RNDIS实现改为USB-NCM。恰巧在过程中看到了部分(后证实无用)用configfs配置USB UVC gadget的init脚本、加上调试时看到了UVC组件的警告日志,受到吸引,想为旧手机Pixel 4a实现这个功能,发挥余热。

我依稀记得支持Oneplus 7的YAAP有实现过相关功能,而两款手机差不多处于同一时代,应该有参考的价值。在GitHub组织项目下乍一搜索,好像没看到什么有趣的东西。但是在搜索引擎上发现了Android官方的相关文档。看起来……只要把提到的东西都实现就好?

一步步探索

内核4.14,老东西啊,得慢慢整整。

linux开启必要的config

就是把驱动里有关USB gadget、Function FS以及UVC的部分启用嘛,没什么大不了的!

启用prop、修改init脚本

起初我以为内核开了相关功能就能自动支持,我错了。

为启用Device as Webcam功能,需要设置 ro.usb.uvc.enabled=true,这样相关菜单才会在设置里显示出来。调试的时候我就先用Magisk/KernelSU的模块功能凑合凑合了。

改init就比较讨厌了,完全没有相关经验,只能凭借自己的编程直觉猜猜猜。事实上由于自己对Android编译系统如何将这些脚本导入并不了解,我围着错误的遗留脚本修改了很久! 为了使用兼容的USB PID/VID,我在GitHub上搜索了相关代码commit,也顺带在yaap的仓库里发现了可以参考的脚本。各位的风格不尽相同,我尽量参考。

在HAL中加入UVC gadget支持

官方文档中的案例给的是AIDL HAL的例子,但是Pixel 4a使用的是HIDL。我不清楚如何从HIDL迁移到AIDL,好在发现二者的代码结构类似。最后我以相当hacky的方式在代码中插入旧版接口中不存在的常量/枚举类对应值,强行兼容相关接口。

V4L2节点黑名单

部分系统视频组件(摄像头、编码器等等)也和V4L2一样使用 /dev/videoN 节点暴露出来,而Device as Webcam服务要找的端点是gadget模块初始化后新创建的节点。服务会顺序遍历所有节点,寻找并选择能输出视频流的节点。为了避免报错(实际摄像头节点被占用)和错用(用了转码节点之类),需要把既有能找到的所有节点屏蔽掉,这就是本节所述黑名单。

此黑名单可用rro overlay的形式覆盖 com.android.DeviceAsWebcam 服务暴露的配置。

说起来我一开始一直以为程序调用的V4L2节点是现有的,浪费了不少时间调试。

对于Pixel 4a,需在 android_code_root/device/google/sunfish 添加对应修改,可以仿照既有例子改改、GitHub搜索类似例子。最后的黑名单(res/raw/ignored_v4l2_nodes.json):

[
    "/dev/video0",
    "/dev/video1",
    "/dev/video2",
    "/dev/video32",
    "/dev/video33",
    "/dev/video34"
]

有预览了,但是不认V4L2节点

日志里看不到什么错误,甚至提到打开了相关的File Descriptor。检查服务程序代码发现预览和视频编码传输在两个线程上运行。

上位机dmesg一直报 Failed to query (GET_DEF) UVC control 2 on unit 2: -110。超时?怕不是单纯没反应。

直觉告诉我服务无声忽略了内部错误。

内存不足?内存怎么会不足?

在logcat中有发现:

Unable to request V4L2 buffers from gadget driver: Out of memory

检查邻近日志,发现 swiotlb buffer is full,这又是什么? 在网上寻找到相关案例后,明白了这是个USB相关的缓存,默认就16MB,好像会影响一些设备使用。不管了,先试试看,临时提升缓存容量到128MB:

su -c "echo 128 > /sys/module/usbcore/parameters/usbfs_memory_mb"

报错消失了,晚点一起丢init脚本里。

还是不行?没有输出?

是我init脚本没写对? 还是我漏了什么配置?

反复尝试后再度检查服务代码,原来Device as Webcam服务用到了老内核驱动尚未完全实现的VIDIOC ioctl接口。

怎么办?不知道,但是可以大力出奇迹。查找主线内核相关commit,然后疯狂cherry pick,不好一次合并就多pick一些,多多pick,多多pick……

唉不是,你这内核怎么有非主线的UVC功能实现?扬了!Revert!

终于,大力成功奇迹,我也终于让USB有了视频输出。关键commit是实现VIDIOC_ENUM_*

终于在上位机上看到视频输出了

我欣喜若狂,开始pick pick pick更多commit,却不知道危机正在靠近……

也就pick了数十个UVC gadget相关的commit……也许不算多……

中途拔USB线就panic?

我有印象!大概率是驱动释放太快、内存释放出了问题!Pick: usb: gadget: uvc: allow for application to cleanly shutdown

怎么没输出了?危机来临!

从有到无了,怎么办?Bisect!不过由于中间commit很乱,所以遇到了不少需要调整commit顺序保证编译通过的状况。

谁竟想结果是scatter gather多线程性能优化完全用不了。用不了用不了,用不了就扬了!总体尝试了四条路线:

  • 维持原状(OK)
  • 在没有sg的基础上合并新功能(OK,极麻烦,难解耦)
    • 甚至让我整出了protocol error
  • 在既有UVC1.5的基础上实现VIDIOC_ENUM_*(NG,后面甚至带来modem crash,没有panic)
  • 直接在最新状况下用hack的方式强制走普通模式(OK,参考yaap得来的结果)

这编译内核真的累啊,不知道为什么每次编译要固定16分钟,像是没有增量编译,测试起来非常考验人心态。

偶尔用一段时间后会panic?

要是有日志看就好了……等等,android有pstore!panic日志可以在 /sys/fs/pstore/* 找到,发现了下面的内容:

[  903.307954] c5   1126 SMP: failed to stop secondary CPUs 0-7
[  903.908145] c5   1126 set_restart_msg: set restart msg = `KP: HwBinder:1113_1 PC:__pi_strcmp+0x88/0x154 LR:uvc_alloc+0x1f4/0x3b4'

uvc_alloc?emm,搜索后发现是自己漏了一个fix。之前提到的实现VIDIOC必须功能的commit引入了问题,修复用的patch是usb: gadget: uvc: don’t put item still in use

感叹:内核折磨人

内核折磨人啊(´;ω ;`)

来看看输出~哎不是,红蓝颜色通道怎么反了啊!

好不容易有了信号,想照照人、却成了Avatar?而且还只在上位机、预览没事?

就是那种,把OpenCV BGR当RGB处理的感觉。幸运的是包括YAAP的维护者在内有几位开发者已经摸清了问题点:高通设备上用硬件加速编码会带来颜色问题,包括RB通道翻转。

恐怕DeviceAsWebcam这个程序就只是为了Tensor平台……

OBS上的小问题

通常用QV4L2来测试摄像头应该是够用。分辨率、编码均能正常调整,就是帧率好像没有真的变过。

但是在OBS这边,每次修改摄像头配置后,摄像头输出都会卡住,需要重载UVC功能/重插USB线,尽管手机上的预览还是没有问题。

暂时找不到问题根源。

总结

  • 改kernel config、打开功能
  • 修uvc gadget驱动、补全API
  • hack HIDL、强兼UVC
  • 编init脚本、初始化节点
  • 改DeviceAsWebcam服务、适配qcom平台

学到了:

  • git log -G
  • git rebase –update-refs
  • git workspace

又及:

  • cherry pick时的信条:相信开发者(实际WTF)
  • 什么叫垂直整合、全栈开发啊

<( _ _ )><(=- . -=)><(=~ O ~=)>……<(=- . -=)>

累了。纯发泄。