long blogs

进一步有进一步惊喜


  • Home
  • Archive
  • Tags
  •  

© 2025 long

Theme Typography by Makito

Proudly published with Hexo

图像处理FFmpeg一把梭

Posted at 2021-03-14 图像处理 ffmpeg 

图像视频基本知识

BMP文件格式解析

由于开源的bmp不能解析使用GDI获得BMP文件,
所以自己使用golang解析BMP文件

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
type SHORT int16
type LONG int32
type BYTE uint8
type WORD uint16
type DWORD uint32

type BITMAPFILEHEADER struct {
bfType uint16 // 2
bfSize uint32 // 4
bfReserved1 uint16 // 2
bfReserved2 uint16 // 2
bfOffBits uint32 // 4
}

type BITMAPINFOHEADER struct {
biSize uint32
biWidth int32
biHeight int32
biPlanes uint16
biBitCount uint16
biCompression uint32
biSizeImage uint32
biXPelsPerMeter int32
biYPelsPerMeter int32
biClrUsed uint32
biClrImportant uint32
}
func bmpDecoder(input []byte) (image.Image, error) {

bmfHeader := BITMAPFILEHEADER{}
bmfHeader.bfType = uint16(WORD(binary.LittleEndian.Uint16(input[0:2])))
if bmfHeader.bfType != 0x4D42 {
fmt.Println("unsupport format")
return nil,nil
}
bmfHeader.bfSize = uint32(DWORD(binary.LittleEndian.Uint32(input[2:6])))
bmfHeader.bfReserved1 = uint16(WORD(binary.LittleEndian.Uint16(input[6:8])))
bmfHeader.bfReserved2 = uint16(WORD(binary.LittleEndian.Uint16(input[8:10])))
bmfHeader.bfOffBits = uint32(DWORD(binary.LittleEndian.Uint32(input[10:14])))

bi := BITMAPINFOHEADER{}
bi.biSize = uint32(DWORD(binary.LittleEndian.Uint32(input[14:18])))
bi.biWidth = int32(LONG(binary.LittleEndian.Uint32(input[18:22])))
bi.biHeight = int32(LONG(binary.LittleEndian.Uint32(input[22:26])))
bi.biPlanes = uint16(WORD(binary.LittleEndian.Uint16(input[26:28])))
bi.biBitCount = binary.LittleEndian.Uint16(input[28:30])
bi.biCompression = binary.LittleEndian.Uint32(input[30:34])
bi.biSizeImage = binary.LittleEndian.Uint32(input[34:38])
bi.biXPelsPerMeter = int32(binary.LittleEndian.Uint32(input[38:42]))
bi.biYPelsPerMeter = int32(binary.LittleEndian.Uint32(input[42:46]))
bi.biClrUsed = binary.LittleEndian.Uint32(input[46:50])
bi.biClrImportant = binary.LittleEndian.Uint32(input[50:54])

fmt.Println(bmfHeader)
fmt.Println(bi)
maxX := int(bi.biWidth)
maxY := int(bi.biHeight)
rgba := image.NewRGBA(image.Rectangle{
Min: image.Point{
X: 0,
Y: 0,
},
Max: image.Point{
X: maxX,
Y: maxY,
},
})
// w -> y
// h -> x
base := bmfHeader.bfOffBits
// 注意:由于位图信息头中的图像高度是正数,所以位图数据在文件中的排列顺序是从左下角到右上角,以行为主序排列的。
// 从左下角到右上角,从左到右一行行扫描
fmt.Println("maxX ", maxX,"maxY ", maxY)
for y := maxY - 1; y >= 0;y -- {
for x := 0;x < maxX ; x ++ {
rgba.Set(x, y, color.NRGBA{
R: input[base + 2],
G: input[base + 1],
B: input[base + 0],
A: input[base + 3],
})
base += 4
}
}
return rgba, nil
}

yuv文件

使用RGBA表示一张图像占用的存储空间太大了,人对色调和饱和度不敏感。对亮度敏感。所以色调和饱和度可以压缩更狠。一张图像,转成yuv格式。亮度y的大小为长*宽。u和v长宽只需要一半就行。
即
y = w * h
u = w /2 * h/2
v = w /2 * h/2

FFMPEG使用技巧

FFMPEG编程

接口说明

获得屏幕视频数据流并编码为H264

使用gdigrab可以获得windows下屏幕的数据。

获得摄像头视频数据流并编码为H264

图像工具类demo

读取BMP文件

rgb转yuv

H.264格式

由一个个NALU结构串联结构组成,
由一个一个startCode分割,怎么分割呢?
startCode有0x000001和0x00000001两种,即有三个字节的1为分割点的或4个字节为分割点的1.
startCode为3个字节.
[0x00_00_01 {NALU} 0x00_00_01 {NALU} 0x00_00_01{NALU} 0x00_00_01]
找到两个startCode就可以找到一个NALU了。

液晶显示屏彩色显示原理

彩色液晶显示器中,每个像素分成三个单元,或称子像素,附加的滤光片分别标记红色、绿色和蓝色。三个子像素可独立进行控制,通过电压大小来改变亮度,每一个子像素对应8bit,能够产生256颜色。一个像素点能够产生256 * 256 * 256 = 2^24种颜色。

三种原色光在每一象素中以0-255 (28)强度组合成从全黑色到全白色之间各种不同的颜色光,当前在计算机硬件中采取每一象素用24bit(比特)表示的方法,所以三种原色光各分到8比特,每一种原色的强度依照8比特的最高值28分为256个值。用这种方法可以组合16777216种颜色。目前已经存在支持10bit位深的显示器。

展开:

  • (0, 0, 0)是黑色
  • (255, 255, 255)是白色
  • (255, 0, 0)是红色
  • (0, 255, 0)是绿色
  • (0, 0, 255)是蓝色
  • (255, 255, 0)是黄色
  • (0, 255, 255)是青色
  • (255, 0, 255)是品红

yuv

yuv: 是一种颜色空间,用于彩色电视与黑白电视之间的信号兼容。

  • Y: 表明明亮度(Luminance或Luma),灰度图。
  • U、V: 表示色度(Chrominance或Chroma), 作用是描述影像的色彩度及饱和度,用于指定像素的颜色。

Y’Cbcr:也称为yuv,

  • Cr: 色度红,反应了RGB输入信号 红色部分与RGB信号亮度值之间的差异
  • Cb:色度蓝,反应了RGB输入信号 红色部分与RGB信号亮度值之间的差异

yuv存储格式

  • planar: 先存储Y,然后U,然后V。
  • packed: yuv交叉存储

常见格式

1. yuv444
1
packet采样(yuv, yuv, yuv)和planar采样(yyyy uuuu vvvv)
2. yuv422

packet采样,YUYV YUYV或UYVY UYVY

3. yuv422p

planar采样,YYYY UU VV

4. yuv420
1
packet采样,`YUV Y YUV Y`
5. yuv420p
1
planar采样
  • I420: YYYY U V

  • YV12: YYYY V U

6. yuv420sp

Y为planar采样,UV为paket采样

  • NV12:

  • NV21:

RGB

RGBA,每种颜色占一个字节。一个屏幕的像素点由Red、Green、Blue、Alpha组成。

浮点数表示:取值范围[0.0,1.0]

整数表示:取值范围[0,255][0,FF]

格式

1. 索引格式

(1)RGB1

每个像素用1个bit,表示0/1两个值,可表示的范围为两种颜色。黑白;需要使用调色板

(2)RGB4

每个像素用4个bit表示,索引0-15,共16种。

(3)RGB8

每个像素用8个bit表示,可以索引0-255,一共256种颜色。

调色板

一个颜色的索引,一个像素里面的值作为调色板里面索引,该索引对应的调色板的值为对应的颜色值。0/1不一定表示黑白。只表示在调色板中的位置。调色板的值才是表示颜色。

2. 像素格式

(1)RGB555

每个像素用16个bit表示,最高位的bit不用。剩下的15个bit,R、G、B分别占用5个bit。

取具体像素值的方法:

  • R = color & 0x7C00 // 0x7C00 = 0111_1100_0000_0000
  • G = color & 0x03E0 // 0x03E0 = 0000_0011_1110_0000
  • B = color & 0x001F // 0x001F = 0000_0000_0001_1111
(2)RGB565

每个像素用16个bit表示,R占用5个、G占用6个、B占用4个。不相等占用。

取值的方法:

  • R = color & 0xF800(1111_1000_0000_0000)
  • G = color & 0x07E0(0000_0111_1110_0000)
  • B = color & 0x001F(0000_0000_0001_1111)
(3)RGB24

R、G、B每个用8bit,一共用24bit。

取值计算方法和上面的一样

(4)RGB32

R、G、B每个8bit表示,共占用24bit,最后8个bit保留

取值计算方法:

  • R = color & 0xFF000000
  • G = color & 0x00FF0000
  • B = color & 0x0000FF00

RGB YUV相互转换

RGB转换成YUV时,U(Cb)、V(Cr)的取值范围整数表示[-128, 127]。浮点数表示[-0.5, 0.5]。

可以统一用一个无符号字节表示[0, 255],对应无符号浮点数[0,1]。有符号和无符号的区别。

YUV -> RGB

1. 常规转换标准

2. BT.601标准 (SD TV)

3. BT.709标准 (HD TV)

RGB->YUV

1. 常规标准转换

2. BT.601标准:(SD TV)

3. BT.709标准: (HD TV)

转换Demo

Y’UV444 -> RGB888

用于Y’UV444格式的RGB转换公式也适用于YUV420(或YUV422)的标准NTSC TV传输格式。对于YUV420,由于每个U或V样本都用来表示形成一个正方形的4个Y样本,因此适当的采样方法可以允许使用以下所示的精确转换公式。

这些公式基于NTSC标准:

整数公式:

使用系数:

方括号的内容表示:计算出来的值,大于255取255,小于0取,中间值不变。

YCbCr(每通道8位)到RGB888的ITU-R标准整数运算

Y’UV422->RGB888

输入:读取4个字节的Y’UV(u,y1,v,y2)

输出:写入6个字节的RGB(R,G,B,R,G,B)

1
2
3
4
u = yuv [0]; 
y1 = yuv [1];
v = yuv [2];
y2 = yuv [3];

可以将其解析为常规Y’UV444格式以获得2个RGB像素信息:

1
2
rgb1 = Y'UV444toRGB888(y1,u,v); 
rgb2 = Y'UV444toRGB888(y2,u,v);

Y’UV422也可以用其他顺序的值表示,例如FourCC格式代码YUY2。

输入:读取4个字节的Y’UV(y1,u,y2,v),(y1,y2,u,v)或(u,v,y1,y2)

Y’UV411->RGB888

输入:读取6个字节的Y’UV

输出:写入12个字节的RGB

1
2
3
4
5
6
7
8
9
10
11
12
//提取YUV分量
u = yuv [0];
y1 = yuv [1];
y2 = yuv [2];
v = yuv [3];
y3 = yuv [4];
y4 = yuv [5];

rgb1 = Y'UV444toRGB888(y1,u,v);
rgb2 = Y'UV444toRGB888(y2,u,v);
rgb3 = Y'UV444toRGB888(y3,u,v);
rgb4 = Y'UV444toRGB888(y4,u,v);

我们从6个字节中获得了4个RGB像素值(4 * 3字节)。这意味着将传输的数据的大小减小一半,而质量下降。

Y’UV420p和(Y’V12或YV12)->RGB888

Y’UV420p是平面格式,这意味着Y’,U和V值被分组在一起而不是散布在一起。这样做的原因是,通过将U和V值分组在一起,图像变得更加可压缩。当给定Y’UV420p格式的图像数组时,所有Y’值首先出现,然后是所有U值,最后是所有V值。

Y’V12格式基本上与Y’UV420p相同,但是它切换了U和V数据:Y’值后跟V值,最后是U值。只要注意从适当的位置提取U和V值,就可以使用相同的算法处理Y’UV420p和Y’V12。

与大多数Y’UV格式一样,Y’值与像素一样多。在X等于高度乘以宽度的情况下,数组中的前X个索引是对应于每个单独像素的Y’值。但是,U和V值只有四分之一。U和V值对应于图像的每个2×2块,这意味着每个U和V条目都适用于四个像素。在Y’值之后,下一个X / 4索引是每个2 x 2块的U值,此后的下一个X / 4索引是V值,它们也适用于每个2 x 2块。

如上图所示,Y’UV420中的Y’,U和V分量分别在顺序块中编码。为每个像素存储AY’值,然后为每个2×2正方形像素块存储一个U值,最后为每个2×2块存储一个V值。在上图中,使用相同的颜色显示了相应的Y’,U和V值。从设备逐行读取字节流,Y’块位于位置0,U块位于位置x×y(在此示例中为6×4 = 24),V块位于位置x ×y +(x×y)/ 4(这里6×4 +(6×4)/ 4 = 30)

参考

(1) https://en.wikipedia.org/wiki/YUV

H.264相关

NALU

H.264原始码流,由一个一个NALU组成。

它的功能分为两层:视频编码层(VCL, Video Coding Layer),和网络提取层(NAL, Network Abstraction Layer)。

VCL数据编码处理的输出,输出的是原始数据比特流(SODB,String of Data bits),被压缩编码后的视频数据序列,真正的二进制码流,但是大小和长度不一定。在传输存储前需要字节对齐,即往原始数据后面添加字节。生成原始字节序列负荷(RBSP, Raw Byte Sequence Payload)。最后被封装到NAL单元中(NALU, NAL Unit)。

每个NALU包括:

  • 一组对应于视频编码的NALU头部信息。这个里面是什么信息????
  • 一个原始字节序列负荷(RBSP, Raw Byte Sequence Payload)。RBSP在原始编码数据(SODB)后面填充bit,一个“1”bit和若干个”0”。用来字节对齐。每个NALU大小一定符合特定的格式。

NALU作为一个独立的单元,只要完整的传送NALU便可以播放了。

Start Code

如何将NALU分割开来,需要一个一个起止码(Start Code Prefix)进行分割。如果NALU对应的Slice为一帧的开始,首帧。使用4字节表示,即0x00 00 00 01。不是首帧或其他数据类型,使用3字节表示0x00 00 01。为了保证起始码唯一,当数据连续出现0x00 00 00、0x00 00 01、0x00 00 02、0x00 00 03时,会在两个0之间插入03。保证只有起止码唯一。

1
2
3
4
0x00 00 00 -> 0x00 00 03 00
0x00 00 01 -> 0x00 00 03 01
0x00 00 02 -> 0x00 00 03 02
0x00 00 03 -> 0x00 00 03 03
NAL Header

1byte的NAL Header头格式,forbidden_bit(1bit) + nal_refence_bit(2bit) + nal_unit_type(5bit)

  • forbidden_bit(1bit)禁止位,有语法冲突,设为1。接收方识别之后需要丢弃这个NALU
  • nal_ref_idc(2 bit)当前NAL的优先级,取值为0~3,值越高表明当前NAL越重要。H.264规定,如果当前NAL是序列参数集,或是图像参数等。该值必须大于0.nal_unit_type为5,nal_ref_idc > 0; nal_unit_type ={6,9,10,11,12}时,nal_ref_idc等于0。
  • nal_unit_type(5 bit)类型如下。
nal_unit_type NAL类型C C
0 未使用
1 未使用Data Partitioning、非IDR图像的Slice 2,3,4
2 使用Data Partitioning、且为Slice A 2
3 使用Data Partitioning、且为Slice B 3
4 使用Data Partitioning、且为Slice C 4
5 IDR图像中的Slice 2,3
6 补充增强信息单元(SEI) 5
7 序列参数集(Sequence Parameter Set,SPS) 0
8 图像参数集(Picture Parameter Set,PPS) 1
9 分界符 6
10 序列结束 7
11 码流结束 8
12 填充 9
13…23 保留
24…31 未使用

nal_unit_type=5时,表示当前NAL是IDR图像的一个片,此时,IDR图像中的每个片的nal_unit_type都应该等于5。

1
2
3
RBSP = SODB+ RBSP trailing bits
NALU = NAL header(1 byte) + RBSP
H.264 = Start Code Prefix + NALU .....+Start Code Prefix + NALU + .....

H.264有两种封装模式:
  (1)annexb模式:传统模式,有start code, SPS和PPS是在ES中;
  (2)mp4模式:没有start code,SPS和PPS是封装在container中,每一个frame前面是这个frame的长度;

SPS的头部是0x67,PPS的头部是0x68,要保持对数据的敏感性。

判断能否都丢帧的依据是,根据NALU包的优先级字节,如果优先级为0,则可以丢弃,如果是IDR、SPS、PPS时,优先级会大于0。

  • SPS:Sequence Parameter Set 序列参数集,H.264码流第一个 NALU
  • PPS:Picture Parameter Set图像参数集,H.264码流第二个 NALU
  • IDR帧:IDR帧属于I 帧。解码器收到IDR frame时,将所有的参考帧队列丢弃 ,这点是所有I帧共有的特性,但是收到IDR帧时,解码器另外需要做的工作就是:把所有的PPS和SPS参数进行更新。由此可见,在编码器端,每发一个 IDR,就相应地发一个 PPS & SPS_nal_unit
  • I帧:帧内编码帧是一种自带全部信息的独立帧,无需参考其它图像便可独立进行解码,视频序列中的第一个帧始终都是I帧。
  • P帧:前向预测编码帧
  • B帧:双向预测内插编码帧

一般H.264原始码流是以SPS->PPS->SEI->IDR->SLICE->SLICE…开头的。

一帧图片数据和NALU的关系

一帧图片被编码之后,编成一个或多个片(slice)。大卸八块了??使用NALU来装载Slice。但是NALU不只装Slice。slice一定用NALU装,NALU不一定装的是slice。

1
2
3
graph LR
一帧图片-->一个或一个以上的slice
一个Slice-->一个NALU传送

帧和片的概念:一帧一定是一张完整的图片。就像拼图一样,一个拼图一定是一张图片,但是拼图会分成一块一块的。那小块的就是片。

slice

一个片又是由什么组成的???片头+片数据。

片头都有那些数据???

片数据由什么组成???

片数据由一个一个宏块组成,至少有一个宏块。宏块是什么?

宏块包含每一个像素的亮度和色度信息,解码的工作就是从宏块中获得像素数据阵列数据。一个宏块包含:宏块类型+预测类型+CPB(Coded Block Pattern)+QP(Quantization Parameter)+宏块数据

宏块数据: 16 × 16的亮度像素 + 8 × 8的Cb +8 × 8的Cr组成。每一个宏块都是长宽为16的YUV420P的数据??? 可以选其他的数据吗??

切片的类型:

  • 0 P-slice. Consists of P-macroblocks (each macro block is predicted using one reference frame) and / or I-macroblocks.
  • 1 B-slice. Consists of B-macroblocks (each macroblock is predicted using one or two reference frames) and / or I-macroblocks.
  • 2 I-slice. Contains only I-macroblocks. Each macroblock is predicted from previously coded blocks of the same slice.
  • 3 SP-slice. Consists of P and / or I-macroblocks and lets you switch between encoded streams.
  • 4 SI-slice. It consists of a special type of SI-macroblocks and lets you switch between encoded streams.

整体结构

一个视频编码之后生成一组又一组的图像(GOP,图像组)。每个GOP形容一个I帧到下一个I帧之间间隔了多少帧。间隔越大,视频体积越小,视频的质量越低。如何理解?假设一个视频的帧率为25f,说明一秒内需要播放25帧的数据。如果只有一个I帧,其余的都是P帧或B帧,那么数据量会很小。如果25帧都是I帧,那就相当于每秒钟播放完整的25张图片。那数据就很大。如何描述一个GOP的参数,图像多少,帧间隔多少,长宽多少,帧率多少。需要传输中的序列(PPS)和帧图像参数(SPS)

雷神音视频博客笔记

音视频数据处理入门:RGB、YUV像素数据处理

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
#include <string.h>
#include <tgmath.h>
#include <stdbool.h>
#include "stdlib.h"
#include "unistd.h"
#include "stdio.h"
#include "ImageUtil.h"

/**
* 分离yuv420p图片的yuv数据
* @param url yuv图片的地址
* @param w 文件宽
* @param h 文件的高
* @param num 图片的大小
* @return
*/
int simplest_yuv420_split(char *url, int w, int h, int num) {
FILE *fp = fopen(url, "rb+");
FILE *fp_y = fopen("output_420p_y.y", "wb+");
FILE *fp_u = fopen("output_420p_u.y", "wb+");
FILE *fp_v = fopen("output_420p_v.y", "wb+");

// y 占用的大小: w * h byte;
// u 占用的大小: w / 2 * h / 2 = w * h / 4 byte;
// v 占用的大小: w / 2 * h / 2 = w * h / 4 byte;
// 总大小: 1 + 1/2 = 3/2(w * h) byte
unsigned char *pic = (unsigned char*) calloc(1, w *h * 3 / 2);
for (int i = 0; i < num; ++i) {
fread(pic, 1, w * h * 3 / 2, fp);
// Y
fwrite(pic, 1, w * h / 4, fp_y);
// U
fwrite(pic, 1, w * h / 4, fp_u);
// V
fwrite(pic, 1, w * h / 4, fp_v);
}
free(pic);
fclose(fp);
fclose(fp_y);
fclose(fp_v);
fclose(fp_u);
return 0;
}

/**
* 分离yuv444p图片数据
* @param url
* @param w
* @param h
* @param num
* @return
*/
int simplest_yuv444_split(char *url, int w, int h, int num) {
FILE *fp = fopen(url, "wb+");
FILE *fp_y = fopen("output_444_y.y", "wb+");
FILE *fp_u = fopen("output_444_u.y", "wb+");
FILE *fp_v = fopen("output_444_v.y", "wb+");

unsigned char *pic = (unsigned char*)calloc(1, w * h * 3);

// yuv_444p格式
// y 占用 w * h byte
// u 占用 w * h byte
// v 占用 w * h byte
// 总的大小: w * h * 3 byte
for (int i = 0; i < num; ++i) {
fread(pic,1, w * h * 3, fp);
fwrite(pic, 1, w * h, fp_y);
fwrite(pic + w * h,1,w * h, fp_u);
fwrite(pic + w * h * 2, 1, w * h, fp_v);
}

free(pic);
fclose(fp);
fclose(fp_y);
fclose(fp_u);
fclose(fp_v);

return 0;
}


/**
* 将yuv420转成灰色
* uv都转成128便可
* @param url
* @param w
* @param h
* @param num
* @return
*/
int simplest_yuv420_gray(char *url, int w, int h, int num) {
FILE *fp = fopen(url, "wb+");
FILE *fp1 = fopen("output_gray.yuv", "wb+");
unsigned char *pic = (unsigned char*)calloc(1, w *h *3 / 2);

for (int i = 0; i < num; ++i) {
fread(pic, 1, w * h * 3 / 2, fp);
// Gray 将uv分量转成128就变成灰度图了
memset(pic +w * h, 128, w * h /2);
fwrite(pic, 1, w * h * 3 / 2, fp1);
}
free(pic);
fclose(fp);
fclose(fp1);

return 0;
}

/**
* 亮度减半
* @param url
* @param w
* @param h
* @param num
* @return
*/
int simplest_yuv_420_halfy(char *url, int w, int h, int num) {
FILE *fp = fopen(url, "rb+");
FILE *out = fopen("output_halfy.yuv", "wb+");
unsigned char *pic = (unsigned char*)malloc(w*h*3/2);

for (int i = 0; i < num; ++i) {
fread(pic, 1, w * h * 3 / 2, fp);
// y减半
for (int j = 0; j < w * h; ++j) {
unsigned char tmp = pic[j] /2;
pic[j] = tmp;
}
fwrite(pic, 1, w *h * 3/ 2, out);
}

free(pic);
fclose(fp);
fclose(out);
return 0;
}


/**
* 给y通道对应边界大小的值设为最大便可以作为边框
* @param url
* @param w
* @param h
* @param border
* @param num
* @return
*/
int simplest_yuv420_border(char *url, int w, int h, int border, int num) {
FILE *fp = fopen(url, "rb+");
FILE *out = fopen("output_boder.yuv", "wb+");
unsigned char *pic = (unsigned char*)calloc(1, w * h * 3 / 2);
for (int i = 0; i < num; ++i) {
fread(pic,1, w * h * 3/2, fp);
// y
for (int j = 0; j < h; ++j) {
for (int k = 0; k < w; ++k) {
if (k < border || k > (w - border) || j < border || j > (h - border)){
pic[j * w +k] = 255;
}
}
}
fwrite(pic, 1, w * h * 3/2, out);
}

free(pic);
fclose(fp);
fclose(out);
return 0;
}

int simplest_yuv420_graybar(int width, int height, int ymin, int ymax, int barnum, char *url_out){
int barwidth;
float lum_inc;
unsigned char lum_temp;
int uv_width, uv_height;
FILE *fp = NULL;
unsigned char *data_y = NULL;
unsigned char *data_u = NULL;
unsigned char *data_v = NULL;
int t = 0, i = 0, j = 0;

barwidth = width / barnum;
lum_inc = (float)(ymax - ymin)/((float )barnum - 1);
uv_width = width / 2;
uv_height = height / 2;

data_y = (unsigned char *)malloc(width * height);
data_u = (unsigned char *)malloc(uv_width * uv_height);
data_v = (unsigned char *)malloc(uv_width * uv_height);

if ((fp = fopen(url_out, "wb+")) == NULL) {
printf("Error: Cannot create file!\n");
return -1;
}

// 生成数据
for(j = 0;j < height;j ++) {
for(i = 0;i < width;i ++) {
t = i / barwidth;
lum_temp= ymin + (char)(t * lum_inc);
data_y[j * width + i] = lum_temp;
}
}
for(j = 0;j < uv_height;j ++){
for(i = 0;i < uv_width;i ++) {
data_u[j * uv_width + i] = 128;
data_v[j * uv_width + i] = 128;
}
}

fwrite(data_y, width * height, 1, fp);
fwrite(data_u, uv_width * uv_height, 1, fp);
fwrite(data_v, uv_height * uv_width, 1, fp);



fclose(fp);
free(data_v);
free(data_u);
free(data_y);




}

/**
* 计算两个yuv的psnr
* @param url1
* @param url2
* @param w
* @param h
* @param num
* @return
*/
int simplest_yuv420_psnr(char *url1, char *url2, int w, int h, int num) {
FILE *fp1 = fopen(url1, "rb+");
FILE *fp2 = fopen(url2, "rb+");
unsigned char *pic1 = (unsigned char*)malloc(w * h);
unsigned char *pic2 = (unsigned char*)malloc(w * h);

for (int i = 0; i < num; ++i) {
fread(pic1, 1, w *h , fp1);
fread(pic2, 1, w * h, fp2);

double mse_sum = 0, mse = 0, psnr = 0;
for (int j = 0; j < w * h; ++j) {
mse_sum += pow((double)(pic1[j] - pic2[j]), 2);
}
mse = mse_sum / (w * h);
psnr = 10 * log10(255.0 * 255.0 / mse);
printf("%5.3f\n", psnr);

fseek(fp1, w *h / 2, SEEK_CUR);
fseek(fp2, w *h / 2, SEEK_CUR);

}

free(pic1);
free(pic2);
fclose(fp1);
fclose(fp2);
return 0;
}


/**
* rbg24图片顺序为[r,g,b],[r,g,b]
* @param url
* @param w
* @param h
* @param num
* @return
*/
int simplest_rgb24_split(char *url, int w, int h, int num){
FILE *fp = fopen(url, "wb+");
FILE *fp_r = fopen("output_r.y", "wb+");
FILE *fp_g = fopen("output_g.y", "wb+");
FILE *fp_b = fopen("output_b.y", "wb+");

unsigned char *pic = (unsigned char*)malloc(w *h * 3);
for (int i = 0; i < num; ++i) {
fread(pic, 1, w * h * 3, fp);
for (int j = 0; j < w * h * 3; j = j + 3) {
// R
fwrite(pic + j, 1, 1, fp_r);
// G
fwrite(pic + j + 1, 1,1,fp_g);
// B
fwrite(pic + j + 2, 1, 1, fp_b);
}
}

free(pic);
fclose(fp);
fclose(fp_r);
fclose(fp_g);
fclose(fp_b);
}


int simplest_rgb24_to_bmp(const char *rgb24path, int width, int height, const char *bmppath) {
int i = 0,j = 0;
BITMAPFILEHEADER fHeader = {0};
BITMAPINFOHEADER iHeader = {0};
int header_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPFILEHEADER);
unsigned char *rgb24_buffer = NULL;
FILE *fp_rgb24 = NULL,*fp_bmp = NULL;

if ((fp_rgb24 = fopen(rgb24path, "rb")) == NULL){
printf("Error: Cannot open input RGB24 file.\n");
return -1;
}
if ((fp_bmp = fopen(bmppath, "wb")) == NULL) {
printf("Error: Cannot open output BMP file.\n");
fclose(fp_rgb24);
return -1;
}
rgb24_buffer = (unsigned char *)malloc(width * height * 3);
fread(rgb24_buffer, 1, width * height * 3, fp_rgb24);


fHeader.bfType = 6677;
fHeader.bfSize = header_size + width * height * 3;
fHeader.bfOffBits = header_size;

iHeader.biSize = sizeof(iHeader);
iHeader.biWidth = width;
iHeader.biHeight = height;
iHeader.biPlanes = 1;
iHeader.biBitCount = 24;

fwrite(&fHeader, 1, sizeof(BITMAPFILEHEADER), fp_bmp);
fwrite(&iHeader, 1, sizeof(BITMAPINFOHEADER), fp_bmp);

// RGB = > BGR
for (j = 0; j < height; ++j) {
for (i = 0; i < width; ++i) {
char temp = rgb24_buffer[j * width * 3 + i + 2];
// exchange
rgb24_buffer[j * width * 3 + i + 2] = rgb24_buffer[j * width * 3 + i + 0];
rgb24_buffer[j * width * 3 + i + 0] = temp;
}
}
fwrite(rgb24_buffer, width * height * 3, 1, fp_bmp);
fclose(fp_rgb24);
fclose(fp_bmp);
free(rgb24_buffer);
printf("finish generate: %s\n", bmppath);
return 0;

}


static clip_value(unsigned char x, unsigned char min_val, unsigned max_val) {
if (x > max_val) {
return max_val;
}else if(x < min_val) {
return min_val;
}else{
return x;
};
}

static int rbg24_to_yuv420(unsigned char *rgb_buf, int w, int h, unsigned char *yuv_buf) {
unsigned char *ptr_y, *ptr_u, *ptr_v, *ptr_rgb;
memset(yuv_buf, 0, w * h * 3 / 2);
ptr_y = yuv_buf;
ptr_u = yuv_buf + w * h;
ptr_v = ptr_u + (w * h * 1 / 4);
unsigned char y,u,v,r,g,b;
for (int j = 0; j < h; ++j) {
ptr_rgb = rgb_buf + w * j * 3;
for (int i = 0; i < w; ++i) {
r = *(ptr_rgb++);
g = *(ptr_rgb++);
b = *(ptr_rgb++);

y = (unsigned char)(( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
u = (unsigned char)(( -38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
v = (unsigned char)(( 112 * r - 94 * g - 18 * b + 128) >> 8) + 128;

*(ptr_y ++) = clip_value(y, 0, 255);

// u / v 在水平方向和垂直的一半
if (j % 2 == 0 && i % 2 == 0) {
*(ptr_u) = clip_value(u, 0, 255);
}else{
if (i % 2 == 0) {
*(ptr_v ++) = clip_value(v, 0, 255);
}
}
}
}
}

/**
* 转换的算法
* Y= 0.299*R+0.587*G+0.114*B
* U=-0.147*R-0.289*G+0.463*B
* V= 0.615*R-0.515*G-0.100*B
* @param url
* @param w
* @param h
* @param num
* @param _ou
* @return
*/
int simplest_rbg24_to_yuv420 (char *rgb24path, int w, int h, int num, char *yuv420path) {
FILE *fp = fopen(rgb24path, "rb+");
FILE *out = fopen(yuv420path, "wb+");
unsigned char *pic_rgb24 = (unsigned char*)malloc(w * h * 3);
unsigned char *pic_yuv420 = (unsigned char*)malloc(w * h * 3 / 2);
for (int i = 0; i < num; ++i) {
fread(pic_rgb24, 1, w * h * 3, fp);
rbg24_to_yuv420(pic_rgb24, w, h, pic_yuv420);
fwrite(pic_yuv420,1, w * h * 3 / 2, out);
}
free(pic_rgb24);
free(pic_yuv420);
fclose(fp);
fclose(out);
return 0;
}

int simplest_rgb24_colorbar(int w, int h, char *out_url) {
unsigned char *data = NULL;
int barwidth;
FILE *fp = NULL;
if ((fp = fopen(out_url, "wb+")) == NULL) {
printf("Error: Cannot create file.\n");
return -1;
}
data = (unsigned char*)malloc(w * h * 3);
barwidth = w / 8;
for (int j = 0; j < h; ++j) {
for (int i = 0; i < w; ++i) {
int barnum = i / barwidth;
switch (barnum) {
case 0:
{
data[(j * w + i) * 3 + 0] = 255;
data[(j * w + i) * 3 + 1] = 255;
data[(j * w + i) * 3 + 2] = 255;
break;
}
case 1:{
data[(j * w + i) * 3 + 0] = 255;
data[(j * w + i) * 3 + 1] = 255;
data[(j * w + i) * 3 + 2] = 0;
break;
}
case 2:
{
data[(j * w + i) * 3 + 0] = 255;
data[(j * w + i) * 3 + 1] = 255;
data[(j * w + i) * 3 + 2] = 255;
break;
}
case 3:
{
data[(j * w + i) * 3 + 0] = 0;
data[(j * w + i) * 3 + 1] = 255;
data[(j * w + i) * 3 + 2] = 255;
break;
}
case 4:
{
data[(j * w + i) * 3 + 0] = 0;
data[(j * w + i) * 3 + 1] = 255;
data[(j * w + i) * 3 + 2] = 0;
break;
}
case 5:
{
data[(j * w + i) * 3 + 0] = 255;
data[(j * w + i) * 3 + 1] = 0;
data[(j * w + i) * 3 + 2] = 255;
break;
}
case 6:
{
data[(j * w + i) * 3 + 0] = 255;
data[(j * w + i) * 3 + 1] = 0;
data[(j * w + i) * 3 + 2] = 0;
break;
}
case 7:
{
data[(j * w + i) * 3 + 0] = 0;
data[(j * w + i) * 3 + 1] = 0;
data[(j * w + i) * 3 + 2] = 255;
break;
}
}
}
}

fwrite(data, w * h * 3 ,1, fp);
fclose(fp);
free(data);
}

视音频数据处理入门: H.264视频码流解析

H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的。他们的结构如下图所示。

每个NALU之间通过startcode进行分隔

1
startcode NALU startcode NALU startcode NALU

startcode有两种0x000001(3byte)和0x00000001(4byte)。两个起始码可以分隔出NALU。

NALU Header

NALU头部固定为2个字节。结构和占用的大小如下

1
2
3
4
5
6
nal_unit_header {
forbidden_zero_bit // 1个bit,必须为0,避免和MPEG-2冲突
nal_unit_type // 6个bit,当前NALU的类型
nah_layer_id // 6个bit,保留
nah_temporal_id_plus1 // 3bit,值减1为该NALU时域层标号。 TemporalId = nuh_temporal_id_plus1 - 1
}

解析DEMO

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
typedef enum {
NALU_TYPE_SLICE = 1,
NALU_TYPE_DPA = 2,
NALU_TYPE_DPB = 3,
NALU_TYPE_DPC = 4,
NALU_TYPE_IDR = 5,
NALU_TYPE_SEI = 6,
NALU_TYPE_SPS = 7,
NALU_TYPE_PPS = 8,
NALU_TYPE_AUD = 9,
NALU_TYPE_EOSEQ = 10,
NALU_TYPE_EOSTREAM = 11,
NALU_TYPE_FILL = 12,
} NaluType;

typedef enum {
NALU_PRIORITY_DISPOSABLE = 0,
NALU_PRIRITY_LOW = 1,
NALU_PRIORITY_HIGH = 2,
NALU_PRIORITY_HIGHEST = 3
}NaluPriority;

FILE *h264bitstream = NULL;

int info2 = 0, info3 = 0;

// startcode == 0x000001 ? 1 : 0
static int FindStartCode2(const unsigned char *buf) {
if (buf[0] != 0 || buf[1] != 0 || buf[2] != 1) return 0;
return 1;
}

// startcode == 0x00000001 ? 1 : 0
static int FindStartCode3(const unsigned char *buf) {
if (buf[0] != 0 || buf[1] != 0 || buf[2] != 0 || buf[3] != 1) return 0;
return 1;
}

typedef struct {
int startcodeprefix_len; // startcoder长裤
unsigned len; // nalu长度,不包括starcode
unsigned max_size; // NAL Unit Buffer size
int forbiden_bit; // always FALSE
int nal_reference_idc; // NALU_PRIORITY_xxx
int nal_unit_type; // NALU_TYPE_xxxx
char *buf; // EBSP内容
}NALU_t;

int GetAnnexbNALU(NALU_t *nalu) {
int pos = 0;
int StartCodeFound, rewind;
unsigned char *buf;
if ((buf = (unsigned char*)calloc(nalu->max_size,sizeof(char))) == NULL){
printf("GetAnnexbNALU:Could not allocate buf memory\n");
}
nalu->startcodeprefix_len = 3;
if (3 != fread(buf, 1, 3, h264bitstream)) {
free(buf);
return 0;
}
info2 = FindStartCode2(buf);
if (1 != info2) {
if (1 != fread(buf + 3, 1, 1,h264bitstream)){
free(buf);
return 0;
}
info3 = FindStartCode3(buf);
if (1 != info3) {
free(buf);
return -1;
}else{
pos = 4;
nalu->startcodeprefix_len = 4;
}
}else{
// found
nalu->startcodeprefix_len = 3;
pos = 3;
}
StartCodeFound = 0;
info2 = 0;
info3 = 0;
while (!StartCodeFound) {
if (feof(h264bitstream)) {
nalu->len = (pos - 1) - nalu->startcodeprefix_len;
memcpy(nalu->buf, &buf[nalu->startcodeprefix_len], nalu->len);
nalu->forbiden_bit = nalu->buf[0] & 0x80;
nalu->nal_reference_idc = nalu->buf[0] & 0x60;
nalu->nal_unit_type = (nalu->buf[0]) & 0x1f;
free(buf);
return pos - 1;
}
buf[pos ++] = fgetc(h264bitstream);
info3 = FindStartCode3(&buf[pos - 4]);
if (1 != info3) {
info2 = FindStartCode2(&buf[pos - 3]);
}
StartCodeFound = (info2 == 1 || info3 == 1);
}


rewind = (info3 == 1)? -4 : -3;
if (0 != fseek(h264bitstream,rewind, SEEK_CUR)) {
free(buf);
printf("GetAnnexbNALU: Cannot Fseek in the bit stream file\n");
}

nalu->len = (pos + rewind) - nalu->startcodeprefix_len;
memcpy(nalu->buf, &buf[nalu->startcodeprefix_len], nalu->len);
nalu->forbiden_bit = nalu->buf[0] & 0x80; //1 bit
nalu->nal_reference_idc = nalu->buf[0] & 0x60; // 2 bit
nalu->nal_unit_type = (nalu->buf[0]) & 0x1f;// 5 bit
free(buf);

return pos + rewind;
}

/**
* 解析H264码流
* @param url
* @return
*/
int simplest_h264_parser(char* url) {
NALU_t *n;
int buffersize = 100000;

FILE *myout = stdout;

h264bitstream = fopen(url, "rb+");
if (NULL == h264bitstream) {
printf("Open file %s err\n", url);
return 0;
}

n = (NALU_t *)calloc(1, sizeof(NALU_t));
if (NULL == n) {
printf("alloc NALU Error\n");
return 0;
}

n -> max_size = buffersize;
n -> buf = (char *)calloc(buffersize, sizeof(char));
if (NULL == n->buf) {
free(n);
printf("Alloc NALU buf failed.\n");
return 0;
}
int data_offset = 0;
int nal_num = 0;

printf("-----+-------- NALU Table ------+---------+\n");
printf(" NUM | POS | IDC | TYPE | LEN |\n");
printf("-----+---------+--------+-------+---------+\n");
// 解析
while (!feof(h264bitstream)) {
int data_length;

data_length = GetAnnexbNALU(n);
char type_str[20]={0};
switch(n->nal_unit_type){
case NALU_TYPE_SLICE:sprintf(type_str,"SLICE");break;
case NALU_TYPE_DPA:sprintf(type_str,"DPA");break;
case NALU_TYPE_DPB:sprintf(type_str,"DPB");break;
case NALU_TYPE_DPC:sprintf(type_str,"DPC");break;
case NALU_TYPE_IDR:sprintf(type_str,"IDR");break;
case NALU_TYPE_SEI:sprintf(type_str,"SEI");break;
case NALU_TYPE_SPS:sprintf(type_str,"SPS");break;
case NALU_TYPE_PPS:sprintf(type_str,"PPS");break;
case NALU_TYPE_AUD:sprintf(type_str,"AUD");break;
case NALU_TYPE_EOSEQ:sprintf(type_str,"EOSEQ");break;
case NALU_TYPE_EOSTREAM:sprintf(type_str,"EOSTREAM");break;
case NALU_TYPE_FILL:sprintf(type_str,"FILL");break;
}
char idc_str[20]={0};
switch(n->nal_reference_idc>>5){
case NALU_PRIORITY_DISPOSABLE:sprintf(idc_str,"DISPOS");break;
case NALU_PRIRITY_LOW:sprintf(idc_str,"LOW");break;
case NALU_PRIORITY_HIGH:sprintf(idc_str,"HIGH");break;
case NALU_PRIORITY_HIGHEST:sprintf(idc_str,"HIGHEST");break;
}

fprintf(myout,"%5d| %8d| %7s| %6s| %8d|\n",nal_num,data_offset,idc_str,type_str,n->len);

data_offset=data_offset+data_length;
nal_num++;

}


if (n) {
if (n->buf) {
free(n->buf);
n->buf = NULL;
}
free(n);
}
return 0;
}

FFMPEG使用命令

使用ffmpeg提取视频关键帧

ffmpeg -i video.m4s -vf select='eq(pic_type\,I)' -vsync 2 -f image2 core-%02d.jpeg

从MP4文件中提取H264

ffmpeg -i ./2153.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 tmp.264

列出windows系统输入输出设备

ffmpeg -list_devices true -f dshow -i dummy
结果:

1
2
3
4
5
6
7
8
[dshow @ 00000296de63df00] DirectShow video devices (some may be both video and audio devices)
[dshow @ 00000296de63df00] "Chicony USB2.0 Camera"
[dshow @ 00000296de63df00] Alternative name "@device_pnp_\\?\usb#vid_04f2&pid_b59e&mi_00#6&20cea5e4&0&0000#
8773d-8f56-11d0-a3b9-00a0c9223196}\global"
[dshow @ 00000296de63df00] DirectShow audio devices
[dshow @ 00000296de63df00] "麦克风 (Realtek High Definition Audio)"
[dshow @ 00000296de63df00] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{CE21CA
78A-4B05-B3E9-46CA93B1CC09}"
读取摄像头的数据

使用上一个步骤生成的结果
ffplay -f dshow -i video="Chicony USB2.0 Camera"
除了使用dshow之外,还可以使用vfwcap播放摄像头数据
ffplay -f vfwcap -i 0
查看设备选项,使用list_options选项
ffmpeg -list_options true -f dshow -i video="Chicony USB2.0 Camera"
输出

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
[dshow @ 0000026aed70e080] DirectShow video device options (from video devices)
[dshow @ 0000026aed70e080] Pin "捕获" (alternative pin name "0")
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=1280x720 fps=30 max s=1280x720 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=1280x720 fps=30 max s=1280x720 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=640x480 fps=30 max s=640x480 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=640x480 fps=30 max s=640x480 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=640x360 fps=30 max s=640x360 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=640x360 fps=30 max s=640x360 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=352x288 fps=30 max s=352x288 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=352x288 fps=30 max s=352x288 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=320x240 fps=30 max s=320x240 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=320x240 fps=30 max s=320x240 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=176x144 fps=30 max s=176x144 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=176x144 fps=30 max s=176x144 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=160x120 fps=30 max s=160x120 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=160x120 fps=30 max s=160x120 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=1280x720 fps=30 max s=1280x720 fps=30
[dshow @ 0000026aed70e080] vcodec=mjpeg min s=1280x720 fps=30 max s=1280x720 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=1280x720 fps=10 max s=1280x720 fps=10
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=1280x720 fps=10 max s=1280x720 fps=10
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=640x480 fps=30 max s=640x480 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=640x480 fps=30 max s=640x480 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=640x360 fps=30 max s=640x360 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=640x360 fps=30 max s=640x360 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=352x288 fps=30 max s=352x288 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=352x288 fps=30 max s=352x288 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=320x240 fps=30 max s=320x240 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=320x240 fps=30 max s=320x240 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=176x144 fps=30 max s=176x144 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=176x144 fps=30 max s=176x144 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=160x120 fps=30 max s=160x120 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=160x120 fps=30 max s=160x120 fps=30
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=1280x720 fps=10 max s=1280x720 fps=10
[dshow @ 0000026aed70e080] pixel_format=yuyv422 min s=1280x720 fps=10 max s=1280x720 fps=10
video=Chicony USB2.0 Camera: Immediate exit requested

跟据这些选项设置摄像头输出参数

  • 设置摄像头分辨率
    ffplay -s 320x240 -f dshow -i video="Chicony USB2.0 Camera"

  • 摄像头数据编码成H.264,发布UDP
    ffmpeg -f dshow -i video="Chicony USB2.0 Camera" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f h264 udp://localhost:6666
    使用-preset:v ultrafast -tune:v zerolatency提高编码速度。

  • 编码为H.264,发布RTP
    ffmpeg -f dshow -i video="Chicony USB2.0 Camera" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency rtp rtp://localhost:6666 > test.sdp
    推流的同时生成sdp文件

  • 摄像头编码为H.264,发布RTMP
    ffmpeg -f dshow -i video="Chicony USB2.0 Camera" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -f flv rtmp://xxxxx.xxx.xx

  • 编码为MPEG2,发布UDP
    ffmpeg -f dshow -i video="Chicony USB2.0 Camera" -vcodec mpeg2video -f mpeg2video udp://localhost:6666

  • 播放MPEG2的UDP流
    ffplay -vcodec mpeg2video udp://localhost:6666

读取屏幕数据
  • 简单的录制
    ffmpeg -f gdigrab -i desktop out.mpg

  • 从屏幕的(10,20)点处开始,抓取640x480的屏幕,设定帧率为5
    ffmpeg -f gdigrab -framerate 5 -offset_x 10 -offset_y 20 -video_size 640x480 -i desktop out.mpg

  • 将屏幕录制后编码成H.264文件
    ffmpeg -f gdigrab -i desktop -r 5 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency Desktop.mkv
    -r 5表示帧率为5

  • 屏幕录制的时候加上话筒声音
    ffmpeg -f gdigrab -i desktop -f dshow -i audio="麦克风 (Realtek High Definition Audio)" -r 5 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec libmp3lame MyDesktop.mkv

  • 屏幕送流和摄像头送流类似
    ffmpeg -f gdigrab -i desktop -r 5 -vcodec mpeg2video -f mpeg2video udp://localhost:6666

  • 抓取屏幕特定窗口
    ffmpeg -f gdigrab -i title="C:\WINDOWS\system32\cmd.exe" -r 5 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency Desktop.mkv

使用上述的方法生成H264流无法正常传送,需要重新设置sps,pps和编码的格式,才顺利的在手机端播放。得先转成yuv,再使用yuv编成h264,这样就可以顺利的使用mediacodec进行编码。

Share 

 Previous post: OpenSSL生成SSL证书 Next post: github有趣项目 

© 2025 long

Theme Typography by Makito

Proudly published with Hexo