【备注:当前章节由于篇幅内容长度问题,因此再次拆分成小章节分析组成,另外由于本人目前工作内容转为了android framework多媒体框架层的维护、优化等日常工作,因此后续会先更新android framework多媒体框架层实现分析,而vlc-sout流媒体处理部分会延缓更新,目前流媒体处理已基本分析了h264数据流打包流程,该部分实现分析基本都在第十五章节每一个小章节中】
接着第十五章节【Part 1】小节分析,可知有如下packetizer模块加载:
若sout不为空时在【vlc/src/input/decoder.c】的【LoadDecoder】方法中会加载【module_need( p_dec, “packetizer”, “$packetizer”, false );】模块。
整个项目中全局查找packetizer模块
a52.c (vlc\modules\packetizer) line 52 : set_capability( "packetizer", 10 )
aes3.c (vlc\modules\codec) line 53 : set_capability( "packetizer", 100 )
av1.c (vlc\modules\packetizer) line 558 : set_capability("packetizer", 50)
avparser.h (vlc\modules\packetizer) line 50 : set_capability( "packetizer", 20 )
copy.c (vlc\modules\packetizer) line 49 : set_capability( "packetizer", 1 )
cvdsub.c (vlc\modules\codec) line 56 : set_capability( "packetizer", 50 )
daala.c (vlc\modules\codec) line 126 : set_capability( "packetizer", 100 )
dirac.c (vlc\modules\packetizer) line 88 : set_capability( "packetizer", 50 )
dts.c (vlc\modules\packetizer) line 48 : set_capability( "packetizer", 10 )
flac.c (vlc\modules\packetizer) line 50 : set_capability("packetizer", 50)
// H264数据流的分组分包器
h264.c (vlc\modules\packetizer) line 63 : set_capability( "packetizer", 50 )
// H265数据流的分组分包器
hevc.c (vlc\modules\packetizer) line 58 : set_capability("packetizer", 50)
kate.c (vlc\modules\codec) line 324 : set_capability( "packetizer", 100 )
lpcm.c (vlc\modules\codec) line 66 : set_capability( "packetizer", 100 )
mlp.c (vlc\modules\packetizer) line 51 : set_capability( "packetizer", 50 )
mpeg4audio.c (vlc\modules\packetizer) line 195 : set_capability("packetizer", 50)
mpeg4video.c (vlc\modules\packetizer) line 56 : set_capability( "packetizer", 50 )
mpegaudio.c (vlc\modules\packetizer) line 89 : set_capability( "packetizer", 10 )
mpegvideo.c (vlc\modules\packetizer) line 76 : set_capability( "packetizer", 50 )
oggspots.c (vlc\modules\codec) line 93 : set_capability("packetizer", 10)
rawvideo.c (vlc\modules\codec) line 72 : set_capability( "packetizer", 100 )
speex.c (vlc\modules\codec) line 105 : set_capability( "packetizer", 100 )
spudec.c (vlc\modules\codec\spudec) line 61 : set_capability( "packetizer", 50 )
svcdsub.c (vlc\modules\codec) line 58 : set_capability( "packetizer", 50 )
theora.c (vlc\modules\codec) line 125 : set_capability( "packetizer", 100 )
vc1.c (vlc\modules\packetizer) line 55 : set_capability( "packetizer", 50 )
vorbis.c (vlc\modules\codec) line 201 : set_capability( "packetizer", 100 )
本章分析【H264推流分包分组流程】
// H264数据流的分组分包器
h264.c (vlc\modules\packetizer) line 63 : set_capability( "packetizer", 50 )
// H265数据流的分组分包器
hevc.c (vlc\modules\packetizer) line 58 : set_capability("packetizer", 50)
模块组件声明:
//【vlc/modules/packetizer/h264.c】
vlc_module_begin ()
set_category( CAT_SOUT )
set_subcategory( SUBCAT_SOUT_PACKETIZER )
set_description( N_("H.264 video packetizer") )
set_capability( "packetizer", 50 )
set_callbacks( Open, Close )
vlc_module_end ()
1、分包分组器模块组件加载初始化:
//【vlc/modules/packetizer/h264.c】
/*****************************************************************************
* Open: probe the packetizer and return score
* When opening after demux, the packetizer is only loaded AFTER the decoder
* That means that what you set in fmt_out is ignored by the decoder in this special case
*****************************************************************************/
static int Open( vlc_object_t *p_this )
{
decoder_t *p_dec = (decoder_t*)p_this;
decoder_sys_t *p_sys;
int i;
// 检查输入格式是否为H264码流
const bool b_avc = (p_dec->fmt_in.i_original_fourcc == VLC_FOURCC( 'a', 'v', 'c', '1' ));
if( p_dec->fmt_in.i_codec != VLC_CODEC_H264 )
return VLC_EGENERIC;
if( b_avc && p_dec->fmt_in.i_extra < 7 )
return VLC_EGENERIC;
/* Allocate the memory needed to store the decoder's structure */
if( ( p_dec->p_sys = p_sys = malloc( sizeof(decoder_sys_t) ) ) == NULL )
{
return VLC_ENOMEM;
}
// 字幕流处理对象创建
p_sys->p_ccs = cc_storage_new();
if( unlikely(!p_sys->p_ccs) )
{
free( p_dec->p_sys );
return VLC_ENOMEM;
}
// 初始化分包分组器,【p_h264_startcode】该值为H264码流的起始码【0x00 0x00 0x01】
// 见第2小节分析
packetizer_Init( &p_sys->packetizer,
p_h264_startcode, sizeof(p_h264_startcode), startcode_FindAnnexB,
p_h264_startcode, 1, 5,
PacketizeReset, PacketizeParse, PacketizeValidate, PacketizeDrain,
p_dec );
// 是否分片,默认不是分片类型NALU信息
p_sys->b_slice = false;
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;
p_sys->b_new_sps = false;
p_sys->b_new_pps = false;
// h264码流 SPS NALU数据分配内存,最大个数默认为31个
for( i = 0; i <= H264_SPS_ID_MAX; i++ )
{
p_sys->sps[i].p_sps = NULL;
p_sys->sps[i].p_block = NULL;
}
p_sys->p_active_sps = NULL;
// h264码流 PPS NALU数据分配内存,最大个数默认为255个
for( i = 0; i <= H264_PPS_ID_MAX; i++ )
{
p_sys->pps[i].p_pps = NULL;
p_sys->pps[i].p_block = NULL;
}
p_sys->p_active_pps = NULL;
// 缓存帧计数最大值,默认32位bit类型的最大值
// cnt此处应该指context单词?TODO
p_sys->i_recovery_frame_cnt = UINT_MAX;
// h264分片对象信息初始化
// 见第3小节分析
h264_slice_init( &p_sys->slice );
// h264直播推流中下一个数据块类型,如是否为连续帧数据
p_sys->i_next_block_flags = 0;
p_sys->b_recovered = false;
// 缓存最大帧个数【默认32位int最大值】
p_sys->i_recoveryfnum = UINT_MAX;
p_sys->i_frame_dts = VLC_TS_INVALID;
p_sys->i_frame_pts = VLC_TS_INVALID;
p_sys->i_dpb_output_delay = 0;
// 图像排序上下文信息初始化
// 见第4小节分析
/* POC */
h264_poc_context_init( &p_sys->pocctx );
p_sys->prevdatedpoc.pts = VLC_TS_INVALID;
// 初始化图像 PTS值
date_Init( &p_sys->dts, 30000 * 2, 1001 );
date_Set( &p_sys->dts, VLC_TS_INVALID );
// 复制ES out输出流数据格式
/* Setup properties */
es_format_Copy( &p_dec->fmt_out, &p_dec->fmt_in );
// H264编码格式流
p_dec->fmt_out.i_codec = VLC_CODEC_H264;
// 需要分组分包数据流进行推流
p_dec->fmt_out.b_packetized = true;
// 视频流时根据帧率计算DTS时间值
if( p_dec->fmt_in.video.i_frame_rate_base &&
p_dec->fmt_in.video.i_frame_rate &&
p_dec->fmt_in.video.i_frame_rate <= UINT_MAX / 2 )
{
date_Change( &p_sys->dts, p_dec->fmt_in.video.i_frame_rate * 2,
p_dec->fmt_in.video.i_frame_rate_base );
}
// 若是avc1类型h264数据流
if( b_avc )
{
// 注译:mp4和matroska封装格式视频才支持的该类型流数据,
// 当需要推流为另外一个媒体流格式那么你需要转换格式处理
/* This type of stream is produced by mp4 and matroska
* when we want to store it in another streamformat, you need to convert
* The fmt_in.p_extra should ALWAYS contain the avcC
* The fmt_out.p_extra should contain all the SPS and PPS with 4 byte startcodes */
// 判断h264流是否为avcC流类型
if( h264_isavcC( p_dec->fmt_in.p_extra, p_dec->fmt_in.i_extra ) )
{
free( p_dec->fmt_out.p_extra );
size_t i_size;
// 此处需要将码流转换为AnnexB流格式类型,因为AnnexB流通常用于实时流,
// 而avcC流格式则常用于存储在硬盘文件中。
// 因此需要转换原始流格式
// 见第5小节分析
p_dec->fmt_out.p_extra = h264_avcC_to_AnnexB_NAL( p_dec->fmt_in.p_extra,
p_dec->fmt_in.i_extra,
&i_size,
&p_sys->i_avcC_length_size );
// 输出格式数据中额外数据大小
p_dec->fmt_out.i_extra = i_size;
// 若该值不为0则为true
p_sys->b_recovered = !!p_dec->fmt_out.i_extra;
if(!p_dec->fmt_out.p_extra)
{
msg_Err( p_dec, "Invalid AVC extradata");
Close( p_this );
return VLC_EGENERIC;
}
}
else
{
msg_Err( p_dec, "Invalid or missing AVC extradata");
Close( p_this );
return VLC_EGENERIC;
}
// 将AVC1格式h264流数据分组分包功能方法指针赋值
// 该方法在第五章分析中调用的
// 见第x小节分析
/* Set callback */
p_dec->pf_packetize = PacketizeAVC1;
}
else
{
/* This type of stream contains data with 3 of 4 byte startcodes
* The fmt_in.p_extra MAY contain SPS/PPS with 4 byte startcodes
* The fmt_out.p_extra should be the same */
// h264流数据分组分包功能方法指针赋值
// 该情况不为AvcC格式时,可能当前码流4字节起始码的SPS PPS数据流,
// 因此不需要转换为AnnexB格式的起始码类型数据流
// 该方法在第五章分析中调用的
/* Set callback */
// 见第xx小节分析
p_dec->pf_packetize = Packetize;
}
// 此处的额外信息其实就是h264头部信息
/* */
if( p_dec->fmt_out.i_extra > 0 )
{
// 分组分包数据头部信息
// 见第6小节分析
packetizer_Header( &p_sys->packetizer,
p_dec->fmt_out.p_extra, p_dec->fmt_out.i_extra );
}
if( b_avc )
{
/* FIXME: that's not correct for every AVC */
if( !p_sys->b_new_pps || !p_sys->b_new_sps )
{
msg_Err( p_dec, "Invalid or missing SPS %d or PPS %d in AVC extradata",
p_sys->b_new_sps, p_sys->b_new_pps );
Close( p_this );
return VLC_EGENERIC;
}
msg_Dbg( p_dec, "Packetizer fed with AVC, nal length size=%d",
p_sys->i_avcC_length_size );
}
/* CC are the same for H264/AVC in T35 sections (ETSI TS 101 154) */
p_dec->pf_get_cc = GetCc;
p_dec->pf_flush = PacketizeFlush;
return VLC_SUCCESS;
}
2、packetizer_Init实现分析:
// 【vlc/modules/packetizer/packetizer_helper.h】
static inline void packetizer_Init( packetizer_t *p_pack,
const uint8_t *p_startcode, int i_startcode,
block_startcode_helper_t pf_start_helper,
const uint8_t *p_au_prepend, int i_au_prepend,
unsigned i_au_min_size,
packetizer_reset_t pf_reset,
packetizer_parse_t pf_parse,
packetizer_validate_t pf_validate,
packetizer_drain_t pf_drain,
void *p_private )
{
// 初始化赋值
// 打包器初始化同步状态
p_pack->i_state = STATE_NOSYNC;
// 初始化字节流
// 见下面分析
block_BytestreamInit( &p_pack->bytestream );
p_pack->i_offset = 0;
// 追加【预设】的Access Unit 访问单元数据大小
// 初始化准备的大小i_au_prepend为1,i_au_min_size最小为5
p_pack->i_au_prepend = i_au_prepend;
// 访问单元数据访问指针
p_pack->p_au_prepend = p_au_prepend;
p_pack->i_au_min_size = i_au_min_size;
// h264起始码【0x00 0x00 0x01】
p_pack->i_startcode = i_startcode;
p_pack->p_startcode = p_startcode;
// 功能指针方法赋值,后续使用
p_pack->pf_startcode_helper = pf_start_helper;
p_pack->pf_reset = pf_reset;
p_pack->pf_parse = pf_parse;
p_pack->pf_validate = pf_validate;
p_pack->pf_drain = pf_drain;
p_pack->p_private = p_private;
}
// 【vlc/build...abi/install/include/vlc/plugins/vlc_block_helper.h】
/*****************************************************************************
* block_bytestream_t management
*****************************************************************************/
static inline void block_BytestreamInit( block_bytestream_t *p_bytestream )
{
// 字节流开始位置指针
p_bytestream->p_chain = p_bytestream->p_block = NULL;
// 字节流结束位置指针
p_bytestream->pp_last = &p_bytestream->p_chain;
// block字节流数据块偏移量即当前block数据读取位置
p_bytestream->i_block_offset = 0;
// 此前所有block数据块的总字节大小
p_bytestream->i_base_offset = 0;
// 总数据大小
p_bytestream->i_total = 0;
}
3、h264_slice_init实现分析:
//【vlc/modules/packetizer/h264_slice.c】
static inline void h264_slice_init( h264_slice_t *p_slice )
{
// NAL类型
p_slice->i_nal_type = -1;
p_slice->i_nal_ref_idc = -1;
// h264 IDR帧id
p_slice->i_idr_pic_id = -1;
// 当前分片数据中的数据帧个数
p_slice->i_frame_num = 0;
// 当前分片数据流类型:I P B帧数据等类型
p_slice->type = H264_SLICE_TYPE_UNKNOWN;
// PPS id值
p_slice->i_pic_parameter_set_id = -1;
// 场域帧标识类型
p_slice->i_field_pic_flag = 0;
// 底场帧类型
p_slice->i_bottom_field_flag = -1;
// 图像排序类型
p_slice->i_pic_order_cnt_type = -1;
p_slice->i_pic_order_cnt_lsb = -1;
p_slice->i_delta_pic_order_cnt_bottom = -1;
p_slice->i_delta_pic_order_cnt0 = 0;
p_slice->i_delta_pic_order_cnt1 = 0;
p_slice->has_mmco5 = false;
}
4、h264_poc_context_init实现分析
//【vlc/modules/packetizer/h264_slice.c】
static inline void h264_poc_context_init( h264_poc_context_t *p_ctx )
{
p_ctx->prevPicOrderCnt.lsb = 0;
p_ctx->prevPicOrderCnt.msb = 0;
// 前一个参考帧解码序列号和偏移量
p_ctx->prevFrameNum = 0;
p_ctx->prevFrameNumOffset = 0;
// 前一个参考引用图像是否为底场
p_ctx->prevRefPictureIsBottomField = false;
// 前一个参考引用图像是否MMCO【内存管理控制操作】标志的值等于5
p_ctx->prevRefPictureHasMMCO5 = false;
}
5、h264_avcC_to_AnnexB_NAL实现分析:
//【vlc/modules/packetizer/h264_nal.c】
uint8_t *h264_avcC_to_AnnexB_NAL( const uint8_t *p_buf, size_t i_buf,
size_t *pi_result, uint8_t *pi_nal_length_size )
{
// 检查最小NAL大小
// 见下面的分析
*pi_result = get_avcC_to_AnnexB_NAL_size( p_buf, i_buf ); /* Does check min size */
if( *pi_result == 0 )
return NULL;
// 读取头6个字节数据信息
/* Read infos in first 6 bytes */
if ( pi_nal_length_size )
*pi_nal_length_size = (p_buf[4] & 0x03) + 1;
// 根据转换后的AnnexB格式的NAL数据大小,计算输出缓存区内存大小,用于存储AnnexB格式数据的NAL数据
uint8_t *p_ret;
uint8_t *p_out_buf = p_ret = malloc( *pi_result );
if( !p_out_buf )
{
*pi_result = 0;
return NULL;
}
p_buf += 5;
// 由前面可知,该处理为将SPS、PPS数据写入AnnexB格式的内存缓存区中【p_out_buf】
// 主要是将AvcC格式数据中的SPS和PPS数据前面加上AnnexB格式的4字节起始码,并返回作为AnnexB NAL数据大小
for ( unsigned int j = 0; j < 2; j++ )
{
const unsigned int i_loop_end = p_buf[0] & (j == 0 ? 0x1f : 0xff);
p_buf++;
for ( unsigned int i = 0; i < i_loop_end; i++)
{
uint16_t i_nal_size = (p_buf[0] << 8) | p_buf[1];
p_buf += 2;
// 添加AnnexB格式的起始码【0x00 0x00 0x00 0x01】
memcpy( p_out_buf, annexb_startcode4, 4 );
p_out_buf += 4;
// 然后将SPS或PPS的数据类型接着放入,并计算大小
memcpy( p_out_buf, p_buf, i_nal_size );
p_out_buf += i_nal_size;
p_buf += i_nal_size;
}
}
return p_ret;
}
//【vlc/modules/packetizer/h264_nal.c】
static size_t get_avcC_to_AnnexB_NAL_size( const uint8_t *p_buf, size_t i_buf )
{
size_t i_total = 0;
// 最小AvcC数据大小为7
if( i_buf < H264_MIN_AVCC_SIZE )
return 0;
// 将当前数据指针位置向后移动5个字节,并大小也随之减小5
p_buf += 5;
i_buf -= 5;
for ( unsigned int j = 0; j < 2; j++ )
{
// 第一个数据为SPS数据,第二个数据才是PPS数据
/* First time is SPS, Second is PPS */
const unsigned int i_loop_end = p_buf[0] & (j == 0 ? 0x1f : 0xff);
p_buf++; i_buf--;
for ( unsigned int i = 0; i < i_loop_end; i++ )
{
if( i_buf < 2 )
return 0;
// [p_buf[0] << 8]右移动8位,此处值应该为0了,因此在或运算则为【p_buf[1]】本身值
// NAL大小
uint16_t i_nal_size = (p_buf[0] << 8) | p_buf[1];
// NAL大小比数据当前数据大小减去2还大,则表示无数据大小
if(i_nal_size > i_buf - 2)
return 0;
// 否则计算AnnexB格式数据中NAL总大小
i_total += i_nal_size + 4;
p_buf += i_nal_size + 2;
i_buf -= i_nal_size + 2;
}
if( j == 0 && i_buf < 1 )
return 0;
}
return i_total;
}
6、packetizer_Header实现分析:
// 【vlc/modules/packetizer/packetizer_helper.h】
static inline void packetizer_Header( packetizer_t *p_pack,
const uint8_t *p_header, int i_header )
{
// 根据h264头部信息大小创建block块数据对象
block_t *p_init = block_Alloc( i_header );
if( !p_init )
return;
// 将header数据地址赋值到block中
memcpy( p_init->p_buffer, p_header, i_header );
block_t *p_pic;
// 见下面的分析
while( ( p_pic = packetizer_Packetize( p_pack, &p_init ) ) )
block_Release( p_pic ); /* Should not happen (only sequence header) */
while( ( p_pic = packetizer_Packetize( p_pack, NULL ) ) )
block_Release( p_pic );
p_pack->i_state = STATE_NOSYNC;
block_BytestreamEmpty( &p_pack->bytestream );
p_pack->i_offset = 0;
}
// 【vlc/modules/packetizer/packetizer_helper.h】
static block_t *packetizer_Packetize( packetizer_t *p_pack, block_t **pp_block )
{
// 见下面的分析
block_t *p_out = packetizer_PacketizeBlock( p_pack, pp_block );
if( p_out )
return p_out;
/* handle caller drain */
if( pp_block == NULL && p_pack->pf_drain )
{
p_out = p_pack->pf_drain( p_pack->p_private );
if( p_out && p_pack->pf_validate( p_pack->p_private, p_out ) )
{
block_Release( p_out );
p_out = NULL;
}
}
return p_out;
}
// 【vlc/modules/packetizer/packetizer_helper.h】
// 将【h264头部信息】buffer负载数据块分包分组
static block_t *packetizer_PacketizeBlock( packetizer_t *p_pack, block_t **pp_block )
{
block_t *p_block = ( pp_block ) ? *pp_block : NULL;
if( p_block == NULL && p_pack->bytestream.p_block == NULL )
return NULL;
if( p_block && unlikely( p_block->i_flags&(BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) ) )
{
// 当前负载数据块不为空,并且数据块标识为【不连续或损坏】时,进入此处,即【不连续或损坏】数据处理
block_t *p_drained = packetizer_PacketizeBlock( p_pack, NULL );
if( p_drained )
// 有需要输出的数据则直接返回
return p_drained;
// true表示损坏数据
const bool b_broken = !!( p_block->i_flags&BLOCK_FLAG_CORRUPTED );
// 标记数据状态为非同步状态
p_pack->i_state = STATE_NOSYNC;
// 清空块数据字节流数据
// 见下面的分析
block_BytestreamEmpty( &p_pack->bytestream );
p_pack->i_offset = 0;
// 重置状态及数据【p_pack的初始化赋值在packetizer_Init方法中】
// 【p_private此处值为decoder层对象,pf_reset调用的是PacketizeReset方法】
// 【PacketizeReset】实现分析见第7小节分析
p_pack->pf_reset( p_pack->p_private, b_broken );
if( b_broken )
{
// 若是损坏的数据块则直接释放清空
block_Release( p_block );
return NULL;
}
}
if( p_block )
// 若有负载数据块
// 见下面的分析
block_BytestreamPush( &p_pack->bytestream, p_block );
// 开启循环处理【打包(分组分包)数据块数据】
for( ;; )
{
bool b_used_ts;
block_t *p_pic;
switch( p_pack->i_state )
{
// 当前分包分组数据状态为【非同步】
case STATE_NOSYNC:
// 查找一个起始码【主要是为了找到h264码流的起始码值并标记当前数据块的类型和处理】
/* Find a startcode */
// 见第8小节分析
// p_pack赋值初始化是在open方法中涉及的【pf_startcode_helper指的是startcode_FindAnnexB方法(vlc/modules/packetizer/h264.c)】
if( !block_FindStartcodeFromOffset( &p_pack->bytestream, &p_pack->i_offset,
p_pack->p_startcode, p_pack->i_startcode,
p_pack->pf_startcode_helper, NULL ) )
// 查找起始码成功则将状态扭转为下一次同步状态
p_pack->i_state = STATE_NEXT_SYNC;
if( p_pack->i_offset )
{
// 不为0时表示在当前块字节流数据中,起始码数据不在字节流开始位置,因此需要移动至起始码字节开始位置
// 需要跳过当前偏移量大小的数据
// 见第9小节分析
block_SkipBytes( &p_pack->bytestream, p_pack->i_offset );
// 然后再将其值为0
p_pack->i_offset = 0;
// 块字节流数据清空
// 该方法虽然处理简单,但一定要注意的是:该方法内部实现只清空释放已读取的数据
block_BytestreamFlush( &p_pack->bytestream );
}
if( p_pack->i_state != STATE_NEXT_SYNC )
// 当前块字节流中没有足够数据【根据上面的分析即没有找到起始码字节数据】
return NULL; /* Need more data */
// 找到起始码之后,将打包器数据读取偏移量加1,
// 进行查找下一个起始码的另一个状态【STATE_NEXT_SYNC】处理
p_pack->i_offset = 1; /* To find next startcode */
/* fallthrough */
case STATE_NEXT_SYNC:
// 查找下一个起始码的【STATE_NEXT_SYNC】状态处理
/* Find the next startcode */
if( block_FindStartcodeFromOffset( &p_pack->bytestream, &p_pack->i_offset,
p_pack->p_startcode, p_pack->i_startcode,
p_pack->pf_startcode_helper, NULL ) )
{
// 查找失败处理
if( pp_block /* not flushing */ || !p_pack->bytestream.p_chain )
// 当前块字节流中没有足够数据【即没有找到起始码字节数据,但不需要清空这些数据】或块字节流的起始块block指针未空,
// 则需要等待更多的数据
return NULL; /* Need more data */
/* When flusing and we don't find a startcode, suppose that
* the data extend up to the end */
// 获取当前块字节流数据中剩余未读取数据大小,并且此处假设数据已读取到末尾了【因为没有找到起始码】
p_pack->i_offset = block_BytestreamRemaining(&p_pack->bytestream);
if( p_pack->i_offset == 0 )
return NULL;
if( p_pack->i_offset <= (size_t)p_pack->i_startcode )
// 若当前剩余数据大小小于等于起始码字节数据大小,则依然表示没有足够多数据
return NULL;
}
// 块字节流数据清空
// 注意:该方法内部实现只清空释放已读取的数据
block_BytestreamFlush( &p_pack->bytestream );
// 获取新的分片数据,并设置其PTS和DTS值
/* Get the new fragment and set the pts/dts */
block_t *p_block_bytestream = p_pack->bytestream.p_block;
// 【i_au_prepend】值在前面分析初始化中值为1,表示需要预设准备的数据大小【即待打包数据指针】,
// 初始化准备的大小i_au_prepend为1,i_au_min_size最小为5。
// 【p_au_prepend】值在前面分析初始化中值为指向起始码【0x00 0x00 0x01】数组的指针,该数据表示需要预设准备的数据【即待打包数据指针】。
// 【i_offset】根据block_FindStartcodeFromOffset该方法处理后,值为指向块字节流数据中的起始码开始位置,
// 并且此处的偏移量在这里的大小代表的含义是【在STATE_NOSYNC状态下找到的前一个起始码开始位置到后一个起始码开始位置之间的数据大小】。
p_pic = block_Alloc( p_pack->i_offset + p_pack->i_au_prepend );
p_pic->i_pts = p_block_bytestream->i_pts;
p_pic->i_dts = p_block_bytestream->i_dts;
// 【&p_pic->p_buffer[p_pack->i_au_prepend]】处理为:从block块数据的负载buffer的第二个字节中开始存取读取到的数据,
// 因此此时第1个字节数据为空的,
// 读取数据大小其实就是上面的【p_pack->i_offset】大小。
block_GetBytes( &p_pack->bytestream, &p_pic->p_buffer[p_pack->i_au_prepend],
p_pic->i_buffer - p_pack->i_au_prepend );
if( p_pack->i_au_prepend > 0 )
// 此处根据前面的分析,可知此处是将读取起始码数组的1个字节数据,其实就是将0x00数据写入负载buffer的第一个字节中。
// 备注:由此分析可知vlc自己实现的推流数据中打包的数据流第一个字节是0x00?? TODO 待后续真实数据分析
memcpy( p_pic->p_buffer, p_pack->p_au_prepend, p_pack->i_au_prepend );
// 重置为0
p_pack->i_offset = 0;
// 解析NAL数据流
/* Parse the NAL */
if( p_pic->i_buffer < p_pack->i_au_min_size )
{
// 若当前打包的图像数据块block数据的负载大小小于5,则直接丢弃该数据
block_Release( p_pic );
p_pic = NULL;
}
else
{
// 图像数据块负载数据访问单元数大于等于5时,进行NAL网络提取层数据解析
// [pf_parse]方法通过前面的分析可知最终调用的是【h264.c中的PacketizeParse方法实现】
// 【PacketizeParse】该实现见第10小节分析
p_pic = p_pack->pf_parse( p_pack->p_private, &b_used_ts, p_pic );
if( b_used_ts )
{
p_block_bytestream->i_dts = VLC_TS_INVALID;
p_block_bytestream->i_pts = VLC_TS_INVALID;
}
}
if( !p_pic )
{
p_pack->i_state = STATE_NOSYNC;
break;
}
if( p_pack->pf_validate( p_pack->p_private, p_pic ) )
{
p_pack->i_state = STATE_NOSYNC;
block_Release( p_pic );
break;
}
/* So p_block doesn't get re-added several times */
if( pp_block )
*pp_block = block_BytestreamPop( &p_pack->bytestream );
p_pack->i_state = STATE_NOSYNC;
return p_pic;
}
}
}
// 【vlc/include/vlc_block_helper.h】
/**
* It flush all data (read and unread) from a block_bytestream_t.
*/
static inline void block_BytestreamEmpty( block_bytestream_t *p_bytestream )
{
block_BytestreamRelease( p_bytestream );
// 见上面分析中,初始化其相关值
block_BytestreamInit( p_bytestream );
}
// 【vlc/include/vlc_block_helper.h】
static inline void block_BytestreamRelease( block_bytestream_t *p_bytestream )
{
block_ChainRelease( p_bytestream->p_chain );
}
// 【vlc/include/vlc_block.h】
static inline void block_ChainRelease( block_t *p_block )
{
// 最终通过循环体进行释放整个数据块链数据
while( p_block )
{
block_t *p_next = p_block->p_next;
block_Release( p_block );
p_block = p_next;
}
}
// 【vlc/include/vlc_block_helper.h】
static inline void block_BytestreamPush( block_bytestream_t *p_bytestream,
block_t *p_block )
{
// 实现问:将[p_block]链表接到【p_bytestream->p_block】总链表的末尾
// 见下面的分析
block_ChainLastAppend( &p_bytestream->pp_last, p_block );
// check,若块字节流对象的【头部】开始数据块指针未赋值,则将其指向【p_block】负载数据块的开始位置
if( !p_bytestream->p_block ) p_bytestream->p_block = p_block;
for( ; p_block; p_block = p_block->p_next )
// 若负载数据块不为空,则计算整个数据块链中所有数据块的总负载数据大小
p_bytestream->i_total += p_block->i_buffer;
}
// 注意:此功能实现比较难理解,但是若是对双重指针和双链表指针熟悉使用的话,
// 就能分析出该方法其实就是将新输入的【p_block】数据块(或块链)加入到当前记录数据块总链表末尾,形成新的总链表
// 【vlc/include/vlc_block.h】
static inline void block_ChainLastAppend( block_t ***ppp_last, block_t *p_block )
{
block_t *p_last = p_block;
// 这个操作是将【ppp_last】双重指针指向的单指针指向当前需要添加的block头结点指针
// 而该指向的【指向的单指针】其实就是此前的总链表末尾数据块指针(根据接下来的分析得出)
**ppp_last = p_block;
// 循环找到负载数据块链尾部数据块指针
while( p_last->p_next ) p_last = p_last->p_next;
// 此处处理注意:意思就是将ppp_last指针指向【最后查到的尾部数据块指针变量本身内存地址,该指针变量指向的值为NULL】
// 【此处“&”与运算符作用为取其指针变量本身的内存地址,ppp_last指针指向该内存地址】
*ppp_last = &p_last->p_next;
}
7、PacketizeReset实现分析:
//【vlc/modules/packetizer/h264.c】
static void PacketizeReset( void *p_private, bool b_broken )
{
decoder_t *p_dec = p_private;
decoder_sys_t *p_sys = p_dec->p_sys;
if( b_broken || !p_sys->b_slice )
{
// 损坏或非分片模式数据时,进入此处
// 丢弃已存储的NAL数据单元数据
// 见下面的分析
DropStoredNAL( p_sys );
// 重置输出端参数值
// 见下面的分析
ResetOutputVariables( p_sys );
p_sys->p_active_pps = NULL;
p_sys->p_active_sps = NULL;
/* POC */
// 图像排序上下文信息初始化 [POC: picture order context]
// 见第4小节分析
h264_poc_context_init( &p_sys->pocctx );
p_sys->prevdatedpoc.pts = VLC_TS_INVALID;
}
// 标记下一个block应该为非连续帧数据
p_sys->i_next_block_flags = BLOCK_FLAG_DISCONTINUITY;
p_sys->b_recovered = false;
p_sys->i_recoveryfnum = UINT_MAX;
date_Set( &p_sys->dts, VLC_TS_INVALID );
}
//【vlc/modules/packetizer/h264.c】
static void DropStoredNAL( decoder_sys_t *p_sys )
{
// 直接释放当前缓存的数据块帧数据,并初始化
block_ChainRelease( p_sys->frame.p_head );
block_ChainRelease( p_sys->leading.p_head );
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;
}
//【vlc/modules/packetizer/h264.c】
static void ResetOutputVariables( decoder_sys_t *p_sys )
{
p_sys->i_frame_dts = VLC_TS_INVALID;
p_sys->i_frame_pts = VLC_TS_INVALID;
// 分片数据类型:I P B等帧类型
p_sys->slice.type = H264_SLICE_TYPE_UNKNOWN;
p_sys->b_new_sps = false;
p_sys->b_new_pps = false;
p_sys->b_slice = false;
// 【补充增强信息单元】SEI数据信息初始化
/* From SEI */
p_sys->i_dpb_output_delay = 0;
p_sys->i_pic_struct = UINT8_MAX;
p_sys->i_recovery_frame_cnt = UINT_MAX;
}
8、block_FindStartcodeFromOffset实现分析:
// 【vlc/include/vlc_block_helper.h】
static inline int block_FindStartcodeFromOffset(
block_bytestream_t *p_bytestream, size_t *pi_offset,
const uint8_t *p_startcode, int i_startcode_length,
block_startcode_helper_t p_startcode_helper,
block_startcode_matcher_t p_startcode_matcher )
{
block_t *p_block, *p_block_backup = 0;
ssize_t i_size = 0;
size_t i_offset, i_offset_backup = 0;
int i_caller_offset_backup = 0, i_match;
// 根据当前打包器数据中记录的数据读取总偏移量加上当前block块字节流数据中偏移量,
// 来查到正确的数据读取位置
/* Find the right place */
i_size = *pi_offset + p_bytestream->i_block_offset;
for( p_block = p_bytestream->p_block;
p_block != NULL; p_block = p_block->p_next )
{
// 遍历整个block块数据链,通过循环减去所有block数据的负载数据大小,来检验size是否正确,当小于0则退出
i_size -= p_block->i_buffer;
if( i_size < 0 ) break;
}
if( unlikely( i_size >= 0 ) )
{
// 若size大于或等于0则进入此处,表示需要读取的数据大小错误,没有足够大的数据来读取
/* Not enough data, bail out */
return VLC_EGENERIC;
}
// 注译:
// 首先寻找startcode起始码第一个字节的出现,如果找到则做更彻底的检查。
/* Begin the search.
* We first look for an occurrence of the 1st startcode byte and
* if found, we do a more thorough check. */
i_size += p_block->i_buffer;
// 此处先减后重新调整该值
*pi_offset -= i_size;
i_match = 0;
// 循环匹配数据块链中所有block数据的起始码,若找到则直接返回成功
for( ; p_block != NULL; p_block = p_block->p_next )
{
// 当前block块数据中进行每个字节的移动检查起始码匹配
for( i_offset = i_size; i_offset < p_block->i_buffer; i_offset++ )
{
/* Use optimized helper when possible */
if( p_startcode_helper && !i_match &&
(p_block->i_buffer - i_offset) > ((size_t)i_startcode_length - 1) )
{
// 条件:没有匹配到并且当前块中待读取剩余数据大小必须大于等于起始码大小
// 对应执行【startcode_FindAnnexB】方法,见第十五章【Part 3】部分1.2.1小节中分析
// 起始码处理寻找AnnexB流格式数据开始位置
const uint8_t *p_res = p_startcode_helper( &p_block->p_buffer[i_offset],
&p_block->p_buffer[p_block->i_buffer] );
if( p_res )
{
// 若不为空,则表示找到了起始码开始位置,并计算打包器的数据读取偏移量值:
// i_offset为此前循环处理过的总block大小,后面括号中处理为起始码距离当前block数据负载开始位置的大小,
// 两个大小相加则表示在当前数据块链中找到的起始码的位置偏移量
*pi_offset += i_offset + (p_res - &p_block->p_buffer[i_offset]);
return VLC_SUCCESS;
}
// 然后将当前i_offset偏移往后移动【起始码减一的位置,减一的原因是:for循环中会加1】
/* Then parsing boundary with legacy code */
i_offset = p_block->i_buffer - (i_startcode_length - 1);
}
// 判断是否匹配到
// 若p_startcode_matcher不为空,经过代码追踪,可知此方法实现目前只有Flac音频流打包器输出时才会传入,
// 因此可见后续音频流媒体打包器分析章节 TODO
// 若p_startcode_matcher为空,则计算【p_block->p_buffer[i_offset] == p_startcode[i_match]】的值,
// 而该计算思路为先检查起始码第一个字节的出现,若为true则做更彻底的检查
bool b_matched = ( p_startcode_matcher )
? p_startcode_matcher( p_block->p_buffer[i_offset], i_match, p_startcode )
: p_block->p_buffer[i_offset] == p_startcode[i_match];
if( b_matched )
{
if( i_match == 0 )
{
// 匹配到了则备份当前第一次匹配到的数据块偏移量值等
p_block_backup = p_block;
i_offset_backup = i_offset;
i_caller_offset_backup = *pi_offset;
}
if( i_match + 1 == i_startcode_length )
{
// 若匹配的字节个数加1等于起始码长度,则i_match代表成功找到起始码结束位置
// 因此计算打包器中记录的起始码开始位置的偏移量【pi_offset】:
// i_offset为此前循环处理过的总block大小,减去起始码结束位置,则表示起始码开始位置,最后相加
/* We have it */
*pi_offset += i_offset - i_match;
return VLC_SUCCESS;
}
// 加1后会再次通过循环重新匹配一次起始码后续的字节值【0x00 0x00 0x00 0x01】
i_match++;
}
else if ( i_match > 0 )
{
// 在match加1过程中没完全匹配整个起始码所有字节值
// 然后将上面match为0时备份数据还原,继续当前block数据块for循环处理,
// 直到整个block块都没有匹配到,则进行下一个block数据处理
/* False positive */
p_block = p_block_backup;
i_offset = i_offset_backup;
*pi_offset = i_caller_offset_backup;
i_match = 0;
}
}
// 当前block数据块所有数据中没有找到起始码,则重新开始下一个block数据块的查找
i_size = 0;
*pi_offset += i_offset;
}
// 失败
*pi_offset -= i_match;
return VLC_EGENERIC;
}
9、block_SkipBytes实现分析:
此小节之后实现分析请查看:
【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 2】【02】