android_virtual_cam(VCAM)实战指南:基于 Xposed 的安卓虚拟摄像头模块配置、控制文件与源码原理解析
移动开发音视频【免费下载链接】com.example.vcam虚拟摄像头 virtual camera项目地址https://gitcode.com/gh_mirrors/co/com.example.vcam点击查看免费下载本文围绕开源仓库 com.example.vcamandroid_virtual_cam应用名 VCAM的英文使用文档展开系统讲解在 Android 5.0 设备上如何安装启用这个基于 Xposed 的虚拟摄像头模块从 LSPosed 作用域选择、存储权限授权到按 Toast 分辨率制作virtual.mp4与1000.bmp再到 7 个控制文件的实时开关机制。同时结合 HookMain.java、VideoToFrames.java 与 MainActivity.java 源码深入说明目录重定向、Camera1/Camera2 预览替换、拍照替换与硬解码链路的底层实现帮助读者既会用、也看得懂原理。文档语言说明本仓库同时提供 简体中文、繁體中文 与 English 三份使用文档本文以英文文档为主体并依据源码对个别笔误进行了校正。项目概述与支持平台android_virtual_cam 是一款基于Xposed框架的安卓虚拟摄像头模块安装并启用后目标应用调用摄像头时模块会拦截系统相机接口把真实的相机预览替换为指定视频画面把拍照结果替换为指定图片从而实现虚拟摄像头效果。项目名称android_virtual_cam / VCAM应用包名com.example.vcam实现方式Xposed 模块入口为 xposed_init 中声明的com.example.vcam.HookMain支持平台Android 5.0对应 app/build.gradle 中minSdk 21当前仓库版本versionName 4.4、versionCode 28见 app/build.gradle法律声明文档明确要求DO NOT USE FOR ANY ILLEGAL PURPOSE使用者需自行承担全部责任与后果从 AndroidManifest.xml 可以看到模块的三个关键元数据xposedmoduletrue、xposeddescription安卓虚拟摄像头、xposedminversion51同时声明了READ_EXTERNAL_STORAGE与WRITE_EXTERNAL_STORAGE两个存储权限。模块安装与在 Xposed 中启用安装模块安装本模块 APK仓库 app/release/app-release.apk 已提供编译产物并在 Xposed 框架中启用。选择作用域对于LSPosed等带有作用域列表的框架需要勾选目标应用而无需选择系统框架。这一点对替换效果至关重要——模块只对被勾选的应用生效。授予存储权限在系统设置中授予目标应用读取本地存储的权限然后强制结束目标应用确保权限与模块配置在下次启动时重新生效。若目标应用本身未申请存储权限则无需授权模块会自动改用应用私有目录详见下文目录重定向机制。从源码看模块依赖 Xposed API 82见 app/build.gradledependencies { compileOnly de.robv.android.xposed:api:82 compileOnly de.robv.android.xposed:api:82:sources }认识 Camera1 目录与核心文件模块的一切替换素材都围绕Camera1目录组织该目录有两种形态目录形态路径适用范围公共目录默认/[内部存储]/DCIM/Camera1/所有有存储权限的应用共用应用私有目录/[内部存储]/Android/data/[应用包名]/files/Camera1/仅对该应用单独生效对应的默认路径在源码中有明确声明HookMain.javapublic static String video_path /storage/emulated/0/DCIM/Camera1/;目录下需要放置两类替换素材virtual.mp4替换相机预览的视频分辨率必须与目标应用预览分辨率一致1000.bmp替换拍照结果的图片分辨率必须与拍照分辨率一致支持把其他格式的图片直接改后缀为.bmp。如果目录不存在请手动创建。注意只需要一级Camera1目录不要在内部再嵌套一层Camera1这是黑屏的常见原因之一详见 FAQ。完整使用步骤按文档 10 步走通全流程以下步骤完整继承自 README_en.md并补充了源码依据。步骤 1安装并启用模块在 Xposed 中启用本模块LSPosed 等带作用域的框架需选择目标应用而非系统框架。步骤 2授权并强制停止目标应用在系统设置中授予目标应用读取本地存储的权限并强制结束目标应用若应用未申请此权限直接跳到步骤 3。步骤 3确认 Camera1 目录位置打开目标应用若应用未获得读取存储的权限会弹出 Toast 提示Camera1目录已被重定向到应用私有目录/[内部存储]/Android/data/[应用包名]/files/Camera1/。若没有该提示则默认目录为/[内部存储]/DCIM/Camera1/目录不存在时请手动创建。注意私有目录下的Camera1仅对该应用单独生效每个应用需分别放置自己的素材。步骤 4制作替换视频virtual.mp4在目标应用中打开相机预览模块会弹出 Toast 提示分辨率宽…… 高……。根据该分辨率制作替换视频命名为virtual.mp4放入Camera1目录。若打开相机没有提示消息则无需调整视频分辨率。源码印证该 Toast 由 HookMain.java 中的预览回调钩子触发同时还会给出预览帧率发现预览\n宽…\n高…\n需要视频分辨率与其完全相同。步骤 5制作替换照片1000.bmp在目标应用中拍照若显示真实图片并弹出 Toast发现拍照及分辨率则按此分辨率准备一张照片命名为1000.bmp放入Camera1目录支持其它格式改后缀为.bmp。若拍照时无 Toast 提示则1000.bmp不参与替换模块不会拦截该应用的拍照。源码印证拍照替换逻辑见 HookMain.java通过钩住Camera.takePicture的PictureCallback在回调中把1000.bmp解码后转成 JPEG 或 YUV_420_888 数据替换原始字节。步骤 6播放视频声音在/[内部存储]/DCIM/Camera1/目录下创建no-silent.jpg文件即可让替换视频在预览时带声音播放全局实时生效。勘误说明英文 README 步骤 6 误写为创建./DCIM/Camera1/Camera1/virtual.mp4实际控制文件名以源码为准——HookMain.java 中MediaPlayer.setVolume(0, 0)的静音逻辑正是由no-silent.jpg是否存在决定中文 README.md 亦为no-silent.jpg。步骤 7临时停用视频替换在/[内部存储]/DCIM/Camera1/目录下创建disable.jpg文件模块即临时恢复真实相机全局实时生效。源码中每个钩子入口都会先检查disable.jpg是否存在存在则直接放行原始调用如 HookMain.java。勘误说明英文 README 步骤 7 同样误写为创建virtual.mp4正确控制文件为disable.jpg见 HookMain.java 与 MainActivity.java。步骤 8关闭 Toast 提示若觉得各步骤的 Toast 消息烦人可在/[内部存储]/DCIM/Camera1/目录下创建no_toast.jpg文件全局实时生效。源码中所有 Toast 输出前都会先检查该文件例如need_to_show_toast !toast_control.exists()HookMain.java。步骤 9强制再次显示目录重定向提示目录重定向提示默认只显示一次。如果错过了该 Toast可在/[内部存储]/DCIM/Camera1/目录下创建force_show.jpg文件来覆盖默认设定全局实时生效。源码中通过私有目录下的has_shown标记文件记录已显示force_show.jpg存在时即使已有标记也会再次弹出提示HookMain.java。步骤 10为每个应用分配独立视频如果需要为每一个应用分配各自的视频可在/[内部存储]/DCIM/Camera1/目录下创建private_dir.jpg文件强制所有应用改用各自的私有目录全局实时生效这样每个应用的Camera1素材互不干扰。便捷配置步骤 6~10 的开关都可以在应用内快捷配置——MainActivity.java 提供了 5 个开关分别对应force_show.jpg强制显示权限提示、disable.jpg临时停用、no-silent.jpg播放视频声音、private_dir.jpg强制私有目录与no_toast.jpg关闭 Toast开关状态与目录中的文件实时双向同步sync_statue_with_files()见 MainActivity.java。控制文件速查表控制文件作用存放位置生效方式virtual.mp4替换相机预览的视频画面Camera1 目录打开相机预览时1000.bmp替换拍照结果的照片Camera1 目录拍照回调时no-silent.jpg播放替换视频的声音/[内部存储]/DCIM/Camera1/全局实时生效disable.jpg临时停用视频替换恢复真实相机/[内部存储]/DCIM/Camera1/全局实时生效no_toast.jpg关闭全部 Toast 提示/[内部存储]/DCIM/Camera1/全局实时生效force_show.jpg强制再次显示目录重定向提示/[内部存储]/DCIM/Camera1/全局实时生效private_dir.jpg强制所有应用使用私有目录/[内部存储]/DCIM/Camera1/全局实时生效除virtual.mp4与1000.bmp需要按分辨率制作外其余 5 个控制文件只需存在/不存在即可切换状态这也是模块设计中最灵活的部分——无需重启应用实时生效。目录重定向机制源码级解析模块在目标应用的Application创建时钩住android.app.Instrumentation.callApplicationOnCreate见 HookMain.java完成video_path的最终决策权限判定读取READ_EXTERNAL_STORAGE与Android 11 的MANAGE_EXTERNAL_STORAGE权限状态累加得到auth_statue。决策逻辑若auth_statue 1即应用无存储权限或存在private_dir.jpg则video_path指向应用私有目录getExternalFilesDir(null)/Camera1/否则使用公共目录/storage/emulated/0/DCIM/Camera1/。重定向提示无权限时弹出 Toast 说明重定向路径并通过私有目录下的has_shown文件保证提示默认只出现一次force_show.jpg可强制重复显示。公共目录不存在时模块会自动尝试创建uni_Camera1_path.mkdir()。这一机制解释了步骤 3 中有没有重定向提示的两种情形也解释了为什么同一台设备上不同应用可能看到不同的Camera1路径。替换链路源码解析Camera1android.hardware.Camera预览替换针对旧版 Camera API模块通过多个钩子协同完成替换HookMain.javaCamera.setPreviewTextureL97-L146用新建的fake_SurfaceTexture纹理 ID 10替换原始SurfaceTexture让真实相机的预览帧流向一个垃圾场同时记录原始相机对象与纹理供后续播放器输出。Camera.setPreviewDisplay/Camera.startPreviewL359-L461创建MediaPlayer并setSurface(...)到应用预览用的 Surface 上循环播放video_path virtual.mp4实现预览即视频声音是否静音由no-silent.jpg决定。setPreviewCallbackWithBuffer/setPreviewCallback/setOneShotPreviewCallback/addCallbackBufferL220-L254拦截预览帧回调通过VideoToFrames解码出的帧数据data_buffer覆盖原始onPreviewFrame的字节数组供依赖帧数据的应用使用。MediaRecorder.setCamera检测到应用触发录像时提示触发了录像但目前无法拦截L270-L285说明录像场景目前不在替换范围内。Camera2 API 替换针对新版 Camera2 API模块钩住CameraManager.openCamera分别处理 Handler 与 Executor 两个重载L148-L217并在相机打开回调onOpened中钩住createCaptureSession系列方法普通、ByOutputConfigurations、高速、可重处理等多个重载L813-L1011把应用的输出配置替换为模块创建的虚拟 Surfacecreate_virtual_surface()纹理 ID 15同时钩住CaptureRequest.Builder.addTarget/removeTarget/buildL507-L637按Surface名称区分预览 Surface 与ImageReader读取 Surface分别交给MediaPlayer播放与硬解码器输出。钩住ImageReader.newInstanceL661-L678弹出应用创建了渲染器宽/高/格式的 Toast提醒一般只需要宽高比与视频相同。最终在process_camera2_play()L691-L788中为每个 Surface 分配MediaPlayer或VideoToFrames解码器按ImageReader的格式256即 JPEG否则 NV21决定输出帧类型。视频帧硬解码VideoToFramesVideoToFrames.java 承担把virtual.mp4解码为逐帧数据的任务核心链路是MediaExtractor MediaCodec 硬件解码selectTrack()选取mime以video/开头的视频轨道L236-L249解码循环中把输出帧写入目标Surface若目标 Surface 为空则取出Image转换为NV21字节存入HookMain.data_bufferL201-L216供 Camera1 预览帧回调使用解码结束后extractor.seekTo(0, 0)循环播放实现视频无限循环L122-L126帧节奏通过presentationTimeUs与真实时间的差值Thread.sleep控制尽量贴近原视频帧率。该实现由 README_en.md 致谢部分提到的 Android-VideoToImages 项目修改而来H.264 硬件解码思路。拍照替换1000.bmp钩住Camera.takePictureL256-L268后模块分别处理两种回调JPEG 回调process_a_shot_jpegL1013-L1054读取video_path 1000.bmp解码为 Bitmap再以Bitmap.CompressFormat.JPEG, 100压缩成 JPEG 字节覆盖onPictureTaken的原始数据YUV 回调process_a_shot_YUVL1056-L1091通过rgb2YCbCr420()将 Bitmap 像素按标准公式转换为 YUV 4:2:0 字节源码注释标明该转换思路来自致谢部分的 JPEG→YUV 技术文章覆盖原始 YUV 数据。两个回调都会先弹出发现拍照\n宽…\n高…\n格式JPEG / YUV_420_888的 Toast并受disable.jpg与no_toast.jpg控制。常见问题FAQQ1前置摄像头方向问题大多数情况下替换前置摄像头的视频需要水平翻转并右旋 90 度并且视频处理后的分辨率应与 Toast 提示的分辨率相同。但有时并不需要调整具体请根据实际情况判断。Q2画面黑屏、相机启动失败目前有些应用并不能成功替换特别是系统相机。另一种常见原因是视频路径不对——检查是否错误地创建了两级Camera1目录如./DCIM/Camera1/Camera1/virtual.mp4实际上只需要一级目录。Q3画面花屏视频分辨率不对请按 Toast 提示的分辨率重新制作virtual.mp4。Q4画面扭曲、变形请使用剪辑软件修改原视频使其宽高比与目标应用屏幕/预览匹配。Q5创建disable.jpg无效这与模块版本有关应用版本 4.0[内部存储]/DCIM/Camera1目录下的控制文件只对具有存储权限的应用生效其余无权限应用应在私有目录下创建控制文件。应用版本 4.1无论在目标应用是否有权限控制文件都应创建在[内部存储]/DCIM/Camera1下。当前仓库版本为4.4见 app/build.gradle因此遵循 4.1 规则即可。问题反馈请直接在项目 issues 中反馈。如果属于 BUG 反馈请附带 Xposed 模块日志信息而非应用日志以便定位 hook 过程中的异常——源码中大量XposedBridge.log(【VCAM】...)输出如开始解码创建预览打开相机C2onCaptureFailed 原因等即为排查线索。致谢与依赖模块的实现借鉴了以下开源成果详见 README_en.md 致谢部分CameraHook提供了相机相关 hook 方法的思路Android-VideoToImagesH.264 硬件解码方案VideoToFrames.java 即由其修改而来JPEG → YUV 转换技术文章用于拍照替换时的像素格式转换HookMain.java 的实现来源。需要再次强调本模块仅可用于合法、合规的测试与个人用途请勿用于任何非法用途使用者需自行承担全部责任与后果。赞分享移动开发音视频【免费下载链接】com.example.vcam虚拟摄像头 virtual camera项目地址https://gitcode.com/gh_mirrors/co/com.example.vcam点击查看免费下载相关推荐安卓虚拟摄像头VCAM完整配置与实战指南安卓虚拟摄像头VCAM完整配置与实战指南 想要在安卓设备上构建专业级的虚拟摄像头环境吗VCAM虚拟相机为您提供了从基础配置到高级应用的完整解决方案。这款基于X移动开发音视频精通安卓虚拟摄像头Xposed模块实战配置进阶指南精通安卓虚拟摄像头Xposed模块实战配置进阶指南 安卓虚拟摄像头技术为开发者提供了强大的摄像头替换能力让您能够在各类应用中无缝替换真实摄像头输入。通过Xp移动开发音视频终极安卓虚拟摄像头设置指南3分钟快速配置Xposed模块终极安卓虚拟摄像头设置指南3分钟快速配置Xposed模块 安卓虚拟摄像头技术让您能够在各种应用中替换真实的摄像头画面使用自定义视频或图片作为摄像头输入。基于移动开发音视频上一篇猫抓 cat-catch 完整教程网页视频一键下载M3U8 流还能自动合并下一篇Magisk终极指南Android系统Root与模块化管理的完整解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考