JSVM是 Scalable Video Coding (SVC) 的参考代码. 目前还在开发中. JSVM 提供了 Visual
Studio & GCC 4.0 两种编译方案.
JSVM Solution 中包含了多个 Project。
分别是
( 1 ) 库
H264AVCCommonLibStatic : SVC的基本库 , 同时被编码和解码所使用。
H264AVCEncoderLibStatic : SVC的编码库
H264AVCDecoderLibStatic : SVC的解码库
H264AVCVideoIOLibStatic : 这个库的功能是为 NAL的读写提供支持。
(2)工具
DownConvertStatic : 对视频进行重采样的工具 , 支持空域和时域的重采样。
H264AVCEncoderLibTestStatic : AVC/SVC 编码器
H264AVCDecoderLibTestStatic : AVC/SVC 解码器
BitStreamExtractorStatic : 可以用来从Global Scalable 的SVC流中取出Sub-
bitstream.
QualityLevelAssignerStatic :
MCTFPreProcessor : 编码前的预处理工具 。
PSNRStatic : PSNR 的计算工具
FixedQPEncoderStatic : 从名字看应该是固定QP的编码器。
SIPAnalyser :
JSVM 源码剖析(1)
宏版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://volvet.blogbus.com/logs/25039893.html
(准备读读JSVM的代码, 写点体会)
JSVM的作者大概从没考虑过代码的可读性吧 . 跟H264的参考代码JM一样, JSVM的代码也是难以理解.
其中有不少古怪的宏,大概让人头痛吧. 不过细细看去, 还是有规律可循的.
本文讨论的宏定义于jsvm/h264extension/inc/macros.h
大部分宏的开头字母是R , 这次R的含义是 Return
比如 RERR , 就是 Return 一个错误值 , 该值定义于ERR 类中的m_nERR.
不在开头位置的字母R , 此时的含义是该宏返回的值并非错误值 , 而是宏所输入的参数
retVal .
比如 ROFR (exp, retVal) 的函数是 if ( !exp ) Return retVal
字母 O , 其含义是判断宏的输入参数 exp 的意思 F 的含义是 Fasle, T 的含义是 TRUE
比如 ROF(exp) 其含义就是 if( !exp ) ERRR
ROT(exp) 其含义为 if( exp ) ERRR
字母 S , 一般位于宏的末尾 , 其含义是无 Assert 操作 .
前面所说的宏 , 除了已经说明的含义外, 还有一个操作是 Assert( 0 )
但是 如果 宏的尾巴上加了S , 那这个宏不会 Assert( 0 )
好了 这下大部分宏的意思就可以看懂了
RERR , 就是 ASSERT (0 ) ; Return ERR值
ROF(exp) 就是 if ( !exp ) { ASSERT (0); RERR }
ROFS(exp) 就是 if( !exp ) { RERR }
ROT(exp) 就是 if( exp ) { ASSERT(0); RERR }
ROTS( exp ) 就是 if ( exp ) { ERRR }
ROFR(exp,retVal) 就是 if( !exp ) { ASSERT(0) ; Return retVal; }
ROFRS(exp, retVal) 就是 if( !exp ) { Return retVal; }
其他大致如此 .
另外还有几个重要的 , 就是 NOK , 就是要检查值是否等于 ERR 中定义的 m_nOK
比如 RNOK(exp ) 就是 if( exp != OK ){ Assert(0); return exp;}
RNOKR(exp,retVal) 就是 if( exp != OK ) { Assert(0); return retVal; }
还有一个是 A , A 表示 Assert
比如 AF 就是 Assert( False )
ANOK(exp) 就是 if( exp != OK ) { Assert(0) }
AOF( exp ) : if( !exp ) { Assert(0) }
AOT( exp) : if( exp ) { Assert( 0 ) }
好了 那些难解的宏 大致就这些了 .
Reference
JSVM 9_13_1
JSVM源码剖析 2 解码测试
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://volvet.blogbus.com/logs/38366629.html
JSVM源码剖析 2 解码测试
测试程序的输入参数解析 封装于 class
DecoderParameter 中
DecoderParameter的主要作用是从命令行中解析输入参数
和打印帮助信息
分别是函数 Init 和 xPrintUsage
主要就是记录下解码的es文件名和解码后输出的yuv文件名
配置 MaxPocDiff .
JSVM的解码测试代码封装于 class
H264AVCDecoderTest 中.
H264AVCDecoderTest 实现了读取es文件 解码, 保存yuv文件的功能
改变编译选项 似乎还可以做SVC - AVC的转码. 我还没试.
外部接口主要是 init 初始化
go 完成解码工作
unint
destroy 析构的工作
关键的处理函数是 xProcessAccessUnit
这个函数
从ReadBitstreamIf 中获取数据 (BinData*)
然后用数据去初始化 NAL 对象, 知道获取完整
图象
然后调用CreateH264AVCDecoder::processNALUnit函数逐个对
读入的NAL进行解码
最后用xOutputPicBuffer 输出解码后的图象
关于输出图象缓存 还有一个class可以说下
就是 H264AVCDecoderTest::BufferParameters
这个东东是做为H264AVCDecoderTest的成员, 在得到图象分辨率之后
被初始化的.
JSVM是对输出图象做边界扩展的, 扩展的方式是 左右32象素, 上下 64象素
它的成员有
UInt m_uiLumaOffset; //图象Y分量左上角象素在buffer的偏移
UInt m_uiCbOffset; //图象Cb分量左上角象素的偏移
UInt m_uiCrOffset; //图象Cr分量左上角象素的偏移
UInt m_uiLumaHeight; //图象高度
UInt m_uiLumaWidth; //图象宽度
UInt m_uiLumaStride; //图象跨度 = 宽度 + 32*2
UInt m_uiBufferSize; //Buffer的大小
UInt m_auiCropping[4]; //图象最终输出的时候裁剪用的
接下来说说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,这块本人不了解,所以就不深入了)