摘要
这篇文章介绍使用FFmpeg实现视频解码,具体操作为读取上一节视频解码所生成的YUV420p格式视频文件,对其进行H.264格式视频编码,并将编码后的数据保存为H.264裸流文件Sample.h264。
初始化FFmpeg
所有操作之前必须先注册FFmpeg组件实现全局初始化。
void VideoEncoding::init()
{
avcodec_register_all();
}
配置编解码器CodecContext
- 这里要进行H.264格式的视频编码,所以要先手动指定编码器名字为
libx264
。更多可用编码器可以使用命令ffmpeg -encoders
查看。 - 接着调用
avcodec_alloc_context3
函数申请CodecContext。 - 然后手动给CodecContext配置各种编码参数。
- 最后调用
avcodec_open2
完成编码器配置。
// Configure AVCodecContext parameters Manually
bool VideoEncoding::initCodecContext()
{
const AVCodec *enc = avcodec_find_encoder_by_name("libx264");
//const AVCodec *enc = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!enc) {
fprintf(stderr, "Failed to find encoder\n");
return true;
}
mCodecCtx = avcodec_alloc_context3(enc);
if (!mCodecCtx) {
printf("Failed to allocate the codec context\n");
return true;
}
// 根据实际情况修改编码器参数
// 这里只进行编码操作,所以分辨率应和原始文件相同
mCodecCtx->width = 1280; //与YUV文件分辨率一致。
mCodecCtx->height = 534;
mCodecCtx->bit_rate = 1000000; //码率1Mbps
mCodecCtx->gop_size = 10;
mCodecCtx->time_base = { 1, 24 };
mCodecCtx->framerate = { 24, 1 }; //帧率
mCodecCtx->max_b_frames = 1;
mCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; //像素格式YUV420p
// 根据需要选择编码器预设编码模式
if (enc->id == AV_CODEC_ID_H264) {
//此模式编码比较慢,会先缓冲几帧后再进行编码
//av_opt_set(mCodecCtx->priv_data, "preset", "slow", 0); // delay ~18 frames
//这种模式编码速度比较快,没有延时
av_opt_set(mCodecCtx->priv_data, "tune", "zerolatency", 0); // no delay
}
// Initialize mCodecCtx to use the given Codec
if (avcodec_open2(mCodecCtx, enc, NULL) < 0) {
printf("Failed to open codec\n");
return true;
}
return false;
}
读取视频、视频编码、写入文件
- 先申请一个buffer用来存放从YUV文件读取到的frame数据。
- 与解码写文件操作相逆,按照格式把文件数据循环装到多个frame结构中。我们使用的是YUV420p格式的数据。
- 使用
avcodec_send_frame
和avcodec_receive_packet
这一对函数进行视频编码,发送一个未编码的frame,接收一个编码后的packet。 - 最后把编码后的packet依次写入文件。
需要注意的是,使用不同的编码预设参数会导致编码的速度。也就是说avcodec_send_frame
发送一个frame到编码器,可能使用avcodec_receive_packet
不能立即接收到一个编码后的packet。所以在发送完所有的frame之后,循环接收编码器返回的编码数据,直接所有的数据接收完毕。最后写入几个字节的H264裸流标识。
bool VideoEncoding::readFrameProc(const char * input, const char * output)
{
FILE *yuvFd = fopen(input, "rb"); //输入的YUV420p文件Sample.yuv。
FILE *outFd = fopen(output, "wb"); //输出文件名Sample.h264。
if (!outFd || !yuvFd) {
fprintf(stderr, "Could not open file\n");
return true;
}
AVFrame *frame = NULL;
//申请一个AVFrame
if (!(frame = av_frame_alloc())) {
printf("Failed to allocate video frame\n");
return true;
}
//使用CodecContext设置AVFrame参数
frame->format = mCodecCtx->pix_fmt; //上一步设置的像素格式 AV_PIX_FMT_YUV420P
frame->width = mCodecCtx->width;
frame->height = mCodecCtx->height;
//为frame申请存储视频的data空间
if (av_frame_get_buffer(frame, 32) < 0) {
printf("Failed to allocate the video frame data\n");
return true;
}
int num = 0, i = 0;
AVPacket pkt;
// read a frame every time
while (!feof(yuvFd)) {
av_init_packet(&pkt);
pkt.data = NULL; // packet data will be allocated by the encoder
pkt.size = 0;
if (av_frame_make_writable(frame)) {
return true;
}
// 读取yuv420p视频文件到frame.data
fread(frame->data[0], 1, mCodecCtx->width *mCodecCtx->height, yuvFd);
fread(frame->data[1], 1, mCodecCtx->width*mCodecCtx->height / 4, yuvFd);
fread(frame->data[2], 1, mCodecCtx->width*mCodecCtx->height / 4, yuvFd);
frame->pts = i++;
//视频编码:发送frame,接收packet
avcodec_send_frame(mCodecCtx, frame);
int ret = avcodec_receive_packet(mCodecCtx, &pkt);
if (!ret) {
printf("Write frame %3d (size=%5d)\n", i, pkt.size);
fwrite(pkt.data, 1, pkt.size, outFd);
av_packet_unref(&pkt);
}
printf("------------------------------------\n");
}
// 编码有延时,获取延时的数据
for (;; i++) {
//持续接收编码器发送过来的数据
avcodec_send_frame(mCodecCtx, NULL);
int ret = avcodec_receive_packet(mCodecCtx, &pkt);
if (ret == 0) {
printf("Write frame %3d (size=%5d)\n", i, pkt.size);
fwrite(pkt.data, 1, pkt.size, outFd);
av_packet_unref(&pkt);
}
else if (ret == AVERROR_EOF) { //接收完毕
printf("Write frame complete\n");
break;
}
else {
printf("Error encoding frame\n");
break;
}
}
//看别人都加上这个标识了(#^.^#)
uint8_t endcode[] = { 0, 0, 1, 0xb7 };
/* add sequence end code to have a real MPEG file */
fwrite(endcode, 1, sizeof(endcode), outFd);
fclose(outFd);
fclose(yuvFd);
av_frame_free(&frame);
return false;
}
释放系统资源
最后要释放相关的资源。
VideoEncoding::~VideoEncoding()
{
avcodec_free_context(&mCodecCtx);
}
示例程序代码
上述示例的完整代码可以从Github下载: https://github.com/lmshao/FFmpeg-Basic 。