之前看到在ATSParser::Pogram::Stream中会创建一个ESQueue,用于存储解析出来的ES data,这个ESQueue到底是用来做什么的呢?这节就来研究研究。

1、构造函数

ESQueue的全名是ElementaryStreamQueue, 构造函数传入两个参数Mode和flags,mode指定了Stream的类型(H264、AC3等),flag用于标记是否加密加扰等信息

ElementaryStreamQueue::ElementaryStreamQueue(Mode mode, uint32_t flags)
: mMode(mode),
mFlags(flags),
mEOSReached(false),
mCASystemId(0),
mAUIndex(0) { ALOGV("ElementaryStreamQueue(%p) mode %x flags %x isScrambled %d isSampleEncrypted %d",
this, mode, flags, isScrambled(), isSampleEncrypted()); // Create the decryptor anyway since we don't know the use-case unless key is provided
// Won't decrypt if key info not available (e.g., scanner/extractor just parsing ts files)
mSampleDecryptor = isSampleEncrypted() ?
#ifdef __ANDROID_APEX__
new SampleDecryptor
#else
new HlsSampleDecryptor
#endif
: NULL;
}

2、appendData

parsePES之后会把获取到ES流添加到ESQueue中,这里以H264为例子看看为什么要加入到ESQueue中?

status_t err = mQueue->appendData(data, size, timeUs, payloadOffset, PES_scrambling_control);

传入参数为:ESData、数据大小、时间、负载的偏移量,PES加扰控制

status_t ElementaryStreamQueue::appendData(
const void *data, size_t size, int64_t timeUs,
int32_t payloadOffset, uint32_t pesScramblingControl) { if (mEOSReached) {
ALOGE("appending data after EOS");
return ERROR_MALFORMED;
} // 这边是一个很重要的判断,mBuffer == NULL 或 mBuffer数据为空时才会进入判断
if (!isScrambled() && (mBuffer == NULL || mBuffer->size() == 0)) {
switch (mMode) {
case H264:
case MPEG_VIDEO:
{
#if 0
if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) {
return ERROR_MALFORMED;
}
#else
uint8_t *ptr = (uint8_t *)data;
          // 检查NAL头,找到开始的偏移量
ssize_t startOffset = -1;
for (size_t i = 0; i + 2 < size; ++i) {
if (!memcmp("\x00\x00\x01", &ptr[i], 3)) {
startOffset = i;
break;
}
} if (startOffset < 0) {
return ERROR_MALFORMED;
} if (mFormat == NULL && startOffset > 0) {
ALOGI("found something resembling an H.264/MPEG syncword "
"at offset %zd",
startOffset);
} data = &ptr[startOffset];
size -= startOffset;
#endif
break;
} // ......
}
}
   // 检查是否需要扩充buffer的大小
size_t neededSize = (mBuffer == NULL ? 0 : mBuffer->size()) + size;
if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
neededSize = (neededSize + 65535) & ~65535; ALOGV("resizing buffer to size %zu", neededSize); sp<ABuffer> buffer = new ABuffer(neededSize);
if (mBuffer != NULL) {
memcpy(buffer->data(), mBuffer->data(), mBuffer->size());
buffer->setRange(0, mBuffer->size());
} else {
buffer->setRange(0, 0);
} mBuffer = buffer;
}
   // 将数据拷贝到mBuffer当中,并设置可读写范围
memcpy(mBuffer->data() + mBuffer->size(), data, size);
mBuffer->setRange(0, mBuffer->size() + size);
// 创建一个RangeInfo来保存当前buffer的长度,时间、偏移量等信息,并保存到队列当中
RangeInfo info;
info.mLength = size;
info.mTimestampUs = timeUs;
info.mPesOffset = payloadOffset;
info.mPesScramblingControl = pesScramblingControl;
mRangeInfos.push_back(info); #if 0
if (mMode == AAC) {
ALOGI("size = %zu, timeUs = %.2f secs", size, timeUs / 1E6);
hexdump(data, size);
}
#endif return OK;
}

这个方法的主要内容就是将ES data加入到mBuffer当中,当mBuffer为NULL或者数据为空时,会去检查NALU开始的标志,如果前三个字节为0x000001则说明到一个新的NALU,同时添加一个RangeInfo

3、dequeueAccessUnit

在上面appendData之后,还要调用dequeueAccessUnit将buffer取出来放到AnotherPacket中保存,看看dequeueAccessUnit是怎么做的?

sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnit() {
if (isScrambled()) {
return dequeueScrambledAccessUnit();
}
// 这边的flag需要根据创建ATSParser时传入的参数来确定
if ((mFlags & kFlag_AlignedData) && mMode == H264) {
if (mRangeInfos.empty()) {
return NULL;
} RangeInfo info = *mRangeInfos.begin();
mRangeInfos.erase(mRangeInfos.begin()); sp<ABuffer> accessUnit = new ABuffer(info.mLength);
memcpy(accessUnit->data(), mBuffer->data(), info.mLength);
accessUnit->meta()->setInt64("timeUs", info.mTimestampUs); memmove(mBuffer->data(),
mBuffer->data() + info.mLength,
mBuffer->size() - info.mLength); mBuffer->setRange(0, mBuffer->size() - info.mLength); if (mFormat == NULL) {
mFormat = new MetaData;
if (!MakeAVCCodecSpecificData(*mFormat, accessUnit->data(), accessUnit->size())) {
mFormat.clear();
}
} return accessUnit;
} switch (mMode) {
case H264:
// 调用到dequeueAccessUnitH264方法
return dequeueAccessUnitH264();
    // ......
default:
if (mMode != MPEG_AUDIO) {
ALOGE("Unknown mode");
return NULL;
}
return dequeueAccessUnitMPEGAudio();
}
}

MPEG2TSExtractor中创建的ATSParser传入flag参数为0,所以这里会调用到dequeueAccessUnitH264。这个方法中会有一些内容看不懂,比如NAL等,接下来会先补充一下基础知识。

VCL :vide coding layer 视频编码层。这层我的理解是做编码实现

SODB :string of data bits 原始数据比特流。由VCL产生的编码后的数据流

NAL :network abstract layer 网络抽象层。这层我理解为将编码后的结构做封装便于传输,一帧数据就是一个NAL单元

RBSP : raw byte squence playlod。将SODB进行封装成为nal_unit得到的,是一个通用封装格式,主要就是将SODB大小补充为8的倍数,补充方式为先补1,然后补0直到达到8的倍数

NALU :组成NAL的单元。将RBSP针对不同的传输网络进行重新封装之后的单元(nal_unit(RBSP)加上NAL header(1byte)) header定义了当前NALU的类型,NALU类型可以是SPS、PPS、SEI、SLICE等等

sequence:一段h264码流由多个sequence组成,一个sequence是一秒,sequence由固定的结构单元,1SPS + 1PPS + 1SEI +(I +P + B),序列中的每个单元前都有0x000001作为分割符,即每个NALU之前都有0x000001。

SPS :sequence parameter sets 序列参数集。保存了一组编码视频序列的全局参数,

PPS :picture parameter set 图像参数集。作用域编码视频序列中的一个或多个独立图像

SEI :supplemental enhancement information 附加增强信息。包括画面定时等信息

参考:NAL单元简介 NAL单元详解 NAL语法结构

看到这些定义大概就知道了ts packet中存储的ES流应该就是一个个NALU单元,一个个NALU单元可以组成NAL,也就是一帧数据。

接下来看看dequeueAccessUnitH264干了什么

sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitH264() {
const uint8_t *data = mBuffer->data(); size_t size = mBuffer->size();
Vector<NALPosition> nals; size_t totalSize = 0;
size_t seiCount = 0; status_t err;
const uint8_t *nalStart;
size_t nalSize;
bool foundSlice = false;
bool foundIDR = false; ALOGV("dequeueAccessUnit_H264[%d] %p/%zu", mAUIndex, data, size);
  // 获取一个NALU单元的数据范围,header + SODB
while ((err = getNextNALUnit(&data, &size, &nalStart, &nalSize)) == OK) {
if (nalSize == 0) continue;
// 1、获取NALU的类型
unsigned nalType = nalStart[0] & 0x1f;
bool flush = false;
// 2、NALU值 1:slice (P帧) 5:IDR 即时解码刷新(I 帧)
if (nalType == 1 || nalType == 5) {
if (nalType == 5) {
foundIDR = true;
}
if (foundSlice) {
//TODO: Shouldn't this have been called with nalSize-1?
ABitReader br(nalStart + 1, nalSize);
         
unsigned first_mb_in_slice = parseUE(&br);
         // 找到新帧时将flush置为true
if (first_mb_in_slice == 0) {
// This slice starts a new frame. flush = true;
}
} foundSlice = true;
// 3、NALU值 9: 7: SPS
} else if ((nalType == 9 || nalType == 7) && foundSlice) {
// Access unit delimiter and SPS will be associated with the
// next frame.
       // 找到一个SPS并且当前切片已经找到,说明当前帧已经结束了,这个SPS是属于下一序列的,flush置为true,
flush = true;
// 4、SEI
} else if (nalType == 6 && nalSize > 0) {
// found non-zero sized SEI
++seiCount;
} if (flush) {
// The access unit will contain all nal units up to, but excluding
// the current one, separated by 0x00 0x00 0x00 0x01 startcodes. size_t auSize = 4 * nals.size() + totalSize;
sp<ABuffer> accessUnit = new ABuffer(auSize);
sp<ABuffer> sei; if (seiCount > 0) {
sei = new ABuffer(seiCount * sizeof(NALPosition));
accessUnit->meta()->setBuffer("sei", sei);
} #if !LOG_NDEBUG
AString out;
#endif size_t dstOffset = 0;
size_t seiIndex = 0;
size_t shrunkBytes = 0;
for (size_t i = 0; i < nals.size(); ++i) {
const NALPosition &pos = nals.itemAt(i); unsigned nalType = mBuffer->data()[pos.nalOffset] & 0x1f; if (nalType == 6 && pos.nalSize > 0) {
if (seiIndex >= sei->size() / sizeof(NALPosition)) {
ALOGE("Wrong seiIndex");
return NULL;
}
NALPosition &seiPos = ((NALPosition *)sei->data())[seiIndex++];
seiPos.nalOffset = dstOffset + 4;
seiPos.nalSize = pos.nalSize;
} #if !LOG_NDEBUG
char tmp[128];
sprintf(tmp, "0x%02x", nalType);
if (i > 0) {
out.append(", ");
}
out.append(tmp);
#endif
          // 拷贝分隔符
memcpy(accessUnit->data() + dstOffset, "\x00\x00\x00\x01", 4); if (mSampleDecryptor != NULL && (nalType == 1 || nalType == 5)) {
uint8_t *nalData = mBuffer->data() + pos.nalOffset;
size_t newSize = mSampleDecryptor->processNal(nalData, pos.nalSize);
// Note: the data can shrink due to unescaping, but it can never grow
if (newSize > pos.nalSize) {
// don't log unless verbose, since this can get called a lot if
// the caller is trying to resynchronize
ALOGV("expected sample size < %u, got %zu", pos.nalSize, newSize);
return NULL;
}
memcpy(accessUnit->data() + dstOffset + 4,
nalData,
newSize);
dstOffset += newSize + 4; size_t thisShrunkBytes = pos.nalSize - newSize;
//ALOGV("dequeueAccessUnitH264[%d]: nalType: %d -> %zu (%zu)",
// nalType, (int)pos.nalSize, newSize, thisShrunkBytes); shrunkBytes += thisShrunkBytes;
}
else {
// 拷贝数据
memcpy(accessUnit->data() + dstOffset + 4,
mBuffer->data() + pos.nalOffset,
pos.nalSize); dstOffset += pos.nalSize + 4;
//ALOGV("dequeueAccessUnitH264 [%d] %d @%d",
// nalType, (int)pos.nalSize, (int)pos.nalOffset);
}
} #if !LOG_NDEBUG
ALOGV("accessUnit contains nal types %s", out.c_str());
#endif const NALPosition &pos = nals.itemAt(nals.size() - 1);
size_t nextScan = pos.nalOffset + pos.nalSize; memmove(mBuffer->data(),
mBuffer->data() + nextScan,
mBuffer->size() - nextScan); mBuffer->setRange(0, mBuffer->size() - nextScan);
       // 取出一个timestamp
int64_t timeUs = fetchTimestamp(nextScan);
if (timeUs < 0LL) {
ALOGE("Negative timeUs");
return NULL;
}
       // 添加I帧标志
accessUnit->meta()->setInt64("timeUs", timeUs);
if (foundIDR) {
accessUnit->meta()->setInt32("isSync", 1);
}
// 创建MediaFormat,提取csd信息
if (mFormat == NULL) {
mFormat = new MetaData;
if (!MakeAVCCodecSpecificData(*mFormat,
accessUnit->data(),
accessUnit->size())) {
mFormat.clear();
}
} if (mSampleDecryptor != NULL && shrunkBytes > 0) {
size_t adjustedSize = accessUnit->size() - shrunkBytes;
ALOGV("dequeueAccessUnitH264[%d]: AU size adjusted %zu -> %zu",
mAUIndex, accessUnit->size(), adjustedSize);
accessUnit->setRange(0, adjustedSize);
} ALOGV("dequeueAccessUnitH264[%d]: AU %p(%zu) dstOffset:%zu, nals:%zu, totalSize:%zu ",
mAUIndex, accessUnit->data(), accessUnit->size(),
dstOffset, nals.size(), totalSize);
mAUIndex++;
       // 返回buffer
return accessUnit;
} NALPosition pos;
pos.nalOffset = nalStart - mBuffer->data();
pos.nalSize = nalSize;
     // 将偏移量记录到nals当中,最后遍历取出
nals.push(pos); totalSize += nalSize;
}
if (err != (status_t)-EAGAIN) {
ALOGE("Unexpeted err");
return NULL;
} return NULL;
}

这里的主要逻辑是遍历数据,查找里面的NALU单元,直到找到完整的一帧(找到下一帧的帧头说明上一帧结束),然后将一帧的数据拼接起来返回给Stream

再看ATSParser::Stream,创建AnotherPacketSource用到的meta信息就是来自于ESQueue中解析到的数据,每次加入到队列中的都是一帧数据

mSource = new AnotherPacketSource(meta);
mSource->queueAccessUnit(accessUnit);

另外看看seek points,只有I帧会被用来初始化seekpoint

        if (pesStartOffset >= 0 && (event != NULL) && !found && mQueue->getFormat() != NULL) {
int32_t sync = 0;
if (accessUnit->meta()->findInt32("isSync", &sync) && sync) {
int64_t timeUs;
if (accessUnit->meta()->findInt64("timeUs", &timeUs)) {
found = true;
event->init(pesStartOffset, mSource, timeUs, getSourceType());
}
}
}

Android 12(S) MultiMedia(十四)ESQueue的更多相关文章

  1. Android图表库MPAndroidChart(十四)——在ListView种使用相同的图表

    Android图表库MPAndroidChart(十四)--在ListView种使用相同的图表 各位好久不见,最近挺忙的,所有博客更新的比较少,这里今天说个比较简单的图表,那就是在ListView中使 ...

  2. Gradle 1.12 翻译——第十四章. 教程 - 杂七杂八

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

  3. Android UI开发第二十四篇——Action Bar

    Action bar是一个标识应用程序和用户位置的窗口功能,并且给用户提供操作和导航模式.在大多数的情况下,当你需要突出展现用户行为或全局导航的activity中使用action bar,因为acti ...

  4. 【转】Android UI开发第二十四篇——Action Bar

    Action bar是一个标识应用程序和用户位置的窗口功能,并且给用户提供操作和导航模式.在大多数的情况下,当你需要突出展现用户行为或全局导航的activity中使用action bar,因为acti ...

  5. Android开发(二十四)——数据存储SharePreference、SQLite、File、ContentProvider

    Android提供以下四种存储方式: SharePreference SQLite File ContentProvider Android系统中数据基本都是私有的,一般存放在“data/data/程 ...

  6. Xamarin.Android开发实践(十四)

    Xamarin.Android之ListView和Adapter 一.前言 如今不管任何应用都能够看到列表的存在,而本章我们将学习如何使用Xamarin去实现它,以及如何使用适配器和自定义适配器(本文 ...

  7. Android核心分析之十四Android GWES之输入系统

          Android输入系统 依照惯例,在研究Android输入系统之前给出输入系统的本质描述:从哲学的观点来看,输入系统就是解决从哪里来又将到哪里去问题.输入的本质上的工作就是收集用户输入信息 ...

  8. Android OpenGL ES(十四)gl10方法解析

    Android 支持 OpenGL 列表 1.GL 2.GL 10 3.GL 10 EXT 4.GL 11 5.GL 11 EXT 6.GL 11 ExtensionPack 我们将使用 GL10 这 ...

  9. <Android 基础(三十四)> TabLayout 从头到脚

    1. 简介 1.TabLayout给我们提供的是一排横向的标签页 2.#newTab()这个方法来创建新的标签页,然后用过#setText()和#setIcon方法分别修改标签页的文本和图标,创建完成 ...

  10. Android学习笔记(十四) Handler理论补充

    一.如何下载Android源码 在SDK Manager中选中Sources for Android SDK. 二.ThreadLocal初步介绍 1)执行ThreadLocal对象(static f ...

随机推荐

  1. 什么是ip协议二

    前言 续前面一章. 正文 看下ip选项: 看一张图: 这个ip选项一般我们不用看,即使你去搞硬件,那么做c++或者c的人会告诉你填啥,按照他们设置即可. 那么ip是如何传输的呢? 先看这张图,这张图的 ...

  2. 实战指南:使用 xUnit 和 ASP.NET Core 进行集成测试【完整教程】

    引言 集成测试可在包含应用支持基础结构(如数据库.文件系统和网络)的级别上确保应用组件功能正常. ASP.NET Core 通过将单元测试框架与测试 Web 主机和内存中测试服务器结合使用来支持集成测 ...

  3. 关于 Data Lake 的概念、架构与应用场景介绍

    数据湖(Data Lake)概念介绍 什么是数据湖(Data Lake)? 数据湖的起源,应该追溯到2010年10月,由 Pentaho 的创始人兼 CTO, James Dixon 所提出,他提出的 ...

  4. Java 定时任务技术趋势

    ​简介:定时任务是每个业务常见的需求,比如每分钟扫描超时支付的订单,每小时清理一次数据库历史数据,每天统计前一天的数据并生成报表等等. 作者:黄晓萌(学仁) Java 中自带的解决方案 使用 Time ...

  5. 浅谈分布式一致性:Raft 与 SOFAJRaft

    简介: SOFAJRaft已开源 作者 | 家纯来源 | 阿里技术公众号 一 分布式共识算法 (Consensus Algorithm) 1 如何理解分布式共识? 多个参与者针对某一件事达成完全一致: ...

  6. [FE] Quasar 性能优化: 减小 vendor.js 尺寸

    默认情况下,出于性能和缓存的原因,Quasar 所有来自 node_modules 的东西都会被注入到 vendor 中. 但是,如果希望从这个 vendor.js 中添加或删除某些内容,可以如下这样 ...

  7. dotnet 性能优化 利用哈希思想优化大对象集合相等判断性能

    利用哈希的其中一个思想,相同的对象的哈希值相同,可以用来提升一些大对象集合的进行对象相等判断的性能.大对象的相等判断指的是有某些类型的相等判断需要用到对象的很多属性或字段进行参与判断逻辑才能判断两个对 ...

  8. C语言结构体的内存分配

    一.结构体内存分配原则 原则一:结构体中元素按照定义顺序存放到内存中,但并不是紧密排列.从结构体存储的首地址开始 ,每一个元素存入内存中时,它都会认为内存是以自己的宽度来划分空间的,因此元素存放的位置 ...

  9. Swift File Manager 三种文件路径查找方法对比

    目录 1. 引言 2. 三种文件路径查找方法 2. 1 NSSearchPathForDirectoriesInDomains(_:_:_:) 2.2 urls(for:in:) 2.3 url(fo ...

  10. ansible系列(27)--ansible的include任务复用

    目录 1. include任务复用 1.1 多个项目调用相同task 1.2 Inlcude结合tags应用 1. include任务复用 有时,我们发现大量的 Playbook 内容需要重复编写,各 ...