08-?音视频技术核心知识|成像技术【重识图片、详解YUV、视频录制、显示BMP图片、显示YUV图片】

一、前言

顺应时代的技术发展潮流,逐步学习并掌握音视频技术核心知识,让技术落地,让知识赋能生活,让科技造福千万灯火。

二、重识图片

要想学好音视频,首先得先好好研究一下图片

1. 像素

下图的分辨率是60×50。

60x50分辨率

用Photoshop放大图片上百倍后,可以清晰地看到图片由若干个方形的色块组成,每一个方形的色块被称为:像素(Pixel)。这张图片的每一行都有60个像素,共50行,总共60*50=3000个像素。

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万种颜色)
    24bit

  • 8bit(能表示256种颜色)
    8bit

  • 7bit(能表示128种颜色)
    7bit

  • 6bit(能表示64种颜色)
    6bit

  • 5bit(能表示32种颜色)
    5bit

  • 4bit(能表示16种颜色)
    4bit

  • 3bit(能表示8种颜色)
    3bit

  • 2bit(能表示4种颜色)
    2bit

  • 1bit(能表示2种颜色)
    1bit

4. 格式

一说到图片,大家应该马上能想到拓展名为jpgpnggif的图片文件。

各种图片

每张图片都有自己的大小,你是否思考过:一张图片的大小是如何计算出来的?为什么dragon01.jpg的大小是288KB?

  • 要想知道一张图片的大小是多少?首先得知道每个像素的大小是多少。

  • 如果位深度是n,那么每个像素的大小就是n个二进制位

下图的分辨率是60×50,位深度是24,所以:

  • 每个像素的大小是:24bit(3字节,1字节=8bit)
  • 图片的理论大小是:(60*50)*(24/8)=9000B≈8.79KB

60x50分辨率

但实际上你会发现:在相同分辨率、相同位深度的前提下,把这张图片存成2种不同的格式(jpg、png),它们的大小是不同的,而且都小于理论上的8.79KB。

不同格式

其实jpg、png都是经过压缩后的图片(具体的压缩算法和原理,就不在此讨论了,大家可以到网上自行搜索),所以它们的大小会比理论值偏小。

图片的压缩类型可以分为2种:

  • 无损压缩
    • 不损失图片质量
    • 压缩比,体积
    • 解压(显示)后能够还原出完整的原始图片数据,不会损失任何图片信息
  • 有损压缩
    • 会损失图片质量
    • 压缩比,体积
    • 解压(显示)后无法还原出完整的原始图片数据,会损失掉一些图片信息
  • 压缩比 = 未压缩大小 / 压缩后大小
压缩类型 位深度
JPG(JPEG) 有损压缩 24bit
PNG 无损压缩 8bit、24bit、32bit
GIF 无损压缩 8bit

5. GIF

众所周知,gif是一种支持动画的图片,所以一般也叫作gif动态图,微信的动态表情包就是基于gif动态图。

GIF动画图片:悟空vs克林

gif动画的实现原理类似手翻书。

手翻书

gif的动画原理是:

  • gif内部存储了很多帧(张)静态图片
  • 在短时间内,连续按顺序地呈现每一帧静态图片,就形成了动画的效果

像上面那张《悟空vs克林》的gif动态图,它内部存储了44帧静态图,只要按顺序从01.jpg播放到44.jpg,就能呈现出连贯的动画效果。

44帧静态图

不管是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字节)
  • CbCr:表示色度(Chrominance、Chroma)
    • Cb(U):蓝色色度分量,占8bit(1字节)
    • Cr(V):红色色度分量,占8bit(1字节)

YCbCr

当Y等于0.5时

2.3 兼容性

原始图像

RGB

YCbCr

根据上面的图片,不难看出:

  • 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:4:4

叉叉代表:亮度。

圆圈代表:色度。

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:2:2

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分量)

4:2:0 MPEG-1

4:2:0 MPEG-2

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

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

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

4:2:0

5.4.1 Planar

  • I420
    • 大多数视频解码器以I420格式输出原始图片
Y Y Y Y











Y Y Y Y











U U

V V

I420

  • 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

4:2:0

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支持的像素格式
    • 如果不设置此选项,默认会跟随输入图片的像素格式
      • 比如可能是rgb24rgba8pal8
      • 可以通过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图片

文本的主要内容是:使用SDL显示一张BMP图片,算是为后面的《显示YUV图片》做准备。

为什么是显示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图片

文本的主要内容是:使用SDL显示一张YUV图片,整体过程跟《显示BMP图片》比较像。

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();

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYejMm94' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片