ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MTK平台Android系统USB摄像头原生API支持实现方案

MTK平台Android系统USB摄像头原生API支持实现方案 简介本资源是面向Android系统开发工程师与MTK平台定制化开发者的技术补丁包旨在解决USB摄像头在Android原生框架下无法被Google相机等标准应用直接调用的兼容性问题——无需依赖libuvc即可让USB摄像头像MIPI摄像头一样通过Android Camera API如Camera HAL2被识别、配置与流式采集。资源基于MT8163平台实现含155个文件涵盖48个C核心逻辑文件如PreviewCmdQueThread.cpp、SingleShot.cpp、31个头文件.h、28个Makefile构建脚本.mk、7个说明类文本.txt及4个Java接口适配文件整体压缩包仅3.61MB结构清晰、模块职责明确便于移植到其他MTK芯片平台。目前已有543人学习下载读者可直接获取完整HAL层修改方案、参数管理机制ParamsManager.update.cpp、内存驱动对接imem_drv.cpp及AAA图像处理适配aaa_hal.cpp等关键实现快速掌握USB Camera在Android原生架构中的深度集成路径。1. 项目概述当MTK遇上原生USB摄像头在Android开发特别是涉及硬件定制的领域里MTK平台因其高集成度和成本优势占据了相当大的市场份额。但做过MTK方案开发的工程师都知道平台厂商提供的SDK和驱动框架往往像一座“围城”——它提供了快速实现基础功能的便利却也把开发者限制在了它预设的路径里。一个典型的痛点就是摄像头调用。MTK通常通过其私有的CameraHal和CameraService来管理摄像头应用层需要通过Android标准的Camera2 API或更早的Camera1 API来访问。这套流程对于内置的MIPI摄像头工作良好但当你需要接入一个标准的USB摄像头时问题就来了。MTK的原生驱动框架默认可能并未开启对USB Video Class设备的完整支持或者其CameraHal没有为USB摄像头预留数据通路。这就导致了一个尴尬的局面你明明在系统/dev/videoX节点下看到了识别到的USB摄像头设备但当你尝试用最标准的AndroidCamera2 API去打开它时却只会得到一个CameraAccessException提示设备不存在或无法连接。这个“MTK平台支持Android原生API打开USB摄像头补丁”项目就是为了拆掉这堵墙。它的核心目标是打通从Android标准API到MTK平台底层USB摄像头驱动之间的壁垒让开发者能够像调用内置摄像头一样使用CameraManager.openCamera()这样的标准接口无缝操作外接的USB摄像头。这不仅仅是增加一个功能那么简单。对于需要外接高分辨率工业相机、特殊光谱相机或简单需要多路视频输入的应用如安防监控、医疗设备、教育录播、直播推流等这个补丁提供了标准化的解决方案。它避免了开发者为了一个USB摄像头去重新发明轮子编写一套独立于系统相机框架的JNI和预览逻辑。通过让USB摄像头融入Android标准的相机服务体系应用可以统一地使用相同的API管理所有摄像头源享受系统级的生命周期管理、权限控制和图像流处理管线极大地提升了开发效率和系统的整洁性。2. 核心需求与方案设计解析2.1 需求拆解我们到底要解决什么问题这个补丁的需求可以分解为三个层次设备识别与枚举系统必须能正确识别并枚举通过USB OTG或Host接口连接的UVC设备。这通常由Linux内核的uvcvideo驱动完成生成/dev/videoX设备节点。在Android层面CameraService需要能扫描到这些节点并将其注册为可用的相机设备分配一个唯一的cameraId。HAL层适配这是最关键的一环。Android的相机硬件抽象层是连接CameraService与具体硬件的桥梁。MTK默认的CameraHal可能只处理其Sensor List中预定义的内置MIPI摄像头。我们需要扩展或修改这个HAL使其能够为USB摄像头设备创建对应的CameraDevice实例并实现标准的HAL接口如device::opendevice::create_stream等。API层透传确保Android框架层CameraService,CameraManager能将来自App的API调用正确地路由到我们为USB摄像头适配的HAL实现上并将视频流数据按标准格式如YUV_420_888,JPEG回传给应用。2.2 方案选型为什么是“补丁”而非“外挂”面对这个需求通常有两种思路一是“外挂式”即完全绕过Android原生相机框架自己写一个JNI库直接通过V4L2接口操作/dev/videoX设备二是“融合式”即修改系统让USB摄像头成为原生框架的一部分。本补丁显然选择了后者。为什么生态兼容性“融合式”方案让USB摄像头对所有使用标准Camera2 API的应用如系统相机App、微信、Zoom等立即可用无需为每个应用单独适配。这是最大的优势。功能完整性应用可以统一使用CameraCharacteristics查询USB摄像头的能力分辨率、帧率、对焦模式等使用CaptureRequest控制参数如白平衡、曝光并利用ImageReader或Surface接收数据。这些功能如果自己实现V4L2控制复杂度极高。维护成本补丁集成到系统后后续Android版本升级只需关注HAL接口的兼容性调整。而“外挂式”方案需要独立维护一整套从应用到驱动的代码且与系统其他部分的交互如权限、生命周期容易出现问题。因此这个补丁的本质是对MTK Android系统源码中相机框架部分进行的一系列增补和修改主要涉及hardware/libhardwareHAL定义、frameworks/avCameraService以及device/mediatekMTK特定HAL实现等目录。2.3 整体架构设计一个可行的补丁实现架构如下[App] Camera2 API | v [Framework] CameraManager - CameraService | v [HAL] MTK CameraHal (Modified) --- [新增] UVC Camera HAL Adapter | | v v [Kernel] MTK ISP/MIPI Driver [Kernel] UVC Driver (uvcvideo) | | v v [Hardware] Built-in Sensor [Hardware] USB Camera核心修改点在CameraService中增强设备发现逻辑使其在扫描/dev目录时不仅能识别MTK HAL上报的设备也能主动探测/dev/videoX并通过某种机制例如调用一个新增的HAL函数来验证其是否为可用的UVC摄像头。扩展或新增一个HAL模块例如在MTK的CameraHal中增加一个UVCDevice类继承自标准的CameraDevice。这个类内部封装了对V4L2系统调用的操作。或者更为清晰的做法是实现一个独立的android.hardware.camera.devicex.x-impl-uvc.so动态库专门处理UVC设备。实现HAL接口这个新增的HAL模块必须实现ICameraDevice定义的所有关键方法如open、getCameraCharacteristics、createStream、submitRequest等。在createStream中需要建立与/dev/videoX的数据通道并启动视频流。数据格式转换与传递从V4L2驱动读出的数据格式通常是YUYV/MJPG需要转换为Android标准的PixelFormat。这可以在HAL层进行转换也可以通过配置Stream的格式让CameraService或GraphicBuffer进行转换。注意修改系统HAL和Service涉及对AOSP代码的深度理解并且需要对应MTK平台特定的代码版本。不同Android版本如Android 10, 11, 12和不同MTK芯片平台如MT6765, MT6785的代码结构可能有差异补丁不能通用。3. 关键实现步骤与代码剖析由于无法提供完整的、针对特定平台和版本的代码这里将详细描述实现过程中的关键步骤、需要修改的文件以及核心代码逻辑你可以将其视为一份详细的移植指南。3.1 环境准备与代码定位首先你需要获取目标设备对应的MTK Android平台源码。这通常来自芯片供应商或设备制造商。假设我们基于Android 11的MTK平台进行开发。需要重点关注的源码目录frameworks/av/services/camera/libcameraservice/CameraService的实现所在。hardware/interfaces/camera/Android官方定义的Camera HAL接口HIDL。vendor/mediatek/proprietary/hardware/mtkcam/MTK相机HAL的核心实现路径可能因平台而异。device/mediatek/[project_name]/项目特定的设备配置和覆盖层。3.2 增强CameraService的设备发现逻辑默认情况下CameraService通过CameraProviderManager加载HAL实现如android.hardware.camera.provider2.4-impl-mediatek.so并等待HAL上报可用的摄像头。我们需要让它也能主动发现UVC设备。修改文件CameraService.cpp(或更可能是在CameraProviderManager.cpp)核心思路在CameraService初始化或定期扫描时遍历/dev/video*。对每个/dev/videoX节点尝试用V4L2的VIDIOC_QUERYCAP命令查询其能力。如果其capabilities包含V4L2_CAP_VIDEO_CAPTURE则判定为一个视频捕获设备。进一步可以检查其驱动名称是否包含“uvcvideo”来确认是UVC设备。对于确认的UVC设备需要为其生成一个唯一的cameraId例如在内置摄像头ID后追加如“2”代表第一个USB摄像头。关键的一步需要通知或“注册”这个新设备到现有的HAL框架中。一种方法是扩展HIDL接口在ICameraProvider中增加一个方法如addExternalCameraDevice(string devicePath)。CameraService调用此方法将/dev/videoX路径传递给HAL。HAL侧则负责创建对应的虚拟相机设备。伪代码示例// 在CameraProviderManager的初始化或某个扫描函数中 void scanForUVCDevices() { DIR *dir opendir(/dev); struct dirent *entry; while ((entry readdir(dir)) ! nullptr) { if (strstr(entry-d_name, video) entry-d_name) { // 以video开头 std::string devPath std::string(/dev/) entry-d_name; int fd open(devPath.c_str(), O_RDWR); if (fd 0) continue; struct v4l2_capability cap; if (ioctl(fd, VIDIOC_QUERYCAP, cap) 0) { if (cap.capabilities V4L2_CAP_VIDEO_CAPTURE) { // 判断是否为UVC if (strstr((char*)cap.driver, uvcvideo) ! nullptr) { std::string cameraId generateUVCameraId(devPath); // 例如 “2” // 调用HAL接口告知其添加此UVC设备 mCameraProvider-addUVCDevice(devPath, cameraId); } } } close(fd); } } closedir(dir); }3.3 实现UVC专用的HAL模块这是补丁的核心。我们需要在MTK的HAL实现中增加对UVC设备的支持。方案A集成到现有MTK HAL中在vendor/mediatek/proprietary/hardware/mtkcam/目录下找到设备打开和创建的入口。通常有一个CameraDevice的工厂类或创建函数。修改它使其在接收到来自CameraService的特定cameraId如我们生成的“2”或设备路径时不是去打开MIPI Sensor而是实例化一个我们新写的UVCameraDevice类。UVCameraDevice需要继承自标准的CameraDevice基类可能是CameraDevice3或MTK自定义的类。其关键实现包括构造函数接收/dev/videoX路径。open()打开V4L2设备文件查询并保存其支持的分辨率、格式、帧率到CameraMetadata中即CameraCharacteristics。createStream()根据请求的格式和分辨率通过VIDIOC_S_FMT设置V4L2设备并分配缓冲区VIDIOC_REQBUFS。submitRequest()启动视频流VIDIOC_STREAMON并开启一个工作线程循环使用VIDIOC_QBUF和VIDIOC_DQBUF获取图像数据然后通过processCaptureResult回调将数据填入StreamBuffer返回给框架层。方案B实现独立的UVC HAL动态库这是一个更清晰、耦合度更低的方案。创建一个新的HIDL服务实现例如android.hardware.camera.provider2.4-impl-uvc。这个Provider只负责管理UVC摄像头。在它的ICameraProvider::getCameraIdList()中它主动扫描/dev/video*并返回UVC设备的ID列表。在ICameraProvider::setCallback()被调用后它像普通HAL一样工作。MTK的系统需要配置为同时加载默认的mtkcamHAL和这个uvcHAL。CameraProviderManager能够管理多个Provider从而实现内置摄像头和USB摄像头的共存。关键数据结构转换示例V4L2 Format to Android Format// 在查询摄像头特性时需要填充supportedStreamConfigurations camera_metadata_entry_t entry ...; for (each v4l2_format supported) { int32_t streamConfig[4]; streamConfig[0] v4l2FormatToAndroidPixelFormat(v4l2_fmt.pixelformat); // 如 V4L2_PIX_FMT_YUYV - HAL_PIXEL_FORMAT_YCbCr_422_I streamConfig[1] v4l2_fmt.width; streamConfig[2] v4l2_fmt.height; streamConfig[3] ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT; // 将streamConfig添加到entry中 }3.4 配置与编译集成修改HAL加载配置在设备的device.mk或相关的init.rc文件里确保系统启动时会加载我们修改后的HAL库或新增的UVC HAL库。权限配置确保/dev/videoX设备的权限允许camera用户组或system用户访问。这通常在ueventd.rc或ueventd.[platform].rc中设置。SELinux策略这是一个巨大的坑你需要为cameraserver进程添加允许访问/dev/videoX设备节点、执行ioctl等操作的SELinux策略。否则即使代码正确也会因权限拒绝而失败。需要修改*.te文件添加类似allow cameraserver video_device:chr_file { open read write ioctl };的规则。编译使用平台编译命令如source build/envsetup.sh; lunch; mmm vendor/mediatek/proprietary/hardware/mtkcam/编译修改后的模块并重新生成系统镜像如boot.img或system.img。4. 实操难点与避坑指南在实际操作中你会遇到比编码更棘手的问题。以下是我在类似项目中的经验总结。4.1 难点一HAL接口版本与兼容性Android的Camera HAL接口经历了HIDL到AIDL的演进。MTK平台在Android 11上可能仍主要使用HIDL如3.x或2.4。你必须精确确定目标系统使用的HAL接口版本并基于正确的接口定义进行开发。错误版本的接口实现会导致CameraService无法正确加载HAL。实操心得在开始编码前先检查/vendor/lib64/hw/或/vendor/lib/hw/目录下已有的camera.*.so文件使用strings命令或readelf查看其依赖的HIDL接口版本。同时仔细阅读AOSP中对应版本的ICameraDevice.hal等接口定义文件。4.2 难点二数据流格式与Gralloc Buffer从V4L2驱动读取的原始数据如YUYV需要放入Android的GraphicBuffer中并通过StreamBuffer返回。这里涉及内存拷贝和格式转换性能是关键。零拷贝尝试理想情况下我们希望V4L2驱动直接将数据写入GraphicBufferDMABUF。这需要内核驱动和Gralloc HAL都支持DMABUF导入/导出。对于UVC设备这通常比较困难。拷贝与转换更实际的方案是在HAL层进行拷贝和格式转换。例如将YUYV转换为NV21或YV12Android更常用的YUV格式。这里必须注意字节对齐Stride问题。GraphicBuffer的stride步长可能大于图像的width直接按width逐行拷贝会导致花屏。你需要使用GraphicBuffer的stride作为行拷贝的宽度。代码片段示例格式转换与拷贝void convertYUYVtoNV21(const uint8_t* srcYuyv, uint8_t* dstY, uint8_t* dstUV, int width, int height, int srcStride, int dstStrideY, int dstStrideUV) { // 简化示例逐行处理注意srcStride是YUYV格式的字节跨度一个YUYV是2个像素占4字节 for (int y 0; y height; y) { const uint8_t* srcLine srcYuyv y * srcStride; uint8_t* dstYLine dstY y * dstStrideY; uint8_t* dstUVLine dstUV (y / 2) * dstStrideUV; // NV21的UV平面是高度减半 for (int x 0; x width; x 2) { // 一次处理两个像素 // 提取Y分量 dstYLine[x] srcLine[0]; dstYLine[x1] srcLine[2]; // 提取UV分量 (NV21是VU交错这里从YUYV的U和V近似计算) dstUVLine[x] srcLine[1]; // U - V (位置交换因是NV21) dstUVLine[x1] srcLine[3]; // V - U srcLine 4; // 移动4个字节到下一对YUYV像素 } } }4.3 难点三性能与延迟USB 2.0的带宽限制和高分辨率下的数据量可能成为瓶颈。对于MJPG格式的摄像头HAL层需要先进行JPEG解码再转换为YUV这会消耗大量CPU资源。分辨率与帧率选择在getCameraCharacteristics中只上报摄像头真正支持且系统性能允许的分辨率-帧率组合。不要盲目支持最高规格。使用硬件加速如果平台有可用的JPEG解码硬件如MTK的MDP或JPEG Decoder尽量利用它来解码MJPG流可以显著降低CPU占用。缓冲区管理合理设置V4L2的缓冲区数量通常4-6个采用乒乓操作避免在数据回调中执行耗时的内存分配或格式转换。4.4 难点四SELinux权限配置这是导致功能在“root”下工作正常而在正式系统中失败的最常见原因。cameraserver进程默认没有访问/dev/videoX的权限。排查与解决步骤抓取SELinux拒绝日志adb shell dmesg | grep avc或adb logcat | grep avc。分析日志会看到类似avc: denied { ioctl } for pidxxx commcameraserver path/dev/video0 devtmpfs inoxxx scontextu:r:cameraserver:s0 tcontextu:object_r:video_device:s0 tclasschr_file的信息。根据拒绝信息在设备源码的SELinux策略文件如device/mediatek/sepolicy/basic/non_plat/目录下的*.te文件中添加对应规则。例如针对上面的拒绝需要添加# 允许 cameraserver 对 video_device 类型的字符文件执行 ioctl 操作 allow cameraserver video_device:chr_file ioctl; # 通常还需要 open, read, write 等基本权限 allow cameraserver video_device:chr_file { open read write getattr };重新编译sepolicy并刷机测试。避坑指南SELinux策略修改后务必使用adb shell getenforce确认处于Enforcing模式并用测试App验证。有时需要同时修改file_contexts文件确保/dev/video*被正确标记为video_device类型。5. 测试验证与问题排查补丁集成后需要通过系统性和针对性的测试来验证功能。5.1 基础功能验证设备枚举使用adb shell dumpsys media.camera命令查看相机服务列表。你应该能看到新增的USB摄像头设备及其ID。检查其Characteristics是否包含正确的分辨率、格式等信息。API调用测试编写一个简单的测试App使用CameraManager.getCameraIdList()获取列表尝试用CameraManager.openCamera()打开USB摄像头的ID。观察是否能成功打开并获取到CameraCharacteristics。预览测试创建CaptureSession添加一个SurfaceView的Surface作为预览目标提交重复的捕获请求。观察是否能看到实时预览画面。5.2 常见问题与排查表问题现象可能原因排查步骤getCameraIdList()中看不到USB摄像头1. UVC驱动未加载或设备未识别。2.CameraService扫描逻辑未生效。3. HAL未正确注册设备。1.adb shell ls /dev/video*确认设备节点存在。2. 检查CameraService日志 (logcat -s CameraService)看是否有扫描和添加UVC设备的日志。3. 检查HAL层日志确认addUVCDevice或类似接口是否被调用。openCamera()失败抛出CameraAccessException1. HAL层open()函数实现有误。2. 权限问题SELinux。3. 设备节点被占用。1. 查看logcat中HAL层的错误信息。2. 检查SELinux拒绝日志 (dmesg | grep avc)。3.adb shell lsof /dev/videoX查看是否有其他进程占用。预览黑屏或花屏1. 数据格式转换错误。2.GraphicBuffer的stride使用错误。3. V4L2缓冲区设置或数据读取错误。1. 确认HAL层输出的图像格式与Surface请求的格式一致。2. 在HAL层将转换后的图像数据先保存为文件在PC上查看是否正确。3. 检查V4L2的VIDIOC_G_FMT和VIDIOC_S_FMT调用是否成功确认实际图像大小。预览卡顿、帧率低1. USB带宽不足特别是高清MJPG。2. HAL层格式转换如JPEG解码耗时过长。3. 内存拷贝开销大。1. 尝试降低分辨率或切换到YUYV格式如果摄像头支持。2. 使用性能分析工具如simpleperf定位热点函数。3. 考虑使用多线程进行格式转换或优化转换算法如使用NEON指令集。应用崩溃特别是Native Crash1. 缓冲区访问越界。2. 多线程同步问题。3. HAL接口回调时序错误。1. 使用address sanitizer编译HAL进行测试。2. 仔细检查所有回调如processCaptureResult是否在正确的线程上下文中调用数据生命周期是否管理得当。3. 增加详细的日志追踪崩溃前的函数调用序列。5.3 稳定性与兼容性测试热插拔在摄像头预览过程中拔掉再插入USB摄像头系统应能正确处理设备移除和重新添加的事件onDisconnected,onAvailable应用不会崩溃。多应用并发同时打开两个App一个使用内置摄像头一个使用USB摄像头观察是否正常工作资源是否冲突。不同UVC摄像头准备多个不同品牌、不同分辨率、不同输出格式YUYV,MJPG,H264的USB摄像头进行测试确保HAL的兼容性。6. 总结与扩展思考实现这个补丁的过程本质上是一次对Android相机系统从应用层到驱动层的深度遍历。它要求开发者不仅熟悉Android框架和HAL接口还要了解Linux V4L2子系统甚至需要调试内核驱动和SELinux策略。成功之后带来的价值是巨大的你的MTK设备获得了标准化的外接摄像头扩展能力。这个补丁还可以进一步扩展和优化支持更多格式除了YUYV和MJPG可以增加对NV12、H264UVC 1.5等格式的本地支持减少转换开销。实现高级控制通过V4L2的扩展控制VIDIOC_S_EXT_CTRLS暴露USB摄像头的白平衡、曝光、对焦等参数并将其映射到AndroidCaptureRequest的键值中让应用可以进行更精细的控制。性能优化探索使用DMABUF实现零拷贝或者利用GPU进行色彩空间转换进一步降低CPU负载和延迟。封装为通用模块将UVC HAL的实现尽可能通用化通过配置文件来适配不同分辨率和格式的摄像头使其更容易移植到其他Android平台如高通、瑞芯微。最后分享一个我踩过的大坑在早期版本中我忽略了GraphicBuffer的stride直接按width拷贝导致在720p及以上分辨率预览时画面右侧会出现严重的扭曲和绿屏。这个问题在低分辨率下不明显但一到高清就暴露。解决方法就是务必使用ANativeWindow的stride或GraphicBuffer的getStride()来作为内存拷贝的行宽度。这个细节在官方文档中不显眼却至关重要。本文还有配套的精品资源点击获取
返回列表