一、前言
顺应时代的技术发展潮流,逐步学习并掌握音视频技术核心知识,让技术落地,让知识赋能生活,让科技造福千万灯火。
二、重识图片
要想学好音视频,首先得先好好研究一下图片。
1. 像素
下图的分辨率是60×50。
用Photoshop放大图片上百倍后,可以清晰地看到图片由若干个方形的色块组成,每一个方形的色块被称为:像素(Pixel)。这张图片的每一行都有60个像素,共50行,总共60*50=3000个像素。
总结一下:
- 每张图片都是由N个像素组成的(N≥1)
- 如果一张图片的分辨率是WxH,那么:
- 每一行都有W个像素,共H行,总共W*H个像素
- 宽度是W像素,高度是H像素
每个像素都有自己独立的颜色,若干个像素就组成了一张色彩缤纷的完整图片。
2. RGB颜色模型
1666年,伟大的科学家牛顿进行了著名的色散实验:用一块三棱镜分解太阳光。
实验发现:太阳光通过三棱镜折射后,会被折射分散成红、橙、黄、绿、蓝、靛、紫7种单色光。其中的红、绿、蓝被称为是色光三原色。
接下来,再看一个很重要的概念:RGB颜色模型(RGB color model),又称为三原色光模式。
- 将红(Red)、绿(Green)、蓝(Blue)三原色的色光以不同的含量相叠加,可以合成产生各种色彩光
每个像素的颜色,可以通过红色、绿色、蓝色以不同的含量混合而成。比如:
- 红色(Red)、绿色(Green)可以合成:黄色(Yellow)
- 红色(Red)、蓝色(Blue)可以合成:洋红色(Magenta)
- 绿色(Green)、蓝色(Blue)可以合成:青色(Cyan)
- 红色(Red)、绿色(Green)、蓝色(Blue)可以合成:白色(White)
3. 位深度
每一个像素的颜色信息是如何存储的呢?
-
取决于图片的位深度(Bit Depth),也称为:色彩深度(Color Depth,简称:色深)
-
如果一张图片的位深度为n,那么它的每一个像素都会使用n个二进制位来存储颜色信息
3.1 24bit位深度的含义
上图的位深度是24,它的具体含义是:
- 每一个像素都会使用24个二进制位来存储颜色信息
- 每一个像素的颜色都是由红(Red)、绿(Green)、蓝(Blue)3个颜色通道合成的
- 每个颜色通道都用8bit来表示其“含量”(值),取值范围是:
- 二进制:00000000~11111111
- 十进制:0~255
- 十六进制:00~FF
- 举例:01000000 11100000 11010000(共24bit)表示绿宝石色(Turquoise)
- 红色的值:二进制01000000,十进制64,十六进制40
- 绿色的值:二进制11100000,十进制224,十六进制E0
- 蓝色的值:二进制11010000,十进制208,十六进制D0
- 64的红色 + 224的绿色 + 208的蓝色 = 绿宝石色
3.2 24bit颜色的表示形式
我们常用2种形式来表示24bit颜色,比如刚才提到的绿宝石色
- 十进制:rgb(64, 224, 208)
- 十六进制:#40E0D0
常见的24bit颜色:
- 红色:rgb(255, 0, 0),#FF0000
- 绿色:rgb(0, 255, 0),#00FF00
- 蓝色:rgb(0, 0, 255),#0000FF
- 黄色:rgb(255, 255, 0),#FFFF00
- 洋红色:rgb(255, 0, 255),#FF00FF
- 青色:rgb(0, 255, 255),#00FFFF
- 白色:rgb(255, 255, 255),#FFFFFF
- 黑色:rgb(0, 0, 0),#000000
- 当红绿蓝全为0时,就是黑色
- 这个其实很容易理解:没有任何光,自然是一片漆黑
- 所以说:黑色是世界上最纯洁的颜色,因为它啥也没有,(づ。◕ᴗᴗ◕。)づ
- 相反,白色是世界上最不纯洁的颜色,因为它啥都有,而且都是满色(全是255)
- 更多颜色,可以参考颜色对照表,红绿蓝的比例不同,合成的颜色也就不同
3.3 颜色数量
如果位深度为n,那么每一个像素能显示2n种颜色。
-
所以,位深度为24时,每一个像素能显示224种颜色,也就是16777216种颜色(约1678万)
-
24bit颜色,也被称为是:真彩色(True Color),也就是常说的24位真彩
3.4 其他位深度
除了24bit,常见的位深度还有:
- 1bit:2种颜色,黑白两色
- 3bit:8种颜色,用于大部分早期的电脑显示器,红绿蓝各占1位
- 8bit:256种颜色,用于最早期的彩色Unix工作站,红色占3位、绿色占3位、蓝色占2位
- 16bit:红色占5位、蓝色占5位、绿色占6位
- 32bit:基于24位,增加8个位的透明通道
- 可以表示带有透明度的颜色
- 比如CSS中的rgba(255, 0, 0, 0.5)表示50%透明度的红色
3.5 不同位深度的对比
位深度越大,能表示的颜色数量就越多,图片也就越鲜艳,颜色过渡就会越平滑。下面的图片来源自Tech-ease。
-
24bit(能表示约1678万种颜色)
-
8bit(能表示256种颜色)
-
7bit(能表示128种颜色)
-
6bit(能表示64种颜色)
-
5bit(能表示32种颜色)
-
4bit(能表示16种颜色)
-
3bit(能表示8种颜色)
-
2bit(能表示4种颜色)
-
1bit(能表示2种颜色)
4. 格式
一说到图片,大家应该马上能想到拓展名为jpg、png、gif的图片文件。
每张图片都有自己的大小,你是否思考过:一张图片的大小是如何计算出来的?为什么dragon01.jpg的大小是288KB?
-
要想知道一张图片的大小是多少?首先得知道每个像素的大小是多少。
-
如果位深度是n,那么每个像素的大小就是n个二进制位
下图的分辨率是60×50,位深度是24,所以:
- 每个像素的大小是:24bit(3字节,1字节=8bit)
- 图片的理论大小是:(60*50)*(24/8)=9000B≈8.79KB
但实际上你会发现:在相同分辨率、相同位深度的前提下,把这张图片存成2种不同的格式(jpg、png),它们的大小是不同的,而且都小于理论上的8.79KB。
其实jpg、png都是经过压缩后的图片(具体的压缩算法和原理,就不在此讨论了,大家可以到网上自行搜索),所以它们的大小会比理论值偏小。
图片的压缩类型可以分为2种:
- 无损压缩
- 不损失图片质量
- 压缩比小,体积大
- 解压(显示)后能够还原出完整的原始图片数据,不会损失任何图片信息
- 有损压缩
- 会损失图片质量
- 压缩比大,体积小
- 解压(显示)后无法还原出完整的原始图片数据,会损失掉一些图片信息
- 压缩比 = 未压缩大小 / 压缩后大小
压缩类型 | 位深度 | |
---|---|---|
JPG(JPEG) | 有损压缩 | 24bit |
PNG | 无损压缩 | 8bit、24bit、32bit |
GIF | 无损压缩 | 8bit |
5. GIF
众所周知,gif是一种支持动画的图片,所以一般也叫作gif动态图,微信的动态表情包就是基于gif动态图。
gif动画的实现原理类似手翻书。
gif的动画原理是:
- gif内部存储了很多帧(张)静态图片
- 在短时间内,连续按顺序地呈现每一帧静态图片,就形成了动画的效果
像上面那张《悟空vs克林》的gif动态图,它内部存储了44帧静态图,只要按顺序从01.jpg播放到44.jpg,就能呈现出连贯的动画效果。
不管是gif动态图,还是手翻书,它们的动画原理其实都基于:视觉暂留(Persistence of vision)现象。
- 当人眼所看到的影像消失后,人眼仍能继续保留其影像约0.1~0.4秒左右,这种现象被称为视觉暂留现象
- 人眼观看物体时,成像于视网膜上,并由视神经输入人脑,感觉到物体的像,但当物体移去时,视神经对物体的印象不会立即消失,而要延续0.1~0.4秒的时间,人眼的这种性质被称为“眼睛的视觉暂留”
- 我们日常使用的日光灯每秒大约熄灭100余次,但我们基本感觉不到日光灯的闪动,这都是因为视觉暂留的作用
- 在一帧图片消失在大脑中之前呈现下一帧图片,反复如此,就可以形成连贯的动画效果
- 电影的帧率是24fps
- fps:每秒的帧数,Frames Per Second
三、详解YUV
本文的主角是多媒体领域非常重要的一个概念:YUV。
1. 简介
YUV,是一种颜色编码方法,跟RGB是同一个级别的概念,广泛应用于多媒体领域中。
也就是说,图像中每1个像素的颜色信息,除了可以用RGB的方式表示,也可以用YUV的方式表示。
2. YUV vs RGB
对比RGB,YUV有哪些不同和优势呢?
2.1 体积更小
- 如果使用RGB
- 比如RGB888(R、G、B每个分量都是8bit)
- 1个像素占用24bit(3字节)
- 如果使用YUV
- 1个像素可以减小至平均只占用12bit(1.5字节)
- 体积为RGB888的一半
2.2 组成
RGB数据由R、G、B三个分量组成。
YUV数据由Y、U、V三个分量组成,现在通常说的YUV指的是YCbCr。
- Y:表示亮度(Luminance、Luma),占8bit(1字节)
- Cb、Cr:表示色度(Chrominance、Chroma)
- Cb(U):蓝色色度分量,占8bit(1字节)
- Cr(V):红色色度分量,占8bit(1字节)
2.3 兼容性
根据上面的图片,不难看出:
- Y分量对呈现出清晰的图像有着很大的贡献
- Cb、Cr分量的内容不太容易识别清楚
此外,你是否感觉:Y分量的内容看着有点眼熟?其实以前黑白电视的画面就是长这样子的。
YUV的发明处在彩色电视与黑白电视的过渡时期。
- YUV将亮度信息(Y)与色度信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的
- 这样的设计很好地解决了彩色电视与黑白电视的兼容性问题,使黑白电视也能够接收彩色电视信号,只不过它只显示了Y分量
- 彩色电视有Y、U、V分量,如果去掉UV分量,剩下的Y分量和黑白电视相同
3. 转换
3.1 公式1
Y = 0.257R + 0.504G + 0.098B + 16
U = -0.148R - 0.291G + 0.439B + 128
V = 0.439R - 0.368G - 0.071B + 128
R = 1.164(Y - 16) + 2.018(U - 128)
G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
B = 1.164(Y - 16) + 1.596(V - 128)
- RGB的取值范围是[0,255]
- Y的取值范围是[16,235]
- UV的取值范围是[16,239]
3.2 公式2
Y = 0.299R + 0.587G + 0.114B
U = 0.564(B - Y) = -0.169R - 0.331G + 0.500B
V = 0.713(R - Y) = 0.500R - 0.419G - 0.081B
R = Y + 1.403V
G = Y - 0.344U - 0.714V
B = Y + 1.770U
- RGB的取值范围是[0, 1]
- Y的取值范围是[0, 1]
- UV的取值范围是[-0.5, 0.5]
3.3 公式3
Y = 0.299R + 0.587G + 0.114B
U = -0.169R - 0.331G + 0.500B + 128
V = 0.500R - 0.419G - 0.081B + 128
R = Y + 1.403(V - 128)
G = Y - 0.343(U - 128) - 0.714(V - 128)
B = Y + 1.770(U - 128)
- RGB的取值范围是[0, 255]
- YUV的取值范围是[0, 255]
4. 色度二次采样
4.1 原理
人眼的视网膜上,分布着两种感光细胞:视杆细胞和视锥细胞。
-
视杆细胞
- 感知光线的强弱
- 没有色彩识别功能
- 负责夜间非彩色视觉
-
视锥细胞
- 感知颜色
- 负责白天彩色视觉
- 如果你的视锥细胞发育不正常,数量太少,那感知颜色就会受阻,可能会导致你色弱
人眼中有上亿个感光细胞,其中视杆细胞占了95%,而视锥细胞仅占5%。
因此,人眼对亮度的敏感程度要高于对色度的敏感程度,人眼对于亮度的分辨要比对颜色的分辨精细一些。
如果把图像的色度分量减少一些,人眼也丝毫感觉不到变化和差异。
4.2 概念
如果在色度分量上进行(相对亮度分量)较低分辨率的采样,也就是存储较多的亮度细节、较少的色度细节,这样就可以在不明显降低画面质量的同时减小图像的体积。上述过程称为:色度二次采样(Chroma Subsampling)。
4.3 采样格式
采样格式通常用A:B:C的形式来表示,比如4:4:4、4:2:2、4:2:0等,其中我们最需要关注的是4:2:0。
- A:一块A*2个像素的概念区域,一般都是4
- B:第1行的色度采样数目
- C:第2行的色度采样数目
- C的值一般要么等于B,要么等于0
上图中,不管是哪种采样格式,Y分量都是全水平、全垂直分辨率采样的,每一个像素都有自己独立的Y分量。
4.3.1 4:4:4
- 第1行采集4组CbCr分量,第2行采集4组CbCr分量
- 每1个像素都有自己独立的1组CbCr分量
- Y分量与CbCr分量的水平方向比例是1:1(每1列都有1组CbCr分量)
- Y分量与CbCr分量的垂直方向比例是1:1(每1行都有1组CbCr分量)
- Y分量与CbCr分量的总比例是1:1
- 1个像素占用24bit(3字节),跟RGB888的体积一样
- 24bpp(bits per pixel)
- 这种格式是没有进行色度二次采样的
叉叉代表:亮度。
圆圈代表:色度。
4.3.2 4:2:2
- 第1行采集2组CbCr分量,第2行采集2组CbCr分量
- 水平方向相邻的2个像素(1行2列)共用1组CbCr分量
- Y分量与CbCr分量的水平方向比例是2:1(每2列就有1组CbCr分量)
- Y分量与CbCr分量的垂直方向比例是1:1(每1行都有1组CbCr分量)
- Y分量与CbCr分量的总比例是2:1
- 1个像素平均占用16bit(2字节)
- 16bpp
- 因为2个像素共占用32bit(4字节 = 2个Y分量 + 1个Cb分量 + 1个Cr分量)
4.3.3 4:2:0
- 第1行采集2组CbCr分量,第2行共享第1行的CbCr分量
- 相邻的4个像素(2行2列)共用1组CbCr分量
- Y分量与CbCr分量的水平方向比例是2:1(每2列就有1组CbCr分量)
- Y分量与CbCr分量的垂直方向比例是2:1(每2行就有1组CbCr分量)
- Y分量与CbCr分量的总比例是4:1
- 1个像素平均占用12bit(1.5字节)
- 12bpp
- 因为4个像素共占用48bit(6字节 = 4个Y分量 + 1个Cb分量 + 1个Cr分量)
5. 存储格式
存储格式,决定了YUV数据是如何排列和存储的。本文只介绍一些常见的存储格式。
5.1 分类
YUV的存储格式可以分为3大类:
- Planar(平面)
- Y、U、V分量分开单独存储
- 名称通常以字母p结尾
- Semi-Planar(半平面)
- Y分量单独存储,U、V分量交错存储
- 名称通常以字母sp结尾
- Packed(紧凑)
- 或者叫Interleaved (交错)
- Y、U、V分量交错存储
5.2 4:4:4
5.2.1 Planar
- I444
Y Y Y Y
Y Y Y Y
U U U U
U U U U
V V V V
V V V V
- YV24
Y Y Y Y
Y Y Y Y
V V V V
V V V V
U U U U
U U U U
5.2.2 Semi-Planar
- NV24
Y Y Y Y
Y Y Y Y
U V U V U V U V
U V U V U V U V
- NV42
Y Y Y Y
Y Y Y Y
V U V U V U V U
V U V U V U V U
5.3 4:2:2
5.3.1 Planar
- I422
Y Y Y Y
Y Y Y Y
U U
U U
V V
V V
- YV16
Y Y Y Y
Y Y Y Y
V V
V V
U U
U U
5.3.2 Semi-Planar
- NV16
Y Y Y Y
Y Y Y Y
U V U V
U V U V
- NV61
Y Y Y Y
Y Y Y Y
V U V U
V U V U
5.3.3 Packed
- UYVY
U Y V Y U Y V Y
U Y V Y U Y V Y
- YUYV
Y U Y V Y U Y V
Y U Y V Y U Y V
- YVYU
Y V Y U Y V Y U
Y V Y U Y V Y U
5.4 4:2:0
5.4.1 Planar
- I420
- 大多数视频解码器以I420格式输出原始图片
Y Y Y Y
Y Y Y Y
U U
V V
- YV12
Y Y Y Y
Y Y Y Y
V V
U U
5.4.2 Semi-Planar
- NV12
Y Y Y Y
Y Y Y Y
U V U V
- NV21
Y Y Y Y
Y Y Y Y
V U V U
6. 格式转换
6.1 其他图片格式转YUV
ffmpeg -i in.png -s 512x512 -pix_fmt yuv420p out.yuv
上述命令生成的yuv文件大小是:393216字节 = 512 * 512 * 1.5字节。
- -s
- 设置图片的尺寸
- 可以用一些固定字符串表示尺寸,比如hd720表示1280×720
- 如果不设置此选项,默认会跟随输入图片的尺寸
- -pix_fmt
- 设置像素格式
- 可以通过ffmpeg -pix_fmts查看FFmpeg支持的像素格式
- 如果不设置此选项,默认会跟随输入图片的像素格式
- 比如可能是rgb24、rgba8、pal8等
- 可以通过ffprobe查看某图片的像素格式,比如ffprobe in.png
6.2 YUV转其他图片格式
ffmpeg -s 512x512 -pix_fmt yuv420p -i in.yuv out.jpg
- 这里必须得设置YUV的尺寸(-s)、像素格式(-pix_fmt)
- 这就类似于:对pcm进行编码时,必须得设置采样率(-ar)、声道数(-ac)、采样格式(-f)
7. 显示YUV
7.1 完整的YUV
可以通过ffplay显示YUV数据。
-
YUV中直接存储的是所有像素的颜色信息(可以理解为是图像的一种原始数据)
-
必须得设置YUV的尺寸(-s)、像素格式(-pix_fmt)才能正常显示
-
这就类似于:播放pcm时,必须得设置采样率(-ar)、声道数(-ac)、采样格式(-f)
ffplay -s 512x512 -pix_fmt yuv420p in.yuv
# 在ffplay中
# -s已经过期,建议改为:-video_size
# -pix_fmt已经过期,建议改为:-pixel_format
ffplay -video_size 512x512 -pixel_format yuv420p in.yuv
7.2 单个分量
可以使用过滤器(filter)显示其中的单个分量(r、g、b、y、u、v)。
# 只显示r分量
ffplay -vf extractplanes=r in.png
# 只显示g分量
ffplay -vf extractplanes=g in.png
# 只显示b分量
ffplay -vf extractplanes=b in.png
# 只显示y分量
ffplay -video_size 512x512 -pixel_format yuv420p -vf extractplanes=y in.yuv
# 只显示u分量
ffplay -video_size 512x512 -pixel_format yuv420p -vf extractplanes=u in.yuv
# 只显示v分量
ffplay -video_size 512x512 -pixel_format yuv420p -vf extractplanes=v in.yuv
- -vf
- 设置视频过滤器
- 等价写法:-filter:v
- extractplanes
- 抽取单个分量的内容到灰度视频流中
四、视频录制01_命令行
本文的主要内容:演示如何利用命令行采集摄像头的视频数据。
阅读本文之前,建议先阅读《音频录制01_命令行》对常用命令作一个基本认识。
1. Windows
1.1 dshow支持的设备
ffmpeg -f dshow -list_devices true -i dummy
输出结果大致如下所示。
DirectShow video devices (some may be both video and audio devices)
"Integrated Camera"
DirectShow audio devices
"麦克风阵列 (Realtek(R) Audio)"
Integrated Camera是我笔记本上自带的摄像头,等会我们就是通过这个摄像头采集视频数据。
1.2 dshow支持的参数
ffmpeg -h demuxer=dshow
输出结果大致如下所示。
dshow indev AVOptions:
-video_size <image_size> set video size given a string such as 640x480 or hd720.
-pixel_format <pix_fmt> set video pixel format (default none)
-framerate <string> set video frame rate
-list_devices <boolean> list available devices (default false)
-list_options <boolean> list available options for specified device (default false)
- -video_size:分辨率
- -pixel_format:像素格式
- -framerate:帧率(每秒采集多少帧画面)
- -list_devices:true表示列出dshow支持的所有设备
- -list_options:true表示列出特定设备支持的所有参数
1.3 摄像头支持的参数
ffmpeg -f dshow -list_options true -i video="Integrated Camera"
输出结果大致如下所示。
DirectShow video device options (from video devices)
Pin "捕获" (alternative pin name "捕获")
vcodec=mjpeg min s=1280x720 fps=30 max s=1280x720 fps=30
vcodec=mjpeg min s=320x180 fps=30 max s=320x180 fps=30
vcodec=mjpeg min s=320x240 fps=30 max s=320x240 fps=30
vcodec=mjpeg min s=352x288 fps=30 max s=352x288 fps=30
vcodec=mjpeg min s=424x240 fps=30 max s=424x240 fps=30
vcodec=mjpeg min s=640x360 fps=30 max s=640x360 fps=30
vcodec=mjpeg min s=640x480 fps=30 max s=640x480 fps=30
vcodec=mjpeg min s=848x480 fps=30 max s=848x480 fps=30
vcodec=mjpeg min s=960x540 fps=30 max s=960x540 fps=30
pixel_format=yuyv422 min s=1280x720 fps=10 max s=1280x720 fps=10
pixel_format=bgr24 min s=1280x720 fps=10 max s=1280x720 fps=10
pixel_format=yuyv422 min s=320x180 fps=30 max s=320x180 fps=30
pixel_format=bgr24 min s=320x180 fps=30 max s=320x180 fps=30
pixel_format=yuyv422 min s=320x240 fps=30 max s=320x240 fps=30
pixel_format=bgr24 min s=320x240 fps=30 max s=320x240 fps=30
pixel_format=yuyv422 min s=352x288 fps=30 max s=352x288 fps=30
pixel_format=bgr24 min s=352x288 fps=30 max s=352x288 fps=30
pixel_format=yuyv422 min s=424x240 fps=30 max s=424x240 fps=30
pixel_format=bgr24 min s=424x240 fps=30 max s=424x240 fps=30
pixel_format=yuyv422 min s=640x360 fps=30 max s=640x360 fps=30
pixel_format=bgr24 min s=640x360 fps=30 max s=640x360 fps=30
pixel_format=yuyv422 min s=640x480 fps=30 max s=640x480 fps=30
pixel_format=bgr24 min s=640x480 fps=30 max s=640x480 fps=30
pixel_format=yuyv422 min s=848x480 fps=20 max s=848x480 fps=20
pixel_format=bgr24 min s=848x480 fps=20 max s=848x480 fps=20
pixel_format=yuyv422 min s=960x540 fps=15 max s=960x540 fps=15
pixel_format=bgr24 min s=960x540 fps=15 max s=960x540 fps=15
可以看到摄像头支持的分辨率、像素格式、帧率等参数。
1.4 录制
ffmpeg -f dshow -i video="Integrated Camera" out.yuv
输出结果大致如下所示。
Input #0, dshow, from 'video=Integrated Camera':
Stream #0:0: Video: mjpeg, yuvj422p, 1280x720, 30 fps
Output #0, rawvideo, to 'out.yuv':
Stream #0:0: Video: rawvideo, yuvj422p, 1280x720, 30 fps
根据输出结果,不难看出:
-
从摄像头采集的数据,最终存成了YUV格式
-
摄像头的默认参数
- 分辨率:1280×720
- 像素格式:yuvj422p
- 帧率:30fps
所以,播放YUV时的命令如下所示。
- 需要注意的是:YUV文件中只存储了图像信息,并没有声音信息
- 因此,播放YUV时是听不到任何声音的
- ffplay的framerate默认值是25
ffplay -video_size 1280x720 -pixel_format yuvj422p -framerate 30 out.yuv
可以自定义参数进行录制。
ffmpeg -f dshow -video_size 640x480 -pixel_format yuyv422 -framerate 30 -i video="Integrated Camera" out.yuv
播放录制好的YUV。
ffplay -video_size 640x480 -pixel_format yuyv422 -framerate 30 out.yuv
2. Mac
2.1 avfoundation支持的设备
ffmpeg -f avfoundation -list_devices true -i ''
输出结果大致如下所示。
AVFoundation video devices:
[0] FaceTime高清摄像头(内建)
[1] Capture screen 0
AVFoundation audio devices:
[0] Soundflower (64ch)
[1] Edu Audio Device
[2] MacBook Pro麦克风
[3] Soundflower (2ch)
0号设备就是Mac自带的摄像头。
2.2 avfoundation支持的参数
ffmpeg -h demuxer=avfoundation
输出结果大致如下所示。
AVFoundation indev AVOptions:
-list_devices <boolean> .D........ list available devices (default false)
-pixel_format <pix_fmt> .D........ set pixel format (default yuv420p)
-framerate <video_rate> .D........ set frame rate (default "ntsc")
-video_size <image_size> .D........ set video size
- -video_size:分辨率
- -pixel_format:像素格式
- 默认是yuv420p
- -framerate:帧率(每秒采集多少帧画面)
- 默认是ntsc,也就是30000/1001,约等于29.970030
- -list_devices:true表示列出avfoundation支持的所有设备
2.3 录制
# 使用0号视频设备
ffmpeg -f avfoundation -i 0 out.yuv
然后你可能会遇到一个错误:这个设备(摄像头)不支持29.970030的帧率。
Selected framerate (29.970030) is not supported by the device
重新设置个30的帧率试试。
ffmpeg -f avfoundation -framerate 30 -i 0 out.yuv
然后你会看到以下提示信息。
- 这个设备(摄像头)不支持yuv420p
- 只支持uyvy422、yuyv422、nv12、0rgb、bgr0
- 并且自动选择使用uyvy422来替代yuv420p
Selected pixel format (yuv420p) is not supported by the input device.
Supported pixel formats:
uyvy422
yuyv422
nv12
0rgb
bgr0
Overriding selected pixel format to use uyvy422 instead.
与此同时,也成功开始采集摄像头的视频数据了。
- 像素格式:uyvy422
- 分辨率:1280×720
- 帧率:30
Input #0, avfoundation, from '0':
Stream #0:0: Video: rawvideo, uyvy422, 1280x720
Output #0, rawvideo, to 'out.yuv':
Stream #0:0: Video: rawvideo, uyvy422, 1280x720, 30 fps
播放录制好的YUV。
ffplay -video_size 1280x720 -pixel_format uyvy422 -framerate 30 out.yuv
五、视频录制02_编程
本文的主要内容:演示如何通过编程采集摄像头的视频数据。
整体的流程跟《音频录制02_编程》类似。
1. 依赖库
需要依赖4个库。
extern "C" {
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
}
2. 宏定义
#ifdef Q_OS_WIN
// 格式名称
#define FMT_NAME "dshow"
// 设备名称
#define DEVICE_NAME "video=Integrated Camera"
// YUV文件名
#define FILENAME "F:/out.yuv"
#else
#define FMT_NAME "avfoundation"
#define DEVICE_NAME "0"
#define FILENAME "/Users/mj/Desktop/out.yuv"
#endif
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
3. 权限申请
在Mac平台,有2个注意点:
- 需要在Info.plist中添加摄像头的使用说明,申请摄像头的使用权限
- 使用Debug模式运行程序
- 不然会出现闪退的情况
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>使用摄像头采集您的靓照</string>
</dict>
</plist>
4. 注册设备
在整个程序的运行过程中,只需要执行1次注册设备的代码。
// 初始化libavdevice并注册所有输入和输出设备
avdevice_register_all();
5. 获取输入格式对象
// 获取输入格式对象
AVInputFormat *fmt = av_find_input_format(FMT_NAME);
if (!fmt) {
qDebug() << "av_find_input_format error" << FMT_NAME;
return;
}
6. 打开输入设备
// 格式上下文
AVFormatContext *ctx = nullptr;
// 传递给输入设备的参数
AVDictionary *options = nullptr;
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "pixel_format", "yuyv422", 0);
av_dict_set(&options, "framerate", "30", 0);
// 打开输入设备
int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, &options);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "avformat_open_input error" << errbuf;
return;
}
7. 打开输出文件
// 打开文件
QFile file(FILENAME);
if (!file.open(QFile::WriteOnly)) {
qDebug() << "file open error" << FILENAME;
// 关闭输入设备
avformat_close_input(&ctx);
return;
}
8. 采集视频数据
// 计算每一帧的大小
AVCodecParameters *params = ctx->streams[0]->codecpar;
int imageSize = av_image_get_buffer_size(
(AVPixelFormat) params->format,
params->width, params->height,
1);
// 数据包
AVPacket *pkt = av_packet_alloc();
while (!isInterruptionRequested()) {
// 不断采集数据
ret = av_read_frame(ctx, pkt);
if (ret == 0) { // 读取成功
// 将数据写入文件
file.write((const char *) pkt->data, imageSize);
/*
这里要使用imageSize,而不是pkt->size。
pkt->size有可能比imageSize大(比如在Mac平台),
使用pkt->size会导致写入一些多余数据到YUV文件中,
进而导致YUV内容无法正常播放
*/
// 释放资源
av_packet_unref(pkt);
} else if (ret == AVERROR(EAGAIN)) { // 资源临时不可用
continue;
} else { // 其他错误
ERROR_BUF(ret);
qDebug() << "av_read_frame error" << errbuf;
break;
}
}
9. 释放资源
// 释放资源
av_packet_free(&pkt);
// 关闭文件
file.close();
// 关闭设备
avformat_close_input(&ctx);
六、显示BMP图片
为什么是显示BMP图片?而不是显示JPG或PNG图片?
- 因为SDL内置了加载BMP的API,使用起来会更加简单,便于初学者学习使用SDL
- 如果想要轻松加载JPG、PNG等其他格式的图片,可以使用第三方库:SDL_image
1. 宏定义
#include <SDL2/SDL.h>
#include <QDebug>
// 出错了就执行goto end
#define END(judge, func) \
if (judge) { \
qDebug() << #func << "Error" << SDL_GetError(); \
goto end; \
}
2. 变量定义
// 窗口
SDL_Window *window = nullptr;
// 渲染上下文
SDL_Renderer *renderer = nullptr;
// 像素数据
SDL_Surface *surface = nullptr;
// 纹理(直接跟特定驱动程序相关的像素数据)
SDL_Texture *texture = nullptr;
3. 初始化子系统
// 初始化Video子系统
END(SDL_Init(SDL_INIT_VIDEO), SDL_Init);
4. 加载BMP
// 加载BMP
surface = SDL_LoadBMP("F:/in.bmp");
END(!surface, SDL_LoadBMP);
5. 创建窗口
// 创建窗口
window = SDL_CreateWindow(
// 窗口标题
"SDL显示BMP图片",
// 窗口X(未定义)
SDL_WINDOWPOS_UNDEFINED,
// 窗口Y(未定义)
SDL_WINDOWPOS_UNDEFINED,
// 窗口宽度(跟图片宽度一样)
surface->w,
// 窗口高度(跟图片高度一样)
surface->h,
// 显示窗口
SDL_WINDOW_SHOWN
);
END(!window, SDL_CreateWindow);
6. 创建渲染上下文
// 创建渲染上下文(默认的渲染目标是window)
renderer = SDL_CreateRenderer(window, -1,
SDL_RENDERER_ACCELERATED |
SDL_RENDERER_PRESENTVSYNC);
if (!renderer) { // 说明开启硬件加速失败
renderer = SDL_CreateRenderer(window, -1, 0);
}
END(!renderer, SDL_CreateRenderer);
7. 创建纹理
// 创建纹理
texture = SDL_CreateTextureFromSurface(
renderer,
surface);
END(!texture, SDL_CreateTextureFromSurface);
8. 渲染
// 设置绘制颜色(这里随便设置了一个颜色:黄色)
END(SDL_SetRenderDrawColor(renderer,
255, 255, 0,
SDL_ALPHA_OPAQUE),
SDL_SetRenderDrawColor);
// 用DrawColor清除渲染目标
END(SDL_RenderClear(renderer),
SDL_RenderClear);
// 复制纹理到渲染目标上
END(SDL_RenderCopy(renderer, texture, nullptr, nullptr),
SDL_RenderCopy);
// 将此前的所有需要渲染的内容更新到屏幕上
SDL_RenderPresent(renderer);
9. 延迟退出
// 延迟3秒退出
SDL_Delay(3000);
10. 释放资源
end:
// 释放资源
SDL_FreeSurface(surface);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
七、显示YUV图片
1. 宏定义
#include <SDL2/SDL.h>
#include <QDebug>
#define END(judge, func) \
if (judge) { \
qDebug() << #func << "error" << SDL_GetError(); \
goto end; \
}
#define FILENAME "F:/res/in.yuv"
#define PIXEL_FORMAT SDL_PIXELFORMAT_IYUV
#define IMG_W 512
#define IMG_H 512
2. 变量定义
// 窗口
SDL_Window *window = nullptr;
// 渲染上下文
SDL_Renderer *renderer = nullptr;
// 纹理(直接跟特定驱动程序相关的像素数据)
SDL_Texture *texture = nullptr;
// 文件
QFile file(FILENAME);
3. 初始化子系统
// 初始化Video子系统
END(SDL_Init(SDL_INIT_VIDEO), SDL_Init);
4. 创建窗口
// 创建窗口
window = SDL_CreateWindow(
// 窗口标题
"SDL显示YUV图片",
// 窗口X(未定义)
SDL_WINDOWPOS_UNDEFINED,
// 窗口Y(未定义)
SDL_WINDOWPOS_UNDEFINED,
// 窗口宽度(跟图片宽度一样)
surface->w,
// 窗口高度(跟图片高度一样)
surface->h,
// 显示窗口
SDL_WINDOW_SHOWN
);
END(!window, SDL_CreateWindow);
5. 创建渲染上下文
// 创建渲染上下文(默认的渲染目标是window)
renderer = SDL_CreateRenderer(window, -1,
SDL_RENDERER_ACCELERATED |
SDL_RENDERER_PRESENTVSYNC);
if (!renderer) { // 说明开启硬件加速失败
renderer = SDL_CreateRenderer(window, -1, 0);
}
END(!renderer, SDL_CreateRenderer);
6. 创建纹理
// 创建纹理
texture = SDL_CreateTexture(renderer,
PIXEL_FORMAT,
SDL_TEXTUREACCESS_STREAMING,
IMG_W, IMG_H);
END(!texture, SDL_CreateTexture);
7. 打开文件
// 打开文件
if (!file.open(QFile::ReadOnly)) {
qDebug() << "file open error" << FILENAME;
goto end;
}
8. 渲染
// 将YUV的像素数据填充到texture
END(SDL_UpdateTexture(texture, nullptr, file.readAll().data(), IMG_W),
SDL_UpdateTexture);
// 设置绘制颜色(画笔颜色)
END(SDL_SetRenderDrawColor(renderer,
0, 0, 0, SDL_ALPHA_OPAQUE),
SDL_SetRenderDrawColor);
// 用绘制颜色(画笔颜色)清除渲染目标
END(SDL_RenderClear(renderer),
SDL_RenderClear);
// 拷贝纹理数据到渲染目标(默认是window)
END(SDL_RenderCopy(renderer, texture, nullptr, nullptr),
SDL_RenderCopy);
// 更新所有的渲染操作到屏幕上
SDL_RenderPresent(renderer);
9. 延迟退出
// 延迟3秒退出
SDL_Delay(3000);
10. 释放资源
end:
file.close();
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();