采用v4l2loopback来实现 虚拟Camera
“Android Camera一站式 学习平台”:https://www.camerahub.cn
Android上面实现虚拟摄像头的思路有很多,本文提供的一个思路是,采用v4l2loopback 来实现。
主要思路是:生成一个video节点,往这个video节点里面写入自定义的内容,应用端则是访问该video节点,进行内容的获取以及展示。
一、v4l2loopback源码下载、编译
1、官网下载源码
https://github.com/gjasny/v4l-utils
2、v4l2loopback源码放置在路径 kernel-4.19/drivers/virtual_camera
***/kernel-4.19/drivers/virtual_camera$ ls -ls 总计 252 84 -rwxr--r-- 1 123456 123456 85987 5月 24 17:35 v4l2loopback.c 12 -rwxr--r-- 1 123456 123456 8899 5月 24 17:35 v4l2loopback_formats.h 4 -rwxr--r-- 1 123456 123456 3053 5月 24 17:35 v4l2loopback.h 4 drwxr-xr-x 2 123456 123456 4096 5月 24 17:35 vagrant3、kernel-4.19/drivers/virtual_camera/Makefile修改:
在原Makefile的基础上,主要添加交叉编译链工具路径
ifneq ($(wildcard .gitversion),) # building a snapshot version V4L2LOOPBACK_SNAPSHOT_VERSION=$(patsubst v%,%,$(shell git describe --always --dirty 2>/dev/null || shell git describe --always 2>/dev/null || echo snapshot)) override KCPPFLAGS += -DSNAPSHOT_VERSION='"$(V4L2LOOPBACK_SNAPSHOT_VERSION)"' endif include Kbuild ifeq ($(KBUILD_MODULES),) #KERNELRELEASE ?= `uname -r` #KERNEL_DIR ?= /lib/modules/$(KERNELRELEASE)/build #+Add start,添加kernel路径,和交叉编译工具路径 ARCH=arm64 KERNEL_DIR=***/kernel-4.19 CROSS_COMPILE=***/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android- #-Add end,添加kernel路径,和交叉编译工具路径4、kernel-4.19/drivers/Makefile文件修改:
添加编译virtual_camera模块内容。
obj-y += virtual_camera/5、make编译生成ko文件
make bootimage 进行编译, out目录下可以看到下面相关文件生成。
***/out/target/product/***_64/obj/KERNEL_OBJ/drivers/virtual_camera$ ls built-in.a modules.builtin modules.order v4l2loopback.ko v4l2loopback.mod.c v4l2loopback.mod.o v4l2loopback.o6、验证ko文件是否生效
adb push v4l2loopback.ko 到vendor/lib64下,
adb push v4l2loopback.ko /vendor/lib64 adb shell chmod 0777 /vendor/lib64/v4l2loopback.ko /vendor/lib64 # insmod v4l2loopback.ko/dev下查看,可以看到多了一个video4的节点。
****:/dev # ls -ls |grep video 0 crw-rw---- 1 media system 81, 0 2024-08-01 11:00 video0 0 crw-rw---- 1 media system 81, 1 2024-08-01 11:00 video1 0 crw-rw---- 1 camera system 81, 2 2024-08-01 11:00 video2 0 crw-rw---- 1 camera system 81, 3 2024-08-01 11:00 video3 0 crwxrwxrwx 1 media system 81, 4 2024-08-01 11:04 video4/sys/class/video4linux 下ls查看,可以看到video4是一个虚拟摄像头。
**** :/sys/class/video4linux # ls -ls total 0 0 lrwxrwxrwx 1 root root 0 2024-08-01 11:15 video0 -> ../../devices/platform/1b100000.dvp/video4linux/video0 0 lrwxrwxrwx 1 root root 0 2024-08-01 11:15 video1 -> ../../devices/platform/1602f000.vdec/video4linux/video1 0 lrwxrwxrwx 1 root root 0 2024-08-01 11:15 video2 -> ../../devices/platform/17020000.venc/video4linux/video2 0 lrwxrwxrwx 1 root root 0 2024-08-01 11:15 video3 -> ../../devices/platform/17030000.jpgenc/video4linux/video3 0 lrwxrwxrwx 1 root root 0 2024-08-01 11:15 video4 -> ../../devices/virtual/video4linux/video4二、v4l2-ctl 源码下载编译
1、源码下载、编译
原始路径:
https://github.com/compilelife/v4l-utils
(针对Android版本修改过的,我用的是这个)
https://github.com/youhandcn/v4l2-ctl-android
v4l2-ctl 的代码放到Android源码路径下,然后进行mm编译。
编译成功后,会在out目录下生成v4l2-ctl文件。将v4l2-ctl文件 adb push到system/bin目录下。则在Android设备上就能使用v4l2-ctl命令。
***/v4l2-ctl-android-master/v4l2-ctl-android-master$ ls -ls 总计 16 4 -rwxr--r-- 1 123456 123456 368 8月 2 00:59 Android.bp 4 drwxr-xr-x 3 123456 123456 4096 8月 2 00:58 include 4 -rwxr--r-- 1 123456 123456 107 1月 26 2021 README.md 4 drwxr-xr-x 4 123456 123456 4096 8月 2 00:58 v4l2***/v4l2-ctl-android-master/v4l2-ctl-android-master$ mm -j8三、yuv420_infiniteloop 测试文件编译和使用
下面命令的意思是,往/dev/video0节点中写入 /data/akiyo_qcif_176_144.yuv 路径下的yuv数据,分辨率是176* 144,30fps。
yuv420_infiniteloop /dev/video4 /data/akiyo_qcif_176_144.yuv 176 144 30
XXX/vendor/examples$ ls -ls 总计 56 4 -rwxr--r-- 1 z00001 z00001 173 8月 2 19:28 Android.mk 4 -rwxr--r-- 1 z00001 z00001 199 8月 2 19:28 Makefile.backup 20 -rwxr--r-- 1 z00001 z00001 17248 8月 2 19:28 ondemandcam 4 -rwxr--r-- 1 z00001 z00001 3022 8月 2 19:28 ondemandcam.c 4 -rwxr--r-- 1 z00001 z00001 2169 8月 2 19:28 README 4 -rwxr--r-- 1 z00001 z00001 971 8月 2 19:28 restarting-writer.sh 8 -rwxr--r-- 1 z00001 z00001 4862 8月 2 19:28 test.c 4 -rwxr--r-- 1 z00001 z00001 2592 8月 2 22:52 yuv420_infiniteloop.c 4 -rwxr--r-- 1 z00001 z00001 3057 8月 2 19:28 yuv4mpeg_to_v4l2.c编写Android.mk文件,编译 yuv420_infiniteloop 。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := yuv420_infiniteloop LOCAL_SRC_FILES := yuv420_infiniteloop.c include $(BUILD_EXECUTABLE)四、其它
1、Android环境下,insmode 安装模块命令
insmode v4l2loopback.ko video_nr=3,4,7 card_label="device number 3","the number four","the last one" max_width=1920 max_height=10802、v4l2-ctl命令的使用
将物理摄像头的视频流输出到虚拟摄像头设备
v4l2-ctl --stream-mmap --stream-count=1 --stream-to=/dev/video6 --stream-from=/dev/video0v4l2-ctl --list-formats-ext # 列出设备支持的所有格式v4l2-ctl --set-fmt-video=width=1920,height=1080,pixelformat=H264 # 设置视频格式v4l2-ctl --stream-mmap --stream-count=100 --stream-to=output.raw # 捕获视频流
3、ffmpeg命令的使用
ffmpeg -f video4linux2 -s 640x480 -r 30 -i /dev/video0 -vcodec copy -f v4l2 /dev/video10 -vcodec copy -f v4l2 /dev/video11
#启动ffmpeg开始向/dev/video0写入数据
ffmpeg -f x11grab -r 15 -s 1280x720 -i :0.0+0,0 -vcodec rawvideo -pix_fmt yuv420p -threads 0 -f v4l2 /dev/video0
4、
yuv测试资源:
http://trace.eas.asu.edu/yuv/
=================================
将YUV数据写到video节点
可以参考yuv420_infiniteloop.c 的实现来。下面是代码示例:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/videodev2.h> int mDevFd; bool mHasSaveYuv = false; int frameCount = 0; int openVideoDevice(int frameWidth,int frameHeight,int frameBytes) { if (mDevFd > 0) { return mDevFd; } struct v4l2_format v; int dev_fd = open("/dev/video4", O_RDWR, 0); if (dev_fd < 0) { ALOGE("Error opening device"); return -1; } v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (ioctl(dev_fd, VIDIOC_G_FMT, &v) == -1) { ALOGE("cannot setup video device"); } v.fmt.pix.width = frameWidth; v.fmt.pix.height = frameHeight; v.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; v.fmt.pix.sizeimage = frameBytes; v.fmt.pix.field = V4L2_FIELD_NONE; if (ioctl(dev_fd, VIDIOC_S_FMT, &v) == -1) { ALOGE("cannot setup video device"); } return dev_fd; } static int save_yuv(char* data,int width,int height) { int ret = 0; FILE *fp = fopen("data/test.yuv", "wb"); if(fp != NULL) { fwrite(data,1,width * height * 3/2,fp); fclose(fp); MY_LOGE("save_yuv success!!!,size:%d * %d",width,height); } ret = 1; return ret; } void writeFrameToVideoNode(IImageBuffer *buffer) { char *ptr; int plane, stride, bufsize,width,height,frmaeSize; if( buffer ) { stride = buffer->getBufStridesInBytes(0); bufsize = buffer->getBufSizeInBytes(0); ptr = (char*)buffer->getBufVA(0); width = buffer->getImgSize().w; height = buffer->getImgSize().h; frmaeSize = stride*height*3/2; mDevFd = openVideoDevice(stride,height,bufsize); if (ptr && mDevFd > 0) { if (!mHasSaveYuv && frameCount == 100) { save_yuv(ptr,width,height); mHasSaveYuv = true; } frameCount++; int ret = write(mDevFd, ptr, frmaeSize); MY_LOGD("writeFrameToVideoNode plane:%d,stride:%d,bufsize:%d,mDevFd:%d,width:%d,height:%d,ret:%d",plane,stride,bufsize,mDevFd,width,height,ret); } } }============ 三方App适配情况
1、美颜相机(com.meitu.meiyancamera)
有个异常,请求的640 * 480 分辨率没有。
Requested preview size 640 x 480 is not supported
------ 测试环境采用的yuv数据是176 * 144的。如果采用640 * 480的资源,应该也是没有问题。因为app已经正常执行了open动作。
08-30 10:54:53.204 I/CameraService( 778): CameraService::connect call (PID -1 "com.meitu.meiyancamera", camera ID 104) and Camera API version 1 08-30 10:54:53.205 I/Camera2ClientBase( 778): Camera 104: Opened. Client: com.meitu.meiyancamera (PID 16365, UID 10099) 08-30 10:54:53.207 D/ExtCamDev@3.4(10061): =========mCameraId.c_str():104 ANDROID_LENS_FACING_EXTERNAL:2======== 08-30 10:54:53.209 I/Camera2-Parameters( 778): initialize: allowZslMode: 0 slowJpegMode 0 08-30 10:54:53.214 E/Camera2-Parameters( 778): set: Requested preview size 640 x 480 is not supported2、一甜相机
-----正常
3、抖音
----- 扫一扫正常,直播待测
下面是所有相关调试代码和相关资料,仅针对 “星球成员” 免费提供,需要的同学可以咨询。
《小驰行动派的知识星球》
————————————————
推荐阅读:
关于博主
《小驰Camera私房菜》小册目录
Camera基础及一些基本概念
Android Camera 学习路线 | 个人推荐
Android Camera开发系列(干货满满)
Camera Hal|如何学习一个新平台
一篇文章带你了解Android 最新Camera框架
学习完Camera入门课程视频,可以去找工作了?
