前言
由于这个领域我以后不打算深入,而且课题也只是交个毕设而已,所以代码中有些地方图方便直接开了全局变量,这样破坏了ffmpeg原有的封装结构。这门这代码只作学习和理论实践使用,千万别拿这个直接去投入开源使用,如果想跟着我改的朋友也请给原ffmpeg代码留好备份。所有代码会等毕设结题后公开。
正文
在上一篇文章中我们介绍了H264相关的知识和基于帧内预测模式的算法。这一片我们会具体在ffmpeg与x264中实现上一篇文章中2.4中所描述的算法
1. ffmpeg与x264对于一帧的处理
这个ffmpeg与x264的更多内容请参考雷霄骅的博客,我们这里直接讲需要了解的结构体和函数。
1.1 ffmpeg中几个结构体的概念
AVFormatContext:记录多媒体文件格式的相关信息,一般来说对应一个多媒体文件的信息。
AVInputFormat/AVOutputFormat:描述一种多媒体格式的信息,一般来说对应一种多媒体文件的信息,如MP4,FLV,MP3等。
AVStream:多媒体文件中的一段多媒体流。比如我们一般的视频文件里面有视频流,音频流,甚至是字幕流等等,每个流都会对应一个AVCodecContext。
AVCodecContext:这是个比较重要的结构体,比较抽象,大致记录的编解码器的一些特征,比如有几个声道,视频宽高等等。还会存一部分需要编解码的数据。可以看做视频画面与编解码器中间的一个过渡用的结构体。每个AVCodecContext都对应一个AVCodec
AVCodec:实实在在的被实例化编解码器。其中包含编解码类别,需要编解码的信息,编解码所用的函数指针等等。
AVFrame:视频中一帧画面,包括画面的yuv信息,以及对应需要播放的音频或字母等,用于直接播放的数据结构。
AVPacket:一个基本的数据包,可以解码成AVFrame。一个AVFrame可能由多个AVPacket解码组成,但是一个AVPacket最多包含一个AVFrame
1.2 部分函数调用结构
int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame, int *got_packet_ptr):
将一个AVFrame编码成AVPacket,优先装进AVCodecContext。如果AVCodecContet装不下了,会弹出一个AVPacketd到avpkt,并将got_packet_ptr置1。这是最外层的编码API函数。
int AVCodecContext*->codec->encode2(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame, int *got_packet_ptr)
位于avcodec_encode_video2中,通过 ret = avctx->codec->encode2(avctx, avpkt, frame, got_packet_ptr);调用
这个是AVCodecContext中对应Codec的编码函数,encode2是一个函数指针,指向编码函数。
实际上就是将视频信息交给编码器后调用编码器的编码函数。
static int X264_frame(AVCodecContext *ctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
这个其实就是AVCodecContext*->codec->encode2所指向的函数(编码器为H264编码器)
可以发现这个函数位于libx264.c中,尚且还是在ffmpeg中的函数,但是在libx264.c中include的头文件中已经发现了x264的头文件,也就是说这里就是ffmpeg调用x264的分界点了。
int x264_encoder_encode( x264_t *h, x264_nal_t **pp_nal, int *pi_nal, x264_picture_t *pic_in, x264_picture_t *pic_out )
x264的最外层API,用于将一帧画面编码成H264格式的一帧,这个函数很长。我们只直在里面找到x264_slices_write()这个函数
x264_slices_write():一边编码一边写入帧的数据(与之对应的还有些文件头和写文件尾的函数)
x264_stack_align( x264_slice_write, h ):这是x264_slices_write()中的一句话,可以理解成x264_slice_write(h)。函数原型是static intptr_t x264_slice_write( x264_t *h ),这样写是为了字节对齐。这个函数写一帧的数据。在里面我们能找到x264_macroblock_analyse( h );这句话
void x264_macroblock_analyse( x264_t *h ) : 这个函数用于分析一个16*16的宏块。我会详细讲一讲这个函数。
1.3 void x264_macroblock_analyse( x264_t *h )
我们会发现其中有个x264_mb_analysis_t类型,这个类型用于存储分析结果,对于一个宏块我们需要分析的内容有,该16*16宏块如何划分,划分之后每个更小的宏块用什么IPM预测等等信息。
我们能发现其中有一行Do the analysis的注释。
在这行注释上面,主要设置了这行的量化系数。我们主要分析这行下面的一部分。
通过VS将代码折叠我们能更好地观察它的结构,这里首先判断这宏块属于哪种帧,对于每种帧,用具体不同的策略来分析,将分析结果保存在analysis中,最后分析完将分析结果反馈给出来决定这个帧之后如何编码
所以如果我们要篡改其中的IPM,就需要篡改它的analysis,使其最后按照我们指定的IPM去进行编码,这里我们继续展开SLICE_TYPE_I的情况
if( h->sh.i_type == SLICE_TYPE_I )
{
intra_analysis:
if( analysis.i_mbrd )
x264_mb_init_fenc_cache( h, analysis.i_mbrd >= 2 );
if( analysis.i_mbrd )
x264_intra_rd( h, &analysis, COST_MAX );
i_cost = analysis.i_satd_i16x16;
h->mb.i_type = I_16x16;
COPY2_IF_LT( i_cost, analysis.i_satd_i4x4, h->mb.i_type, I_4x4 );
COPY2_IF_LT( i_cost, analysis.i_satd_i8x8, h->mb.i_type, I_8x8 );
if( analysis.i_satd_pcm < i_cost )
h->mb.i_type = I_PCM;
else if (analysis.i_mbrd >= 2)
{
x264_intra_rd_refine(h, &analysis);
}
}
注:COPY2_IF_LT是个宏定义,相当于Copy if little,ji如果新的划分方法更优,就copy成新的方法。
其中x264_mb_analyse_intra(), x264_intra_rd(), x264_intra_rd_refine(),都是用来计算不同划分方式的编码代价的。其中I_16x16表示该宏块不划分,I_4x4表示宏块划分成16个4*4宏块,I_8x8表示划分成4个8*8宏块。
当SLICE_TYPE_I这个分支执行完&#x