/**
* 最简单的基于FFmpeg的视频编码器
* Simplest FFmpeg Video Encoder
*
* 雷霄骅 Lei Xiaohua
* [email protected]
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序实现了YUV像素数据编码为视频码流(H264,MPEG2,VP8等等)。
* 是最简单的FFmpeg视频编码方面的教程。
* 通过学习本例子可以了解FFmpeg的编码流程。
* This software encode YUV420P data to H.264 bitstream.
* It's the simplest video encoding software based on FFmpeg.
* Suitable for beginner of FFmpeg
*/
#include
#include
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#ifdef __cplusplus
};
#endif
#endif
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame){
ret=0;
break;
}
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
int main(int argc, char* argv[])
{
AVFormatContext* pFormatCtx;
AVOutputFormat* fmt;
AVStream* video_st;
AVCodecContext* pCodecCtx;
AVCodec* pCodec;
AVPacket pkt;
uint8_t* picture_buf;
AVFrame* pFrame;
int picture_size;
int framecnt=0;
FILE *in_file = fopen("/home/file/ds_480x272.yuv", "rb"); //Input raw YUV data
int in_w=352,in_h=288; //Input data's width and height
int framenum=100; //Frames to encode
char out_file[40];
if(argc < 2)
{
printf("error,please input encode file format\n");
return -1;
}
av_register_all();
strncpy(out_file,argv[1],sizeof(argv[1]));
//Method 2.
avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
fmt = pFormatCtx->oformat;
//Open output URL
if (avio_open2(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE,NULL,NULL) < 0){
printf("Failed to open output file! \n");
return -1;
}
pCodec = avcodec_find_encoder(fmt->video_codec);
if (!pCodec){
printf("Can not find encoder! \n");
return -1;
}
video_st = avformat_new_stream(pFormatCtx, pCodec);
if (video_st==NULL){
return -1;
}
video_st->time_base.num = 1;
video_st->time_base.den = 30;
pCodecCtx = video_st->codec;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 30;
pCodecCtx->bit_rate = 512000;
pCodecCtx->gop_size= 60;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;
pCodecCtx->max_b_frames= 0;
// Set Option
AVDictionary *param = 0;
//H.264
if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "profile", "main", 0);
}
//H.265
if((pCodecCtx->codec_id == AV_CODEC_ID_HEVC)||(pCodecCtx->codec_id == AV_CODEC_ID_H265)){
av_dict_set(¶m, "x265-params", "crf=25", 0);
av_dict_set(¶m, "preset", "fast", 0);
av_dict_set(¶m, "tune", "zero-latency", 0);
}
if (avcodec_open2(pCodecCtx, NULL,¶m) < 0){
printf("Failed to open encoder! \n");
return -1;
}
pFrame = av_frame_alloc();
picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
picture_buf = (uint8_t *)av_malloc(picture_size);
avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
//Write File Header
avformat_write_header(pFormatCtx,NULL);
av_new_packet(&pkt,picture_size);
av_dump_format(pFormatCtx, 0, out_file, 1);
for (int i=0; i //Read raw YUV data
if (fread(picture_buf, 1, picture_size, in_file) <= 0){
printf("Failed to read raw data! \n");
return -1;
}else if(feof(in_file)){
break;
}
//PTS
pFrame->pts=i;
int got_picture=0;
//Encode
int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);
if(ret < 0){
printf("Failed to encode! \n");
return -1;
}
if (got_picture==1){
printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);
framecnt++;
pkt.stream_index = video_st->index;
ret = av_write_frame(pFormatCtx, &pkt);
av_free_packet(&pkt);
}
}
//Flush Encoder
int ret = flush_encoder(pFormatCtx,0);
if (ret < 0) {
printf("Flushing encoder failed\n");
return -1;
}
//Write file trailer
av_write_trailer(pFormatCtx);
//Clean
if (video_st){
avcodec_close(video_st->codec);
av_free(pFrame);
av_free(picture_buf);
}
//avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
fclose(in_file);
return 0;
}
以上代码是在雷神基础上做了少量改动,使用他的程序对编码流程进行分析,原文地址点击打开链接
程序刚开始先调用av_register_all,注册所有的muxer和编解码。av_register_all函数调用register_all()函数开进行注册。
void av_register_all(void)
{
static AVOnce control = AV_ONCE_INIT;
ff_thread_once(&control, register_all);
}
register_all()在最开始的是调用avcodec_register_all注册编解码器。注册函数根据configure自动生成的config.h中的宏,比如来注册libopenH264编码器。把所有的编解码器注册完成之后。开始注册muxer。
#define CONFIG_LIBOPENH264_ENCODER 1
#define REGISTER_ENCODER(X, x) \
{ \
extern AVCodec ff_##x##_encoder; \
if (CONFIG_##X##_ENCODER) \
avcodec_register(&ff_##x##_encoder); \
}
#define REGISTER_DECODER(X, x) \
{ \
extern AVCodec ff_##x##_decoder; \
if (CONFIG_##X##_DECODER) \
avcodec_register(&ff_##x##_decoder); \
}
avcodec_register()把所有的编码器添加到last_avcodec链表中。
av_cold void avcodec_register(AVCodec *codec)
{
AVCodec **p;
avcodec_init();
p = last_avcodec;
codec->next = NULL;
while(*p || avpriv_atomic_ptr_cas((void * volatile *)p, NULL, codec))
p = &(*p)->next;
last_avcodec = &codec->next;
if (codec->init_static_data)
codec->init_static_data(codec);
}
注册muxer和注册编解码器差不多,也是通过config.h中的宏选择是否注册。然后调用av_register_output_format()函数把所有的muxer.
#define CONFIG_H264_MUXER 1
#define REGISTER_MUXER(X, x) \
{ \
extern AVOutputFormat ff_##x##_muxer; \
if (CONFIG_##X##_MUXER) \
av_register_output_format(&ff_##x##_muxer); \
}
static void register_all(void)
{
avcodec_register_all();
/* (de)muxers */
REGISTER_MUXER (A64, a64);
REGISTER_DEMUXER (AA, aa);
...
}
AVFormatContext *avformat_alloc_context(void)
{
AVFormatContext *ic;
ic = av_malloc(sizeof(AVFormatContext));
if (!ic) return ic;
avformat_get_context_defaults(ic);
ic->internal = av_mallocz(sizeof(*ic->internal));
if (!ic->internal) {
avformat_free_context(ic);
return NULL;
}
ic->internal->offset = AV_NOPTS_VALUE;
ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
ic->internal->shortest_end = AV_NOPTS_VALUE;
return ic;
}
static void avformat_get_context_defaults(AVFormatContext *s)
{
memset(s, 0, sizeof(AVFormatContext));
s->av_class = &av_format_context_class;
s->io_open = io_open_default;
s->io_close = io_close_default;
av_opt_set_defaults(s);
}
初始化之后,在没有指定输出格式时,会根据你输入的文件名或者文件后缀名在已经注册的muxer中去选择最合适的输出格式。遍历玩链表完后,选择匹配系数最高的muxer。并且赋值给avctx中的oformat。
int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat,
const char *format, const char *filename)
{
AVFormatContext *s = avformat_alloc_context();
int ret = 0;
*avctx = NULL;
if (!s)
goto nomem;
if (!oformat) {
if (format) {
oformat = av_guess_format(format, NULL, NULL);
if (!oformat) {
av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
ret = AVERROR(EINVAL);
goto error;
}
} else {
oformat = av_guess_format(NULL, filename, NULL);
if (!oformat) {
ret = AVERROR(EINVAL);
av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
filename);
goto error;
}
}
}
s->oformat = oformat;
if (s->oformat->priv_data_size > 0) {
s->priv_data = av_mallocz(s->oformat->priv_data_size);
if (!s->priv_data)
goto nomem;
if (s->oformat->priv_class) {
*(const AVClass**)s->priv_data= s->oformat->priv_class;
av_opt_set_defaults(s->priv_data);
}
} else
s->priv_data = NULL;
if (filename)
av_strlcpy(s->filename, filename, sizeof(s->filename));
*avctx = s;
return 0;
nomem:
av_log(s, AV_LOG_ERROR, "Out of memory\n");
ret = AVERROR(ENOMEM);
error:
avformat_free_context(s);
return ret;
}
然后是调用avio_open2,根据你输入的文件名,选择一个合适的协议。
int avio_open2(AVIOContext **s, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options)
{
return ffio_open_whitelist(s, filename, flags, int_cb, options, NULL, NULL);
}
ffurl_alloc会根据你输出的文件名在protocol_list中选择使用哪种协议。protocol_list中的协议数目是可以在configure配置,通过以下的选项进行使能和关闭。必要时可以通过关闭不使用的协议来达到精简库大小的目的。
--enable-protocol=NAME enable protocol NAME
--disable-protocol=NAME disable protocol NAME
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb)
{
const URLProtocol *p = NULL;
p = url_find_protocol(filename);
if (p)
return url_alloc_for_protocol(puc, p, filename, flags, int_cb);
*puc = NULL;
if (av_strstart(filename, "https:", NULL))
av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
"openssl, gnutls "
"or securetransport enabled.\n");
return AVERROR_PROTOCOL_NOT_FOUND;
}
创建一路新的stream,并且根据AVCodec对码流中的codec进行初始化。如果是第一次调用,nb_streams为0,每次调用,都会在基础上累加,最大不超过s->max_streams,max_streams默认是1000。
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
{
AVStream *st;
int i;
AVStream **streams;
if (s->nb_streams >= FFMIN(s->max_streams, INT_MAX/sizeof(*streams))) {
if (s->max_streams < INT_MAX/sizeof(*streams))
av_log(s, AV_LOG_ERROR, "Number of streams exceeds max_streams parameter (%d), see the documentation if you wish to increase it\n", s->max_streams);
return NULL;
}
streams = av_realloc_array(s->streams, s->nb_streams + 1, sizeof(*streams));
if (!streams)
return NULL;
s->streams = streams;
st = av_mallocz(sizeof(AVStream));
if (!st)
return NULL;
if (!(st->info = av_mallocz(sizeof(*st->info)))) {
av_free(st);
return NULL;
}
st->info->last_dts = AV_NOPTS_VALUE;
#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
st->codec = avcodec_alloc_context3(c);
if (!st->codec) {
av_free(st->info);
av_free(st);
return NULL;
}
FF_ENABLE_DEPRECATION_WARNINGS
#endif
st->internal = av_mallocz(sizeof(*st->internal));
if (!st->internal)
goto fail;
st->codecpar = avcodec_parameters_alloc();
if (!st->codecpar)
goto fail;
st->internal->avctx = avcodec_alloc_context3(NULL);
if (!st->internal->avctx)
goto fail;
if (s->iformat) {
#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
/* no default bitrate if decoding */
st->codec->bit_rate = 0;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
/* default pts setting is MPEG-like */
avpriv_set_pts_info(st, 33, 1, 90000);
/* we set the current DTS to 0 so that formats without any timestamps
* but durations get some timestamps, formats with some unknown
* timestamps have their first few packets buffered and the
* timestamps corrected before they are returned to the user */
st->cur_dts = RELATIVE_TS_BASE;
} else {
st->cur_dts = AV_NOPTS_VALUE;
}
st->index = s->nb_streams;
st->start_time = AV_NOPTS_VALUE;
st->duration = AV_NOPTS_VALUE;
st->first_dts = AV_NOPTS_VALUE;
st->probe_packets = MAX_PROBE_PACKETS;
st->pts_wrap_reference = AV_NOPTS_VALUE;
st->pts_wrap_behavior = AV_PTS_WRAP_IGNORE;
st->last_IP_pts = AV_NOPTS_VALUE;
st->last_dts_for_order_check = AV_NOPTS_VALUE;
for (i = 0; i < MAX_REORDER_DELAY + 1; i++)
st->pts_buffer[i] = AV_NOPTS_VALUE;
st->sample_aspect_ratio = (AVRational) { 0, 1 };
#if FF_API_R_FRAME_RATE
st->info->last_dts = AV_NOPTS_VALUE;
#endif
st->info->fps_first_dts = AV_NOPTS_VALUE;
st->info->fps_last_dts = AV_NOPTS_VALUE;
st->inject_global_side_data = s->internal->inject_global_side_data;
st->internal->need_context_update = 1;
s->streams[s->nb_streams++] = st;
return st;
fail:
free_stream(&st);
return NULL;
}
初始化编码器,在初始化之前,可以通过av_dict_set(¶m, "profile", "main", 0);设置一些编码器参数,比如bitrate。
// Set Option
AVDictionary *param = 0;
//H.264
if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "profile", "main", 0);
}
if (avcodec_open2(pCodecCtx, NULL,¶m) < 0){
printf("Failed to open encoder! \n");
return -1;
}
获取一帧图片的需要多少内存
int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height)
{
return av_image_get_buffer_size(pix_fmt, width, height, 1);
}
把pFrame里面的data绑定给picture_buf,根据pix_fmt,width,height来分配y,u,v各个通道的地址和大小
avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4],
const uint8_t *src, enum AVPixelFormat pix_fmt,
int width, int height, int align)
{
int ret, i;
ret = av_image_check_size(width, height, 0, NULL);
if (ret < 0)
return ret;
ret = av_image_fill_linesizes(dst_linesize, pix_fmt, width);
if (ret < 0)
return ret;
for (i = 0; i < 4; i++)
dst_linesize[i] = FFALIGN(dst_linesize[i], align);
return av_image_fill_pointers(dst_data, pix_fmt, height, (uint8_t *)src, dst_linesize);
}
根据输入的pix_fmt来填充yuv每个分量的行长(跨距)
int av_image_fill_linesizes(int linesizes[4], enum AVPixelFormat pix_fmt, int width)
{
int i, ret;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
int max_step [4]; /* max pixel step for each plane */
int max_step_comp[4]; /* the component for each plane which has the max pixel step */
memset(linesizes, 0, 4*sizeof(linesizes[0]));
if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
return AVERROR(EINVAL);
av_image_fill_max_pixsteps(max_step, max_step_comp, desc);
for (i = 0; i < 4; i++) {
if ((ret = image_get_linesize(width, i, max_step[i], max_step_comp[i], desc)) < 0)
return ret;
linesizes[i] = ret;
}
return 0;
}
根据height和linesize来计算以及pix_fmt来计算每个元素的大小,并把data指针指向ptr,返回一帧输入图片的总大小
int av_image_fill_pointers(uint8_t *data[4], enum AVPixelFormat pix_fmt, int height,
uint8_t *ptr, const int linesizes[4])
{
int i, total_size, size[4] = { 0 }, has_plane[4] = { 0 };
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
memset(data , 0, sizeof(data[0])*4);
if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
return AVERROR(EINVAL);
data[0] = ptr;
if (linesizes[0] > (INT_MAX - 1024) / height)
return AVERROR(EINVAL);
size[0] = linesizes[0] * height;
if (desc->flags & AV_PIX_FMT_FLAG_PAL ||
desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL) {
data[1] = ptr + size[0]; /* palette is stored here as 256 32 bits words */
return size[0] + 256 * 4;
}
for (i = 0; i < 4; i++)
has_plane[desc->comp[i].plane] = 1;
total_size = size[0];
for (i = 1; i < 4 && has_plane[i]; i++) {
int h, s = (i == 1 || i == 2) ? desc->log2_chroma_h : 0;
data[i] = data[i-1] + size[i-1];
h = (height + (1 << s) - 1) >> s;
if (linesizes[i] > INT_MAX / h)
return AVERROR(EINVAL);
size[i] = h * linesizes[i];
if (total_size > INT_MAX - size[i])
return AVERROR(EINVAL);
total_size += size[i];
}
return total_size;
}
avformat_write_header函数初始化了muxer。在初始化muxer的时候,因为H264支持重排,所以它的pts和dts可以不一样。然后调用了write_header_internal函数,这个函数会去检验创建了几条码流。如果是保存到文件,只能允许创建一路码流。
int avformat_write_header(AVFormatContext *s, AVDictionary **options)
{
int ret = 0;
int already_initialized = s->internal->initialized;
int streams_already_initialized = s->internal->streams_initialized;
if (!already_initialized)
if ((ret = avformat_init_output(s, options)) < 0)
return ret;
if (!(s->oformat->check_bitstream && s->flags & AVFMT_FLAG_AUTO_BSF)) {
ret = write_header_internal(s);
if (ret < 0)
goto fail;
}
if (!s->internal->streams_initialized) {
if ((ret = init_pts(s)) < 0)
goto fail;
if (s->avoid_negative_ts < 0) {
av_assert2(s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO);
if (s->oformat->flags & (AVFMT_TS_NEGATIVE | AVFMT_NOTIMESTAMPS)) {
s->avoid_negative_ts = 0;
} else
s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE;
}
}
return streams_already_initialized;
fail:
if (s->oformat->deinit)
s->oformat->deinit(s);
return ret;
}
创建一个picture_size大小的包
av_new_packet(&pkt,picture_size);
因为H264muxer的flags只有AVFMT_NOTIMESTAMPS,所以是调用write_packet(s, pkt);去把码流写到文件中,虽然最后write_packet也是调用muxer里write_packet
int av_write_frame(AVFormatContext *s, AVPacket *pkt)
{
int ret;
ret = prepare_input_packet(s, pkt);
if (ret < 0)
return ret;
if (!pkt) {
if (s->oformat->flags & AVFMT_ALLOW_FLUSH) {
if (!s->internal->header_written) {
ret = s->internal->write_header_ret ? s->internal->write_header_ret : write_header_internal(s);
if (ret < 0)
return ret;
}
ret = s->oformat->write_packet(s, NULL);
flush_if_needed(s);
if (ret >= 0 && s->pb && s->pb->error < 0)
ret = s->pb->error;
return ret;
}
return 1;
}
ret = do_packet_auto_bsf(s, pkt);
if (ret <= 0)
return ret;
#if FF_API_COMPUTE_PKT_FIELDS2 && FF_API_LAVF_AVCTX
ret = compute_muxer_pkt_fields(s, s->streams[pkt->stream_index], pkt);
if (ret < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
return ret;
#endif
ret = write_packet(s, pkt);
if (ret >= 0 && s->pb && s->pb->error < 0)
ret = s->pb->error;
if (ret >= 0)
s->streams[pkt->stream_index]->nb_frames++;
return ret;
}
int ff_raw_write_packet(AVFormatContext *s, AVPacket *pkt)
{
avio_write(s->pb, pkt->data, pkt->size);
return 0;
}
h264的write_packet函数可以选择直接写到文件也可以先写到数组里,等数组满了再写到文件。如果想要直接写到文件,在avio_open2函数里flag参数要加上AVIO_FLAG_DIRECT
void avio_write(AVIOContext *s, const unsigned char *buf, int size)
{
if (s->direct && !s->update_checksum) {
avio_flush(s);
writeout(s, buf, size);
return;
}
while (size > 0) {
int len = FFMIN(s->buf_end - s->buf_ptr, size);
memcpy(s->buf_ptr, buf, len);
s->buf_ptr += len;
if (s->buf_ptr >= s->buf_end)
flush_buffer(s);
buf += len;
size -= len;
}
}
最后是flush_encode,这个不是必须的。比如libopenh264的capabilities没有CODEC_CAP_DELAY,所以不需要flush_encode
AVCodec ff_libopenh264_encoder = {
.name = "libopenh264",
.long_name = NULL_IF_CONFIG_SMALL("OpenH264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
.type = AVMEDIA_TYPE_VIDEO,
.id = AV_CODEC_ID_H264,
.priv_data_size = sizeof(SVCContext),
.init = svc_encode_init,
.encode2 = svc_encode_frame,
.close = svc_encode_close,
.capabilities = AV_CODEC_CAP_AUTO_THREADS,
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_INIT_CLEANUP,
.pix_fmts = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUV420P,
AV_PIX_FMT_NONE },
.priv_class = &class,
};