博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 2】【03】
阅读量:662 次
发布时间:2019-03-15

本文共 37083 字,大约阅读时间需要 123 分钟。

此章节分析承接上一章分析:

10.1.5、bs_read_ue实现分析:

// 【vlc/include/vlc_bits.h】// 读取指数哥伦布编码,其实就是哥伦布解码过程得到真实的int数值/* Read unsigned Exp-Golomb code */static inline uint_fast32_t bs_read_ue( bs_t * bs ){
unsigned i = 0; // 获取h264片数据中第一个exp-golomb code 指数哥伦布编码 // 是一种压缩编码算法(视频编码中有用到这个了,h264,avs) // 原理举例如下: // K阶哥伦布码由如下步骤生成: // a、 将数字以二进制的形式表达,去掉最低的K个bit之后+1; // b、 计算留下的bit数,讲此数减1,即需要在数字前添加的0的数目; // c、 将a中去掉的K bit补回至最低位 // 实现为:读取1个比特位数二进制值 // bs_read1见下面的分析 // 【未读取也返回0】,若返回0,并且还未读到最后一个字节, // 且i小于32即未读取超过32比特位即4字节数据时,再次进行读取1个比特位数二进制值 // 直到读到的1个比特位二进制值为1或末尾字节或读取超过了32位后结束读取。 // 其实最终结果就是将码流已读取比特位置移动到当前比特位置二进制值为1的位置。 // [因为指数哥伦布编码字节二进制值中的前面是哥伦布编码添加的0的值,因此需要跳过/去掉读取] while( bs_read1( bs ) == 0 && bs->p < bs->p_end && i < 31 ) i++; // 1U表示无符号整数1 // 此处为哥伦布解码处理不好描述,通过一个1阶哥伦布编码举例来说明: // 【int型数值2】编码过程 // 如int型数值2的二进制表示为【10】,通过a步骤去掉二进制末尾【0】得到【1】二进制,然后加上1得到【10】, // 在通过b步骤得到需要添加的0个数为:2 -1 = 1,即得到【010】, // 然后通过c步骤在【010】后添加a步骤中去掉的【0】,得到最终哥伦布编码结果为【0100】 // 【int型数值2】解码过程 // 那么可以知道通过上面的计算后,i的值肯定为1即数字前添加的0的个数值, // 当前i_left数据指向的是【0100】中1的位置, // 因此解码时1U<
<<1值得到上面b步骤中的留下的bit数即为2,得到【10】, // 然后减去1得到a中去掉最低的K个bit之后的二进制【01】, // 根据bs_read方法的此前分析结果可知,方法返回的值为读取i位比特数二进制值, // 然后转为int值,此处i为1,并且根据上面的分析此时读到的int值为1 // 因此【01】与其1相加后,最后得到【10】二进制,即就是int值为2的数值。因此解码成功。 // 其他数值或K阶编码的解码过程也类似。 return (1U << i) - 1 + bs_read( bs, i );}// 【vlc/include/vlc_bits.h】static inline uint32_t bs_read1( bs_t *s ){
// 该方法从命名可以看出码流读取1个比特位数的二进制值 if( s->p < s->p_end ) {
// 若当前数据指针已读位置没有到尾部,则进入 unsigned int i_result; // 减去待读取的1个比特位数 s->i_left--; // 将当前字节值和剩余可用比特位数进行右移运算,得到读取的高位二进制值, // 然后和0x01进行与运算,从而获取待读取的1个比特位数的二进制表示的int值 i_result = ( *s->p >> s->i_left )&0x01; if( s->i_left == 0 ) {
// 若刚好读取完当前字节中剩余位数,则进行下一个字节的forward流程处理 // 该方法分析见上面小节相关分析 bs_forward( s, 1 ); // 因为是新字节数据,因此可用位数变为8 s->i_left = 8; } return i_result; } // 未读取到,则返回0 return 0;}

10.1.6、bs_read_se实现分析:

// 【vlc/include/vlc_bits.h】/* Read signed Exp-Golomb code */static inline int_fast32_t bs_read_se( bs_t *s ){
// NOTE:方法中的se和ue分别代表了int32_t和uint32_t两种有无符号类型32位表示的int值 // 见上分析,先获取到无符号32位整数int值 uint_fast32_t val = bs_read_ue( s ); // 然后和0x01进行与运算计算其二进制最后一位值是否为1,然后进行无符号转有符号整数 TODO return (val & 0x01) ? (int_fast32_t)((val + 1) / 2) : -(int_fast32_t)(val / 2);}

10.2、ActivateSets实现分析:

//【vlc/modules/packetizer/h264.c】static void ActivateSets( decoder_t *p_dec, const h264_sequence_parameter_set_t *p_sps,                                            const h264_picture_parameter_set_t *p_pps ){
decoder_sys_t *p_sys = p_dec->p_sys; p_sys->p_active_pps = p_pps; p_sys->p_active_sps = p_sps; if( p_sps ) {
// H264码流的h264档次和等级 p_dec->fmt_out.i_profile = p_sps->i_profile; p_dec->fmt_out.i_level = p_sps->i_level; // 获取图像大小 // 见10.2.1小节分析 (void) h264_get_picture_size( p_sps, &p_dec->fmt_out.video.i_width, &p_dec->fmt_out.video.i_height, &p_dec->fmt_out.video.i_visible_width, &p_dec->fmt_out.video.i_visible_height ); // vui表示视频格式等额外信息 // SAR值即采样宽高比[分辨率] if( p_sps->vui.i_sar_num != 0 && p_sps->vui.i_sar_den != 0 ) {
p_dec->fmt_out.video.i_sar_num = p_sps->vui.i_sar_num; p_dec->fmt_out.video.i_sar_den = p_sps->vui.i_sar_den; } if( !p_dec->fmt_out.video.i_frame_rate || !p_dec->fmt_out.video.i_frame_rate_base ) {
// 存储帧率的分子和分母int值【h264格式相关】,根据PTS值来估算帧率 // H.264码流中一般没有帧率,比特率信息到是可以获取,可参考码流语法, // 码流有VUI信息,其有个标志 timing_info_present_flag 若等于1, // 则码流中有num_units_in_tick 和 time_scale。 // 计算帧率framerate = time_scale/num_units_in_tick。 /* on first run == if fmt_in does not provide frame rate info */ /* If we have frame rate info in the stream */ if(p_sps->vui.b_valid && p_sps->vui.i_num_units_in_tick > 0 && p_sps->vui.i_time_scale > 1 ) {
// 更新PTS的时间缩放单位【时间尺度】,其实用来计算PTS值的 date_Change( &p_sys->dts, p_sps->vui.i_time_scale, p_sps->vui.i_num_units_in_tick ); } /* else use the default num/den */ // 若上面未进入if,则会使用默认的PTS的时间尺度来估算帧率的分子、分母int值 p_dec->fmt_out.video.i_frame_rate = p_sys->dts.i_divider_num >> 1; /* num_clock_ts == 2 */ p_dec->fmt_out.video.i_frame_rate_base = p_sys->dts.i_divider_den; } if( p_dec->fmt_in.video.primaries == COLOR_PRIMARIES_UNDEF ) // 视频数据的输入格式信息的基色类型未定义时 // 获取基色信息 //【通过查阅资料可知此处的基色类型其实是YUV与RGB之间的转换矩阵标准的选择】色彩空间和色彩范围的处理 // 见10.2.2小节分析 h264_get_colorimetry( p_sps, &p_dec->fmt_out.video.primaries, &p_dec->fmt_out.video.transfer, &p_dec->fmt_out.video.space, &p_dec->fmt_out.video.b_color_range_full ); if( p_dec->fmt_out.i_extra == 0 && p_pps ) {
// 若输出格式信息中额外数据【即PPS SPS等】不存在,并且pps有数据,则进行赋值处理 const block_t *p_spsblock = NULL; const block_t *p_ppsblock = NULL; // H264_SPS_ID_MAX最大为31 // 通过sps id匹配对应的SPS信息,然后将其block值赋值 for( size_t i=0; i<=H264_SPS_ID_MAX && !p_spsblock; i++ ) if( p_sps == p_sys->sps[i].p_sps ) p_spsblock = p_sys->sps[i].p_block; // H264_PPS_ID_MAX最大为255 // 通过pps id匹配对应的PPS信息,然后将其block值赋值 for( size_t i=0; i<=H264_PPS_ID_MAX && !p_ppsblock; i++ ) if( p_pps == p_sys->pps[i].p_pps ) p_ppsblock = p_sys->pps[i].p_block; if( p_spsblock && p_ppsblock ) {
// SPS和PPS都不为空时则读取它们的数据并保存在fmt_out输出格式数据中 // pps和sps总数据大小 size_t i_alloc = p_ppsblock->i_buffer + p_spsblock->i_buffer; // 分配存储数据的内存空间 p_dec->fmt_out.p_extra = malloc( i_alloc ); if( p_dec->fmt_out.p_extra ) {
uint8_t*p_buf = p_dec->fmt_out.p_extra; // 记录sps和pps总数据大小 p_dec->fmt_out.i_extra = i_alloc; // 先存储sps数据,再存储pps数据 memcpy( &p_buf[0], p_spsblock->p_buffer, p_spsblock->i_buffer ); memcpy( &p_buf[p_spsblock->i_buffer], p_ppsblock->p_buffer, p_ppsblock->i_buffer ); } } } }}

10.2.1、h264_get_picture_size实现分析:

//【vlc/modules/packetizer/h264_nal.c】bool h264_get_picture_size( const h264_sequence_parameter_set_t *p_sps, unsigned *p_w, unsigned *p_h,                            unsigned *p_vw, unsigned *p_vh ){
// X、Y轴【宽高】裁剪单位 unsigned CropUnitX = 1; // frame_mbs_only_flag 本句法元素等于 0 时表示本序列中所有图像的编码模式都是帧, // 没有其他编码模式存在;本句法元素等于 1 时表示本序列中图像的编码模式可能是帧, // 也可能是场或帧场自适应,某个图像具体是哪一种要由其他句法元素决定。 unsigned CropUnitY = 2 - p_sps->frame_mbs_only_flag; if( p_sps->b_separate_colour_planes_flag != 1 ) {
// 色度不同颜色平面标志位不为1,即为0时 // idc[Importance indicator] if( p_sps->i_chroma_idc > 0 ) {
// 色度重要性指示位大于0时 // 临时宽高值裁剪单位 unsigned SubWidthC = 2; unsigned SubHeightC = 2; if( p_sps->i_chroma_idc > 1 ) {
// 色度重要性指示位大于1时 // 临时高值裁剪单位改为1 SubHeightC = 1; if( p_sps->i_chroma_idc > 2 ) // 色度重要性指示位大于2时则将临时宽值裁剪单位也改为1 SubWidthC = 1; } // 更新值 CropUnitX *= SubWidthC; CropUnitY *= SubHeightC; } } // pic_width_in_mbs_minus1 本句法元素加 1 后指明图像宽度,以宏块MB为单位: // PicWidthInMbs = pic_width_in_mbs_minus1 + 1 通过这个句法元素解码器可以 // 计算得到亮度分量以像素为单位的图像宽度: PicWidthInSamplesL = PicWidthInMbs * 16 *p_w = 16 * p_sps->pic_width_in_mbs_minus1 + 16; // pic_height_in_map_units_minus1 本句法元素加 1 后指明图像高度: // PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1 *p_h = 16 * p_sps->pic_height_in_map_units_minus1 + 16; // 由上面可知,若本序列中所有图像的编码模式都是帧即frame_mbs_only_flag为0时, // 需要将高度再乘以2,否则为1时就是其本身大小 *p_h *= ( 2 - p_sps->frame_mbs_only_flag ); // frame_cropping_flag用于指明解码器是否要将图像裁剪后输出,如果是的话, // 后面紧跟着的四个句法元素分别指出左右、上下裁剪的宽度。 // 将左右、上下裁剪的长度乘以裁剪单位值得到需要裁剪的真正宽高值, // 然后用图像原始宽高值减去裁剪的对应值,得到需要显示的宽高值。 *p_vw = *p_w - ( p_sps->frame_crop.left_offset + p_sps->frame_crop.right_offset ) * CropUnitX; *p_vh = *p_h - ( p_sps->frame_crop.bottom_offset + p_sps->frame_crop.top_offset ) * CropUnitY; return true;}

10.2.2、h264_get_colorimetry实现分析:

色彩空间和色彩范围的处理【通过查阅资料可知此处的基色类型其实是YUV与RGB之间的转换矩阵标准的选择】

//【vlc/modules/packetizer/h264_nal.c】bool h264_get_colorimetry( const h264_sequence_parameter_set_t *p_sps,                           video_color_primaries_t *p_primaries,                           video_transfer_func_t *p_transfer,                           video_color_space_t *p_colorspace,                           bool *p_full_range ){
if( !p_sps->vui.b_valid ) return false; // 主要就是将视频格式中的色彩空间YUV基色类型转为vlc中支持的类型 *p_primaries = hxxx_colour_primaries_to_vlc( p_sps->vui.colour.i_colour_primaries ); // 类似上面的处理,即将色彩空间YUV转RGB的转换方法类型转为vlc中支持的类型 *p_transfer = hxxx_transfer_characteristics_to_vlc( p_sps->vui.colour.i_transfer_characteristics ); // 类似上面的处理,即将色彩空间YUV转RGB的转换矩阵系数为vlc中支持的系数类型 *p_colorspace = hxxx_matrix_coeffs_to_vlc( p_sps->vui.colour.i_matrix_coefficients ); *p_full_range = p_sps->vui.colour.b_full_range; return true;}//【vlc/modules/packetizer/hxxx_nal.h】static inline video_color_primaries_thxxx_colour_primaries_to_vlc( enum hxxx_colour_primaries i_colour ){
switch( i_colour ) {
case HXXX_PRIMARIES_BT470BG: return COLOR_PRIMARIES_BT601_625; case HXXX_PRIMARIES_BT601_525: case HXXX_PRIMARIES_SMTPE_240M: return COLOR_PRIMARIES_BT601_625; case HXXX_PRIMARIES_BT709: return COLOR_PRIMARIES_BT709; case HXXX_PRIMARIES_BT2020: return COLOR_PRIMARIES_BT2020; case HXXX_PRIMARIES_BT470M: case HXXX_PRIMARIES_RESERVED0: case HXXX_PRIMARIES_UNSPECIFIED: case HXXX_PRIMARIES_RESERVED3: case HXXX_PRIMARIES_GENERIC_FILM: case HXXX_PRIMARIES_SMPTE_ST_428: default: return COLOR_PRIMARIES_UNDEF; }}

10.3、IsFirstVCLNALUnit实现分析:

//【vlc/modules/packetizer/h264.c】static bool IsFirstVCLNALUnit( const h264_slice_t *p_prev, const h264_slice_t *p_cur ){
// 注译:主编码图像的第一个VCL NAL单元的检测 /* Detection of the first VCL NAL unit of a primary coded picture * (cf. 7.4.1.2.4) */ if( p_cur->i_frame_num != p_prev->i_frame_num || p_cur->i_pic_parameter_set_id != p_prev->i_pic_parameter_set_id || p_cur->i_field_pic_flag != p_prev->i_field_pic_flag || !p_cur->i_nal_ref_idc != !p_prev->i_nal_ref_idc ) // 第一个VCL NAL单元判断条件: // 1、前后两片【帧】信息的帧序列号不相同时,代表是新的图像信息 // 2、pps图像参数集的id不相同时 // 3、片中图像场类型不同时 // 4、NAL重要性标志位不同时 // 以上4种情况任何一种发生都认为是新图像信息的开始【即新序列图像中的第一个VCL NAL单元信息】 return true; if( (p_cur->i_bottom_field_flag != -1) && (p_prev->i_bottom_field_flag != -1) && (p_cur->i_bottom_field_flag != p_prev->i_bottom_field_flag) ) // 当前片信息的底场标识不一样时,代表是新的图像信息 return true; if( p_cur->i_pic_order_cnt_type == 0 && ( p_cur->i_pic_order_cnt_lsb != p_prev->i_pic_order_cnt_lsb || p_cur->i_delta_pic_order_cnt_bottom != p_prev->i_delta_pic_order_cnt_bottom ) ) // 当前片信息的POC为0模式并且POC图像展示序号不一样时,代表是新的图像信息 return true; else if( p_cur->i_pic_order_cnt_type == 1 && ( p_cur->i_delta_pic_order_cnt0 != p_prev->i_delta_pic_order_cnt0 || p_cur->i_delta_pic_order_cnt1 != p_prev->i_delta_pic_order_cnt1 ) ) // 当前片信息的POC为0模式并且POC图像展示序号不一样时,代表是新的图像信息 return true; if( ( p_cur->i_nal_type == H264_NAL_SLICE_IDR || p_prev->i_nal_type == H264_NAL_SLICE_IDR ) && ( p_cur->i_nal_type != p_prev->i_nal_type || p_cur->i_idr_pic_id != p_prev->i_idr_pic_id ) ) // 当前帧片信息的NALU类型有一个时IDR帧,并且不相同或IDR id不相同时,代表是新的图像信息 return true; return false;}

10.4、HxxxParse_AnnexB_SEI实现分析:

//【vlc/modules/packetizer/hxxx_sei.c】void HxxxParse_AnnexB_SEI(const uint8_t *p_buf, size_t i_buf,                          uint8_t i_header, pf_hxxx_sei_callback cb, void *cbdata){
// hxxx_strip_AnnexB_startcode该方法实现请见前面的分析 if( hxxx_strip_AnnexB_startcode( &p_buf, &i_buf ) ) // 若移除/跳过AnnexB流格式的起始码成功时解析SEI单元数据 // 见下面的分析 HxxxParseSEI(p_buf, i_buf, i_header, cb, cbdata);}//【vlc/modules/packetizer/hxxx_sei.c】void HxxxParseSEI(const uint8_t *p_buf, size_t i_buf, uint8_t i_header, pf_hxxx_sei_callback pf_callback, void *cbdata){
bs_t s_ep3b; bs_t s; unsigned i_bitflow = 0; bool b_continue = true; char buf[256]; int i = 0; if( i_buf <= i_header ) // 当前NAL头部大小【单位字节】大于等于当前数据块负载数据大小时 return; // 初始化s_ep3b码流信息对象,其跳过了NAL头部信息 bs_init( &s_ep3b, &p_buf[i_header], i_buf - i_header ); /* skip nal unit header */ // 记录的当前码流读取位置 s_ep3b.p_fwpriv = &i_bitflow; // 转换功能【是否将模拟3字节转换为RBSP即原始字节序列负载数据】 // 作用其实就是:解码时脱壳操作即去掉NALU RBSP中的防伪0x03字节【避免与起始码竞争】 // 见10.1.2小节分析 s_ep3b.pf_forward = hxxx_bsfw_ep3b_to_rbsp; /* Does the emulated 3bytes conversion to rbsp */ // 【bs_remain】见上面的分析即获取数据的总可读比特位数 // 【此处提到的最大65535个字节数原因是bs_remain返回int有符号类型值,若是32位则+正数最大2Exp16 - 1为65535】 /* While a NAL can technically be up to 65535 bytes, an SEI NAL will never be anywhere near that size */ if (bs_remain(&s_ep3b) / 8 > sizeof(buf)) // 码流中数据大于输入的数据大小,理论上此处不会发生的 return; while (bs_remain(&s_ep3b) > 0) // 若有可读比特位,则进行8比特数的读取, // 转换每8位比特二进制值表示的int值保存在char数组中【最大为256个字节数据】 buf[i++] = bs_read(&s_ep3b, 8); // 将产生的RBSP字节数据解析为SEI信息 /* Parse the resulting RBSP bytes as SEI */ // 初始化s码流对象值 bs_init( &s, buf, i); // bs_aligned实现:[见10.4.1小节分析] // 判断码流每个字节可读比特位数是否是字节对齐的即是否是8字节的倍数 while( bs_remain( &s ) >= 8 && bs_aligned( &s ) && b_continue ) {
// 码流中可读比特数必须大于等于8 /* Read type */ unsigned i_type = 0; // 读取SEI类型,该类型定义在【H.264/H.265 annex D】标准中 while( bs_remain( &s ) >= 8 ) {
// 循环尝试读取类型值,从数据开始位置读取每个字节表示的值, // 若不为0xff则表示找到了并退出查找,0xff表示无效值 const uint8_t i_byte = bs_read( &s, 8 ); i_type += i_byte; if( i_byte != 0xff ) break; } /* Read size */ unsigned i_size = 0; while( bs_remain( &s ) >= 8 ) {
// 循环尝试读取SEI数据大小值,从数据开始位置读取每个字节表示的值, // 若不为0xff则表示找到了并退出查找,0xff表示无效值 const uint8_t i_byte = bs_read( &s, 8 ); i_size += i_byte; if( i_byte != 0xff ) break; } // 检查SEI码流数据可读比特数,若小于8则退出循环处理 /* Check room */ if( bs_remain( &s ) < 8 ) break; hxxx_sei_data_t sei_data; sei_data.i_type = i_type; // bs_pos方法实现返回:( 8 * ( s->p - s->p_start ) + 8 - s->i_left ) // 即计算的是已读数据比特数 // 保存码流数据中的起始偏移量【即已读数据比特数】 /* Save start offset */ const unsigned i_start_bit_pos = bs_pos( &s ); switch( i_type ) {
/* Look for pic timing, do not decode locally */ case HXXX_SEI_PIC_TIMING: {
// 从SPS NALU单元数据中获取SEI图像时间 // picture timing sei提供了关于当前access unit的CPB removal delay和DPB output delay信息。 // 一些分析软件分析码流的VBV Buffer模型时可以根据这些语法元素计算每个access unit // 从CPB中的移出时间,验证码流是否有上下溢。 sei_data.p_bs = &s; // 该方法调用的是:ParseSeiCallback // 见10.5小节分析 b_continue = pf_callback( &sei_data, cbdata ); } break; /* Look for user_data_registered_itu_t_t35 */ case HXXX_SEI_USER_DATA_REGISTERED_ITU_T_T35: {
// 视频字幕标准处理流程 size_t i_t35; uint8_t *p_t35 = malloc( i_size ); if( !p_t35 ) break; for( i_t35 = 0; i_t35
= 8; i_t35++ ) p_t35[i_t35] = bs_read( &s, 8 ); // 附加信息 /* TS 101 154 Auxiliary Data and H264/AVC video */ if( i_t35 > 4 && p_t35[0] == 0xb5 /* United States */ ) {
// 该字幕类型为美国US格式 if( p_t35[1] == 0x00 && p_t35[2] == 0x31 && /* US provider code for ATSC / DVB1 */ i_t35 > 7 ) {
// ATSC / DVB1字幕编码类型 switch( VLC_FOURCC(p_t35[3],p_t35[4],p_t35[5],p_t35[6]) ) {
case VLC_FOURCC('G', 'A', '9', '4'): if( p_t35[7] == 0x03 ) {
sei_data.itu_t35.type = HXXX_ITU_T35_TYPE_CC; sei_data.itu_t35.u.cc.i_data = i_t35 - 8; sei_data.itu_t35.u.cc.p_data = &p_t35[8]; b_continue = pf_callback( &sei_data, cbdata ); } break; default: break; } } else if( p_t35[1] == 0x00 && p_t35[2] == 0x2f && /* US provider code for DirecTV */ p_t35[3] == 0x03 && i_t35 > 5 ) {
// DirecTV字幕编码类型 /* DirecTV does not use GA94 user_data identifier */ sei_data.itu_t35.type = HXXX_ITU_T35_TYPE_CC; sei_data.itu_t35.u.cc.i_data = i_t35 - 5; sei_data.itu_t35.u.cc.p_data = &p_t35[5]; b_continue = pf_callback( &sei_data, cbdata ); } } free( p_t35 ); } break; case HXXX_SEI_FRAME_PACKING_ARRANGEMENT: {
// 帧包装SEI信息处理 // 帧包装SEI(frame_packing_arrangement)包括作为优先级信息的裁切忽略标志(frame_cropping_override_flag), // 其指示当显示包装图像时是否优先使用帧包装SEI。当指示在显示包装图像时优先使用帧包装SEI时, // 裁切忽略标志是1,而当在显示包装图像时不优先使用帧包装SEI时,裁切忽略标志是0。 // 读取跳过无符号类型的指数哥伦布编码值 bs_read_ue( &s ); // 读取一个比特数的值 if ( !bs_read1( &s ) ) {
// 若为0 // 读取7个比特数表示的int值,即帧包装SEI信息类型 sei_data.frame_packing.type = bs_read( &s, 7 ); // 读取跳过1个比特数 bs_read( &s, 1 ); // 帧包装SEI信息是否为第一个左端信息 if( bs_read( &s, 6 ) == 2 ) /*intpr type*/ sei_data.frame_packing.b_left_first = false; else sei_data.frame_packing.b_left_first = true; sei_data.frame_packing.b_flipped = bs_read1( &s ); // 帧包装SEI信息:是否为帧或场 sei_data.frame_packing.b_fields = bs_read1( &s ); sei_data.frame_packing.b_frame0 = bs_read1( &s ); } else // 若为1,表示取消帧包装SEI类型信息值的使用 sei_data.frame_packing.type = FRAME_PACKING_CANCEL; } break; /* Look for SEI recovery point */ case HXXX_SEI_RECOVERY_POINT: {
// SEI信息图像错误时视频帧恢复点位置 // 读取图像错误时恢复帧数 sei_data.recovery.i_frames = bs_read_ue( &s ); // 是否精准匹配 //bool b_exact_match = bs_read( &s, 1 ); // 是否损坏链【是否有哦中断】 //bool b_broken_link = bs_read( &s, 1 ); // 片【视频帧GOP计数改变值】 //int i_changing_slice_group = bs_read( &s, 2 ); // 该方法调用的是:ParseSeiCallback // 见10.5小节分析 b_continue = pf_callback( &sei_data, cbdata ); } break; case HXXX_SEI_MASTERING_DISPLAY_COLOUR_VOLUME: {
// 精确展示色域【更好的展示图像细节】 if ( bs_remain( &s ) < (16*6+16*2+32+32) ) /* not enough data */ // 若没有足够数据读取则退出switch break; // colorprim:Specify color primaries to use when converting to RGB, // 可理解为色域,常见的有Rec.709(全高清广播标准)、Rec.2020(4K/8K广播标准BT.2020)、Adobe RGB、P3等 for ( size_t i = 0; i < 6 ; ++i) // 色域值 sei_data.colour_volume.primaries[i] = bs_read( &s, 16 ); for ( size_t i = 0; i < 2 ; ++i) // 白点位置,可网上查阅该部分知识点 sei_data.colour_volume.white_point[i] = bs_read( &s, 16 ); // 最大最小亮度值 sei_data.colour_volume.max_luminance = bs_read( &s, 32 ); sei_data.colour_volume.min_luminance = bs_read( &s, 32 ); // 该方法调用的是:ParseSeiCallback // 见10.5小节分析 b_continue = pf_callback( &sei_data, cbdata ); } break; case HXXX_SEI_CONTENT_LIGHT_LEVEL: {
// SEI内容亮度等级 if ( bs_remain( &s ) < (16+16) ) /* not enough data */ break; // 最大content light level sei_data.content_light_lvl.MaxCLL = bs_read( &s, 16 ); sei_data.content_light_lvl.MaxFALL = bs_read( &s, 16 ); // 该方法调用的是:ParseSeiCallback // 见10.5小节分析 b_continue = pf_callback( &sei_data, cbdata ); } break; default: /* Will skip */ break; } // 保存码流数据中的数据读取偏移量【即已读数据比特数】 const unsigned i_end_bit_pos = bs_pos( &s ); /* Skip unsparsed content */ if( i_end_bit_pos - i_start_bit_pos > i_size * 8 ) /* Something went wrong with _ue reads */ // 若本次循环待读数据大小都已读取,则退出while break; // 若待读取数据大小未读取完毕,则跳过剩余的待读取数据大小,继续下一次while循环读取SEI数据处理 bs_skip( &s, i_size * 8 - ( i_end_bit_pos - i_start_bit_pos ) ); }}

10.4.1、bs_aligned实现分析:

static inline bool bs_aligned( bs_t *s ){
// 判断码流每个字节可读比特位数是否是字节对齐的即是否是8字节的倍数 return s->i_left % 8 == 0;}

10.5、ParseSeiCallback实现分析:

裁切忽略标志是1,而当在显示包装图像时不优先使用帧包装SEI时,裁切忽略标志是0。            if( p_dec->fmt_in.video.multiview_mode == MULTIVIEW_2D )            {
// 输出流多视图模式格式:视频多视图模式为2D模式时,获取输出流的视图模式 video_multiview_mode_t mode; switch( p_sei_data->frame_packing.type ) {
case FRAME_PACKING_INTERLEAVED_CHECKERBOARD: mode = MULTIVIEW_STEREO_CHECKERBOARD; break; case FRAME_PACKING_INTERLEAVED_COLUMN: mode = MULTIVIEW_STEREO_COL; break; case FRAME_PACKING_INTERLEAVED_ROW: mode = MULTIVIEW_STEREO_ROW; break; case FRAME_PACKING_SIDE_BY_SIDE: mode = MULTIVIEW_STEREO_SBS; break; case FRAME_PACKING_TOP_BOTTOM: mode = MULTIVIEW_STEREO_TB; break; case FRAME_PACKING_TEMPORAL: mode = MULTIVIEW_STEREO_FRAME; break; case FRAME_PACKING_TILED: default: mode = MULTIVIEW_2D; break; } p_dec->fmt_out.video.multiview_mode = mode; } } break; /* Look for SEI recovery point */ case HXXX_SEI_RECOVERY_POINT: {
// SEI信息图像错误时视频帧恢复位置 if( !p_sys->b_recovered ) msg_Dbg( p_dec, "Seen SEI recovery point, %d recovery frames", p_sei_data->recovery.i_frames ); // 图像错误时恢复帧数 p_sys->i_recovery_frame_cnt = p_sei_data->recovery.i_frames; } break; default: /* Will skip */ break; } return true;}

10.6、OutputPicture实现分析:

从打包模块解码对象中获取输出图像数据

//【vlc/modules/packetizer/h264.c】static block_t *OutputPicture( decoder_t *p_dec ){
decoder_sys_t *p_sys = p_dec->p_sys; block_t *p_pic = NULL; block_t **pp_pic_last = &p_pic; if( unlikely(!p_sys->frame.p_head) ) {
// 若帧数据为空则清空处理,以下方法见前面已分析的流程 assert( p_sys->frame.p_head ); DropStoredNAL( p_sys ); ResetOutputVariables( p_sys ); cc_storage_reset( p_sys->p_ccs ); return NULL; } // 绑定已匹配或参考的PPS和SPS NALU单元信息,均不能为空 /* Bind matched/referred PPS and SPS */ const h264_picture_parameter_set_t *p_pps = p_sys->p_active_pps; const h264_sequence_parameter_set_t *p_sps = p_sys->p_active_sps; if( !p_pps || !p_sps ) {
DropStoredNAL( p_sys ); ResetOutputVariables( p_sys ); cc_storage_reset( p_sys->p_ccs ); return NULL; } if( !p_sys->b_recovered && p_sys->i_recoveryfnum == UINT_MAX && p_sys->i_recovery_frame_cnt == UINT_MAX && p_sys->slice.type == H264_SLICE_TYPE_I ) {
// 注译:没有办法在I片【帧】错误时使用SEI信息,仅仅通过I片【帧】同步 /* No way to recover using SEI, just sync on I Slice */ // 因此这种情况下不能使用SEI,即标记当前已恢复状态为true,让其后面不用再处理恢复流程,只通过I帧同步 p_sys->b_recovered = true; } // I帧需要sps pps信息【NALU单元】 bool b_need_sps_pps = p_sys->slice.type == H264_SLICE_TYPE_I && p_sys->p_active_pps && p_sys->p_active_sps; /* Handle SEI recovery */ if ( !p_sys->b_recovered && p_sys->i_recovery_frame_cnt != UINT_MAX && p_sys->i_recoveryfnum == UINT_MAX ) {
// 该情况处理SEI恢复信息 // 错误恢复帧数 p_sys->i_recoveryfnum = p_sys->slice.i_frame_num + p_sys->i_recovery_frame_cnt; // 错误恢复开始帧计数值 p_sys->i_recoverystartfnum = p_sys->slice.i_frame_num; b_need_sps_pps = true; /* SPS/PPS must be inserted for SEI recovery */ // 注译:使用SEI信息进行错误恢复,预滚i_recovery_frame_cnt个参考【帧】图像 // 备注:预滚(Preroll)概念【或预缓存】:一个sink元素只有当有一个buffer被缓冲到sink pad里面时, // 才能够完成到PAUSED状态的改变,这个过程就被称为预滚(Preroll),这样做是为了能够尽快的进入到PLAYING状态, // 以免给用户造成视觉上的延迟。 // 预滚(Preroll)在音视频同步方面是非常关键的,确保不会有buffer被sink元素抛弃。 msg_Dbg( p_dec, "Recovering using SEI, prerolling %u reference pics", p_sys->i_recovery_frame_cnt ); } if( p_sys->i_recoveryfnum != UINT_MAX ) {
// 若恢复帧数不为默认值 assert(p_sys->b_recovered == false); // 最大帧数 const unsigned maxFrameNum = 1 << (p_sps->i_log2_max_frame_num + 4); if( ( p_sys->i_recoveryfnum > maxFrameNum && p_sys->slice.i_frame_num < p_sys->i_recoverystartfnum && p_sys->slice.i_frame_num >= p_sys->i_recoveryfnum % maxFrameNum ) || ( p_sys->i_recoveryfnum <= maxFrameNum && p_sys->slice.i_frame_num >= p_sys->i_recoveryfnum ) ) {
// 若条件成立,则表示【从SEI恢复点恢复完成】,因此将恢复点功能标记设为已处理完成 // 备注:vlc中支持SEI Recovery Point功能的 p_sys->i_recoveryfnum = UINT_MAX; p_sys->b_recovered = true; msg_Dbg( p_dec, "Recovery from SEI recovery point complete" ); } } // 若有需要则收集PPS/SPS信息到p_xpsnal数据块中 /* Gather PPS/SPS if required */ block_t *p_xpsnal = NULL; block_t **pp_xpsnal_tail = &p_xpsnal; if( b_need_sps_pps || p_sys->b_new_sps || p_sys->b_new_pps ) {
for( int i = 0; i <= H264_SPS_ID_MAX && (b_need_sps_pps || p_sys->b_new_sps); i++ ) {
if( p_sys->sps[i].p_block ) // SPS不为空时 // 【block_Duplicate】备份block即数据拷贝到新block对象中。 // 该方法的实现前面第6小节中已分析,就是将新block数据块链接到【p_xpsnal】总链表的末尾 block_ChainLastAppend( &pp_xpsnal_tail, block_Duplicate( p_sys->sps[i].p_block ) ); } for( int i = 0; i < H264_PPS_ID_MAX && (b_need_sps_pps || p_sys->b_new_pps); i++ ) {
if( p_sys->pps[i].p_block ) // 同上实现原理 block_ChainLastAppend( &pp_xpsnal_tail, block_Duplicate( p_sys->pps[i].p_block ) ); } } // 现在重建NAL序列数据,插入PPS/SPS信息(如果有的话) /* Now rebuild NAL Sequence, inserting PPS/SPS if any */ if( p_sys->leading.p_head && (p_sys->leading.p_head->i_flags & BLOCK_FLAG_PRIVATE_AUD) ) {
// Access Unit Delimiter访问单元分隔符: // 一般文档没有对AUD进行描叙,其实这是一个帧开始的标志,字节顺序为:00 00 00 01 09 f0 // 从结构上看,有start code, 所以确实是一个NALU,类型09在H264定义里就是AUD(分割器)。 // 大部分播放器可以在没有AUD的情况下正常播放。 // 此处取出leading头数据块,将其放入【pp_pic_last】指向的【p_pic】总数据块链表尾部 // 备注:此时【p_pic】指向链表为null,因此【p_pic】直接指向了【p_au】的数据块 block_t *p_au = p_sys->leading.p_head; p_sys->leading.p_head = p_au->p_next; p_au->p_next = NULL; block_ChainLastAppend( &pp_pic_last, p_au ); } if( p_xpsnal ) // 在上面AU分隔符数据块链表尾部加入上面的SPS和PPS组成的单链表【p_xpsnal】 block_ChainLastAppend( &pp_pic_last, p_xpsnal ); if( p_sys->leading.p_head ) // 同上分析,在【pp_pic_last】指向的【p_pic】总数据块链表尾部加入【p_sys->leading.p_head】数据 block_ChainLastAppend( &pp_pic_last, p_sys->leading.p_head ); assert( p_sys->frame.p_head ); if( p_sys->frame.p_head ) // 同上分析,在【pp_pic_last】指向的【p_pic】总数据块链表尾部加入【p_sys->frame.p_head】视频帧数据 block_ChainLastAppend( &pp_pic_last, p_sys->frame.p_head ); // 上面已经获取完所有关于H264视频码流数据, // 因此此处重置清空这些数据块链指针指向NULL /* Reset chains, now empty */ p_sys->frame.p_head = NULL; p_sys->frame.pp_append = &p_sys->frame.p_head; p_sys->leading.p_head = NULL; p_sys->leading.pp_append = &p_sys->leading.p_head; // 将上面数据块链表数据聚集/打包成一个数据块block对象中保存 // 见10.6.1小节分析 p_pic = block_ChainGather( p_pic ); if( !p_pic ) {
// 当前图像数据为空则失败处理 ResetOutputVariables( p_sys ); cc_storage_reset( p_sys->p_ccs ); return NULL; } // MASK值为0xff000000,~MASK取反为0x00ffffff, // 然后和当前flags值进行与运算,目的是清除高位【模块私有】的flags /* clear up flags gathered */ p_pic->i_flags &= ~BLOCK_FLAG_PRIVATE_MASK; // 注译:对PTS修正,交错帧场(多个AU/块) /* for PTS Fixup, interlaced fields (multiple AU/block) */ // OC: order count序列号, POC: picture order count图像显示序列号 // tFOC: TopFieldOrderCnt表示顶场POC,bFOC:BottomFieldOrderCnt表示底场POC int tFOC = 0, bFOC = 0, PictureOrderCount = 0; // 计算当前片【帧/场】图像POC参数值 // 见10.6.2小节分析 h264_compute_poc( p_sps, &p_sys->slice, &p_sys->pocctx, &PictureOrderCount, &tFOC, &bFOC ); // 获取时钟时间戳计数 // 见10.6.3小节分析 unsigned i_num_clock_ts = h264_get_num_ts( p_sps, &p_sys->slice, p_sys->i_pic_struct, tFOC, bFOC ); // frame_mbs_only_flag 本句法元素等于 0 时表示本序列中所有图像的编码模式都是帧, // 没有其他编码模式存在;本句法元素等于 1 时表示本序列中图像的编码模式可能是帧, // 也可能是场或帧场自适应,某个图像具体是哪一种要由其他句法元素决定。 if( p_sps->frame_mbs_only_flag == 0 && p_sps->vui.b_pic_struct_present_flag ) {
// 帧编码模式并且视频可用信息VUI中的图像结构类型存在标志位存在时 switch( p_sys->i_pic_struct ) {
// 注译:顶场和底场片信息 /* Top and Bottom field slices */ // 顶场Slice case 1: // 底场Slice case 2: // 增加标识:当前图像数据块包含单场【隔行帧】 p_pic->i_flags |= BLOCK_FLAG_SINGLE_FIELD; // i_bottom_field_flag:底场标志位 // 1、存在则增加标识为:BLOCK_FLAG_BOTTOM_FIELD_FIRST即隔行帧的第一个底场 // 2、不存在则标识为:BLOCK_FLAG_TOP_FIELD_FIRST即隔行帧的第一个顶场 p_pic->i_flags |= (!p_sys->slice.i_bottom_field_flag) ? BLOCK_FLAG_TOP_FIELD_FIRST : BLOCK_FLAG_BOTTOM_FIELD_FIRST; break; // 注译:以下每种类型片信息都包含了更多场即不止一个场 /* Each of the following slices contains multiple fields */ case 3: p_pic->i_flags |= BLOCK_FLAG_TOP_FIELD_FIRST; break; case 4: p_pic->i_flags |= BLOCK_FLAG_BOTTOM_FIELD_FIRST; break; case 5: p_pic->i_flags |= BLOCK_FLAG_TOP_FIELD_FIRST; break; case 6: p_pic->i_flags |= BLOCK_FLAG_BOTTOM_FIELD_FIRST; break; default: break; } } // 注译:设置视频帧PTS和DTS给当前block数据块时戳信息 /* set dts/pts to current block timestamps */ p_pic->i_dts = p_sys->i_frame_dts; p_pic->i_pts = p_sys->i_frame_pts; // 注译:若当前数据块进行了分割/分片【多个AU访问单元数据或多个数据块】,则检查修复丢失的DTS时戳信息 /* Fixup missing timestamps after split (multiple AU/block)*/ // 检查当前图像DTS if( p_pic->i_dts <= VLC_TS_INVALID ) p_pic->i_dts = date_Get( &p_sys->dts ); if( p_sys->slice.type == H264_SLICE_TYPE_I ) // 当前为I片则初始化该值【前一个参考图像过期POC值】为无效即0 p_sys->prevdatedpoc.pts = VLC_TS_INVALID; if( p_pic->i_pts == VLC_TS_INVALID ) {
// 当前图像PTS无效 if( p_sys->prevdatedpoc.pts > VLC_TS_INVALID && date_Get( &p_sys->dts ) != VLC_TS_INVALID ) {
// 不为I片【帧】时,此处实现:PTS根据DTS来计算得出 date_t pts = p_sys->dts; // 该过期POC时间设置给pts date_Set( &pts, p_sys->prevdatedpoc.pts ); // POC差量【此处得到的是样本数】:顶场POC - 前一个过期的POC中的个数 int diff = tFOC - p_sys->prevdatedpoc.num; if( diff > 0 ) // 增加差量时间 // 该方法的分析见此前章节中已有的分析 date_Increment( &pts, diff ); else // 减少差量时间 date_Decrement( &pts, -diff ); // 根据上面的计算得到当前图像合适的PTS p_pic->i_pts = date_Get( &pts ); // 注译:非单调递增的dts在一些视频33333 33333…35000 /* non monotonically increasing dts on some videos 33333 33333...35000 */ if( p_pic->i_pts < p_pic->i_dts ) // 若PTS小于DTS则将其和DTS相同 p_pic->i_pts = p_pic->i_dts; } /* In case there's no PTS at all */ else if( CanSwapPTSwithDTS( &p_sys->slice, p_sps ) ) {
// 根据注释此处没有PTS值,因此直接和DTS相同即可 // 该方法实现见10.6.4小节分析 p_pic->i_pts = p_pic->i_dts; } else if( p_sys->slice.type == H264_SLICE_TYPE_I && date_Get( &p_sys->dts ) != VLC_TS_INVALID ) {
// 为I帧并且DTS有效 // IDR帧没有PTS,图像显示时间完全是瞎的, // 因此此处默认处理为DTS值基础上加上两个样本数据的时长即为PTS /* Hell no PTS on IDR. We're totally blind */ date_t pts = p_sys->dts; date_Increment( &pts, 2 ); p_pic->i_pts = date_Get( &pts ); } } else if( p_pic->i_dts == VLC_TS_INVALID && CanSwapPTSwithDTS( &p_sys->slice, p_sps ) ) {
// 当前图像PTS有效时但DTS无效并且可以将PTS和DTS交换 p_pic->i_dts = p_pic->i_pts; if( date_Get( &p_sys->dts ) == VLC_TS_INVALID ) // 因此检查p_sys中保存的dts值若为无效,则保存当前图像的dts date_Set( &p_sys->dts, p_pic->i_pts ); } if( p_pic->i_pts > VLC_TS_INVALID ) {
// 若图像PTS有效,则赋值【当前图像PTS和POC值】给该对象对应字段【即前一个过期POC信息】保存起来 p_sys->prevdatedpoc.pts = p_pic->i_pts; p_sys->prevdatedpoc.num = PictureOrderCount; } if( p_pic->i_length == 0 ) {
// 若当前图像播放时长为空,则计算 // p_sys保存的下个解码DTS时间点【可能为当前图像PTS显示时间点】 date_t next = p_sys->dts; // 增加时戳信息组个数的样本数据时长作为当前图像应该播放结束的时间点 // 【时戳信息语法元素的内容说明源时间,拍摄时间或理想的播放时间】 date_Increment( &next, i_num_clock_ts ); // 因此,当前图像播放时长 = 当前图像播放结束时间点 - 当前图像PTS播放时间点 p_pic->i_length = date_Get( &next ) - date_Get( &p_sys->dts ); }// Debug时可以打开调试#if 0 msg_Err(p_dec, "F/BOC %d/%d POC %d %d rec %d flags %x ref%d fn %d fp %d %d pts %ld len %ld", tFOC, bFOC, PictureOrderCount, p_sys->slice.type, p_sys->b_recovered, p_pic->i_flags, p_sys->slice.i_nal_ref_idc, p_sys->slice.i_frame_num, p_sys->slice.i_field_pic_flag, p_pic->i_pts - p_pic->i_dts, p_pic->i_pts % (100*CLOCK_FREQ), p_pic->i_length);#endif // 更新保存当前的DTS时间,用于修正下一个图像帧的时戳 /* save for next pic fixups */ if( date_Get( &p_sys->dts ) != VLC_TS_INVALID ) {
// 当前全局记录的DTS最新值有效 if( p_sys->i_next_block_flags & BLOCK_FLAG_DISCONTINUITY ) // 下个数据块标志位为非连续帧块数据,则清空缓存的DTS值, // 这样就会在下个数据块处理时,不用同步上一个数据的时间戳 date_Set( &p_sys->dts, VLC_TS_INVALID ); else // 若是连续帧数据,则根据当前图像时戳信息组个数作为样本个数来估算下个DTS时间点 date_Increment( &p_sys->dts, i_num_clock_ts ); } if( p_pic ) {
// 当前图像标志位加上当前【下一个块标志位】 p_pic->i_flags |= p_sys->i_next_block_flags; // 并将下一次的赋值为0【未定义标志位】 p_sys->i_next_block_flags = 0; } switch( p_sys->slice.type ) {
// 判断当前图像数据块图像片【帧】类型 case H264_SLICE_TYPE_P: // 添加标识:P帧 p_pic->i_flags |= BLOCK_FLAG_TYPE_P; break; case H264_SLICE_TYPE_B: // B帧 p_pic->i_flags |= BLOCK_FLAG_TYPE_B; break; case H264_SLICE_TYPE_I: // I帧 p_pic->i_flags |= BLOCK_FLAG_TYPE_I; default: break; } if( !p_sys->b_recovered ) {
// 若还未进行恢复点处理,则标记当前图像状态 if( p_sys->i_recoveryfnum != UINT_MAX ) /* recovering from SEI */ // 若是从恢复点SEI信息中恢复的,则标记当前图像为预滚【预缓存】图像 p_pic->i_flags |= BLOCK_FLAG_PREROLL; else // 否则标记当前图像需要Drop丢弃掉 p_pic->i_flags |= BLOCK_FLAG_DROP; } // 此处也是清除该模块私有标识【BLOCK_FLAG_PRIVATE_AUD】 p_pic->i_flags &= ~BLOCK_FLAG_PRIVATE_AUD; // 在图像输出之后进行重置当前保存打包器解码对象的相关值 /* reset after output */ ResetOutputVariables( p_sys ); // 提交字幕存储相关信息给p_ccs /* CC */ cc_storage_commit( p_sys->p_ccs, p_pic ); return p_pic;}

10.6.1、block_ChainGather实现分析:【请查看下一章节分析】

转载地址:http://wopmz.baihongyu.com/

你可能感兴趣的文章