linux下调用摄像头拍照-v4l2与ffmpeg

我的实际操作记录

v4l2获取图像部分

找到了这位大佬的博客[6],是本文学习的基础。具体操作总结如下。

1
2
3
4
5
6
v4l2-ctl --list-devices #显示当前连接的摄像头设备(必须)
v4l2-ctl -d /dev/video0 #连接到一个设备(必须)
v4l2-ctl -D #输出设备的驱动信息
v4l2-ctl -L #输出当前设备支持的控制选项
v4l2-ctl --list-formats-ext #注意⚠️返回值写着摄像头支持的所有输出模式,如YUYV(必须)
v4l2-ctl --verbose --set-fmt-video=width=640,height=480,pixelformat=YUYV --stream-mmap --stream-count=1 --stream-to=test.yuv #在当前位置生成图片,注意pixelformat报错时回到5️⃣(必须)

但我在第6️⃣步时没能成功输出能看的图片,于是下载了obs试试可行性,确认可行。obs作为开源软件可以方便的查看它的原代码。下附部分obs返回的调试消息[5]

  1. 通过在开源代码里寻找,找到了疑似实现功能的代码片段[1]。但是我看不懂😵‍💫,于是搜寻其他信息
  2. 我虽然知道YUV和RGB不同,却不知道还有.yuv这种格式,6️⃣步所得的结果并不能以.jpg方式打开/.yuv格式打开结果不正常,因此需要进行转换。我使用网上找到的命令(下代码块第一行),打开能看到大概外形但是像素位置错乱。估计因为没有选择正确的”-pix_fmt”
  3. 通过指定原-pix_fmt获得了正确结果(下代码块第二行)[2]
    1
    2
    ffmpeg -s 640x480 -i test.yuv aa.jpeg  #(错误代码)
    ffmpeg -y -s:v 640x480 -pix_fmt yuyv422 -i test.yuv result.jpg #(正确代码)

还有一段命令也是我找到的,但我没试过可行性同时也忘了记录来源:

ffmpeg -s 640x480 -i bbb.yuv -ss 00:00:00 -c:v libx264 -s:v 640x480 -preset slow -t 00:08:20 output.mp4

创建内存盘部分

信息挖掘

随后上网寻找创建内存盘的方法,参考了如下几个网站的内容

  1. https://zhuanlan.zhihu.com/p/67170890
  2. https://www.linuxdashen.com/%E4%B8%BA%E4%BD%A0%E7%9A%84linux%E7%B3%BB%E7%BB%9F%E5%88%9B%E5%BB%BAram-disk
  3. https://cloud.tencent.com/developer/article/1767470

实际执行[3][4]

创建内存盘

1
2
mkdir /dev/shm/user.camcap_cache #创建内存盘
ln -s /dev/shm/user.camcap_cache ./cache #在当前目录下创建软链接方便使用

解决问题

然后我写了个shell脚本,越写越上头,不负责任的说这是我写过最美的代码。
需要安装v4l2和ffmpeg,其他什么都不要,奏似这么轻量。
扩展阅读

https://chtseng.wordpress.com/2022/07/18/使用v4l2-ctl調整-usb-camera參數/
http://zhaoxuhui.top/blog/2021/09/23/v4l2-introduction-and-usb-camera-bayer-raw-data.html
https://silencewt.github.io/2015/04/29/v4l2的学习建议和流程解析/

脚本及执行方法

例:复制到capcam.sh中

  1. chmod +x capcam.sh
  2. 执行./capcam.sh
  3. 结束
  4. 另外,./capcam.sh clean清理目录,./capcam.sh mod手动定义捕获参数
1
2
3
4
5
6
import os
import time#移植时无需移植
while 1:
a = os.popen('./capcam.sh')
time.sleep(1)#移植时无需移植
#print(a.read())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/bin/sh
#补充 文件使用:./[文件名].sh clean,如./v4l2test.sh clean 清理文件;./v4l2test.sh mod 修改volatile变量
ROM_CAPTURE_PATH="./rom_camcache" #数据暂存路径,SELECT_CACHE_LOC值选择实际数据存放的位置
SELECT_CACHE_LOC=0 #可选值0 or 1,0:选择内存盘;1:选择ROM_CAPTURE_PATH指定地址
RAM_DIR_NAME="user.camcap_cache" #RAM临时文件文件名
IS_HARDCODE=1 #值为1时使用程序中硬编码的摄像头捕获参数
### Volatile parameter
DEVICE_LOC=2
CAP_WIDTH=2560
CAP_HEIGHT=720
CAP_PIXFMT=MJPG
CAP_SUFFIX=jpeg #jpeg/raw depends on CAP_PIXFMT is MJPEG/YUYV etc.此处需要修改!!我后来发现如果是仅YUYV的摄像头而这里还是jpeg就会报错没有找到raw文件
###
PIX_FMT_FFMPEG="yuyv422" #ffmpeg需要原像素格式的信息,我的设备只有这一种,所以我的代码就不对其他格式适配了。
#摄像头初始化处也需要制定像素格式,但是参数拼写不一样,需要进一步完善代码可以从这里做起。
start=`date +%s.%N`
echo "\nROM_CAPTURE_PATH=${ROM_CAPTURE_PATH}\t#数据暂存路径,SELECT_CACHE_LOC值选择实际数据存放的位置\nSELECT_CACHE_LOC=${SELECT_CACHE_LOC}\t#可选值0 or 1,0:选择内存盘;1:选择ROM_CAPTURE_PATH指定地址\nRAM_DIR_NAME=${RAM_DIR_NAME}\t\t#RAM临时文件文件名\nIS_HARDCODE=${IS_HARDCODE}\t\t#值为1时使用程序中硬编码的摄像头捕获参数\n###Volatile parameter###\nDEVICE_LOC=${DEVICE_LOC}\nCAP_WIDTH=${CAP_WIDTH}\nCAP_HEIGHT=${CAP_HEIGHT}\nCAP_PIXFMT=${CAP_PIXFMT}\n###\nPIX_FMT_FFMPEG=${PIX_FMT_FFMPEG}"
if [ -n "$1" ]
then
if [ $1 = "clean" ]
then
echo "Cleaning the dir in RAM:"
rm -ir /dev/shm/${RAM_DIR_NAME}
echo "Cleaning the link to the dir in RAM:"
rm ${RAM_DIR_NAME}_link
echo "Cleaning the dir in ROM:"
rm -ir ${ROM_CAPTURE_PATH}
echo "Process Done."
return
fi
if [ $1 = "mod" ]
then
IS_HARDCODE=0
fi
fi

if [ ${SELECT_CACHE_LOC} -eq 0 ] #选择内存盘
then
echo "creating RAM disk"
if [ ! -d /dev/shm/${RAM_DIR_NAME} ]
then
mkdir /dev/shm/${RAM_DIR_NAME} #创建内存盘,需要确认df -h显示/dev/shm挂载方式为tmpfs
fi
if [ ! -d ./${RAM_DIR_NAME}_link ]
then
ln -s /dev/shm/${RAM_DIR_NAME} ./${RAM_DIR_NAME}_link #在当前目录下创建软链接方便使用
fi
else #选择ROM_CAPTURE_PATH指定地址
if [ ! -d ./${ROM_CAPTURE_PATH} ]
then
mkdir ${ROM_CAPTURE_PATH}
fi
fi
v4l2-ctl --list-devices #显示当前连接的摄像头设备
if [ ${IS_HARDCODE} -eq 1 ]
then
echo "using the hardcode now"
echo "Choose:/dev/video${DEVICE_LOC}"
echo "resolution:${CAP_WIDTH}x${CAP_HEIGHT} in ${CAP_PIXFMT}"
else
if [ -n "$2" ]
then
DEVICE_LOC=$2
else
echo "选择设备(格式:若选择/dev/video0,则输入0。没有检查输入所以自己注意):"
read DEVICE_LOC
fi
echo "Choose:/dev/video${DEVICE_LOC}"
echo "在如下信息中选择摄像头参数"
v4l2-ctl -d${DEVICE_LOC} --list-formats-ext #注意⚠️返回值写着摄像头支持的所有输出模式,如YUYV
if [ -n "$3" ]
then
CAP_WIDTH=$3
else
echo "请输入捕捉宽度"
read CAP_WIDTH
fi
if [ -n "$4" ]
then
CAP_HEIGHT=$4
else
echo "请输入捕捉高度"
read CAP_HEIGHT
fi
if [ -n "$5" ]
then
CAP_PIXFMT=$5
else
echo "请输入像素格式"
read CAP_PIXFMT
fi
fi
if [ ${CAP_PIXFMT} = "YUYV" ] #捕获格式不为YUYV时不进行转码
then
CAP_SUFFIX=raw #为之前出现的bug作修补即前面volatile的警告
fi
if [ ${SELECT_CACHE_LOC} -eq 0 ] #选择内存盘
then
v4l2-ctl --verbose \
-d${DEVICE_LOC} \
--set-fmt-video=width=${CAP_WIDTH},height=${CAP_HEIGHT},pixelformat=${CAP_PIXFMT} \
--stream-mmap \
--stream-count=1 \
--stream-to=./${RAM_DIR_NAME}_link/output.${CAP_SUFFIX}
if [ ${CAP_PIXFMT} = "YUYV" ] #捕获格式不为YUYV时不进行转码
then
ffmpeg -y -s:v ${CAP_WIDTH}x${CAP_HEIGHT} -pix_fmt ${PIX_FMT_FFMPEG} -i ./${RAM_DIR_NAME}_link/output.raw ./${RAM_DIR_NAME}_link/output.jpeg
fi
else #选择ROM_CAPTURE_PATH指定地址
v4l2-ctl --verbose \
-d${DEVICE_LOC} \
--set-fmt-video=width=${CAP_WIDTH},height=${CAP_HEIGHT},pixelformat=${CAP_PIXFMT} \
--stream-mmap \
--stream-count=1 \
--stream-to=${ROM_CAPTURE_PATH}/output.${CAP_SUFFIX}
if [ ${CAP_PIXFMT} = "YUYV" ] #捕获格式不为YUYV时不进行转码
then
ffmpeg -y -s:v ${CAP_WIDTH}x${CAP_HEIGHT} -pix_fmt ${PIX_FMT_FFMPEG} -i ${ROM_CAPTURE_PATH}/output.raw ${ROM_CAPTURE_PATH}/output.jpeg
fi
fi
end=`date +%s.%N`
getTiming(){ #https://blog.csdn.net/bowenlaw/article/details/103789158
start=$1
end=$2
start_s=$(echo $start | cut -d '.' -f 1)
start_ns=$(echo $start | cut -d '.' -f 2)
end_s=$(echo $end | cut -d '.' -f 1)
end_ns=$(echo $end | cut -d '.' -f 2)
time=`expr $end_s \* 1000 - $start_s \* 1000 + $end_ns / 1000000 - $start_ns / 1000000`
echo "$time ms"
}
result=$(getTiming $start $end)
echo $result

附录

  1. https://github.com/obsproject/obs-studio/blob/c5015d0e6cb180d138e1a9e2258afd903254b9ea/plugins/linux-v4l2/v4l2-input.c
  2. https://stackoverflow.com/questions/70961566/how-to-convert-raw-yuv-image-to-jpg
  3. https://unix.stackexchange.com/questions/26364/how-can-i-create-a-tmpfs-as-a-regular-non-root-user
  4. https://superuser.com/questions/389500/how-can-i-mount-a-tmpfs-without-root
  5. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    info: v4l2-input: /dev/video1 seems to not support video capture
    info: v4l2-input: Found device 'USB Camera: USB Camera' at /dev/video0
    info: v4l2-input: Start capture from /dev/video2
    error: v4l2-input: Unable to open device
    error: v4l2-input: Initialization failed, errno: 没有那个文件或目录
    info: v4l2-input: Start capture from /dev/video0
    info: v4l2-input: Input: 0
    info: v4l2-input: Found input 'Camera 1' (Index 0)
    info: v4l2-controls: setting default for Power Line Frequency to 2
    info: v4l2-input: Pixelformat: YUYV 4:2:2 (available)
    info: v4l2-input: Pixelformat: RGB3 (Emulated) (unavailable)
    info: v4l2-input: Pixelformat: BGR3 (Emulated) (available)
    info: v4l2-input: Pixelformat: YU12 (Emulated) (available)
    info: v4l2-input: Pixelformat: YV12 (Emulated) (available)
    info: v4l2-input: Stepwise and Continuous framesizes are currently hardcoded
    info: v4l2-input: Resolution: 640x480
    info: v4l2-input: Pixelformat: YUYV
    info: v4l2-input: Linesize: 1280 Bytes
    info: v4l2-input: Framerate: 30.00 fps
    info: v4l2-input: /dev/video0: select timeout set to 166666 (5x frame periods)
  6. http://zhaoxuhui.top/blog/2021/09/23/v4l2-introduction-and-usb-camera-bayer-raw-data.html

linux下调用摄像头拍照-v4l2与ffmpeg
https://zhaosn.github.io/2022/v4l2-theLinuxVideoSolution/
作者
Zhao SN
发布于
2022年10月27日
更新于
2023年3月24日
许可协议