FFmpeg開發筆記(三十七)分析SRS對HLS協議裡TS包的插幀操作

aqi00發表於2024-07-13
《FFmpeg開發實戰:從零基礎到短影片上線》一書的“2.1.2 音影片檔案的封裝格式”介紹了影片流的PS格式和TS格式。由於TS包的長度固定,從TS流的任一片段開始都能獨立解碼,因此可以把TS當成音影片檔案的封裝格式。

鑑於TS包的獨立解碼特性,HLS協議引入了TS格式作為傳輸單元。HLS協議的實現原理是對一個大的媒體分片,並將分片後的檔案路徑記錄於m3u8檔案,客戶端依據該m3u8檔案即可獲取對應的分片列表,再依次播放分片內容。每個TS分片都以SPS與PPS等配置幀開頭,其中指定了影片的規格資訊及其編碼引數,因此每個TS片段都能正常解析播放。關於SPS與PPS的詳細說明參見之前的文章《解析H.264碼流中的SPS幀和PPS幀》。
上述的分片檔案便是一個個以TS格式封裝的影片資源,那麼當直播源來自一個MP4檔案的時候,流媒體伺服器又是怎麼把MP4檔案轉化為一個個TS分片的呢?
以SRS為例,它在組裝TS包時做了特殊處理,在每個TS包的開頭位置,就自動插入SPS與PPS等配置幀。具體程式碼在SRS框架的trunk/src/main/srs_main_ingest_hls.cpp,檢視該原始碼的SrsIngestHlsOutput::on_ts_video函式,找到以下的程式碼片段,可見程式在寫入H.264流時,先寫入SPS幀和PPS幀,再寫入I幀、P幀和B幀。

if ((ret = write_h264_sps_pps(dts, pts)) != ERROR_SUCCESS) {
    return ret;
}

if ((ret = write_h264_ipb_frame(ibps, frame_type, dts, pts)) != ERROR_SUCCESS) {
    // drop the ts message.
    if (ret == ERROR_H264_DROP_BEFORE_SPS_PPS) {
        return ERROR_SUCCESS;
    }
    return ret;
}

找到write_h264_sps_pps函式的定義程式碼如下,發現函式內部在封裝序列頭時依次輸入了SPS幀和PPS幀:

// h264 raw to h264 packet.
std::string sh;
if ((err = avc->mux_sequence_header(h264_sps, h264_pps, sh)) != srs_success) {
    // TODO: FIXME: Use error
    ret = srs_error_code(err);
    srs_freep(err);
    return ret;
}

進一步跟蹤mux_sequence_header的定義來源,詳細的定義程式碼在SRS框架的trunk/src/protocol/srs_protocol_raw_avc.cpp,檢視該原始碼的SrsRawH264Stream::mux_sequence_header函式,找到以下的程式碼片段,可見程式依據ISO_IEC_14496-15的文件規範,先後寫入了sequenceParameterSet的NAL單元(即SPS幀),以及pictureParameterSet的NAL單元(即PPS幀)。

// sps
if (true) {
    // 5.3.4.2.1 Syntax, ISO_IEC_14496-15-AVC-format-2012.pdf, page 16
    // numOfSequenceParameterSets, always 1
    stream.write_1bytes(uint8_t(0xe0 | 0x01));
    // sequenceParameterSetLength
    stream.write_2bytes((int16_t)sps.length());
    // sequenceParameterSetNALUnit
    stream.write_string(sps);
}

// pps
if (true) {
    // 5.3.4.2.1 Syntax, ISO_IEC_14496-15-AVC-format-2012.pdf, page 16
    // numOfPictureParameterSets, always 1
    stream.write_1bytes(0x01);
    // pictureParameterSetLength
    stream.write_2bytes((int16_t)pps.length());
    // pictureParameterSetNALUnit
    stream.write_string(pps);
}

由此可見,SRS在每個TS包頭都寫入了SPS幀和PPS幀,確保TS包是擁有SPS和PPS的完整H.264分片。只有加上SPS與PPS,客戶端才能正常拉流解析資料,才能正常渲染影片畫面。
更多詳細的FFmpeg開發知識參見《FFmpeg開發實戰:從零基礎到短影片上線》一書。


相關文章