最近的文章将探讨如何设计H.264/SVC解码器。由于SVC是在H.264AVC的基础上扩展起来的,所以,要想解码SVC必须首先解出AVC的普通H.264码流,这方面的开源解码器,处理相关的JM验证模型,还有P264和FFMPEG的H.264解码部分。其中P264是中国的李世平依据X.264代码提炼完善的,可惜已经很久没有更新维护。这里假想要做一个类似X264架构的解码器:
1. 设计和FFMPEG接口,重用上下文结构
由于FFMPEG的灵活注册架构,使得很多编解码器软件易于被其注册整合,在设计编解码器的时候,我们可以利用FFMPEG的成熟架构来统一API,使得预定的编解码器有良好的用户接口。看看在FFMPEG中的h264_decoder结构如下:
AVCodec h264_decoder = {
"h264",
CODEC_TYPE_VIDEO,
CODEC_ID_H264,
sizeof(H264Context),
decode_init,
NULL,
decode_end,
decode_frame,
/*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 | CODEC_CAP_DELAY,
.flush= flush_dpb,
.long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
.pix_fmts= ff_hwaccel_pixfmt_list_420,
};
decode_init,decode_end和decode_frame构成了这里的解码基本API,那么相应的可以在代码中设计如下仿照X264设计如下接口:
int zsvc_nal_decode( void *, int *, int b_annexeb, zsvc_nal_t *nal );
/****************************************************************************
* Decoder functions:
****************************************************************************/
zsvc_t *zsvc_decoder_open ( zsvc_param_t * );
int zsvc_encoder_reconfig( zsvc_t *, zsvc_param_t * );
int zsvc_encoder_headers( zsvc_t *, zsvc_nal_t **, int * );
int zsvc_encoder_encode ( zsvc_t *, zsvc_nal_t **, int *, zsvc_picture_t *, zsvc_picture_t * );
void zsvc_encoder_close ( zsvc_t * );
在FFMPEG调用X264的上下文数据中,分别使用x264_param_t、x264_t和x264_picture_t三个结构,在假想的解码器重可以沿用这些结构体,以后根据实际使用情况进行增减。
2. 编解码器对比
让我们来看看编解码器的结构,如下图所示,可以重复利用的部分都已经用灰色标明。由此可见,从X264基础来改造一个解码器有很多重用部分,需要自我实现的部分是解码API和熵解码(包括SPS,PPS和残差等各语法单元)。其它部分均可以从编码器中分离得到,由此可见实现解码器是编码器的基础,两者有许多公用部分,究其原因是编码过程中的参考帧不是直接的YUV数据,而是经过变换和量化,并做运动补偿的重构帧,因此重构过程包括了除了熵解码外的整个解码过程。
3. 基本解码框架
当从.264的已编码文件中读出每个NAL,然后将根据nalType如下分别读取解析数据,整个过程包括对SPS和PPS的处理,还有具体的VCL帧处理,其中对IDR帧和非IDR帧解码的区别就是多了一个参考帧队列的初始化过程。
switch( nal->i_type )
{
case NAL_SPS:
if( ( i_ret = zsvc_sps_read( &bs, h->sps_array ) ) < 0 )
{
fprintf( stderr, "zsvc: zsvc_sps_read failed\n" );
}
break;
case NAL_PPS:
if( ( i_ret = zsvc_pps_read( &bs, h->pps_array ) ) < 0 )
{
fprintf( stderr, "zsvc: zsvc_pps_read failed\n" );
}
break;
case NAL_SLICE_IDR:
//fprintf( stderr, "zsvc: NAL_SLICE_IDR\n" );
zsvc_slice_idr( h ); //note: then do below
case NAL_SLICE:
if( ( i_ret = zsvc_slice_decode( h, &bs, nal ) ) < 0 )
{
fprintf( stderr, "zsvc: zsvc_slice_decode failed\n" );
}
else if(h->b_pic_output)
{
*pic_out = h->pic_dec;
}
break;
case NAL_SLICE_DPA:
case NAL_SLICE_DPB:
case NAL_SLICE_DPC:
fprintf( stderr, "partitioned stream unsupported\n" );
i_ret = -1;
break;
case NAL_SEI:
default:
break;
}
本文一般性探讨了一个H.264解码器的基本框架,具体模块化合实现还有很多工作要做,留待以后继续探索。
(罗索客) |