天天看點

【十五】【vlc-android】vlc-sout流媒體輸出端源碼實作分析【Part 2】【01】

【備注:目前章節由于篇幅内容長度問題,是以再次拆分成小章節分析組成,另外由于本人目前工作内容轉為了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】

繼續閱讀