最简单的基于 FFmpeg 的编码器 - 纯净版(不包含 libavformat)
- 最简单的基于 FFmpeg 的视频编码器(YUV 编码为 HEVC(H.265))
- 正文
- 结果
- 工程文件下载
最简单的基于 FFmpeg 的视频编码器(YUV 编码为 HEVC(H.265))
参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的编码器-纯净版(不包含libavformat)
正文
本文记录一个更加“纯净”的基于 FFmpeg 的视频编码器。此前记录过一个基于 FFmpeg 的视频编码器:最简单的基于 FFmpeg 的视频编码器(YUV 编码为 HEVC(H.265))。
这个视频编码器调用了 FFmpeg 中的 libavformat 和 libavcodec 两个库完成了视频编码工作。但是这不是一个“纯净”的编码器。上述两个库中 libavformat 完成封装格式处理,而 libavcodec 完成编码工作。一个“纯净”的编码器,理论上说只需要使用 libavcodec 就足够了,并不需要使用 libavformat。
本文记录的编码器就是这样的一个“纯净”的编码器,它仅仅通过调用 libavcodec 将 YUV 数据编码为 H.264/HEVC 等格式的压缩视频码流。
仅使用 libavcodec(不使用 libavformat)编码视频的流程如下图所示:
流程图中关键函数的作用如下所列:
- avcodec_register_all():注册所有的编解码器。
- avcodec_find_encoder():查找编码器。
- avcodec_alloc_context3():为 AVCodecContext 分配内存。
- avcodec_open2():打开编码器。
- avcodec_encode_video2():编码一帧数据。
两个存储数据的结构体如下所列:
- AVFrame:存储一帧未编码的像素数据。
- AVPacket:存储一帧压缩编码数据。
简单记录一下这个只使用 libavcodec 的“纯净版”视频编码器和使用 libavcodec+libavformat 的视频编码器的不同。
- 下列与libavformat相关的函数在“纯净版”视频编码器中都不存在:
- av_register_all():注册所有的编解码器,复用/解复用器等等组件。其中调用了 * avcodec_register_all()注册所有编解码器相关的组件。
- avformat_alloc_context():创建 AVFormatContext 结构体。
- avformat_alloc_output_context2():初始化一个输出流。
- avio_open():打开输出文件。
- avformat_new_stream():创建 AVStream 结构体。avformat_new_stream() 中会调用 avcodec_alloc_context3() 创建 AVCodecContext 结构体。
- avformat_write_header():写文件头。
- av_write_frame():写编码后的文件帧。
- av_write_trailer():写文件尾。
- 新增了如下几个函数:
- avcodec_register_all():只注册编解码器有关的组件。
- avcodec_alloc_context3():创建 AVCodecContext 结构体。
可以看出,相比于“完整”的编码器,这个纯净的编码器函数调用更加简单,功能相对少一些,相对来说更加的“轻量”。
源代码:
// Simplest FFmpeg Video Encoder - Pure.cpp : 定义控制台应用程序的入口点。
///**
* 最简单的基于 FFmpeg 的视频编码器(纯净版)
* Simplest FFmpeg Video Encoder Pure
*
* 源程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本程序实现了 YUV 像素数据编码为视频码流(H264,MPEG2,VP8 等等)。
* 它仅仅使用了 libavcodec(而没有使用 libavformat)。
* 是最简单的 FFmpeg 视频编码方面的教程。
* 通过学习本例子可以了解 FFmpeg 的编码流程。
*
* This software encode YUV420P data to video bitstream
* (Such as H.264, H.265, VP8, MPEG2 etc).
* It only uses libavcodec to encode video (without libavformat)
* It's the simplest video encoding software based on FFmpeg.
* Suitable for beginner of FFmpeg
*/#include "stdafx.h"#include <stdio.h>
#include <stdlib.h>// 解决报错:fopen() 函数不安全
#pragma warning(disable:4996)// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}#define __STDC_CONSTANT_MACROS#ifdef _WIN32
// Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif// test different codec
#define TEST_H264 0
#define TEST_HEVC 1int main(int argc, char* argv[])
{AVCodec *pCodec;AVCodecContext *pCodecCtx = NULL;AVFrame *pFrame;AVPacket pkt;FILE *fp_in;FILE *fp_out;int ret;int got_output = 0;int y_size;int i = 0;int framecnt = 0;char filename_in[] = "ds_480x272.yuv";#if TEST_HEVCAVCodecID codec_id = AV_CODEC_ID_HEVC;char filename_out[] = "ds.hevc";
#elseAVCodecID codec_id = AV_CODEC_ID_H264;char filename_out[] = "ds.h264";
#endifconst int in_width = 480, in_height = 272; // Input data's width and heightint framenum = 100; // Frames to encode avcodec_register_all();pCodec = avcodec_find_encoder(codec_id);if (!pCodec){// 没有找到合适的编码器printf("Can't find encoder.\n");return -1;}pCodecCtx = avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf("Can't allocate video codec context.\n");return -1;}pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;pCodecCtx->width = in_width;pCodecCtx->height = in_height;pCodecCtx->bit_rate = 400000;pCodecCtx->gop_size = 10;pCodecCtx->time_base.num = 1;pCodecCtx->time_base.den = 25;// H.264// pCodecCtx->me_range = 16;// pCodecCtx->max_qdiff = 4;// pCodecCtx->qcompress = 0.6;// pCodecCtx->qmin = 10;// pCodecCtx->qmax = 51;// Optional ParampCodecCtx->max_b_frames = 1;if (codec_id == AV_CODEC_ID_H264){av_opt_set(pCodecCtx->priv_data, "preset", "slow", 0);}ret = avcodec_open2(pCodecCtx, pCodec, NULL);if (ret < 0){// 编码器打开失败printf("Failed to open encoder.\n");return -1;}pFrame = av_frame_alloc();if (!pFrame){printf("Can't allocate video frame.\n");return -1;}pFrame->format = pCodecCtx->pix_fmt;pFrame->width = pCodecCtx->width;pFrame->height = pCodecCtx->height;ret = av_image_alloc(pFrame->data, pFrame->linesize, pCodecCtx->width, pCodecCtx->height,pCodecCtx->pix_fmt, 16);if (ret < 0){printf("Can't allocate raw picture buffer.\n");return -1;}// Input raw datafp_in = fopen(filename_in, "rb");if (!fp_in){printf("Can't open %s.\n", filename_in);return -1;}// Output bitstreamfp_out = fopen(filename_out, "wb");if (!fp_out){printf("Can't open %s.\n", filename_out);return -1;}y_size = pCodecCtx->width * pCodecCtx->height;// Encodefor (i = 0; i < framenum; i++){av_init_packet(&pkt);pkt.data = NULL; // packet data will be allocated by the encoderpkt.size = 0;// Read raw YUV dataif (fread(pFrame->data[0], 1, y_size, fp_in) <= 0 ||fread(pFrame->data[1], 1, y_size / 4, fp_in) <= 0 ||fread(pFrame->data[2], 1, y_size / 4, fp_in) <= 0){return -1;}else if (feof(fp_in)){break;}// PTSpFrame->pts = i;// Encode the frameret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_output);if (ret < 0){printf("Error encoding frame.\n");return -1;}if (got_output){printf("Succeed to encode frame: %5d\tsize:%5d.\n", framecnt, pkt.size);framecnt++;fwrite(pkt.data, 1, pkt.size, fp_out);av_free_packet(&pkt);}}// Flush Encoderfor (got_output = 1; got_output; i++){ret = avcodec_encode_video2(pCodecCtx, &pkt, NULL, &got_output);if (ret < 0){printf("Error encoding frame.\n");return -1;}if (got_output){printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d.\n", pkt.size);fwrite(pkt.data, 1, pkt.size, fp_out);av_free_packet(&pkt);}}fclose(fp_out);avcodec_close(pCodecCtx);av_free(pCodecCtx);av_freep(&pFrame->data[0]);av_frame_free(&pFrame);system("pause");return 0;
}
结果
通过设定定义在程序开始的宏,确定需要使用的编码器。
// test different codec
#define TEST_H264 0
#define TEST_HEVC 1
输入文件是“ds_480x272.yuv”,如下图所示:
当 TEST_H264 设置为 1 的时候,编码 H.264 文件“ds.h264”。
程序运行的截图如下所示:
输出的 H.264 文件如下图所示:
当 TEST_HEVC 设置为 1 的时候,解码 HEVC 文件“ds.hevc”。
程序运行的截图如下所示:
输出的 HEVC 文件如下图所示:
工程文件下载
GitHub:UestcXiye / Simplest-FFmpeg-Video-Encoder-Pure
CSDN:Simplest FFmpeg Video Encoder - Pure.zip