FFmpeg 开发——将视频 YUV 格式编码成 H264

首先开始的时候我们插入一张雷神大大的图帮助大家理解一下我们今天的操作究竟属于那一步 。

FFmpeg 开发——将视频 YUV 格式编码成 H264

文章插图
 
从上图可以看出我们要做的,就是将像素层的 YUV 格式,编码出编码层的 h264数据 。
首先熟悉一下今天我们要用到的 ffmpeg 中的函数和结构体
  • AVFormatContext: 数据文件操作者,主要是用于存储音视频封装格式中包含的信息, 在工程当中占着具足轻重的地位,因为很多函数都要用到它作为参数 。同时,它也是我们进行解封装的功能结构体 。
  • AVOutputFormat: 输出的格式,包括音频封装格式、视频装格式、字幕封装格式,所有封装格式都在 AVCodecID 这个枚举类型上面了
  • AVStream: 一个装载着视频/音频流信息的结构体,包括音视频流的长度,元数据信息,其中 index 属性用于标识视频/音频流 。
  • AVCodecContext: 这个结构体十分庞大,但它的主要是用于编码使用的,结构体中的的 AVCodec *codec 就是编码所采用的编码器器, 当然,这个结构体中要存入视频的基本参数,例如宽高等,存入音频的基本参数,声道,采样率等 。
  • AVCodec:编码器,设置编码类型,像素格式,视频宽高,fps(每秒帧数), 用于编解码音视频编码层使用 。
  • AVIOContext:用于管理输入输出结构体 。例如解码的情况下,将一个视频文件中的数据先从硬盘中读入到结构体中的 buffer 中,然后送给解码器用于解码,后面我们会用到 。
  • AVFrame: 结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息 。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据 。编码的时候也存储了相关的数据 。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体 。
好了,上面就是我们这次解封装用到的结构体的大概解析,那么我们就上代码,好好分析一番 。
1、先取个霸气点的函数名,通过输入一个 yuv 文件路径,然后将文件数据进行编码,输出 H264文件 。
yuvCodecToVideoH264(const char *input_file_name)2、打开输入的 yuv 文件, 并设置我们 h264 文件的输出路径,
FILE *in_file = fopen(input_file, "rb");// 因为我们在 IOS 工程当中,所以输出路径当然要设置本机的路径了const char* out_file = [[NSTemporaryDirectory() stringByAppendingPathComponent:@"dash.h264"] cStringUsingEncoding:NSUTF8StringEncoding];3、获取 yuv 视频中的信息
// 注册 ffmpeg 中的所有的封装、解封装 和 协议等,当然,你也可用以下两个函数代替// * @see av_register_input_format()// * @see av_register_output_format() av_register_all();//用作之后写入视频帧并编码成 h264,贯穿整个工程当中AVFormatContext* pFormatCtx;pFormatCtx = avformat_alloc_context();// 通过这个函数可以获取输出文件的编码格式, 那么这里我们的 fmt 为 h264 格式(AVOutputFormat *)fmt = av_guess_format(NULL, out_file, NULL);pFormatCtx->oformat = fmt;4、将输出文件中的数据读入到程序的 buffer 当中,方便之后的数据写入,也可以说缓存数据写入
// 打开文件的缓冲区输入输出,flags 标识为AVIO_FLAG_READ_WRITE ,可读写if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){printf("Failed to open output file! n");return;}5、创建流媒体数据,规范流媒体的编码格式,设置视频流的 fps
AVStream* video_st;// 通过媒体文件控制者获取输出文件的流媒体数据,这里 AVCodec * 写 0 , 默认会为我们计算出合适的编码格式video_st = avformat_new_stream(pFormatCtx, 0);// 设置 25 帧每秒 ,也就是 fps 为 25video_st->time_base.num = 1;video_st->time_base.den = 25;if (video_st==NULL){return ;}6、为输出文件设置编码所需要的参数和格式
// 用户存储编码所需的参数格式等等AVCodecContext* pCodecCtx;// 从媒体流中获取到编码结构体,他们是一一对应的关系,一个 AVStream 对应一个AVCodecContext pCodecCtx = video_st->codec;// 设置编码器的 id,每一个编码器都对应着自己的 id,例如 h264 的编码 id 就是 AV_CODEC_ID_H264pCodecCtx->codec_id = fmt->video_codec;// 设置编码类型为 视频编码pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;// 设置像素格式为 yuv 格式pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// 设置视频的宽高pCodecCtx->width = 480;pCodecCtx->height = 720;// 设置比特率,每秒传输多少比特数 bit,比特率越高,传送速度越快,也可以称作码率,// 视频中的比特是指由模拟信号转换为数字信号后,单位时间内的二进制数据量 。pCodecCtx->bit_rate = 400000;// 设置图像组层的大小 。// 图像组层是在 MPEG 编码器中存在的概念,图像组包 若干幅图像, 组头包 起始码、GOP 标志等,如视频磁带记录器时间、控制码、B 帧处理码等;pCodecCtx->gop_size=250;// 设置 25 帧每秒 ,也就是 fps 为 25pCodecCtx->time_base.num = 1;pCodecCtx->time_base.den = 25;//设置 H264 中相关的参数//pCodecCtx->me_range = 16;//pCodecCtx->max_qdiff = 4;//pCodecCtx->qcompress = 0.6;pCodecCtx->qmin = 10;pCodecCtx->qmax = 51;// 设置 B 帧最大的数量,B帧为视频图片空间的前后预测帧, B 帧相对于 I、P 帧来说,压缩率比较大,也就是说相同码率的情况下,// 越多 B 帧的视频,越清晰,现在很多打视频网站的高清视频,就是采用多编码 B 帧去提高清晰度,// 但同时对于编解码的复杂度比较高,比较消耗性能与时间pCodecCtx->max_b_frames=3;// 可选设置AVDictionary *param = 0;//H.264if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {// 通过--preset的参数调节编码速度和质量的平衡 。av_dict_set(¶m, "preset", "slow", 0);// 通过--tune的参数值指定片子的类型,是和视觉优化的参数,或有特别的情况 。// zerolatency: 零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码av_dict_set(¶m, "tune", "zerolatency", 0);


推荐阅读