0%

FFmpeg实战 YUV

命令生成YUV:

1
2
3
4
5
6
7
8
9
10
11
ffmpeg -i input.mp4 \
-an \
-c:v rawvideo \
-pix_fmt yuv420p out.yuv

# 提取各分量
ffmpeg -i input.mp4 \
-filter_complex 'extractplanes=y+u+v[y][u][v]' \
-map '[y]' y.yuv \
-map '[u]' u.yuv \
-map '[v]' v.yuv

播放YUV:

1
2
3
4
5
6
7
ffplay -pix_fmt yuv420p -s 宽x高 out.yuv

# 只播放Y分量
ffplay -pix_fmt yuv420p -s 宽x高 -vf extractplanes='y' out.yuv

# 播放各分量文件,y是全尺寸,u、v分量要使用一般的分辨率
ffplay -pix_fmt gray -s 宽x高 out.yuv

播放速度会不一样。

  • c:v,视频编解码器
  • vf,简单滤镜/滤波
  • filter_complex,复杂滤波器

代码实现

可以在音频采集的基础上,增加:

  • 修改设备名称
  • 增加参数
  • 修改文件名及文件数据的大小
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
// 打开设备
void open_device() {
av_log_set_level(AV_LOG_DEBUG);

// 小于零则出错
int ret = 0;
// <video device>:<audio device>
// 0,本机摄像头;1,桌面
char *device_name = "0";

// 设置摄像头选项
AVDictionary *options = NULL;
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "framerate", "30", 0);
av_dict_set(&options, "pixel_format", "nv12", 0); // FFmpeg会默认指定YUV420P,但会不支持会切换到摄像头支持的第一种格式,mac的摄像头只支持uyvy422、yuyv422、nv12、0rgb、bgr0

// 1 注册设备
avdevice_register_all();

// 2 获取格式
AVInputFormat *inputFormat = av_find_input_format("avfoundation");

// 3 打开设备。这会同时创建上下文。
ret = avformat_open_input(&fmt_ctx, device_name, inputFormat, &options);
if (ret < 0 || !fmt_ctx) {
goto __ERROR;
}
printf("成功打开视频设备\n");

return;
__ERROR:
log_error(ret);
return;
}

/// 采集视频并写入文件
void read_video() {
if (!fmt_ctx) {
printf("不能使用设备\n");
goto __ERROR;
}

/// 准备文件
// 创建文件,权限:w(写入)b(写入二进制)+(若文件不存在则创建)
FILE *output_yuv = fopen("/Users/bq/Movies/test/video.yuv", "wb+");
if (!output_yuv) {
printf("文件创建失败\n");
}

// 使用栈空间分配AVPacket
AVPacket pkt;
av_init_packet(&pkt);

int count = 0;
int ret = 0;

while ((ret == 0 || ret == AVERROR(EAGAIN)) && count < 100) {
// 读取视频数据
ret = av_read_frame(fmt_ctx, &pkt);

if (ret == AVERROR(EAGAIN)) continue;
printf("[%d] pkt size is %d\n", count, pkt.size);
count++;

/**
* NV12 -> YUV420
* NV12: YYYYYYYYUVUV
* YUV420: YYYYYYYYUUVV
* */

// 拷贝数据到输入
const size_t y_length = kWidth * kHeight;
const size_t u_v_length = y_length / 4;
// 拷贝Y数据
memcpy(frame->data[0], pkt.data, y_length);
// 处理UV,Y数据后面是UV,对YUV数据分层
const int stride = 2;
for (int i = 0; i < u_v_length; i++) {
const size_t base = y_length + i * stride;
frame->data[1][i] = pkt.data[base];
frame->data[2][i] = pkt.data[base + 1];
}
// 输出YUV
fwrite(frame->data[0], 1, y_length, output_yuv);
fwrite(frame->data[1], 1, u_v_length, output_yuv);
fwrite(frame->data[2], 1, u_v_length, output_yuv);

av_packet_unref(&pkt);
}

printf("完成写入\n");

// 这里不写return,会一直执行下去
__ERROR:
// 关闭文件
fclose(output_yuv);
}

欢迎关注我的其它发布渠道