流媒体分析之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 NAL
            data = 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 bit
            buf     = 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 NAL
            data = 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 bit
            buf     = 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 packet
        mpegts_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;
}

猜你喜欢

转载自blog.csdn.net/u012794472/article/details/126781710