流媒体分析之srt 协议mpegts 封装
一、TS 格式標準介紹
TS是一種音視頻封裝格式,全稱為MPEG2-TS。其中TS即"Transport Stream"的縮寫。
先簡要介紹一下什么是MPEG2-TS:
DVD的音視頻格式為MPEG2-PS,全稱是Program Stream。而TS的全稱則是Transport Stream。MPEG2-PS主要應用于存儲的具有固定時長的節目,如DVD電影,而MPEG-TS則主要應用于實時傳送的節目,比如實時廣播的電視節目。這兩種格式的主要區別是什么呢?簡單地打個比喻說,你將DVD上的VOB文件的前面一截cut掉(或者干脆就是數據損壞),那么就會導致整個文件無法解碼了,而電視節目是你任何時候打開電視機都能解碼(收看)的。
所以,MPEG2-TS格式的特點就是要求從視頻流的任一片段開始都是可以獨立解碼的。
我們可以看出,TS格式是主要用于直播的碼流結構,具有很好的容錯能力。通常TS流的后綴是.ts、.mpg或者.mpeg,多數播放器直接支持這種格式的播放。TS流中不包含快速seek的機制,只能通過協議層實現seek。HLS協議基于TS流實現的。
二、TS 格式詳解
2. TS碼流整體結構
MPEG-2中規定TS傳輸包的長度是固定的,長度為188字節。標準規定每個TS包只能包含一個基本流的數據,不存在跨基本流的情況。
所有的TS包都分為包頭和凈荷部分。TS包中可以填入很多東西(填入的東西都是填入到凈荷部分),有:視頻、音頻、數據(包括PSI、SI以及其它任何形式的數據)。TS只是傳輸層的協議,所以比較多的面向錯誤處理的誤碼糾正。
用c語言描述下MPEG-TS碼流,如下:
MPEG_transport_stream() {
do {
transport_packet()
} while (nextbits() == sync_byte)
}
下圖是對TS碼流的一個分層結構:
?
TS包頭
TS包的包頭提供關于傳輸方面的信息:同步、有無差錯、有無加擾、PCR(節目參考時鐘)等標志。TS包的包頭長度不固定,前32比特(4個字節)固定,后面可能跟有自適應字段(適配域)。32個比特(4個字節)是最小包頭。包頭的結構固定如下:
各字段含義如下:
- sync_byte(同步字節):固定為0x47;該字節由解碼器識別,使包頭和有效負載可相互分離。
- transport_error_indicator(傳輸錯誤標志):‘1’表示在相關的傳輸包中至少有一個不可糾正的錯誤位。當被置1后,在錯誤被糾正之前不能重置為0。
- payload_unit_start_indicator(負載起始標志):為1時,表示當前TS包的有效載荷中包含PES或者PSI的起始位置;在前4個字節之后會有一個調整字節,其的數值為后面調整字段的長度length。因此有效載荷開始的位置應再偏移1+[length]個字節。
- transport_priority(傳輸優先級標志):‘1’表明當前TS包的優先級比其他具有相同PID, 但此位沒有被置‘1’的TS包高。
- PID:指示存儲與分組有效負載中數據的類型。PID值0x0000—0x000F保留。其中0x0000為PAT保留;0x0001為CAT保留;0x1fff為分組保留,即空包。標準中定義的PID分配見下表:
| PID值 | 描述 |
| 0 | PAT(Program Association Table) |
| 1 | CAT(Conditional Access Table) |
| 3-0xF | Reserved |
| 0x10-0x1FFE | 自定義PID,可用于PMT的pid、network的pid或者其他目標 |
| 0x1FFF | 空包 |
| - | 注意PCR的PID可以選擇0、1或者0x10-0x1FFE的任意值。 |
- transport_scrambling_control(加擾控制標志):表示TS流分組有效負載的加密模式。空包為‘00’,如果傳輸包包頭中包括調整字段,不應被加密。其他取值含義是用戶自定義的。
- adaptation_field_control(適配域控制標志):表示包頭是否有調整字段或有效負載。‘00’為ISO/IEC未來使用保留;‘01’僅含有效載荷,無調整字段;‘10’ 無有效載荷,僅含調整字段;‘11’ 調整字段后為有效載荷,調整字段中的前一個字節表示調整字段的長度length,有效載荷開始的位置應再偏移[length]個字節。空包應為‘10’。
- continuity_counter(連續性計數器):隨著每一個具有相同PID的TS流分組而增加,當它達到最大值后又回復到0。范圍為0~15。
關于adaption_filed字段建議參考標準文檔的ch2.4.3.4 Adaptation field一節。
TS包負載部分
TS包中凈荷所傳輸的信息包括兩種類型:
- 視頻、音頻的PES包以及輔助數據;
- 節目專用信息PSI。
當然,TS包也可以是空包。空包用來填充TS流,可能在重新進行多路復用時被插入或刪除。
在系統復用時,視頻、音頻的ES流需進行打包形成視頻、音頻的 PES流,輔助數據(如圖文電視信息)不需要打成PES包。
3. 節目專用信息PSI(Program Specific Information)
在TS流中傳輸的主要有四類表格,其中包含了解復用和顯示節目相關的信息。
節目信息的結構性的描述如下;
- 節目關聯表Program Association Table (PAT) 0x0000
- 節目映射表Program Map Tables (PMT)
- 條件接收表Conditional Access Table (CAT) 0x0001
- 網絡信息表Network Information Table(NIT) 0x0010
- 傳輸流描述表Transport Stream Description Table(TSDT) 0x02
其中PMT中定義了與特定節目相關的PID信息,比如音頻包pid、視頻包pid以及pcr的pid;CAT表格用于流加擾情況下配置參數;NIT是可選的,標準中未詳細定義;TSDT也是可選的。
這些表格信息保存到TS中,需要先切分成section,然后放到TS包中。
這里僅詳細說明PAT和PMT表的構成,其他表格建議參考標準文檔。
PAT表
TS流中會定期出現PAT表。PAT表提供了節目號和對應PMT表格的PID的對應關系。
其具體結構如下圖:
?
第一個字段table_id,8位,用于標識PSI section負載數據的類型。其取值含義如下:
| Value | description |
| 0x00 | program_association_section |
| 0x01 | conditional_access_section (CA_section) |
| 0x02 | TS_program_map_section |
| 0x03 | TS_description_section |
| 0x04 | ISO_IEC_14496_scene_description_section |
| 0x05 | ISO_IEC_14496_object_descriptor_section |
| 0x06-0x37 | ITU-T Rec. H.222.0 / ISO/IEC 13818-1 reserved |
| 0x38-0x3F | Defined in ISO/IEC 13818-6 |
| 0x40-0xFE | User private |
| 0xFF | forbidden |
PAT中定義的節目號(program_number)與PMT_PID的映射。當節目號為0時,存儲的是network_PID。
詳細定義建議參考2.4.4.3 Program association Table一節。
PMT表
PMT在傳送流中用于指示組成某一套節目的視頻、音頻和數據在傳送流中的位置,即對應的TS包的PID值,以及每路節目的節目時鐘參考(PCR)字段的位置。
其結構定義如下:
其中的stream_type標識了對應pid的類型,比如音頻、視頻或者其他類型(具體建議參考2.4.4.9 Semantic definition of fields in Transport Stream program map section一節)。
4. PES包
PES包使用固定的24位起始碼0x000001和一個8為的stream-id,用于說明當前包的類型。PES包中可以包含DTS/PTS等時間戳信息。整體結構如下圖:
?
?
PES包非定長,音頻的PES包小于等于64K,視頻的一般為一幀一個PES包。一幀圖象的PES包通常要由許多個TS包來傳輸。MPEG-2中規定,一個PES包必須由整數個TS包來傳輸。如果承載一個PES包的最后一個TS包沒能裝滿,則用填充字節來填滿;當下一個新的PES包形成時,需用新的TS包來開始傳輸。
PES包的結構如下:
PES_packet() {
packet_start_code_prefix : 24
stream_id : 8
PES_packet_length: 16
optional_pes_header
pes_packet_data
}
- packet_start_code_prefix:24位起始碼,固定必須是'0000 0000 0000 0000 0000 0001' (0x000001)。用于標識包的開始。
- stream_id:在PS流中該字段標識其存儲的基本流的類型和索引號,在TS流中該字段僅標識其存儲的基本流的類型。
- PES_packet_length:16位,用于存儲PES包的長度。
- optional_pes_header需要視stream_id類型而定,其長度不固定(這里包含DTS/PTS時間戳信息)。
- pes_packet_data其長度是PES_packet_length定義的長度值。
最后兩個字段的解析,建議參考標準文件的2.4.3.7 Semantic definition of fields in PES packet一節。
5.ffmpeg 實現封裝:
AVOutputFormat ff_mpegts_muxer = {.name = "mpegts",.long_name = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),.mime_type = "video/MP2T",.extensions = "ts,m2t,m2ts,mts",.priv_data_size = sizeof(MpegTSWrite),.audio_codec = AV_CODEC_ID_MP2,.video_codec = AV_CODEC_ID_MPEG2VIDEO,.init = mpegts_init,.write_packet = mpegts_write_packet,.write_trailer = mpegts_write_end,.deinit = mpegts_deinit,.check_bitstream = mpegts_check_bitstream,.flags = AVFMT_ALLOW_FLUSH | AVFMT_VARIABLE_FPS | AVFMT_NODIMENSIONS,.priv_class = &mpegts_muxer_class, };?mpegts_write_packet 函數:
static int mpegts_write_packet(AVFormatContext *s, AVPacket *pkt) {if (!pkt) {mpegts_write_flush(s);return 1;} else {return mpegts_write_packet_internal(s, pkt);} }mpegts_write_packet_internal
static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) {AVStream *st = s->streams[pkt->stream_index];int size = pkt->size;uint8_t *buf = pkt->data;uint8_t *data = NULL;MpegTSWrite *ts = s->priv_data;MpegTSWriteStream *ts_st = st->priv_data;const int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) * 2;const int64_t max_audio_delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) / 2;int64_t dts = pkt->dts, pts = pkt->pts;int opus_samples = 0;int side_data_size;uint8_t *side_data = NULL;int stream_id = -1;side_data = av_packet_get_side_data(pkt,AV_PKT_DATA_MPEGTS_STREAM_ID,&side_data_size);if (side_data)stream_id = side_data[0];if (ts->copyts < 1) {if (pts != AV_NOPTS_VALUE)pts += delay;if (dts != AV_NOPTS_VALUE)dts += delay;}if (ts_st->first_pts_check && pts == AV_NOPTS_VALUE) {av_log(s, AV_LOG_ERROR, "first pts value must be set\n");return AVERROR_INVALIDDATA;}ts_st->first_pts_check = 0;//H264 封裝if (st->codecpar->codec_id == AV_CODEC_ID_H264) {const uint8_t *p = buf, *buf_end = p + size;uint32_t state = -1;int extradd = (pkt->flags & AV_PKT_FLAG_KEY) ? st->codecpar->extradata_size : 0;int ret = ff_check_h264_startcode(s, st, pkt);if (ret < 0)return ret;if (extradd && AV_RB24(st->codecpar->extradata) > 1)extradd = 0;do {p = avpriv_find_start_code(p, buf_end, &state);av_log(s, AV_LOG_TRACE, "nal %"PRId32"\n", state & 0x1f);if ((state & 0x1f) == 7)extradd = 0;} while (p < buf_end && (state & 0x1f) != 9 &&(state & 0x1f) != 5 && (state & 0x1f) != 1);if ((state & 0x1f) != 5)extradd = 0;if ((state & 0x1f) != 9) { // AUD NALdata = av_malloc(pkt->size + 6 + extradd);if (!data)return AVERROR(ENOMEM);memcpy(data + 6, st->codecpar->extradata, extradd);memcpy(data + 6 + extradd, pkt->data, pkt->size);AV_WB32(data, 0x00000001);data[4] = 0x09;data[5] = 0xf0; // any slice type (0xe) + rbsp stop one bitbuf = data;size = pkt->size + 6 + extradd;}//AAC 封裝} else if (st->codecpar->codec_id == AV_CODEC_ID_AAC) {if (pkt->size < 2) {av_log(s, AV_LOG_ERROR, "AAC packet too short\n");return AVERROR_INVALIDDATA;}if ((AV_RB16(pkt->data) & 0xfff0) != 0xfff0) {int ret;AVPacket pkt2;if (!ts_st->amux) {av_log(s, AV_LOG_ERROR, "AAC bitstream not in ADTS format ""and extradata missing\n");} else {av_init_packet(&pkt2);pkt2.data = pkt->data;pkt2.size = pkt->size;av_assert0(pkt->dts != AV_NOPTS_VALUE);pkt2.dts = av_rescale_q(pkt->dts, st->time_base, ts_st->amux->streams[0]->time_base);ret = avio_open_dyn_buf(&ts_st->amux->pb);if (ret < 0)return ret;ret = av_write_frame(ts_st->amux, &pkt2);if (ret < 0) {ffio_free_dyn_buf(&ts_st->amux->pb);return ret;}size = avio_close_dyn_buf(ts_st->amux->pb, &data);ts_st->amux->pb = NULL;buf = data;}}// h265 封裝} else if (st->codecpar->codec_id == AV_CODEC_ID_HEVC) {const uint8_t *p = buf, *buf_end = p + size;uint32_t state = -1;int extradd = (pkt->flags & AV_PKT_FLAG_KEY) ? st->codecpar->extradata_size : 0;int ret = check_hevc_startcode(s, st, pkt);if (ret < 0)return ret;if (extradd && AV_RB24(st->codecpar->extradata) > 1)extradd = 0;do {p = avpriv_find_start_code(p, buf_end, &state);av_log(s, AV_LOG_TRACE, "nal %"PRId32"\n", (state & 0x7e)>>1);if ((state & 0x7e) == 2*32)extradd = 0;} while (p < buf_end && (state & 0x7e) != 2*35 &&(state & 0x7e) >= 2*32);if ((state & 0x7e) < 2*16 || (state & 0x7e) >= 2*24)extradd = 0;if ((state & 0x7e) != 2*35) { // AUD NALdata = av_malloc(pkt->size + 7 + extradd);if (!data)return AVERROR(ENOMEM);memcpy(data + 7, st->codecpar->extradata, extradd);memcpy(data + 7 + extradd, pkt->data, pkt->size);AV_WB32(data, 0x00000001);data[4] = 2*35;data[5] = 1;data[6] = 0x50; // any slice type (0x4) + rbsp stop one bitbuf = data;size = pkt->size + 7 + extradd;}// opus封裝} else if (st->codecpar->codec_id == AV_CODEC_ID_OPUS) {if (pkt->size < 2) {av_log(s, AV_LOG_ERROR, "Opus packet too short\n");return AVERROR_INVALIDDATA;}/* Add Opus control header */if ((AV_RB16(pkt->data) >> 5) != 0x3ff) {uint8_t *side_data;int side_data_size;int i, n;int ctrl_header_size;int trim_start = 0, trim_end = 0;opus_samples = opus_get_packet_samples(s, pkt);side_data = av_packet_get_side_data(pkt,AV_PKT_DATA_SKIP_SAMPLES,&side_data_size);if (side_data && side_data_size >= 10) {trim_end = AV_RL32(side_data + 4) * 48000 / st->codecpar->sample_rate;}ctrl_header_size = pkt->size + 2 + pkt->size / 255 + 1;if (ts_st->opus_pending_trim_start)ctrl_header_size += 2;if (trim_end)ctrl_header_size += 2;data = av_malloc(ctrl_header_size);if (!data)return AVERROR(ENOMEM);data[0] = 0x7f;data[1] = 0xe0;if (ts_st->opus_pending_trim_start)data[1] |= 0x10;if (trim_end)data[1] |= 0x08;n = pkt->size;i = 2;do {data[i] = FFMIN(n, 255);n -= 255;i++;} while (n >= 0);av_assert0(2 + pkt->size / 255 + 1 == i);if (ts_st->opus_pending_trim_start) {trim_start = FFMIN(ts_st->opus_pending_trim_start, opus_samples);AV_WB16(data + i, trim_start);i += 2;ts_st->opus_pending_trim_start -= trim_start;}if (trim_end) {trim_end = FFMIN(trim_end, opus_samples - trim_start);AV_WB16(data + i, trim_end);i += 2;}memcpy(data + i, pkt->data, pkt->size);buf = data;size = ctrl_header_size;} else {/* TODO: Can we get TS formatted data here? If so we will* need to count the samples of that too! */av_log(s, AV_LOG_WARNING, "Got MPEG-TS formatted Opus data, unhandled");}}if (ts_st->payload_size && (ts_st->payload_size + size > ts->pes_payload_size ||(dts != AV_NOPTS_VALUE && ts_st->payload_dts != AV_NOPTS_VALUE &&dts - ts_st->payload_dts >= max_audio_delay) ||ts_st->opus_queued_samples + opus_samples >= 5760 /* 120ms */)) {mpegts_write_pes(s, st, ts_st->payload, ts_st->payload_size,ts_st->payload_pts, ts_st->payload_dts,ts_st->payload_flags & AV_PKT_FLAG_KEY, stream_id);ts_st->payload_size = 0;ts_st->opus_queued_samples = 0;}if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO || size > ts->pes_payload_size) {av_assert0(!ts_st->payload_size);// for video and subtitle, write a single pes packetmpegts_write_pes(s, st, buf, size, pts, dts,pkt->flags & AV_PKT_FLAG_KEY, stream_id);ts_st->opus_queued_samples = 0;av_free(data);return 0;}if (!ts_st->payload_size) {ts_st->payload_pts = pts;ts_st->payload_dts = dts;ts_st->payload_flags = pkt->flags;}memcpy(ts_st->payload + ts_st->payload_size, buf, size);ts_st->payload_size += size;ts_st->opus_queued_samples += opus_samples;av_free(data);return 0; }總結
以上是生活随笔為你收集整理的流媒体分析之srt 协议mpegts 封装的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【升级U8+】升级U8错误:数据库中已存
- 下一篇: MarkdownPad入门级编写不完全指