本篇记录在 android8 的 IMX8QM 平台移植 v4l2loopback 虚拟摄像头实战过程记录;其中主旨是记录整个过程、已经期间出现的踩坑过程,后期虚拟摄像驱动还有移植到多核ARM平台上,此文以作备忘。
IMX8QM 平台NXP厂家提供的 Android8 中包含着摄像头HAL驱动和通用Camera接口内容,需要把 NXP 的Camera HAL 移除并添加虚拟摄像头的HAL驱动;
[email protected]库生成规制如下:
@hardware/interface/camera/provider/2.4/defualt/Android.bp
cc_library_shared {
name: "[email protected]",
defaults: ["hidl_defaults"],
proprietary: true,
relative_install_path: "hw",
srcs: ["CameraProvider.cpp"],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"libcutils",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"liblog",
"libhardware",
"libcamera_metadata"
],
static_libs: [
"[email protected]"
]
}
cc_binary {
name: "[email protected]",
defaults: ["hidl_defaults"],
proprietary: true,
relative_install_path: "hw",
srcs: ["service.cpp"],
compile_multilib: "32",
init_rc: ["[email protected]"],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libbinder",
"liblog",
"libutils",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
],
}
在《虚拟摄像头之四: 谁在调用 v4l2_camera_HAL 摄像头驱动》文章中清晰梳理 CameraProvider 作为物理相机管理服务开机启动,CameraProviderManager 通过 'legacy/0’服务名称,
获取该服务、并用 Binder 与之通讯;我们接下来的任务是把 Camera.imx8.so 库替换为 v4l2llpback.so 库,把虚拟摄像HAL驱动添加到 android系统中。
修改 imx8q的BoardConfigCommon.mk 内容如下:
@device/fsl/imx8q/BoardConfigCommon.mk
# 关闭 NXP 原厂的 CameraHAL 的驱动
#BOARD_HAVE_IMX_CAMERA := true
# 开启 CAMERA_V4L2_HAL 的驱动,虚拟相机驱动
USE_CAMERA_V4L2_HAL := true
BOARD_HAVE_USB_CAMERA := false
修改第二部分内容。
@device/fsl/imx8q/ProductConfigCommon.mk
# camera
ifneq ($(PRODUCT_IMX_CAR),true)
PRODUCT_PACKAGES += \
android.hardware.camera.provider@2.4-impl \
android.hardware.camera.provider@2.4-service \
camera.device@1.0-impl \
camera.device@3.2-impl \
camera.imx8
#camera.v4l2
endif
@hardware/libhardware/modules/camera/3_4/Android.mk
# V4L2 Camera HAL.
# ==============================================================================
include $(CLEAR_VARS)
LOCAL_MODULE := camera.imx8
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_VENDOR_MODULE := true
@device/fsl/imx8q/mek_8q/early.init.cfg
insmod vendor/lib/modules/wlan.ko
# 增加开机加载项
insmod vendor/lib/modules/v4l2loopback.ko
系统在启动调用的 hw_get_module(CAMERA_HARDWARE_MODULE_ID, (const hw_module_t **)&rawModule) 就是 @hardware/libhardware/modules/camera/3.4/v4l2_camera_hal.cpp 的驱动内容。
# dmesg
[ 46.420434] /home/robot/android_build/vendor/nxp-opensource/kernel_imx/drivers/virtual_camera/v4l2loopback.c:2000[v4l2_loopback_open]
[ 46.433328] v4l2-loopback[2026]: opened dev:ffff8008f03d8000 with image: (null)
[ 46.441759] /home/robot/android_build/vendor/nxp-opensource/kernel_imx/drivers/virtual_camera/v4l2loopback.c:2027[v4l2_loopback_open]
[ 46.455045] v4l2-loopback[774]: cap->capabilities=0x85008003
[ 46.462542] v4l2-loopback[777]: cap->capabilities=0x85008003
[ 46.468765] v4l2-loopback[796]: cap->capabilities=0x85008003
[ 46.474685] video4: VIDIOC_QUERYCAP: driver=v4l2 loopback, card=v4l2loopback video device (0x00, bus=platform:v4l2loopback-000, version=0x00040e62, capabilities=0x85208003, device_caps=0x05208003
[ 46.614770] /home/robot/android_build/vendor/nxp-opensource/kernel_imx/drivers/virtual_camera/v4l2loopback.c:2036[v4l2_loopback_close]
[ 46.626969] /home/robot/android_build/vendor/nxp-opensource/kernel_imx/drivers/virtual_camera/v4l2loopback.c:2153[try_free_buffers]
[ 46.639130] /home/robot/android_build/vendor/nxp-opensource/kernel_imx/drivers/virtual_camera/v4l2loopback.c:2136[free_buffers]
[ 46.650688] v4l2-loopback[2137]: freeing image@ (null) for dev:ffff8008f03d8000
[ 46.658828] /home/robot/android_build/vendor/nxp-opensource/kernel_imx/drivers/virtual_camera/v4l2loopback.c:2058[v4l2_loopback_close]
@hardware/libhardware/hardware.c
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
int i = 0;
char prop[PATH_MAX] = {0};
char path[PATH_MAX] = {0};
char name[PATH_MAX] = {0};
char prop_name[PATH_MAX] = {0};
if (inst)
snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
else
strlcpy(name, class_id, PATH_MAX);
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* First try a property specific to the class and possibly instance */
snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
if (property_get(prop_name, prop, NULL) > 0) {
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
/* Nothing found, try the default */
if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
goto found;
}
return -ENOENT;
found:
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
return load(class_id, path, module);
}
ubuntu 上装载驱动
下载 v4l2loopback 源码编译安装
make clean
make
sudo make install
sudo depmod -a
sudo insmod ./v4l2loopback.ko devices=2
使用 ffmpeg 和 ffplay 验证驱动。
需要先制作YUV420格式的视频数据,然后通过 yuv420_infiniteloop 工具把数据写入虚拟摄像头中;
ffmpeg -f x11grab -s 640*480 -framerate 30 -i :0.0+10,00 out640_480.mp4
ffmpeg -f x11grab -s 640*480 -framerate 25 -i :0.0+0,0 -vcodec rawvideo -pix_fmt yuv420p out64_480_420p.yuv
停止录制,按q键即可,或者Ctrl + C ;
ffmpeg -i out640_480.mp4 -metadata:s:v rotate="90" -codec copy rotate640_480.mp4
ffmpeg -i out640_480.mp4 -vcodec rawvideo -an -pix_fmt yuv420P -s 640*480 -y out640_480_420.yuv
ffmpeg -i out640_480.mp4 -vcodec rawvideo -an -pix_fmt nv12 -s 640*480 -y out64_48_nv12.yuv
ffplay -f rawvideo -video_size 640x480 -pix_fmt yuv420p out640_480.yuv
FFMPEG -i out640_480.mp4 out640_480.mpeg
FFMPEG -i out640_480.mp4 out640_480.mjpeg
FFMPEG -i out640_480.mp4 out640_480.h264
ffmpeg -i out480_640.mp4 -vcodec rawvideo -an -pix_fmt yu12 -s 480*640 -y out480_640_yu12.yuv
``cpp
surface size
yuv420_infiniteloop /dev/video4 /sdcard/Movies/out640_480.yuv 1920 1080 30
关于 ffmpeg 视频转换方法参考下面链接:
https://blog.csdn.net/kl1411/article/details/121701003
# 虚拟摄像头预览
笔者采用 v4l2loopback.ko 驱动, 在测试中发现相机画面闪烁,通过调整喂视频流帧速率把30帧调整1帧,
可以清晰看到一帧正常显示、一帧是绿屏。通过logcat和 dmesg对比发现,Android frameworks 中申请流
的数量为3路,而v4l2_loopback.ko 库限制为2路流。 在函数 vidioc_reqbufs 中会判断申请 buffer 的
数量是否大于设备流最大数量,如下:
```cpp
static int vidioc_reqbufs(struct file *file, void *fh,
struct v4l2_requestbuffers *b)
{
struct v4l2_loopback_device *dev;
struct v4l2_loopback_opener *opener;
int i;
MARK();
dev = v4l2loopback_getdevice(file);
opener = fh_to_opener(fh);
dprintk("reqbufs: memory=%d, count=%d, number=%d\n", b->memory, b->count, dev->buffers_number);
if (opener->timeout_image_io) {
if (b->memory != V4L2_MEMORY_MMAP)
return -EINVAL;
b->count = 1;
return 0;
}
init_buffers(dev);
switch (b->memory) {
case V4L2_MEMORY_MMAP:
/* do nothing here, buffers are always allocated */
if (b->count < 1 || dev->buffers_number < 1)
return 0;
if (b->count > dev->buffers_number) //> 此处限制流的数量不能超出最大数量, 在 android frameworks中申请数量为 3 路,
b->count = dev->buffers_number; //> v4l2_loopback.ko 中最大值为 2 路流,导致摄像头显示缺失帧。
/* make sure that outbufs_list contains buffers from 0 to used_buffers-1
* actually, it will have been already populated via v4l2_loopback_init()
* at this point */
if (list_empty(&dev->outbufs_list)) {
for (i = 0; i < dev->used_buffers; ++i)
list_add_tail(&dev->buffers[i].list_head,
&dev->outbufs_list);
}
/* also, if dev->used_buffers is going to be decreased, we should remove
* out-of-range buffers from outbufs_list, and fix bufpos2index mapping */
if (b->count < dev->used_buffers) {
struct v4l2l_buffer *pos, *n;
list_for_each_entry_safe (pos, n, &dev->outbufs_list,
list_head) {
if (pos->buffer.index >= b->count)
list_del(&pos->list_head);
}
/* after we update dev->used_buffers, buffers in outbufs_list will
* correspond to dev->write_position + [0;b->count-1] range */
i = dev->write_position;
list_for_each_entry (pos, &dev->outbufs_list,
list_head) {
dev->bufpos2index[i % b->count] =
pos->buffer.index;
++i;
}
}
opener->buffers_number = b->count;
if (opener->buffers_number < dev->used_buffers)
dev->used_buffers = opener->buffers_number;
dprintk("reqbufs: dev->used_buffers=%d, count=%d\n", dev->used_buffers, b->count);
return 0;
default:
return -EINVAL;
}
}
哪如何调整 dev->buffers_number 参数缺省配置值呢,可在 v4l2_loopback.c 中检索 V4L2LOOPBACK_DEFAULT_MAX_BUFFERS 宏定义内容如下:
/* module parameters */
static int debug = 3; //> default = 0
module_param(debug, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "debugging level (higher values == more verbose)");
//#define V4L2LOOPBACK_DEFAULT_MAX_BUFFERS 2
#define V4L2LOOPBACK_DEFAULT_MAX_BUFFERS 4
static int max_buffers = V4L2LOOPBACK_DEFAULT_MAX_BUFFERS;
module_param(max_buffers, int, S_IRUGO);
MODULE_PARM_DESC(max_buffers,
"how many buffers should be allocated [DEFAULT: " STRINGIFY2(
V4L2LOOPBACK_DEFAULT_MAX_BUFFERS) "]");
可以看到笔者修改流的数量最大为 4 路流,此处问题解决,摄像头隔帧绿屏另有原因,是NXP的libg2d.ko 闭源GPU驱动问题,采用opencl库摄像头显示
就正常、没有绿屏现象;因此问题专用文章描述解决此bug的过程,所有简单描述之。
笔者使用的是 google 开源的 Camera2BasicFragement 工程,源码地址 https://github.com/googlearchive/android-Camera2Basic.git, 拍照回调函数中
有判断自动聚焦状态、由于虚拟摄像头暂时未实现聚焦功能、调整一下代码就能够实现拍照。
/**
* A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
*/
private CameraCaptureSession.CaptureCallback mCaptureCallback
= new CameraCaptureSession.CaptureCallback() {
private void process(CaptureResult result) {
switch (mState) {
case STATE_PREVIEW: {
// We have nothing to do when the camera preview is working normally.
break;
}
case STATE_WAITING_LOCK: {
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (afState == null) {
captureStillPicture();
} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
mState = STATE_PICTURE_TAKEN;
captureStillPicture();
} else {
runPrecaptureSequence();
}
} else { //> don't use autofocus,增加创建拍照 request
captureStillPicture();
}
break;
}
case STATE_WAITING_PRECAPTURE: {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
mState = STATE_WAITING_NON_PRECAPTURE;
}
break;
}
case STATE_WAITING_NON_PRECAPTURE: {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
mState = STATE_PICTURE_TAKEN;
captureStillPicture();
}
break;
}
}
}
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
process(partialResult);
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
process(result);
}
};
笔者通过ffmpeg制作如下格式的视频数据流文件,从数据大小和图像质量上对比;
robot@ubuntu:~/camera_test$ ls -l
-rw-rw-r-- 1 robot robot 2311538 Aug 29 12:13 out640_480.h264
-rw-rw-r-- 1 robot robot 24084256 Aug 29 13:17 out640_480.mjpeg
-rw-rw-r-- 1 robot robot 2520783 Aug 25 09:37 out640_480.mp4
-rw-rw-r-- 1 robot robot 2990080 Aug 29 12:08 out640_480.mpeg
-rw-rw-r-- 1 robot robot 894566400 Aug 29 10:38 out640_480.yuv
从图像质量和大小权衡的话、h264格式是非常理想的选择。
linux 内核 v4l2 驱动详解
https://www.codenong.com/cs105578727/