使用FFmpeg库把YUV420P文件编码为H264文件,FFmpeg版本为4.4.2-0。
需要yuv测试文件的,可以从我上传的MP4文件中用ffmpeg提取,命令如下:
bash
ffmpeg -i <输入MP4文件名> -pix_fmt yuv420p <输出YUV文件名>
例如:
ffmpeg -i input.mp4 -pix_fmt yuv420p output.yuv
代码如下:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/mem.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
int main(int argc, char *argv[])
{
int ret = -1;
int frame_index = 0;
const char *input_file = argv[1];
const char *output_file = argv[2];
FILE *input_yuv = NULL;
AVFormatContext *format_context = NULL;
AVCodecContext *codec_context = NULL;
AVStream *stream = NULL;
AVCodec *codec = NULL;
AVFrame *frame = NULL;
AVPacket *packet = NULL;
const AVOutputFormat *ofmt = NULL;
int in_width = 1280; // 输入YUV文件的宽度
int in_height = 720; // 输入YUV文件的高度
enum AVPixelFormat in_pix_fmt = AV_PIX_FMT_YUV420P; // 输入YUV文件的像素格式
int frame_rate = 24; // 输出视频帧率
if (argc < 3)
{
fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
return -1;
}
input_yuv = fopen(input_file, "rb");
if (!input_yuv)
{
fprintf(stderr, "open input file failed\n");
goto end;
}
// 分配输出格式上下文
avformat_alloc_output_context2(&format_context, NULL, NULL, output_file);
if (!format_context)
{
fprintf(stderr, "avformat_alloc_output_context2 failed\n");
goto end;
}
ofmt = format_context->oformat;
// 查找编码器
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
goto end;
}
printf("codec name: %s\n", codec->name);
// 创建新的视频流
stream = avformat_new_stream(format_context, NULL);
if (!stream)
{
fprintf(stderr, "avformat_new_stream failed\n");
goto end;
}
// 分配编码器上下文
codec_context = avcodec_alloc_context3(codec);
if (!codec_context)
{
fprintf(stderr, "avcodec_alloc_context3 failed\n");
goto end;
}
/* 设置编码器参数 */
codec_context->codec_id = AV_CODEC_ID_H264;
codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
codec_context->width = in_width;
codec_context->height = in_height;
codec_context->time_base = (AVRational){1, frame_rate}; // 设置时间基
codec_context->framerate = (AVRational){frame_rate, 1}; // 设置帧率
codec_context->bit_rate = 1456 * 1000; // 设置比特率
codec_context->gop_size = frame_rate; // 设置GOP大小
codec_context->max_b_frames = 1; // 设置最大B帧数,不需要B帧时设置为0
av_opt_set(codec_context->priv_data, "profile", "main", 0); // 设置h264画质级别
// 打开编码器
if (avcodec_open2(codec_context, codec, NULL) < 0)
{
fprintf(stderr, "avcodec_open2 failed\n");
goto end;
}
// 将编码器参数复制到流
int result = avcodec_parameters_from_context(stream->codecpar, codec_context);
if (result < 0)
{
fprintf(stderr, "avcodec_parameters_from_context failed\n");
goto end;
}
// 打开输出文件
if (!(ofmt->flags & AVFMT_NOFILE)) // 检查输出格式是否需要文件存储
{
result = avio_open(&format_context->pb, output_file, AVIO_FLAG_WRITE);
if (result < 0)
{
fprintf(stderr, "open output file failed\n");
goto end;
}
}
// 写文件头
result = avformat_write_header(format_context, NULL);
if (result < 0)
{
fprintf(stderr, "avformat_write_header failed\n");
goto end;
}
frame = av_frame_alloc();
packet = av_packet_alloc();
if (!frame || !packet)
{
fprintf(stderr, "allocate frame or packet failed\n");
goto end;
}
// 设置帧参数
frame->format = codec_context->pix_fmt;
frame->width = codec_context->width;
frame->height = codec_context->height;
// 分配帧数据缓冲区
result = av_frame_get_buffer(frame, 0);
if (result < 0)
{
fprintf(stderr, "av_frame_get_buffer failed\n");
goto end;
}
// 计算输入缓冲区大小
int input_buffer_size = av_image_get_buffer_size(in_pix_fmt, in_width, in_height, 1);
uint8_t *input_buffer = (uint8_t *)av_malloc(input_buffer_size);
if (!input_buffer)
{
fprintf(stderr, "av_malloc failed\n");
goto end;
}
// 编码循环
while (1)
{
// 读取YUV数据到缓冲区
result = fread(input_buffer, 1, input_buffer_size, input_yuv);
if (result <= 0)
{
break;
}
// 填充帧数据
av_image_fill_arrays(frame->data, frame->linesize, input_buffer, in_pix_fmt, in_width, in_height, 1);
frame->pts = frame_index;
frame_index++;
// 发送帧到编码器
result = avcodec_send_frame(codec_context, frame);
if (result < 0)
{
fprintf(stderr, "avcodec_send_frame error (errmsg '%s')\n", av_err2str(result));
goto end;
}
// 接收编码后的数据包
while (result >= 0)
{
result = avcodec_receive_packet(codec_context, packet);
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF)
{
break;
}
else if (result < 0)
{
fprintf(stderr, "avcodec_receive_packet error (errmsg '%s')\n", av_err2str(result));
goto end;
}
packet->stream_index = stream->index;
// 将时间戳从编码器时间基转换到流时间基
av_packet_rescale_ts(packet, codec_context->time_base, stream->time_base);
// 写数据包到输出文件
result = av_interleaved_write_frame(format_context, packet);
if (result < 0)
{
fprintf(stderr, "av_interleaved_write_frame failed\n");
av_packet_unref(packet);
goto end;
}
av_packet_unref(packet);
}
}
av_free(input_buffer);
// 发送NULL帧到编码器,刷新编码器内部缓冲区
result = avcodec_send_frame(codec_context, NULL);
while (result >= 0)
{
result = avcodec_receive_packet(codec_context, packet);
if (result == AVERROR_EOF)
{
break;
}
else if (result < 0)
{
fprintf(stderr, "avcodec_receive_packet error (errmsg '%s')\n", av_err2str(result));
goto end;
}
packet->stream_index = stream->index;
av_packet_rescale_ts(packet, codec_context->time_base, stream->time_base);
result = av_interleaved_write_frame(format_context, packet);
if (result < 0)
{
fprintf(stderr, "av_interleaved_write_frame failed\n");
av_packet_unref(packet);
goto end;
}
av_packet_unref(packet);
}
// 写文件尾
av_write_trailer(format_context);
ret = 0;
end:
if (frame)
av_frame_free(&frame);
if (packet)
av_packet_free(&packet);
if (codec_context)
avcodec_free_context(&codec_context);
if (ofmt && !(ofmt->flags & AVFMT_NOFILE))
avio_close(format_context->pb);
if (format_context)
avformat_free_context(format_context);
if (input_yuv)
fclose(input_yuv);
return ret;
}