流媒体分析之srt 协议mpegts 封装
一、TS 格式標(biāo)準(zhǔn)介紹
TS是一種音視頻封裝格式,全稱為MPEG2-TS。其中TS即"Transport Stream"的縮寫。
先簡要介紹一下什么是MPEG2-TS:
DVD的音視頻格式為MPEG2-PS,全稱是Program Stream。而TS的全稱則是Transport Stream。MPEG2-PS主要應(yīng)用于存儲的具有固定時(shí)長的節(jié)目,如DVD電影,而MPEG-TS則主要應(yīng)用于實(shí)時(shí)傳送的節(jié)目,比如實(shí)時(shí)廣播的電視節(jié)目。這兩種格式的主要區(qū)別是什么呢?簡單地打個(gè)比喻說,你將DVD上的VOB文件的前面一截cut掉(或者干脆就是數(shù)據(jù)損壞),那么就會導(dǎo)致整個(gè)文件無法解碼了,而電視節(jié)目是你任何時(shí)候打開電視機(jī)都能解碼(收看)的。
所以,MPEG2-TS格式的特點(diǎn)就是要求從視頻流的任一片段開始都是可以獨(dú)立解碼的。
我們可以看出,TS格式是主要用于直播的碼流結(jié)構(gòu),具有很好的容錯(cuò)能力。通常TS流的后綴是.ts、.mpg或者.mpeg,多數(shù)播放器直接支持這種格式的播放。TS流中不包含快速seek的機(jī)制,只能通過協(xié)議層實(shí)現(xiàn)seek。HLS協(xié)議基于TS流實(shí)現(xiàn)的。
二、TS 格式詳解
2. TS碼流整體結(jié)構(gòu)
MPEG-2中規(guī)定TS傳輸包的長度是固定的,長度為188字節(jié)。標(biāo)準(zhǔn)規(guī)定每個(gè)TS包只能包含一個(gè)基本流的數(shù)據(jù),不存在跨基本流的情況。
所有的TS包都分為包頭和凈荷部分。TS包中可以填入很多東西(填入的東西都是填入到凈荷部分),有:視頻、音頻、數(shù)據(jù)(包括PSI、SI以及其它任何形式的數(shù)據(jù))。TS只是傳輸層的協(xié)議,所以比較多的面向錯(cuò)誤處理的誤碼糾正。
用c語言描述下MPEG-TS碼流,如下:
MPEG_transport_stream() {
do {
transport_packet()
} while (nextbits() == sync_byte)
}
下圖是對TS碼流的一個(gè)分層結(jié)構(gòu):
?
TS包頭
TS包的包頭提供關(guān)于傳輸方面的信息:同步、有無差錯(cuò)、有無加擾、PCR(節(jié)目參考時(shí)鐘)等標(biāo)志。TS包的包頭長度不固定,前32比特(4個(gè)字節(jié))固定,后面可能跟有自適應(yīng)字段(適配域)。32個(gè)比特(4個(gè)字節(jié))是最小包頭。包頭的結(jié)構(gòu)固定如下:
各字段含義如下:
- sync_byte(同步字節(jié)):固定為0x47;該字節(jié)由解碼器識別,使包頭和有效負(fù)載可相互分離。
- transport_error_indicator(傳輸錯(cuò)誤標(biāo)志):‘1’表示在相關(guān)的傳輸包中至少有一個(gè)不可糾正的錯(cuò)誤位。當(dāng)被置1后,在錯(cuò)誤被糾正之前不能重置為0。
- payload_unit_start_indicator(負(fù)載起始標(biāo)志):為1時(shí),表示當(dāng)前TS包的有效載荷中包含PES或者PSI的起始位置;在前4個(gè)字節(jié)之后會有一個(gè)調(diào)整字節(jié),其的數(shù)值為后面調(diào)整字段的長度length。因此有效載荷開始的位置應(yīng)再偏移1+[length]個(gè)字節(jié)。
- transport_priority(傳輸優(yōu)先級標(biāo)志):‘1’表明當(dāng)前TS包的優(yōu)先級比其他具有相同PID, 但此位沒有被置‘1’的TS包高。
- PID:指示存儲與分組有效負(fù)載中數(shù)據(jù)的類型。PID值0x0000—0x000F保留。其中0x0000為PAT保留;0x0001為CAT保留;0x1fff為分組保留,即空包。標(biāo)準(zhǔn)中定義的PID分配見下表:
| PID值 | 描述 |
| 0 | PAT(Program Association Table) |
| 1 | CAT(Conditional Access Table) |
| 3-0xF | Reserved |
| 0x10-0x1FFE | 自定義PID,可用于PMT的pid、network的pid或者其他目標(biāo) |
| 0x1FFF | 空包 |
| - | 注意PCR的PID可以選擇0、1或者0x10-0x1FFE的任意值。 |
- transport_scrambling_control(加擾控制標(biāo)志):表示TS流分組有效負(fù)載的加密模式。空包為‘00’,如果傳輸包包頭中包括調(diào)整字段,不應(yīng)被加密。其他取值含義是用戶自定義的。
- adaptation_field_control(適配域控制標(biāo)志):表示包頭是否有調(diào)整字段或有效負(fù)載。‘00’為ISO/IEC未來使用保留;‘01’僅含有效載荷,無調(diào)整字段;‘10’ 無有效載荷,僅含調(diào)整字段;‘11’ 調(diào)整字段后為有效載荷,調(diào)整字段中的前一個(gè)字節(jié)表示調(diào)整字段的長度length,有效載荷開始的位置應(yīng)再偏移[length]個(gè)字節(jié)。空包應(yīng)為‘10’。
- continuity_counter(連續(xù)性計(jì)數(shù)器):隨著每一個(gè)具有相同PID的TS流分組而增加,當(dāng)它達(dá)到最大值后又回復(fù)到0。范圍為0~15。
關(guān)于adaption_filed字段建議參考標(biāo)準(zhǔn)文檔的ch2.4.3.4 Adaptation field一節(jié)。
TS包負(fù)載部分
TS包中凈荷所傳輸?shù)男畔▋煞N類型:
- 視頻、音頻的PES包以及輔助數(shù)據(jù);
- 節(jié)目專用信息PSI。
當(dāng)然,TS包也可以是空包。空包用來填充TS流,可能在重新進(jìn)行多路復(fù)用時(shí)被插入或刪除。
在系統(tǒng)復(fù)用時(shí),視頻、音頻的ES流需進(jìn)行打包形成視頻、音頻的 PES流,輔助數(shù)據(jù)(如圖文電視信息)不需要打成PES包。
3. 節(jié)目專用信息PSI(Program Specific Information)
在TS流中傳輸?shù)闹饕兴念惐砀?#xff0c;其中包含了解復(fù)用和顯示節(jié)目相關(guān)的信息。
節(jié)目信息的結(jié)構(gòu)性的描述如下;
- 節(jié)目關(guān)聯(lián)表Program Association Table (PAT) 0x0000
- 節(jié)目映射表Program Map Tables (PMT)
- 條件接收表Conditional Access Table (CAT) 0x0001
- 網(wǎng)絡(luò)信息表Network Information Table(NIT) 0x0010
- 傳輸流描述表Transport Stream Description Table(TSDT) 0x02
其中PMT中定義了與特定節(jié)目相關(guān)的PID信息,比如音頻包pid、視頻包pid以及pcr的pid;CAT表格用于流加擾情況下配置參數(shù);NIT是可選的,標(biāo)準(zhǔn)中未詳細(xì)定義;TSDT也是可選的。
這些表格信息保存到TS中,需要先切分成section,然后放到TS包中。
這里僅詳細(xì)說明PAT和PMT表的構(gòu)成,其他表格建議參考標(biāo)準(zhǔn)文檔。
PAT表
TS流中會定期出現(xiàn)PAT表。PAT表提供了節(jié)目號和對應(yīng)PMT表格的PID的對應(yīng)關(guān)系。
其具體結(jié)構(gòu)如下圖:
?
第一個(gè)字段table_id,8位,用于標(biāo)識PSI section負(fù)載數(shù)據(jù)的類型。其取值含義如下:
| 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中定義的節(jié)目號(program_number)與PMT_PID的映射。當(dāng)節(jié)目號為0時(shí),存儲的是network_PID。
詳細(xì)定義建議參考2.4.4.3 Program association Table一節(jié)。
PMT表
PMT在傳送流中用于指示組成某一套節(jié)目的視頻、音頻和數(shù)據(jù)在傳送流中的位置,即對應(yīng)的TS包的PID值,以及每路節(jié)目的節(jié)目時(shí)鐘參考(PCR)字段的位置。
其結(jié)構(gòu)定義如下:
其中的stream_type標(biāo)識了對應(yīng)pid的類型,比如音頻、視頻或者其他類型(具體建議參考2.4.4.9 Semantic definition of fields in Transport Stream program map section一節(jié))。
4. PES包
PES包使用固定的24位起始碼0x000001和一個(gè)8為的stream-id,用于說明當(dāng)前包的類型。PES包中可以包含DTS/PTS等時(shí)間戳信息。整體結(jié)構(gòu)如下圖:
?
?
PES包非定長,音頻的PES包小于等于64K,視頻的一般為一幀一個(gè)PES包。一幀圖象的PES包通常要由許多個(gè)TS包來傳輸。MPEG-2中規(guī)定,一個(gè)PES包必須由整數(shù)個(gè)TS包來傳輸。如果承載一個(gè)PES包的最后一個(gè)TS包沒能裝滿,則用填充字節(jié)來填滿;當(dāng)下一個(gè)新的PES包形成時(shí),需用新的TS包來開始傳輸。
PES包的結(jié)構(gòu)如下:
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)。用于標(biāo)識包的開始。
- stream_id:在PS流中該字段標(biāo)識其存儲的基本流的類型和索引號,在TS流中該字段僅標(biāo)識其存儲的基本流的類型。
- PES_packet_length:16位,用于存儲PES包的長度。
- optional_pes_header需要視stream_id類型而定,其長度不固定(這里包含DTS/PTS時(shí)間戳信息)。
- pes_packet_data其長度是PES_packet_length定義的長度值。
最后兩個(gè)字段的解析,建議參考標(biāo)準(zhǔn)文件的2.4.3.7 Semantic definition of fields in PES packet一節(jié)。
5.ffmpeg 實(shí)現(xiàn)封裝:
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 函數(shù):
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; }總結(jié)
以上是生活随笔為你收集整理的流媒体分析之srt 协议mpegts 封装的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【升级U8+】升级U8错误:数据库中已存
- 下一篇: MarkdownPad入门级编写不完全指