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、抖音
----- 扫一扫正常,直播待测
下面是所有相关调试代码和相关资料,仅针对 “星球成员” 免费提供,需要的同学可以咨询。
《小驰行动派的知识星球》
————————————————
推荐阅读: