TS码流解析(三)PES

全新系列文章已更新:


我们常说的音视频数据流在TS中被称为Elementary Stream(ES),也称为原始码流(裸流)。ES流本身不含有传输所需的所有信息,为了在传输过程中同时携带PTS(Presentation Time Stamp,显示时间戳)、DTS(Decoding Time Stamp,解码时间戳)等信息,会将ES流加上一个Header,封装成PES(Packet Elementary Stream)包。

1、PES包的接收

PES包往往比较长,所以需要分段传输,这样会有一个问题,接收了多个TS包后我们如何知道哪一包TS是PES的开头,哪一包又是PES的结尾?

TS header中有一个负载开始指示符(payload_unit_start_indicator,PUSI),表示当前TS包的负载是否属于新的一帧:

  • PUSI为0,说明当前的PES包还未接收完成,负载属于当前PES包中的一部分内容,我们需要把这包的负载拼接到之前接收的负载之后;
  • PUSI为1,说明前面一个PES包已经接收完成,当前接收的负载属于一个新的PES包,这时候我们可以来解析前面一个PES包,可以理解为,只有在后面一个PES包到达时我们才能知道前面一个PES包接收完成了。

这里有一点要说明,大多数情况下可以认为一个PES包就是一帧数据,对应着一个PTS和一个DTS,但是也有特殊的情况,一帧数据可能会跨多个PES包。

此外,TS包头中还有一个continuity_counter字段,我们可以用它判断PES包是否连续。PES包接收完成后我们就可以愉快的做解析工作了。

以下是Android的接收流程:

status_t ATSParser::Stream::parse(
        unsigned continuity_counter,
        unsigned payload_unit_start_indicator,
        unsigned transport_scrambling_control,
        unsigned random_access_indicator,
        ABitReader *br, SyncEvent *event) {
    if (mQueue == NULL) {
        return OK;
    }
    // 1
    if (mExpectedContinuityCounter >= 0
            && (unsigned)mExpectedContinuityCounter != continuity_counter) {
        mPayloadStarted = false;
        mPesStartOffsets.clear();
        mBuffer->setRange(0, 0);
        mSubSamples.clear();
        mExpectedContinuityCounter = -1;
        if (!payload_unit_start_indicator) {
            return OK;
        }
    }
    // 2
    mExpectedContinuityCounter = (continuity_counter + 1) & 0x0f;
    // 3
    if (payload_unit_start_indicator) {
        off64_t offset = (event != NULL) ? event->getOffset() : 0;
        if (mPayloadStarted) {
            
            status_t err = flush(event);

            if (err != OK) {
                ALOGW("Error (%08x) happened while flushing; we simply discard "
                      "the PES packet and continue.", err);
            }
        }

        mPayloadStarted = true;
        // There should be at most 2 elements in |mPesStartOffsets|.
        while (mPesStartOffsets.size() >= 2) {
            mPesStartOffsets.erase(mPesStartOffsets.begin());
        }
        mPesStartOffsets.push_back(offset);
    }

    if (!mPayloadStarted) {
        return OK;
    }

    size_t payloadSizeBits = br->numBitsLeft();
    if (payloadSizeBits % 8 != 0u) {
        ALOGE("Wrong value");
        return BAD_VALUE;
    }

    size_t neededSize = mBuffer->size() + payloadSizeBits / 8;
    if (!ensureBufferCapacity(neededSize)) {
        return NO_MEMORY;
    }
    // 4
    memcpy(mBuffer->data() + mBuffer->size(), br->data(), payloadSizeBits / 8);
    mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8);

    if (mScrambled) {
        mSubSamples.push_back({payloadSizeBits / 8,
                 transport_scrambling_control, random_access_indicator});
    }

    return OK;
}
  1. 检查连续计数器是否符合预期,如果不符合预期则直接丢弃已经接收的PES包;
  2. 将预期的连续计数器加一;
  3. 如果PUSI为1,将mPayloadStarted置为1表示负载开始,等到下次PUSI为1时执行flush方法解析前一个PES包;
  4. 将数据存储到mBuffer中,如果已经有了数据就拼接到mBuffer后面;

2、PES包的解析

PES包结构整体如下:

在这里插入图片描述
一个完整的PES包有三个部分:

  • PES packet header:PES包包头;
  • Optional PES header:包头可选字段,
  • PES packet data:PES包的主体,包含音频或视频数据;

在这里插入图片描述

PES packet header包含三个部分,

  • Packet Start Code Prefix:PES包开始码,固定为0x000001;
  • Stream ID:标识了该PES包中包含的数据类型,例如是否是音频、视频或其他数据流;
  • PES Packet Length:表示整个PES包(包括header和data)的长度(字节为单位),如果它为零,则表示该PES包是一个无界长度的数据流;

这里要说明一下Stream ID的作用,我们在parse PES包时会不知道该包的数据到底是audio、video还是其他,所以PES包用Stream ID来做标记。不同的数据类型,也会影响是否存在Optional PES header,在网上其他文章中可能会默认所有TS包都包含下面会讲的flag,但是实际上只有部分PES包包含下面会讲的PTS/DTS flags等可选数据。

一般来说一个流就是一个Track,对应一个Stream Id。

接下来我们一起来看正常音频/视频的可选字段结构:

在这里插入图片描述

  • Markers:固定的比特标记;
  • Scrambling control:指示是否对数据进行了加扰;
  • Priority:优先权标记;
  • Data alignment indicator:数据对齐标志。
  • Copy right:版权;
  • Original or Copy:原始或拷贝标记;
  • PTS DTS flags:表示是否会包含PTS或DTS等时间戳信息;
  • ESCR flag:表示是否包含ESCR(Elementary Stream Clock Reference)字段;
  • ES rate flag:表示是否包含ES码率字段;

关注公众号《青山渺渺》阅读完整内容; 如有问题可在公众号后台私信,也可进入音视频开发技术分享群一起讨论!

在这里插入图片描述

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值