“Android Camera一站式 学习平台”https://www.camerahub.cnAndroid上面实现虚拟摄像头的思路有很多本文提供的一个思路是采用v4l2loopback 来实现。主要思路是生成一个video节点往这个video节点里面写入自定义的内容应用端则是访问该video节点进行内容的获取以及展示。一、v4l2loopback源码下载、编译1、官网下载源码https://github.com/gjasny/v4l-utils2、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路径和交叉编译工具路径 ARCHarm64 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-androidv4l2-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* 14430fps。yuv420_infiniteloop /dev/video4 /data/akiyo_qcif_176_144.yuv 176 144 30XXX/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_nr3,4,7 card_labeldevice number 3,the number four,the last one max_width1920 max_height10802、v4l2-ctl命令的使用将物理摄像头的视频流输出到虚拟摄像头设备v4l2-ctl --stream-mmap --stream-count1 --stream-to/dev/video6 --stream-from/dev/video0v4l2-ctl --list-formats-ext # 列出设备支持的所有格式v4l2-ctl --set-fmt-videowidth1920,height1080,pixelformatH264 # 设置视频格式v4l2-ctl --stream-mmap --stream-count100 --stream-tooutput.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.00,0 -vcodec rawvideo -pix_fmt yuv420p -threads 0 -f v4l2 /dev/video04、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/ExtCamDev3.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入门课程视频可以去找工作了