分析FFMPEG中H264编码流程

xiaoxiao2021-02-28  42

/**  * 最简单的基于FFmpeg的视频编码器  * Simplest FFmpeg Video Encoder  *   * 雷霄骅 Lei Xiaohua  * leixiaohua1020@126.com  * 中国传媒大学/数字电视技术  * 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 <stdio.h> #include <string.h> #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:]\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<framenum; 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: ]\tsize:]\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); ... } avformat_alloc_output_context2这个函数先申请内存,并根据av_format_context_class这里面的avformat_options对ic进行初始化 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, };

转载请注明原文地址: https://www.6miu.com/read-2632679.html

最新回复(0)