采用v4l2loopback来实现 虚拟Camera

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 vagrant
3、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.o
6、验证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=1080
2、v4l2-ctl命令的使用

将物理摄像头的视频流输出到虚拟摄像头设备

v4l2-ctl --stream-mmap --stream-count=1 --stream-to=/dev/video6 --stream-from=/dev/video0

v4l2-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 supported

2、一甜相机

     -----正常

3、抖音

    ----- 扫一扫正常,直播待测

下面是所有相关调试代码和相关资料,仅针对 “星球成员” 免费提供,需要的同学可以咨询。

04a8acd24016014316f2ca63ff382967.png

《小驰行动派的知识星球》

————————————————

b14206509c22c1bce9df0754dca752cd.jpeg


推荐阅读:

关于博主 

《小驰Camera私房菜》小册目录

Camera基础及一些基本概念

Android Camera 学习路线 | 个人推荐

Android Camera开发系列(干货满满)

Camera Hal|如何学习一个新平台

一篇文章带你了解Android 最新Camera框架

学习完Camera入门课程视频,可以去找工作了?


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小驰行动派

谢谢老板,今晚吃鸡~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值