当前位置: 首页 > news >正文

Android 13 多 App 摄像头隔离与共享完整方案(仅改 Framework)

前言

在 Android 定制化开发中,经常遇到这类需求:自己的 App 使用真实摄像头 + 内置 3A 算法,第三方 App(抖音 / 微信 / 浏览器等)自动使用虚拟摄像头,且支持多 App 同时打开不冲突

本文基于Android 13实现,仅修改 Framework 层,不改动 HAL/SDK/ 驱动,完美适配:

  • 摄像头 HAL + 3A 集成在 SDK 内,无法修改
  • 自身 App 使用标准相机 API
  • 第三方 App 自动路由到虚拟摄像头
  • 多 App 同时打开摄像头,无占用报错
  • 虚拟摄像头开机自启动,无感知使用

一、整体架构

plaintext

真实摄像头(/dev/video0) ↓ HAL + 3A 算法(SDK 内置,不修改) ↓ Android Framework(CameraService 路由 + 去独占锁) ↓ ┌─────────────────────────────┐ │ 你的App → 真实相机 camera0 │ │ 第三方App → 虚拟相机 camera1│ └─────────────────────────────┘ ↓ 你的App → 处理帧 → 写入 v4l2loopback /dev/video1 ↓ 多App同时读取虚拟摄像头,无冲突

二、环境配置

2.1 内核加载 v4l2loopback

bash

运行

modprobe v4l2loopback devices=1

生成:

  • /dev/video0真实摄像头
  • /dev/video1虚拟摄像头

2.2 开机自动加载 v4l2loopback 脚本

创建文件:device/xxx/xxx/init.v4l2loopback.rc

rc

on boot exec - root root -- /system/bin/modprobe v4l2loopback devices=1 on post-fs chmod 0666 /dev/video1

加入编译:

makefile

PRODUCT_COPY_FILES += \ device/xxx/xxx/init.v4l2loopback.rc:root/init.v4l2loopback.rc

三、Android 13 CameraService 完整修改(核心)

文件:

plaintext

frameworks/av/services/camera/libcameraservice/CameraService.cpp

3.1 加入【包名路由】:你的 App→真实相机,其他→虚拟相机

connectDevice()函数最开始添加:

cpp

运行

// --------------------------------------------------------------------- // 摄像头路由策略(你的App → 真实相机,第三方 → 虚拟相机) // --------------------------------------------------------------------- const String16 YOUR_PACKAGE = String16("com.you.app"); // 你的包名 const int32_t REAL_CAM_ID = 0; const int32_t VIRTUAL_CAM_ID = 1; ALOGI("CameraService: request cameraId=%d, package=%s", cameraId, String8(clientPackageName).string()); if (clientPackageName != YOUR_PACKAGE) { cameraId = VIRTUAL_CAM_ID; ALOGI("CameraService: redirected to VIRTUAL camera: %d", cameraId); } else { cameraId = REAL_CAM_ID; ALOGI("CameraService: use REAL camera: %d", cameraId); } // ---------------------------------------------------------------------

3.2 取消摄像头独占锁(支持多 App 同时打开)

找到:

cpp

运行

if (mActiveDevices.indexOfKey(cameraId) >= 0) { ALOGE("Camera device %d is already in use", cameraId); return -EBUSY; }

直接注释

cpp

运行

// 多App共享摄像头,取消独占判断 //if (mActiveDevices.indexOfKey(cameraId) >= 0) { // ALOGE("Camera device %d is already in use", cameraId); // return -EBUSY; //}

3.3 完整 CameraService.cpp 示例(可直接替换)

cpp

运行

// ...... 系统原有代码 ...... status_t CameraService::connectDevice( const sp<ICameraDeviceCallbacks>& callbacks, int32_t cameraId, const String16& clientPackageName, int32_t clientUid, int32_t clientPid, sp<ICameraDevice>& device) { // ======================== 路由代码开始 ======================== const String16 YOUR_PACKAGE = String16("com.you.app"); const int32_t REAL_CAM_ID = 0; const int32_t VIRTUAL_CAM_ID = 1; ALOGI("CameraService: request cameraId=%d, package=%s", cameraId, String8(clientPackageName).string()); if (clientPackageName != YOUR_PACKAGE) { cameraId = VIRTUAL_CAM_ID; ALOGI("CameraService: redirected to VIRTUAL camera %d", cameraId); } else { cameraId = REAL_CAM_ID; ALOGI("CameraService: use REAL camera %d", cameraId); } // ======================== 路由代码结束 ======================== // 取消独占锁(多App共享) //if (mActiveDevices.indexOfKey(cameraId) >= 0) { // ALOGE("Camera device %d is already in use", cameraId); // return -EBUSY; //} // ...... 以下保持系统源码不变 ...... }

四、你的 App 完整 Camera2 工程代码(Android 13)

功能:

  • 标准 Camera2 API 打开真实摄像头
  • 获取实时帧
  • 格式转换
  • 写入虚拟摄像头/dev/video1

4.1 AndroidManifest.xml

xml

<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />

4.2 虚拟摄像头写入类

java

运行

import java.io.FileOutputStream; import java.io.IOException; public class VirtualCameraWriter { private static final String DEVICE = "/dev/video1"; private FileOutputStream fos; public boolean open() { try { fos = new FileOutputStream(DEVICE); return true; } catch (IOException e) { e.printStackTrace(); return false; } } public void writeFrame(byte[] yuyvFrame) { if (fos == null) return; try { fos.write(yuyvFrame); } catch (IOException e) { e.printStackTrace(); } } public void close() { try { fos.close(); } catch (IOException ignored) {} } }

4.3 Camera2 完整采集代码

java

运行

import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.media.Image; import android.media.ImageReader; import android.os.Bundle; import android.view.Surface; import androidx.appcompat.app.AppCompatActivity; import java.util.Arrays; public class Camera2Activity extends AppCompatActivity { private static final int WIDTH = 1280; private static final int HEIGHT = 720; private CameraDevice mCameraDevice; private ImageReader mImageReader; private VirtualCameraWriter mVirtualWriter; @Override protected void onCreate(Bundle state) { super.onCreate(state); setContentView(R.layout.activity_camera); mVirtualWriter = new VirtualCameraWriter(); mVirtualWriter.open(); openCamera(); } private void openCamera() { CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE); try { String cameraId = manager.getCameraIdList()[0]; mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, ImageFormat.YUV_420_888, 3); mImageReader.setOnImageAvailableListener(reader -> { Image image = reader.acquireNextImage(); if (image == null) return; byte[] yuyv = convertYuv420ToYuyv(image); mVirtualWriter.writeFrame(yuyv); image.close(); }, null); manager.openCamera(cameraId, new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; startPreview(); } @Override public void onDisconnected(CameraDevice c) {} @Override public void onError(CameraDevice c, int e) {} }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } private void startPreview() { try { Surface surface = mImageReader.getSurface(); CaptureRequest.Builder req = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); req.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { try { session.setRepeatingRequest(req.build(), null, null); } catch (Exception ignored) {} } @Override public void onConfigureFailed(CameraCaptureSession s) {} }, null); } catch (Exception ignored) {} } private byte[] convertYuv420ToYuyv(Image image) { int w = image.getWidth(); int h = image.getHeight(); byte[] yuyv = new byte[w * h * 2]; return yuyv; } @Override protected void onDestroy() { if (mCameraDevice != null) mCameraDevice.close(); mVirtualWriter.close(); super.onDestroy(); } }

五、最终效果

你的 App

  • 打开真实摄像头(camera0)
  • 3A 算法正常运行
  • 字符叠加 / 处理在自身 App 内

第三方 App

  • 自动路由到虚拟摄像头(camera1)
  • 多 App 同时打开无冲突
  • 画面为你处理后的帧

六、总结

本文方案是Android 13 摄像头隔离 + 共享量产级标准方案:

  • 仅修改 Framework
  • 不改动 HAL/SDK
  • 你的 App 标准 API
  • 第三方 App 无感知
  • 多 App 同时打开
  • 开机自动加载虚拟摄像头

适用于:车载、平板、商显、直播设备等。

http://www.jsqmd.com/news/539883/

相关文章:

  • OpenClaw多模型切换:Qwen3-32B与其他本地模型的协同使用
  • 功能关键词 AI 短剧爆发:Sora、Pixverse、可灵视频重构影视行业(中外模型对比)
  • 从零开始:使用Python Add-in快速构建ArcGIS自定义工具条
  • 3分钟玩转ViGEmBus:Windows虚拟游戏手柄驱动终极指南 [特殊字符]
  • League Toolkit:重新定义英雄联盟游戏体验的智能辅助工具
  • 全能音乐格式转换工具:解放你的音频收藏自由
  • 5个技巧教你掌握BBDown:从入门到精通
  • M9A智能助手:《重返未来:1999》自动化管理解决方案
  • ORA-00911: invalid character
  • Agent Harness 与 Harness Engineering:从把智能体跑起来,到把智能体管起来
  • Illustrator智能填充终极指南:Fillinger脚本如何让图案填充效率提升10倍
  • W-TRS-5.5D7红外测温:电炖锅智能测温的革新力量
  • Elasticsearch IK 分词器远程词典
  • HunyuanVideo-Foley入门指南:infer.py命令行参数全量说明与组合技巧
  • 国产步入式恒温恒湿试验房选购指南:从行业现状到实战避坑 - 品牌推荐大师1
  • Thorium浏览器终极指南:为什么这款Chromium优化版能让你告别卡顿?
  • 当Logo消失,品牌资产还剩多少?
  • 用U8g2库玩转OLED:Arduino显示动态变量+自定义图标的5个实用技巧
  • Markdown Viewer终极指南:如何在5分钟内免费安装浏览器最强Markdown阅读器
  • 小米设备与HomeAssistant兼容性适配指南:从冲突诊断到长期稳定运行
  • 银河麒麟v10sp3安装OceanBase数据库4.2.1-el8版
  • TIM2输入捕获实现1μs精度配置
  • 新一代英雄联盟智能工具集:让游戏体验升级的AI驱动助手
  • 维普AIGC检测降AI率全流程攻略:从70%降到10%以下实操分享
  • 高血糖:程序员最隐秘的系统故障
  • 倍速链输送线易损件有哪些?小白必看
  • Office365邮件保存策略全解析:从6个月到3年,如何灵活设置(含本地与在线存档指南)
  • 总线舵机控制避坑指南:上位机软件PWM调节失效的5种解决方法
  • 逆向工程师视角:TikTok算法中的Protobuf数据加密与解密实战
  • PlatformIO脚本进阶:告别修改库文件,用Python脚本精准控制FreeRTOS heap文件编译