马上就要毕业了,写这篇文章的主要目的是为了纪念去年学习研究jsvm的那段时光。其中回头想想发现硕士研究生阶段好好学习的日子还真的不多:第一学期上上课就过去了,第二学期开始好好学习视频编码,第三学期忙碌着找工作,到第四学期就开始写论文搞答辩盼毕业。哎,原来自己两年来脚踏实地好好学习的时间才那么几个月……每每想到这些,心里还是忍不住要感谢实验室给我这样一个紧张的学习平台,avs会议3个月一次,每次都像打仗时的慌着做提案。虽然很累,那段日子天天晚上加班,也不记得暗地里骂了多少次,但是回头想想,不是那样一种环境,那样一种压力,自己如今可能仍然是一无所获。
以后可能不会再继续做视频压缩方面的东东了,所以在对jsvm、jm、rm、sm等暂时告别之际,不舍之情还是有一些的,平时学习的资料可能到最后压个盘,也不会再拿出来好好的研究学习了,在ksarea里面放着或许还会偶尔翻翻。不管怎样,相信这段学习的经历永远是偶一笔不小的财富。
本人学习jsvm的时间不长,但是感触还是有一些的。在正式看jsvm代码之前,做了一段时间的理论学习工作,当然主要是针对可伸缩视频编码技术。在后期看代码的时候,才发觉得前段时间的理论学习是很重要的。看jsvm的速度完全取决于你对可伸缩编码技术的了解程度和对c++编程的熟练程度,当然,后者相对而言次要些,但是如果你要在jsvm上做一定修改的话,那么后者就显得相当重要了。
下面本人就自己对jsvm的认识做一个简单的介绍。由于当时看jsvm代码也不到1个月,所以认识很浅,文字也不够深入或许还存在一些认识上的错误,但本人的初衷是好的,希望能对读者有个抛砖引玉的作用吧,呵呵。
Jsvm里面工程很多,主要包括:编码、解码、采样、码流截取等等。在此主要面向编码部分。
- 首先从编码的配置文件说起吧,包括encoder.cfg 和 layer-i.cfg 。encoder.cfg 里面主要设置的是:输出文件名、待编码的帧数、帧率、质量增强层使用何种粒度大小、运动补偿参考帧是否采用增强层、GOP大小、I帧插入间隔、参考帧数目、基本层编码模式和运动搜索、环路滤波、空域增强层相关的配置情况等等,而layer-i.cfg主要是对空域可伸缩而言,layer0就是基本层了,其他的是逐步递增的空域增强层。其实在配置文件中,对时域、空域和质量都有设置,只是空域可伸缩的设置参数是单独拿出来的,而时域和质量的主要在encoder里面(时域->MCTF)。在layer-i.cfg中,就和普通的sm及jm的配置差不多了,基本上就是此层的输入文件大小、输入文件名、重建文件名、输入输出帧率、QP之类的,不同的是多了:NumFGSLayers、FGSMotion、InterLayerPred和BaseQuality,分别表示细粒度可伸缩层(即质量增强层)的层数、是否在FGS层中的改善运动矢量、本层是否采用向下的层间预测、本层基本质量层的质量级别(貌似是针对质量分层而言)。
- 然后再说说编码器里几个主要的类的定义:
在InputPicBuffer.h头文件中定义了class InputAccessUnit和class InputPicBuffer。
在SequenceStructure.h头文件中定义了class FrameSpec、class FormattedStringParser 和class SequenceStructure
其中,class SequenceStructure主要包括:
{
private:
定义自己的成员变量。。。。
定义class FrameDescriptor {}
定义class SequencePart {}
定义class FrameSequencePart : public SequencePart {}
其中FrameSequencePart公有继承于SequencePart,又自己增添了一些私有成员变量:
Bool m_bInit;
FrameDescriptor* m_pacFrameDescriptor;
UInt m_uiNumberOfFrames;
UInt m_uiCurrentFrame;
UInt m_uiNumberOfRepetitions;
UInt m_uiCurrentRepetition;
UInt m_uiMinDPBSizeRef;
UInt m_uiMinDPBSizeNonRef; 和一些公有成员函数。
定义class GeneralSequencePart : public SequencePart
其中GeneralSequencePart公有继承于SequencePart,又自己增添了一些私有成员变量:
Bool m_bInit;
SequencePart** m_papcSequencePart;
UInt m_uiNumberOfParts;
UInt m_uiCurrentPart;
UInt m_uiNumberOfRepetitions;
UInt m_uiCurrentRepetition;
UInt m_uiMinDPBSizeRef;
UInt m_uiMinDPBSizeNonRef; 和一些公有成员函数。
}
在GOPEncoder.h头文件中定义了class AccessUnit、class AccessUnitList、class MCTFEncoder和class PicOutputData ,其中class PicOutputData是模板类MyList的一个实例化对象:
typedef MyList<PicOutputData> PicOutputDataList;
在 h264AVCommonIf.h 中,定义模板类MyList,公共继承于std的list类。MyList中有popBack popFront pushBack pushFront find 函数及重载了+=操作符。
template< class T >
class MyList : public std::list< T >
{
public:
typedef typename std::list<T>::iterator MyIterator;
MyList& operator += ( const MyList& rcMyList)
{
if ( ! rcMyList.empty() )
{ insert( this->end(), rcMyList.begin(), rcMyList.end());}
return *this;
}
T popBack()
{ T cT = this->back(); this->pop_back(); return cT; }
T popFront()
{ T cT = this->front(); this->pop_front(); return cT; }
Void pushBack( const T& rcT )
{ if( sizeof(T) == 4) { if( rcT != NULL ){ push_back( rcT);} } }
Void pushFront( const T& rcT )
{ if( sizeof(T) == 4) { if( rcT != NULL ){ push_front( rcT);} } }
MyIterator find( const T& rcT )
{ return std::find( this->begin(), this->end(), rcT ); }
};
这个模板类通用于整个编解码过程当中,通过用不同类实例化此模板,从而可以得到编解码过程中所需要的所有列表。比如:参考帧列表、输入/输出列表、质量分层列表、及SEI信息列表等等。在这里:typedef MyList<PicOutputData> PicOutputDataList; 是定义了图像输出数据的链表。
在PicEncoder.h头文件中定义了class PicEncoder ;
在SliceEncoder.h头文件中定义了class SliceEncoder;
在MbCoder.h头文件中定义了class MbCoder;
在MBEncoder.h头文件中定义了class MbEncoder
class MbEncoder : protected MbCoder , public UvlcWriter , protected BitCounter
MbEncoder 保护继承于MbCoder,MbCoder的public 和protected成员都是MbEncoder的保护成员;MbEncoder 公共继承于UvlcWriter,UvlcWriter的public 和protected成员分别是MbEncoder的公共成员和保护成员;MbEncoder 保护继承于BitCounter,BitCounter的public 和protected成员都是MbEncoder的保护成员;
在CodingParameter.h头文件中定义了class CodingParameter、class EncoderConfigLineBase、class LayerParameters、class SampleWeightingParams 和class CodingParameter。
当然,jsvm里面的类是相当的多的,这里只是提出一部分比较重要的类拿出来说说。
- 接下来说说JSVM编码器的一些主要函数
Main函数: ( H264AVCEncoderLibTest.cpp )
主要部分是H264AVCEncoderTest 类对象指针pcH264AVCEncoderTest所指的go ()函数,在 H264AVCEncoderTest .cpp有函数原型。
Go函数 主要分为8部分:
1. 初始化
2. 写参数信息
3. 进入layer循环,输入必要的参数信息:
for( uiLayer = 0; uiLayer < uiNumLayers; uiLayer++ )
4. 开始一个帧一个帧的编码:
for( uiFrame = 0; uiFrame < uiMaxFrame; uiFrame++ )
{
4-1 编码每一个帧时,layer循环 获取缓存存储图像,并且读入每个layer的帧信息:
for( uiLayer = 0; uiLayer < uiNumLayers; uiLayer++ )
{ m_apcReadYuv[uiLayer]->readFrame(。。。。。) }
4-2 开始编码:m_pcH264AVCEncoder->process(。。。。。。)
4-3 写每一帧编码后的传输单元NAL unit,并且释放临时缓存;
4-4 写每一帧编码后的重建图像,并且释放临时缓存;
}
5. 结束编码
6. 写所有帧编码后的传输单元NAL unit,并且释放临时缓存
7. 写所有帧编码后的重建图像,并且释放临时缓存
8. 计算输出显示的参数信息,比如psnr值等等。
Process 函数,主要分为: (PicEncoder.cpp)
===== fill lists =====
for( UInt uiLayer = 0; uiLayer <= uiHighestLayer; uiLayer++ )
判断编码模式,如果是AVC模式的话:(是否是AVC模式由编码配置文件encoder.cfg中的BaseLayerMode 项值决定)
则运行函数:m_pcPicEncoder ->process //见下面PicEncoder::process函数
否则:
运行函数m_pcH264AVCEncoder->process //见下面H264AVCEncoder::process函数
PicEncoder::process函数: (PicEncoder.cpp)
1. 输入图片信息
2. 编码图片头信息等
3. 得到下一帧
4. 初始化图像
5. 编码
6. 存储图像
H264AVCEncoder::process函数: (H264AVCEncoder.cpp)
1. 输入当前GOP信息
2. 编码当前GOP (运行函数:xProcessGOP)
3. 更新图像列表
xProcessGOP( apcPicBufferOutputList, apcPicBufferUnusedList )函数:(H264AVCEncoder.cpp)
1. 初始化GOP
2. 在GOP范围内获取每一层的可获得的信息单元,并且编码:
for( uiAUIndex = 0; uiAUIndex <= 64; uiAUIndex++ )
{
for( uiLayer = 0;
uiLayer < m_pcCodingParameter->getNumberOfLayers(); uiLayer ++ )
{ m_apcMCTFEncoder[uiLayer]->process(。。。。) 。。。。}
}
3. 更新图像缓存列表
for( uiLayer = 0; uiLayer < m_pcCodingParameter->getNumberOfLayers(); uiLayer++ )
{
//—– set output list —–
//—– update unused list —–
//—– reset lists —–
}
MCTFEncoder::process函数: (GOPEncoder.cpp)
1. 初始化相关参数
2. 更新更高层图像(我认为:在编码一个GOP,特别是使用FGS技术时,为了提高编码效率,编码帧有时是需要参考已编码帧的高质量重建图像的,那么就需要在编码之前,整理好已编码重建帧的高质量图像,即需要更新更高层图像)
3. 编码此GOP内的anchor帧(即判断此帧是否是anchor帧,若是,则进入;若不是则进入4步骤),其中最主要的是xEncodeKeyPicture函数(后续介绍)
4. 编码此GOP内的非anchor帧,其中最主要的是xEncodeNonKeyPicture函数(后续介绍)
5. 结束GOP编码
在MCTFEncoder::xEncodeKeyPicture和MCTFEncoder::xEncodeNonKeyPicture函数中(GOPEncoder.cpp)比较主要的是xEncodeLowPassSignal、xEncodeHighPassSignal、xEncodeFGSLayer等函数。这些函数都是GOP层面的,至于其中的具体过程就不再一一的描述了。
在jsvm里面,虽然编码过程很复杂(因为可伸缩的算法本身就比较复杂),但是最底层的MB结构还是和普通的单层编码结构一样的,序列-->GOP-->frame-->slice-->Mb。只是在编码的过程中分了很多种情况,首先编一个frame时,除了考虑它是哪种类型(I/P/B)的帧外,还要考虑它在时域中(即GOP内)的位置情况:看它是不是anchor帧(GOP内第一个anchor帧应该是I帧,那么不参考其他帧编码,而第二个anchor帧可以是P帧或I帧,若是P帧,则要参考本GOP内前面anchor帧的重建图像),若不是anchor帧,则参考帧也必须在此GOP以内,为的是防止误差传递(这个属于参考帧管理的内容);除了考虑时域外,还要同时考虑此帧是属于哪个空域层的,编码Mb时看是否需要做层间预测(此处说的层间预测包括-宏块划分方式层间预测、运动矢量层间预测和编码残差层间预测等);最后还要考虑质量可伸缩问题(即FGS,这块本人不了解,所以就不深入了)。
- 另外,对于jsvm编码后的码流大小和普通的jm码流大小基本对比如下:
(前提条件是 参数设置基本相同)
① svc码流 (cif +qcif)
② 单层的 h264码流 (cif)
③ h264的simulcast (cif+qcif)
通常是: ① * 1.3 ≈ ③ ; ② * 1.1 ≈① ; ② * 1.43 ≈③
先写到此为止吧,写了这么多,真够累的。
本文应该尚未结束,我想。 呵呵, 待日后更新吧。
太长。。。
哈哈哈
超赞一个.
对照你的描述我在JSVM软件上看了一遍流程. JSVM这代码真不是人写的,要不是没有你的指引我早就被这么多的名字给淹没了.
在MCTFEncoder::process里(我看程序里好像写着是LayerEncoder::process,还有前面的是否是AVC模式好像是由encoder.cfg中的AVCMode参数来决定的, 也有可能是我弄错了.)你还可以写很多,呵呵,期待你继续把inter picture motion estimation/motion compensation, transform, CAVLC/CABAC, inter layer prediction…..blahblah也都写写.
再一次赞一个, 能把这么大的程序这么逻辑化写出来的人绝对是牛人呀.
呵呵,adrian 说的太夸张了,其实我觉得看代码之前的理论学习很重要。代码中没有明确的注释,那么很多时候都需要自己去“猜”,把自己掌握的理论和代码进行比较,这里说的“猜”也算是一种感觉吧,coding感,呵呵。而猜对猜错则就需要靠自己进一步的程序验证了。上面写到MCTFEncoder::process就暂停了,以后偶有心情时再对照着代码来补充吧。
谢谢adrian对 ksarea的支持。 ^_^
Sha:
我想既然SVC都成为了H.264/AVC的extension了,那应该有了standard document,不过我怎么搜也没找到,试了下draft document也没能找到.不知道你有没有?
adrian:好像是有的吧,但是我没有用过,因为当时看jsvm的时候,没有去找。但是后来想到jvt官方网站上有jsvm源码下,那么也应该有相应的文档吧。貌似有的,你去找找
我貌似找到了,貌似是jsvm-9版本的补充文档:《Joint Draft 10 of SVC Amendment》,是07年California开的第23次会议的输出文档里找到的。adrian,你还要么?
Sha:
我去了JVT的官方网址没有找到下JSVM源码的地方. 是不是应该有地方可以直接下载这些文档?
下载JSVM这个software到是有地方可以,但里面也只有一个software manual可以读,下载the archive of standard document是没找到
Sha:
我在JVT2007_4_SanJose里找到了你说的joint draft 10 of SVC Amendment, 不过发现H.264/AVC 的standard document里也有SVC的描述, 想想既然SVC都已经是H.264/AVC的extension了,那标准也应该写在H.264/AVC的标准里.你觉的呢? 是看哪个?
adrian: 说实话,那些文档我自己都没有好好的看过,所以我不清楚看哪个比较好,呵呵,只是以前偶尔查资料的时候会去翻翻。
我觉得如果你对可伸缩技术很了解的话,那么看这些文档是比较好的,两个都看看吧,一看就知道哪个对jsvm写得更深入了,毕竟是联合草案,不管看哪个都会对你有帮助的(若想把jsvm学好,那么H264/AVC也必需要很牛X,毕竟jsvm里面的基本层就是按照H264/AVC编的);如果你对可伸缩编码技术本身了解得不够深入,那么我认为从网上下一些牛人写的论文(特别是综述之类的)来看,会进度更快,当你功力达到一定程度,再好好攻克一下草案,面对jsvm的代码就会唏哩哗啦的统统都可以搞定啦,嘿嘿。 ^_^
你好!
看了你文章,感觉不但你的k shome,而且技术文章写得不错,呵呵
但要指出你的一个小错误:是否执行m_pcPicEncoder ->process 不是由encoder.cfg里的BaseLayerMode觉得,而是由MVCMode的值决定的。可惜你不再做H.264SVC(你文章上所述)了,不然,可以和你讨论讨论!
hh
美女,在深圳还好吧。要注意身体哦。
关于JSVM,请教你个问题哈。
这个代码有实现层次P帧的功能没?
我想关闭里面的层次B帧的结构(目的是关掉B帧看是否有层次结构,有的话就证明有层次P帧),但是不关掉层次结构。如果将Gopsize设为2的话,B帧的确没有了,但是时域层次也没有了,因此不能得出是否有层次P帧的结构。
我想请教下,是否有别的关闭层次B帧的方法(不关掉层次结构,即不用Gopsize改为2的方法)。
希望最后不要得出这样的结论:JSVM代码里面没有层次P帧的结构,需要自己添加。
xiaoxian:
我觉得如果是实现你说的这种hierachical P frame应该就是在temporal scalability中达到a structure with a structural encoder/decoder delay of zero, which means not use the future frames as reference frames. 我知道在JSVM single layer coding中的一个SequenceFormatString的parameter设置是能达到这种效果的.
Sha:
JSVM程序阅读还有续嘛? 期待你把jsvm中有关预测和估计的部分也写写……
好久好久好久好久没有来了……
今天突然来,看见楼上两位已经展开技术层面上的讨论了,心里难免一阵阵的感动。
哎,本人周一就要开始入职了,据说是搞智能计算,和jsvm相差10万8千里。虽然如此,但是仍然欢迎两位就上面的技术问题,在此进行更进一步的讨论 ^_^
“ksarea 将永远为大家提供免费的讨论平台”哈哈,让本人也从中受益吧,谢谢了。
Sha
JSVM中对SVC motion estimation算法的实现到底在哪个位置呀??? 找了很久也没找到…..help,SOS…