在用libx264做h264压缩的时候,我们可以通过命令 ffmpeg -h encoder=libx264 来查看它所支持的输入格式
Encoder libx264 [libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10]:
General capabilities: delay threads
Threading capabilities: auto
Supported pixel formats: yuv420p yuvj420p yuv422p yuvj422p yuv444p yuvj444p nv12 nv16 nv21
yuv422p不是YUY2, 可以看见我从USB摄像头直接取出的数据YUY2是不能直接当作输入格式的。(采集YUY2数据查看上一篇文章https://my.oschina.net/noevilme/blog/4462358)
常有3种方案来做转换:
- 自己按像素点做转换
- 使用libyuv
- 使用ffmpeg swscale
我推荐第2种和第3种, 大佬们的算法都有优化的,除非你觉得自己更牛X。据江湖传言libyuv是swscale的几倍速度,我没测过哈。
转换前先了解一下YUY2和I420数据格式,话说YUV各种名称实在有点乱,参考http://fourcc.org/yuv.php
YUY2, YUYV, YUV422 这三个都是YUY2的别称,ffmpeg定义AV_PIX_FMT_YUYV422。
Y U Y V Y U Y V
Y U Y V Y U Y V
Y U Y V Y U Y V
Y U Y V Y U Y V
Y U Y V Y U Y V
Y U Y V Y U Y V
Y U Y V Y U Y VI420, IYUV, YUV420P, YU12, 前面这几个都是I420的名字,其中YUV420P又是几个格式的统称,特定环境下就是I420,ffmpeg定义AV_PIX_FMT_YUV420P。
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
U U U U U U U U
V V V V V V V V
一、使用libyuv::YUY2ToI420
libyuv需要自己编译安装https://github.com/lemenkov/libyuv
// Convert YUY2 to I420.
LIBYUV_API
int YUY2ToI420(const uint8_t* src_yuy2,
int src_stride_yuy2,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_u,
int dst_stride_u,
uint8_t* dst_v,
int dst_stride_v,
int width,
int height);
上面是接口定义,stride参数是指图片格式行距。
int libyuv_convert(const char *input_file, const char *output_file, int width,
int height) {
FILE *in_fp = fopen(input_file, "rb");
if (!in_fp) {
std::cout << "open input failure" << std::endl;
return 1;
}
FILE *out_fp = fopen(output_file, "wb");
if (!out_fp) {
std::cout << "open output failure" << std::endl;
return 1;
}
uint8_t *yuy2_image = new uint8_t[width * height * 2];
uint8_t *i420_image = new uint8_t[width * height * 3 / 2];
while (fread(yuy2_image, 1, width * height * 2, in_fp) ==
(size_t)width * height * 2) {
uint8_t *i420_y = i420_image;
uint8_t *i420_u = i420_y + width * height;
uint8_t *i420_v = i420_u + width * height / 4;
libyuv::YUY2ToI420(yuy2_image, width * 2, i420_y, width, i420_u,
width / 2, i420_v, width / 2, width, height);
fwrite(i420_image, 1, width * height * 3 / 2, out_fp);
}
delete[] i420_image;
delete[] yuy2_image;
fclose(in_fp);
fclose(out_fp);
return 0;
}
二、使用sws_scale
swscale库是ffmpeg的一部分,所以在操作的时候用AVFrame结构更加方便,不然得自己定义一个二维数组。
int ffmpeg_convert2(const char *input_file, const char *output_file, int width,
int height) {
SwsContext *context;
FILE *in_fp, *out_fp;
in_fp = fopen(input_file, "rb");
if (!in_fp) {
std::cout << "open input failure" << std::endl;
return 1;
}
out_fp = fopen(output_file, "wb");
if (!out_fp) {
std::cout << "open out file failure" << std::endl;
fclose(in_fp);
return 1;
}
context = sws_getContext(width, height, AV_PIX_FMT_YUYV422, width, height,
AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, nullptr,
nullptr, nullptr);
AVFrame *av_frame_in = av_frame_alloc();
auto yuy2_image_size =
av_image_alloc(av_frame_in->data, av_frame_in->linesize, width, height,
AV_PIX_FMT_YUYV422, 1);
AVFrame *av_frame_out = av_frame_alloc();
auto i420_image_size =
av_image_alloc(av_frame_out->data, av_frame_out->linesize, width,
height, AV_PIX_FMT_YUV420P, 1);
while (fread(av_frame_in->data[0], 1, yuy2_image_size, in_fp) ==
(size_t)yuy2_image_size) {
sws_scale(context, av_frame_in->data, av_frame_in->linesize, 0, height,
av_frame_out->data, av_frame_out->linesize);
fwrite(av_frame_out->data[0], 1, i420_image_size, out_fp);
}
sws_freeContext(context);
av_freep(&av_frame_in->data[0]);
av_freep(&av_frame_out->data[0]);
av_frame_free(&av_frame_in);
av_frame_free(&av_frame_out);
fclose(in_fp);
fclose(out_fp);
return 0;
}
调用函数
#include <cstdio>
#include <iostream>
#include <libyuv/convert_from.h>
#include <libyuv/cpu_id.h>
#include <libyuv/planar_functions.h>
#include <libyuv/row.h>
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libyuv.h>
#include <libyuv/convert.h>
};
int main(int argc, char **argv) {
const char *input_file =
"1280x720_yuy2.yuv";
const char *output_file = "yuv_1280x720_i420.yuv";
libyuv_convert(input_file, output_file, 1280, 720);
ffmpeg_convert2(input_file, "ff_1280x720_i420.yuv", 1280, 720);
return 0;
}