多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

iOS虚拟摄像头开发:越狱注入与设备列表掉包实战

iOS虚拟摄像头开发:越狱注入与设备列表掉包实战 简介面向iOS开发者的虚拟摄像头插件源码包聚焦系统级摄像头数据流替换与Hook技术适合有Objective-C或Swift基础、想深入底层框架的开发者。资源详细呈现通过MobileSubstrate注入系统相机进程、Hook AVFoundation中AVCaptureSession相关方法以替换原始视频流的过程并以AVCaptureVideoDataOutputSampleBufferDelegate协议为核心展示视频帧替换的代码实现。同时包含基于SwiftUI打造的摄像头应用覆盖摄像头切换、滤镜选择与视频源配置以及视频选择器中PHPickerViewController的调用、UISlider控制视频时长和裁剪导出功能涉及多个关键iOS开发知识点。包体共5个文件包括2个HTML说明页面、1个JS脚本及配置文件整体约15KB结构紧凑便于快速查阅。已有267人学习下载对需要了解iOS多媒体底层原理和SwiftUI实际应用的开发者具有较高参考价值。1. iOS 虚拟摄像头插件越狱注入与设备列表掉包这件事iOS 上做虚拟摄像头和 Windows 上装一个虚拟摄像头驱动完全不是一回事。系统没有开放任何“添加虚拟视频设备”的公开 API你在非越狱环境里见过的所谓 iOS 虚拟摄像头插件要么是屏幕镜像转投屏要么是推流转发画面先出系统、再进电脑延迟和链路多跳了一层。这套代码包走的是越狱注入路线直接在 AVCaptureDevice 的设备列表里塞一个自定义视频源FaceTime、微信这类 App 拿到它和拿真摄像头一样。它解决的问题很窄但明确自动化测试、直播联动、演示场景里不必每次摆个手机对着镜头放素材画面。适合手上有越狱机、愿意自己编译插件而不是四处找现成 deb 的开发者。如果你是准备拿它在非越狱设备上跑那基本是空想iOS 这个系统天生不给这条路。2. 方案选型三条“虚拟摄像头”路线以及这套源码包的落点2.1 注入路线为什么绕不开 Theos 和 dylib在 iOS 里摄像头不是一个能随便注册的硬件对象。系统在设计初就把 AVCaptureDevice 的实例来源写死在 camera daemon 和系统预置的扩展里普通 App 进程没有权限向设备列表里追加对象连模拟器里的摄像头都是特殊处理的。你想在设备本地“伪造”一个摄像头就必须绕过沙盒的进程边界把一段自定义代码注入到目标进程。越狱场景恰好提供了这个注入点。Cydia Substrate或者新一点的 Substitute / libhooker会在进程启动时根据/Library/MobileSubstrate/DynamicLibraries下的 plist 配置把对应的 dylib 加载进目标进程内存。dylib 是动态链接库系统加载器能在进程启动期间决定它的地址空间映射比静态库灵活得多这也是为什么越狱插件几乎都打成 dylib 而不是直接编译进某个 App。dylib 一旦加载成功就能用 Objective-C runtime 的方法交换改写系统类的方法实现。Theos 里的 Logos 语法本质上是把这种 method swizzling 写成看着像宏的%hook/%orig编译期再转换成真实的 runtime 调用。这套代码包的核心就是按这种经典结构组织的一个虚拟设备对象 VCVirtualDevice一个 hook 入口 hook_xm.x再加打包脚本和资源文件。你可能会问为什么不用一个普通 App 内嵌 dylib 的方式让 App 自己加载因为目标 App 根本不是你的你没法在 FaceTime 或抖音里导入自己的动态库只能在系统层面注入。关于适用性判断我说一个比较省心的结论只要你的越狱工具把 Substitute 或 libhooker 跑起来了iOS 15、16 上这条链路依然走得通。唯一变化是部分老插件在 arm64e 设备上要重新编译这套包的 Makefile 已经处理了架构你只需要把 SDK 版本对准目标机的系统版本。这也是我为什么坚持劝你别在网上找别人编译好的老版本 deb架构和 SDK 不匹配装上去十个里有八个是开机白屏。2.2 非越狱的替换方案各自卡在哪里很多刚接触虚拟摄像头的人会先试三条非越狱的路我一起说清楚省得你在错误方向浪费时间。第一条是 AirPlay 屏幕镜像加电脑端虚拟摄像头。把 iOS 屏幕投到 Mac再用 OBS 的虚拟摄像头把整个窗口变成“摄像头”输出。这条适合连麦和线上演示代价是电脑必须一直在旁边投出来的是整个屏幕没法单独投某一块自定义画面分辨率还被镜像链路限制。你要想在手机里的微信视频上看到虚拟画面这条做不到。第二条是 ReplayKit 广播扩展加录屏回放。ReplayKit 能拿到应用内屏幕帧但你要么实时传给自己的处理管线要么写成文件再循环读。写文件的方式有明显延迟文件越积越大长时间测试还得定期清理。而且它产出的是一个文件流不是摄像头设备App 侧感知不到。第三条是 RTMP/RTSP 推流再拉流。iOS 端推到流媒体服务器电脑端再拉回来当作视频源。这条链路多了一个服务器中转延迟通常在 1 到 3 秒Wi-Fi 抖动时直接卡播。三个方案的共同死穴是目标 App 在打开 AVCaptureDevice 时拿到的依然是系统那只真摄像头你虚拟出来的画面只能出现在电脑端会议软件里不会出现在手机内的 App 里。如果你的需求恰好是“让手机里的视频 App 以为我有一台摄像头”那就没得选必须走注入。判断标准很简单你要是拿虚拟画面去开 Zoom、腾讯会议非越狱方案够用你要是做 iOS 自动化测试、给某个 App 伪造前置摄像头画面直接看这套代码包。2.3 代码包里的文件结构与职责拿到代码包我建议先别急着 make install把文件结构和职责先过一遍。这套包按我常见维护习惯组织解开压缩包大致是下面这张表文件 / 目录职责打开后先看什么hook_xm.xLogos 注入入口hook AVCaptureDevice 的设备查询方法%hook后面跟的方法签名VCVirtualDevice.m/.h虚拟设备对象包含设备名、format、出帧线程init方法和nextSampleBuffer实现virtualcam.plist决定 dylib 注入到哪些进程filter 段里的 BundleID 列表MakefileTheos 编译配置ARCHS、TARGET、THEOS_PACKAGE_SCHEMEcontroldeb 包元信息Package 名称和版本号resources/样例视频、循环片段、测试图案样例视频编码是不是 H.264核对文件时最怕的是拿到一个把 hook 写死在-startRunning里的包。那种实现只能在采集会话已经跑起来之后替换几帧很多 App 的采集时序根本不给你这个时机错一步就白屏。正确的实现是 hook 设备列表让系统以为你多了一个前置设备等AVCaptureDeviceInput创建时你再按照系统要求的格式老实出帧。我拿到包会先开 VCVirtualDevice.m确认三件事有没有实现deviceType、localizedName、position这几个描述性方法有没有声明匹配 BGRA 与 NV12 的 format 列表有没有一个独立出帧线程而不是在卖帧方法里临时现算。这三点都具备说明出帧链路是独立的注入进 SpringBoard 进程也不容易被相机权限回收后面的调参才有意义。这套代码包的边界也要说清楚它只处理本地虚拟视频源的注入与出帧不包含底层显示链路改造也不负责把流推到外网服务器。资源文件里默认放了两张测试图案和一段 demo mp4你拿到手直接编译就能跑通后面再按需替换。如果你是第一次写越狱插件建议先用 Theos 编译一个 hello world 的 tweak 项目练手再来动这套包不然排查问题时分不清是工具链问题还是插件逻辑问题。3. 从零编译插件Theos 环境、工程配置与注入代码落点3.1 Theos 工具链搭建先对齐 SDK 与架构编译越狱插件基本绕不开 Theos。我演示的环境里常见做法是把工具链放到 /opt/theos然后导出环境变量。注意如果你用的是 macOS 上的 usbmuxd安装到设备这一步需要配合 SSHmake install默认走 scp 加 ssh设备上要装好 OpenSSH 服务。git clone --recursive theos 仓库地址 /opt/theos echo export THEOS/opt/theos ~/.zshrc source ~/.zshrc export SDKVERSION15.0 export ARCHSarm64 arm64e这里最关键的是SDKVERSION它必须匹配你目标机的系统版本。iOS 15 的越狱机配 15.0 的 SDK 来编产物才能在系统镜像里找到对应符号定义你拿 iOS 16 的 SDK 编译完装到 iOS 15 上大概率是启动直接就崩还要花时间排查是注入时机问题还是 SDK 问题。我在 iOS 16.1 的机器上偷懒用了 15.5 的 SDK插件装完设备列表里能看到虚拟设备一开摄像头就秒退最后重编才消掉。提示:SDKVERSION 指 SDK 包版本不是 deployment target。Theos 的 sdks 目录里放的是完整 SDK 镜像缺了就去找对应 iOS 版本的 SDK 放入/opt/theos/sdks/。别拿 Xcode 自带的 iOS SDK 目录直接给 Theos 用路径和符号引用都不一样。Makefile 里的目标配置同样要改。我一般会在工程里放一个固定的目标头防止每次重新拉环境后配置漂移TARGET : iphone:clang:latest:15.0 ARCHS arm64 arm64e INSTALL_TARGET_PROCESSES SpringBoardTARGET里的latest是编译器版本15.0是 SDK 版本INSTALL_TARGET_PROCESSES会让安装完成后自动重启 SpringBoard省去手动 sbreload。装好后可以用一条命令确认环境ls /opt/theos/sdks/ make -C . version第一条列出 sdk第二条从 Makefile 里读出当前目标配置。如果输出版本为unknown说明环境变量没导出回到 source 那一步检查。SDK 和工具链版本不一致的问题在虚拟摄像头这种要调用 AVFoundation 深层接口的项目上特别明显因为错误往往到运行时才爆发不在编译期报错。3.2 看懂 hook_xm.x设备列表是怎么被掉包的整个插件最核心的就是那几段 hook。我直接贴出来你对照着看。%hook AVCaptureDevice (NSArrayAVCaptureDevice * *)devices { NSArrayAVCaptureDevice * *originals %orig; VCVirtualDevice *virtualCam [[VCVirtualDevice alloc] init]; return [originals arrayByAddingObject:virtualCam]; } (NSArrayAVCaptureDevice * *)devicesWithMediaType:(AVMediaType)mediaType { NSArrayAVCaptureDevice * *originals %orig; if ([mediaType isEqualToString:AVMediaTypeVideo]) { VCVirtualDevice *virtualCam [[VCVirtualDevice alloc] init]; return [originals arrayByAddingObject:virtualCam]; } return originals; } (AVCaptureDevice *)defaultDeviceWithDeviceType:(AVCaptureDeviceType)deviceType mediaType:(AVMediaType)mediaType position:(AVCaptureDevicePosition)position { AVCaptureDevice *device %orig; if (deviceType AVCaptureDeviceTypeBuiltInWideAngleCamera position AVCaptureDevicePositionFront [mediaType isEqualToString:AVMediaTypeVideo]) { return [[VCVirtualDevice alloc] init]; } return device; } %end%orig是 Logos 里最值钱的机制它保存了被 hook 方法的原始实现。第二段 hook 只对AVMediaTypeVideo追加虚拟设备音频采集链路完全不受影响这样后台收音还走真麦克风避免真机测试时连声音都要换源。第三段 hook 解决的是 5.3 里那些不问完整设备列表、直接按设备类型取默认摄像头的 App。VCVirtualDevice 的内部实现重点在于让系统相信它是一只完整的摄像头。初始化时给它一个localizedName比如 “VirtualCam”并声明deviceType:builtInWideAngleCamera、position:front这样前台 App 用默认前置摄像头时正好踩中我们追加的这个对象。出帧逻辑放在独立线程里用信号量控制往nextSampleBuffer塞数据。格式上不要声明一堆乱七八糟的 pixel format只声明系统主采的kCVPixelFormatType_32BGRA和偶尔会用的 NV12 两种就够了声明太多反而让 App 选到一个你没实现的格式黑屏。关于%new如果需要在类里新增一个系统本来没有的方法比如- (void)switchVideoSource:就在方法前加%new然后用%ctor在库加载时注册一些初始化事件。这套包里%ctor常用于读取 plist 配置并启动出帧线程。编译期如果看到 Logos 报ERROR: Can find no class %hook先检查方法前-和的声明写没写错这是个容易翻车的小细节。3.3 编译打包并装进越狱设备工程目录下直接执行几条命令make clean make package scp -P 2222 .theos/obj/debug/com.example.virtualcam_1.0.0_iphoneos-arm.deb root设备IP:/tmp/ ssh -p 2222 root设备IP dpkg -i /tmp/com.example.virtualcam_1.0.0_iphoneos-arm.deb sbreloadmake clean在改动 Makefile 后必须跑Theos 的产物缓存不总是主动重生成 plistmake package会根据 control 文件生成 deb 包后面两条是传包、安装并重载 SpringBoard。包名冲突时 dpkg 会提示another version is installed先dpkg -r com.example.virtualcam移除旧包再装。如果你不确定设备端口先用ssh root设备IP连一次确认密码正确再带-P 2222的 scp 传输。装完别急着开 FaceTime。先手动开一次系统相机让 AVCaptureDevice 的类方法把设备列表缓存刷新一遍然后杀掉相机进程第二次打开才会看到 “VirtualCam” 出现在可用设备列表里。很多第一次搞的人装完直接测微信报错说找不到摄像头其实就是这个缓存坑。另外如果越狱工具是带/var/jb前缀的新方案deb 安装路径不在/Library/MobileSubstrate/而在/var/jb/Library/MobileSubstrate/下先确认 dylib 落到哪个目录再决定改 plist 还是改打包配置。4. 把任意视频接进虚拟摄像头视频源接入、参数调优与日志验证4.1 视频源三种模式本地文件、RTSP 拉流和测试图案虚拟摄像头本体只是个壳里面跑什么画面由视频源决定。这套包的配置里预设了三种 sourceType用一张表看取舍最清楚sourceType延迟特征适用场景明显短板local_file可控循环读帧演示 DEMO、回归测试需要准备符合格式的视频文件rtsp1~3 秒接局域网监控画面、远端素材依赖 Wi-Fi 稳定性抖动会绿屏pattern无源延迟分辨率/帧率调优、黑屏排查不是真实画面不适合展示我在调分辨率时一律先用 pattern把源的问题撇开确认虚拟设备本身出帧没问题了再切 local_file。如果你一上来就切 rtsp画面卡了根本判断不清是插件问题还是推流链路问题排查成本直接翻倍。还有一个铁律给 local_file 用的视频编码尽量选 H.264 Baseline 或 Main别放一个重编码的 B 帧标记多的文件循环读帧时 seek 回起点容易出现花屏。选 H.264 不是因为 iOS 不支持别的编码而是 VideoToolbox 对 H.264 的硬件解码支持最成熟AVAssetReader 拿到的 CMSampleBuffer 格式也最稳定。用 HEVC 也能跑但出帧线程的 CPU 占用会明显抬头扎到老设备上掉帧就从日志里看出来了。4.2 从视频文件循环读帧并交付给虚拟设备下面这段是从本地文件读帧的最简实现放在 VCVirtualDevice 的推帧线程里每次取一帧交给调用方。- (CMSampleBufferRef)nextSampleBuffer { if (!self.reader) { [self buildReader]; } CMSampleBufferRef sample [self.readerOutput copyNextSampleBuffer]; if (sample NULL) { // 循环回到文件头重新读注意 seek 后稍等再拷贝 [self.reader seekToTime:kCMTimeZero completionHandler:nil]; return [self.readerOutput copyNextSampleBuffer]; } return sample; } - (void)buildReader { NSURL *url [NSURL fileURLWithPath:self.videoPath]; AVURLAsset *asset [AVURLAsset assetWithURL:url]; NSError *error nil; self.reader [AVAssetReader assetReaderWithAsset:asset error:error]; if (error) { os_log(OS_LOG_DEFAULT, VCDev: reader error %, error); return; } AVAssetReaderTrackOutput *output [AVAssetReaderTrackOutput trackOutputWithTrack:[asset tracksWithMediaType:AVMediaTypeVideo].firstObject outputSettings:{ (__bridge NSString *)kCVPixelBufferPixelFormatTypeKey : (kCVPixelFormatType_32BGRA) }]; [self.reader addOutput:output]; self.readerOutput output; [self.reader startReading]; }逻辑上就是 AVAssetReader 按帧向外吐 CMSampleBuffer吐空了就 seek 回起点循环播放。两个参数要特别留意输出像素格式固定转成 BGRA因为系统默认给预览层的就是 BGRA少一次转换少一层 CPUseekToTime:完成后要等一小会儿再继续 copy不然有概率拿到黑帧我习惯加 0.05 秒的空转黑帧概率会低到基本看不见。另一个关键是时间戳。视频文件里的 PTS 不能直接沿用否则系统会认为两帧间隔异常把画面挂起。在交付前最好重新打上主机时钟的时间戳if (sample) { CMItemCount count 1; CMTime pts CMClockGetTime(CMClockGetHostTimeClock()); CMSampleBufferSetOutputPresentationTimeStamp(sample, pts); }CMClockGetHostTimeClock取的是系统单调时钟不受网络对时影响用在这里比CACurrentMediaTime更符合采集链路的口径。RTSP 拉流那一版实现不一样它是在后台线程用 VideoToolbox 硬解 H.264解码出来的像素缓冲再重新包装成 CMSampleBuffer。那部分代码依赖第三方库稍多一般放在 rtsp_helper 模块里首次编译记得检查头文件路径是否写进 Makefile 的EXTRA_CFLAGS否则会报找不到rtsp_helper.h的低级错。4.3 用日志验证调用链不靠肉眼判断亮屏插件是不是被加载、虚拟设备是不是被 App 查询到、出帧有没有空转这三件事都要有日志证据。代码包里已经在关键节点埋了 os_log日志前缀统一用VCDev。连接 Mac 后这样抓log stream --predicate eventMessage CONTAINS VCDev --style compact同时打开目标 App 的前置摄像头你会依次看到四类关键行日志关键字含义看到后做什么devices queried设备列表被查询注入生效说明 dylib 已加载virtual cam selected虚拟设备被选中为采集源进入调帧阶段frame delivered每帧正常交付确认出帧线程活着pixel buffer dropped帧被系统丢弃调 fps 或改分辨率只盯着满屏的 frame delivered 还不够要确认有没有出现 pixel buffer dropped如果 dropped 持续增长说明出帧速率和 AVCaptureDeviceInput 要求的 fps 不一致回 plist 把 fps 调成和源文件帧率一致即可。如果你的 Mac 不方便直接用 log stream也可以装一个idevicesyslog级别的工具做过滤效果等价。拿日志判断比对着镜头看画面靠谱得多因为黑屏可能是预览层问题也可能是虚拟设备根本没被选中肉眼分不出来只有日志链路能定位到具体环节。我每次改完代码都会把上面四类日志当成检查清单缺哪一行就回到对应模块查而不是反复开关 App 碰运气。5. 踩坑排查注入不生效、黑屏、闪退的五个高频问题前面把编译和安装跑通了下面这些坑我几乎每次换机器都要撞上一两个。按“现象 → 原因 → 解决”的格式写你直接对号入座。5.1 插件装完系统相机提示“相机不可用”现象dpkg 安装成功、SpringBoard 也重载了打开系统相机 App 直接弹错误或者取景器一片灰。原因最常见的是 SDK 与目标系统版本不匹配也就是 3.1 里说的那个坑。其次virtualcam.plist 的 filter 段写错了注入范围系统相机 App 进程没加载 dylib但 SpringBoard 加载了导致你在相机 App 里看不到虚拟设备或者看得到设备列表但无法取流。解决先执行dpkg -l | grep virtualcam确认包存在再 SSH 到设备执行ls -l /Library/MobileSubstrate/DynamicLibraries/ | grep VCD确认 hook_xm.dylib 和对应 plist 同时在。如果文件都在卸载后用与目标系统匹配的 SDK 重编一次如果 plist 不在多半是安装路径被/var/jb前缀影响把 plist 复制到动态库同目录再重启即可。这一步如果跳过直接改代码浪费时间还容易把问题扩大。5.2 设备列表里能看到 VirtualCam但画面全黑现象调试日志已经打到 virtual cam selected预览画面却是黑的切到拍照模式甚至直接崩溃。原因最常见的两个。一个是像素格式不匹配部分 App 会向设备索要 NV12 采样而 VCVirtualDevice 的 nextSampleBuffer 只实现了 BGRA另一个是出帧频率低于设备要求的 fps系统拿不到帧就一直挂起等待。解决把 plist 里的 sourceType 先改成 pattern排除片源问题然后在 VCVirtualDevice 的 format 声明里同补上 NV12并让出帧线程按 30fps 驱动。如果还黑抓日志看 pixel buffer dropped 是否持续增长有增长就把帧率调高 2~3 fps直到日志里 drop 计数停住。这个坑我以前翻车过罪魁就是源视频只有 24fps而设备声明是 30fps系统等帧等到超时。5.3 第三方 App 依然调用真摄像头虚拟设备只在系统相机里出现现象系统相机能看到 VirtualCam 并正常出画面但微信视频、抖音直播还是真摄像头。原因这类 App 的自定义采集逻辑不走AVCaptureDevice devices查询而是直接调用defaultDeviceWithDeviceType:mediaType:position:去拿前置摄像头而早期版本的 hook 没覆盖这个类方法。另一个可能是 filter plist 里注入范围写窄了第三方 App 进程压根没加载动态库。解决在 hook_xm.x 里补上defaultDeviceWithDeviceType:的 hook让前置宽角摄像头这一类返回虚拟设备同时检查 filter 段确认注入目标是 SpringBoard 还是所有进程。如果你的目标 App 用的是 Camera 框架而不是 AVFoundation那就要额外 hook Camera 目录下对应的类。这套代码包目前覆盖的是 AVFoundation 这条路用之前先确认目标 App 的采集框架免得白忙一场。5.4 编译踩坑找不到 AVCaptureDevice 等系统头文件现象make package执行到编译一步报一堆 AVCaptureDevice not found、UIKIT_CLASS_AVAILABLE is undefined 之类的错误。原因Theos 的 SDK 目录被换过或者 SDKVERSION 变量指定了一个不存在的版本号编译时头文件搜索路径直接失效。这不是代码问题是环境问题。解决先执行find $THEOS/sdks -maxdepth 1 -type d看有哪些 SDK再把export SDKVERSION15.0改成实际文件夹名里的版本。确认 SDK 没缺还报错的话检查 Makefile 里的XCODE_SELECT是否被系统自带的 Xcode 抢走显式指向 Theos 内置工具链就可以。遇到这类报错先看 SDK 路径再看代码顺序不要反不然就是瞎折腾。5.5 重启或注销后插件不再加载现象刚装完一切正常重启越狱设备回桌面后相机里虚拟设备消失日志里完全没有 VCDev 输出。原因iOS 15 后的越狱环境里MobileLoader 在重启后第一轮回 SpringBoard 时不会主动加载第三方动态库要等桌面启动完成、注入进程跑起来才补加载。还有一种情况是越狱工具用的是 libhooker而插件还是老式 substrate 风格的 plist 格式加载列表不识别。解决装完插件先 sbreload 触发一次全量重载再进越狱工具设置确认注入列表里有 com.example.virtualcam。用 libhooker 的就把 DynamicLibraries 下的 plist 结构保持为它识别的 Filter 字段格式别沿用旧式的 SafeMode 字段。这属于那种“看着像玄学、其实就是配置文件格式没跟上工具链”的坑检查时按顺序排除就好。6. 进阶验证把视频源配置外置让插件支持热切换最后一公里我建议把视频源相关参数全部外置到/var/mobile/Library/Preferences/com.example.virtualcam.plist这样切换演示视频、改分辨率、调帧率都不用重新编译安装。代码包编译一次放进设备后日常维护就变成改配置、重启相机 App 的事。# 检查当前生效配置 plutil -p /var/mobile/Library/Preferences/com.example.virtualcam.plist # 热切换视频源只改 sourcePath再重启相机进程 plutil -replace sourcePath -string file:///var/mobile/Media/Videos/demo2.mp4 \ /var/mobile/Library/Preferences/com.example.virtualcam.plist killall Camera改完配置后打开相机日志里第一行如果打VCDev: config loaded (demo2.mp4)说明外置配置生效了。这个做法最大的价值是把“改代码—重编—重装”半小时的流程压缩成“改一行 plist—杀进程”十秒的流程。我测试机上的冒烟流程固定在四步开机重载后先开 pattern 源验证分辨率匹配再切 local_file 确认读帧正常然后跑一遍目标 App 看日志有没有 pixel buffer dropped最后用第三方 App 走一次视频通话验证 hook 覆盖范围。每一步的关键日志我都提前写好 grep 关键字抓不到就说明对应模块有问题。这和一开始直接把源路径写死在代码里相比差别很大。写死的话每次换素材都要重新 make时间一长人就不想频繁验证外置配置后随手改一行就愿意多试几种分辨率组合。从那以后我每次更新 VCVirtualDevice 或 hook_xm.x都强制把这条冒烟流程完整走一遍不跳过任何一步黑屏这个坑基本不再出现。希望帮到你。本文还有配套的精品资源点击获取
返回列表