承接上一章节分析:【五】Android MediaPlayer整体架构源码分析 -【prepareAsync/prepare数据准备处理流程】【Part 3】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
【此章节小节编号就接着上一章节排列】
readBuffer(trackType)方法定义实现:
// [frameworks/av/media/libmediaplayerservice/nuplayer/GenericSource.cpp]
void NuPlayer::GenericSource::readBuffer(
media_track_type trackType, int64_t seekTimeUs, MediaPlayerSeekMode mode,
int64_t *actualTimeUs, bool formatChange) {
// 当前类型的媒体track对象
Track *track;
// 当前track最大缓冲区buffer数量(个数),默认为1
size_t maxBuffers = 1;
switch (trackType) {
case MEDIA_TRACK_TYPE_VIDEO:
// video track时,设置视频track缓冲区最大数为8个
track = &mVideoTrack;
// 注译:缓冲区buffer数量太大将会影响seek流程即太大可能会导致seek耗时很久
maxBuffers = 8; // too large of a number may influence seeks
break;
case MEDIA_TRACK_TYPE_AUDIO:
// audio track时,设置音频track缓冲区最大数为64个
track = &mAudioTrack;
maxBuffers = 64;
break;
case MEDIA_TRACK_TYPE_SUBTITLE:
// 字幕流track
track = &mSubtitleTrack;
break;
case MEDIA_TRACK_TYPE_TIMEDTEXT:
// 时序文本流track
// 这个track其实也是字幕流数据,但是它是专门的字幕文件加载的
track = &mTimedTextTrack;
break;
default:
TRESPASS();
}
if (track->mSource == NULL) {
return;
}
// 若不为空,则初始化其值为seek时间戳【可能为-1】
if (actualTimeUs) {
*actualTimeUs = seekTimeUs;
}
// 媒体源读取操作项属性值对象
MediaSource::ReadOptions options;
// 若请求设置的seek时间大于等于0(默认为-1),
// 则设置该seek的媒体时间戳和seek模式
bool seeking = false;
if (seekTimeUs >= 0) {
options.setSeekTo(seekTimeUs, mode);
seeking = true;
}
// 是否支持非阻塞模式一次性读取多个(足够多的)数据
// 备注:将会执行具体媒体提取器解复用模块的音视频track具体实现对象的对应supportReadMultiple()方法
const bool couldReadMultiple = (track->mSource->supportReadMultiple());
if (couldReadMultiple) {
// 支持一次性读取多个数据时则设置非阻塞模式读取数据
options.setNonBlocking();
}
// 根据当前数据源track类型获取数据代数值,其作用和前面章节中分析到的代数值类似功能。
int32_t generation = getDataGeneration(trackType);
// for循环读取当前track数据并解复用数据后返回
for (size_t numBuffers = 0; numBuffers < maxBuffers; ) {
// 创建数据缓冲区集合对象
Vector<MediaBufferBase *> mediaBuffers;
status_t err = NO_ERROR;
// 获取当前track数据源对象
sp<IMediaSource> source = track->mSource;
// 真正读取底层数据时不需要加锁读取
mLock.unlock();
if (couldReadMultiple) {
// 支持非阻塞模式一次性读取多次(足够多的)数据
err = source->readMultiple(
&mediaBuffers, maxBuffers - numBuffers, &options);
} else {
// 高通解复用模块默认采用的是不支持非阻塞模式读取数据,
// 即一次读取一个buffer数据,因此分析此处即可。
// 初始化一个缓冲buffer指针,用于指向读取到的buffer对象
MediaBufferBase *mbuf = NULL;
// 传入track数据源读取操作项值,读取一个缓冲buffer,并将其数据地址赋值给指针mbuf
// 备注:由上面分析可知,此处最终将会执行具体媒体提取器解复用模块的
// 音视频track具体实现对象的对应read()方法。
// 由此实现可知,每个buffer内存是由底层实现分配的。
err = source->read(&mbuf, &options);
if (err == OK && mbuf != NULL) {
// 读取成功则将该buffer已解复用数据放入数据缓冲区
mediaBuffers.push_back(mbuf);
}
}
// 读取数据解复用完成后再次加锁
mLock.lock();
// 本次buffer读取完成则清除所有非持久性选项。【只用于为多次读取缓冲区buffer时】
options.clearNonPersistent();
size_t id = 0;
// 本次读取buffer完成后本次读取的缓冲区buffer总个数,
// [couldReadMultiple]值若为false则本次只读取一个buffer数据
size_t count = mediaBuffers.size();
// 此处则检查当前track是否被改变了,因为上面释放锁读取数据的原因。
// 检查条件就是再次获取全局的当前track类型的数据代数值和上面获取的是否一致,
// 若不一致则表示此次获取的已解复用track数据无效,
// 则释放本次读取的所有buffer内存,并结束本次track数据。
// 关于MediaBufferBase类相关实现原理,暂不展开分析,可自行分析
// in case track has been changed since we don't have lock for some time.
if (generation != getDataGeneration(trackType)) {
for (; id < count; ++id) {
mediaBuffers[id]->release();
}
// 表明需要结束本次track请求(不管本次请求获取解复用buffer数据是否足够)
break;
}
// for循环处理本次读取到的buffer数据,并将其放入【AnotherPacketSource】本地buffer缓存队列中
for (; id < count; ++id) {
int64_t timeUs;
// 媒体缓冲buffer数据,可将其看做一帧视频或一帧音频
MediaBufferBase *mbuf = mediaBuffers[id];
// 获取当前track数据buffer的媒体时间
// 【注意此值非视频关键帧时间,仅仅只是获取当前buffer数据的媒体时间】
// 【也就是当前帧数据应该显示的媒体时间点】
if (!mbuf->meta_data().findInt64(kKeyTime, &timeUs)) {
// 若buffer数据对应的媒体时间为空,则dump打印数据
mbuf->meta_data().dumpToLog();
// 通知当前track的【AnotherPacketSource】对象的signalEOS方法,
// 即它会记录下当前track读取并解复用buffer数据的状态,
// 会用于解码器来获取数据时作为判断数据源读取状态并做相应的处理。
// 该方法见3.8.1小节分析
track->mPackets->signalEOS(ERROR_MALFORMED);
// 然后结束本次获取的后面buffer数据,因为下面会将其丢弃
break;
}
// 并记录当前track类型的最后一个buffer数据的媒体时间
if (trackType == MEDIA_TRACK_TYPE_AUDIO) {
mAudioTimeUs = timeUs;
} else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
mVideoTimeUs = timeUs;
}
// 判断是否需要放入数据非连续性类型信息项到本地非连续类型缓存队列
// 见3.8.2小节分析
queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
// 此处是将底层buffer数据类型转换为本地缓冲区buffer数据类型
// 见3.8.3小节分析
sp<ABuffer> buffer = mediaBufferToABuffer(mbuf, trackType);
// actualTimeUs的作用是:真实音视频媒体时间戳,
// 也就是说它记录的是第一次【numBuffers = 0】获取解复用数据buffer(可能多个)的最后一个buffer的媒体时间戳。
if (numBuffers == 0 && actualTimeUs != nullptr) {
*actualTimeUs = timeUs;
}
if (seeking && buffer != nullptr) {
// seek获取数据时
sp<AMessage> meta = buffer->meta();
if (meta != nullptr && mode == MediaPlayerSeekMode::SEEK_CLOSEST
&& seekTimeUs > timeUs) {
// 本次seek模式是支持seek到非关键帧时,则为本次seek到的每一个buffer数据,
// 添加key字段名为"resume-at-mediaTimeUs"的恢复播放媒体时间点媒体元数据信息
// 即用于后面解码后准备渲染时,用来判断是否应该显示出来,因为支持到非关键帧seek模式,
// 因此必须判断当前渲染帧媒体时间是否达到seek时间点,达到了则允许显示。
// 因此也会有这段时间内可能造成的卡顿现象。
sp<AMessage> extra = new AMessage;
extra->setInt64("resume-at-mediaTimeUs", seekTimeUs);
meta->setMessage("extra", extra);
}
}
// 添加buffer到本地已解复用数据缓存队列中
// 见3.8.4小节分析
track->mPackets->queueAccessUnit(buffer);
// 重置标志位
formatChange = false;
seeking = false;
// 注意此处已读取buffer数据个数的处理,因为解复用模块可能提供一次读取多个buffer数据,
// 因此此处内循环中更新外循环的计数器
++numBuffers;
}
if (id < count) {
// 注意:此处处理就是上面for循环中未获取到kKeyTime字段buffer媒体时间戳元数据的值的情况处理,
// 即若是全部buffer都获取成功,则id和count的值一定相等。因此此处处理即是释放发生该错误及其后面buffer的数据内存。
// Error, some mediaBuffer doesn't have kKeyTime.
for (; id < count; ++id) {
mediaBuffers[id]->release();
}
// 然后结束本次track请求,即本次若未读取到maxBuffers指定数量的buffer,也不会继续获取
break;
}
// 检查本轮for循环获取解复用buffer的底层read方法返回状态
if (err == WOULD_BLOCK) {
// 底层数据不足,结束本次track请求
break;
} else if (err == INFO_FORMAT_CHANGED) {
// track信息格式发生变化时,注意此处代码未开放的。当然此方法的实现已分析过。
// 注意发生该错误时不会结束本次track请求即未读到maxBuffers指定数量的buffer,会继续获取
#if 0
track->mPackets->queueDiscontinuity(
ATSParser::DISCONTINUITY_FORMATCHANGE,
NULL,
false /* discard */);
#endif
} else if (err != OK) {
// 底层解复用数据发生其他异常时
// 判断是否需要放入数据非连续性类型信息项到本地非连续类型缓存队列
// 见3.8.2小节分析
queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
// 如上分析过的,会记录下当前track读取并解复用buffer数据的状态,
// 会用于解码器来获取数据时作为判断数据源读取状态并做相应的处理。
// 该方法见3.8.1小节分析
track->mPackets->signalEOS(err);
// 结束本次track请求
break;
}
// 执行到此处,会(判断)继续当前for循环下一轮获取buffer数据
}
// 此处会处理流媒体网络数据源的处理,暂不分析,后续有时间再分析下【也可以参考我的VLC系列章节分析内容】
if (mIsStreaming
&& (trackType == MEDIA_TRACK_TYPE_VIDEO || trackType == MEDIA_TRACK_TYPE_AUDIO)) {
status_t finalResult;
int64_t durationUs = track->mPackets->getBufferedDurationUs(&finalResult);
// TODO: maxRebufferingMarkMs could be larger than
// mBufferingSettings.mResumePlaybackMarkMs
int64_t markUs = (mPreparing ? mBufferingSettings.mInitialMarkMs
: mBufferingSettings.mResumePlaybackMarkMs) * 1000LL;
if (finalResult == ERROR_END_OF_STREAM || durationUs >= markUs) {
if (mPreparing || mSentPauseOnBuffering) {
Track *counterTrack =
(trackType == MEDIA_TRACK_TYPE_VIDEO ? &mAudioTrack : &mVideoTrack);
if (counterTrack->mSource != NULL) {
durationUs = counterTrack->mPackets->getBufferedDurationUs(&finalResult);
}
if (finalResult == ERROR_END_OF_STREAM || durationUs >= markUs) {
if (mPreparing) {
notifyPrepared();
mPreparing = false;
} else {
sendCacheStats();
mSentPauseOnBuffering = false;
sp<AMessage> notify = dupNotify();
notify->setInt32("what", kWhatResumeOnBufferingEnd);
notify->post();
}
}
}
return;
}
postReadBuffer(trackType);
}
}
3.8.1、track->mPackets->signalEOS(ERROR_MALFORMED)实现分析:
记录下当前track读取并解复用buffer数据的状态,会用于解码器来获取数据时作为判断数据源读取状态并做相应的处理。
// [frameworks/av/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp]
void AnotherPacketSource::signalEOS(status_t result) {
CHECK(result != OK);
// 记录当前解复用数据结果状态,并唤醒mCondition条件变量上的第一个等待wait线程,
// 其实该wait线程后续分析中会涉及到就是解码器来获取当前缓存的已解复用数据缓冲队列中的数据,
// 若该队列中没有数据就将会wait,其实就是消费者和生产者设计模式,有新数据加入则立即唤醒。
Mutex::Autolock autoLock(mLock);
mEOSResult = result;
mCondition.signal();
}
3.8.2、queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track)实现分析:
判断是否需要放入数据非连续性类型信息项到本地非连续类型缓存队列
// [frameworks/av/media/libmediaplayerservice/nuplayer/GenericSource.cpp]
void NuPlayer::GenericSource::queueDiscontinuityIfNeeded(
bool seeking, bool formatChange, media_track_type trackType, Track *track) {
// 看英文注释即可知道啥意思,默认formatChange为false即普通seek操作
// formatChange && seeking: track whose source is changed during selection
// formatChange && !seeking: track whose source is not changed during selection
// !formatChange: normal seek
if ((seeking || formatChange)
// 此处只音视频处理track信息格式改变或音视频track执行了seek操作的请求,一般情况下处理seeking情况。
&& (trackType == MEDIA_TRACK_TYPE_AUDIO
|| trackType == MEDIA_TRACK_TYPE_VIDEO)) {
// 记录此时的的非连续性数据标记,也就是说执行seek操作或track格式改变,
// 那么就代表当前播放的数据肯定为非连续数据,因此需要记录该情况下来。
// 若是seek期间track信息格式发生变化则数据非连续类型为DISCONTINUITY_FORMATCHANGE,否则为DISCONTINUITY_NONE。
// 该类型DiscontinuityType定义,见下面定义
ATSParser::DiscontinuityType type = (formatChange && seeking)
? ATSParser::DISCONTINUITY_FORMATCHANGE
: ATSParser::DISCONTINUITY_NONE;
// 放入数据非连续性类型信息项到本地非连续类型缓存队列
// 见下面的分析
track->mPackets->queueDiscontinuity(type, NULL /* extra */, true /* discard */);
}
}
DiscontinuityType定义:
// [frameworks/av/media/libstagefright/mpeg2ts/ATSParser.h]
struct ATSParser : public RefBase {
enum DiscontinuityType {
DISCONTINUITY_NONE = 0,
DISCONTINUITY_TIME = 1,
DISCONTINUITY_AUDIO_FORMAT = 2,
DISCONTINUITY_VIDEO_FORMAT = 4,
DISCONTINUITY_ABSOLUTE_TIME = 8,
DISCONTINUITY_TIME_OFFSET = 16,
// For legacy reasons this also implies a time discontinuity.
DISCONTINUITY_FORMATCHANGE =
DISCONTINUITY_AUDIO_FORMAT
| DISCONTINUITY_VIDEO_FORMAT
| DISCONTINUITY_TIME,
DISCONTINUITY_FORMAT_ONLY =
DISCONTINUITY_AUDIO_FORMAT
| DISCONTINUITY_VIDEO_FORMAT,
};
}
track->mPackets->queueDiscontinuity(type, NULL /* extra /, true / discard */)实现分析:
// [frameworks/av/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp]
void AnotherPacketSource::queueDiscontinuity(
ATSParser::DiscontinuityType type,
const sp<AMessage> &extra,
bool discard) {
Mutex::Autolock autoLock(mLock);
// 下面的 mBuffers 即buffer缓冲区表示的是本地已解复用音频或视频数据缓存队列
// 音频或视频数据非连续时discard为true,
// 需要丢弃flush清空此前缓存的“无效”数据即本地已解复用数据缓存队列中无效数据。
if (discard) {
// Leave only discontinuities in the queue.
// 遍历当前解复用数据缓存队列
// 关于ABuffer类声明和实现,见下面的分析
List<sp<ABuffer> >::iterator it = mBuffers.begin();
while (it != mBuffers.end()) {
sp<ABuffer> oldBuffer = *it;
// 查找该旧buffer数据中的"discontinuity"元数据的值类型
int32_t oldDiscontinuityType;
if (!oldBuffer->meta()->findInt32(
"discontinuity", &oldDiscontinuityType)) {
// 未查找到,则丢弃该old buffer
it = mBuffers.erase(it);
continue;
}
// 查询到该buffer是非连续数据则保留它
++it;
// 注意:为什么需要这里做的原因是,例如该方法会在seeking时候每次只加入一个buffer到mBuffers中,
// 那么后续的其它buffer也会执行该方法,那么就会再次discard流程处理,
// 但此前mBuffers里面的数据已经有了seeking有效的buffer了,因此必须保留这些有效的非连续数据buffer。
}
// 备注:DiscontinuitySegment类相关实现见此前相关章节,
// 该数据队列在初始化时只放入了一个元素(其数据初始化为默认【无效】信息)。
for (List<DiscontinuitySegment>::iterator it2 = mDiscontinuitySegments.begin();
it2 != mDiscontinuitySegments.end();
++it2) {
DiscontinuitySegment &seg = *it2;
// 此处也就是清空当前对象seg中字段缓存的值(默认处理为【无效】信息)
seg.clear();
}
}
// 此处再次将该EOS标志位改为OK,
// 因为该全局变量可能在前面某个请求发生错误时设置成了解复用数据的错误状态,
// 因此必须在有新的非连续的有效数据进入队列时将其改为成功,使得解码器可以正确获取数据。
mEOSResult = OK;
// 初始化上一次(最近一次)媒体数据加入队列的媒体时间戳
mLastQueuedTimeUs = 0;
// 清除最近媒体数据加入队列(读取的解复用数据)的媒体元数据信息的消息对象(AMessage)
mLatestEnqueuedMeta = NULL;
if (type == ATSParser::DISCONTINUITY_NONE) {
// 若只是seeking或者只是track数据格式变化,则直接此处return。
// 注意:此处当前buffer还未放入到 mBuffers 缓冲区。
return;
}
// seek期间track信息格式发生变化则数据非连续类型为DISCONTINUITY_FORMATCHANGE
// 再次加入一个数据非连续片段对象【默认值】到列表中
mDiscontinuitySegments.push_back(DiscontinuitySegment());
// 创建一个新的buffer对象
// 见下面的分析
sp<ABuffer> buffer = new ABuffer(0);
// 存储当前buffer数据的元数据信息,即"discontinuity"非连续类型值,枚举强转为int32类型值
buffer->meta()->setInt32("discontinuity", static_cast<int32_t>(type));
// 设置额外数据对象,从上面调用可知此时传递的是NULL
buffer->meta()->setMessage("extra", extra);
// 然后将本次有效的非连续类型的音频或视频解复用数据缓存到该缓冲区队列
mBuffers.push_back(buffer);
// 然后唤醒此时解码器可能正在该条件变量上等待新数据加入时唤醒的解码器获取解复用数据线程
mCondition.signal();
}
ABuffer类声明:
该类比较重要,它是解复用数据存储的buffer对象。可以看到其实也比较简单,见下面的详细分析。
// [frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h]
namespace android {
struct AMessage;
struct ABuffer : public RefBase {
// 显式构造函数
explicit ABuffer(size_t capacity);
ABuffer(void *data, size_t capacity);
// 返回当前(视频或音频)帧数据访问指针,即返回该数据内存开始地址
uint8_t *base() { return (uint8_t *)mData; }
// 返回当前开始读取数据的内存地址
uint8_t *data() { return (uint8_t *)mData + mRangeOffset; }
// Buffer容量大小
size_t capacity() const { return mCapacity; }
// Buffer(可读取)数据大小
size_t size() const { return mRangeLength; }
// Buffer已读取数据大小
size_t offset() const { return mRangeOffset; }
// 设置数据可访问范围,即偏移量和(可读取)数据大小
void setRange(size_t offset, size_t size);
// 深拷贝复制另一个buffer的数据内存
// create buffer from dup of some memory block
static sp<ABuffer> CreateAsCopy(const void *data, size_t capacity);
// 设置和获取int32类型数据
void setInt32Data(int32_t data) { mInt32Data = data; }
int32_t int32Data() const { return mInt32Data; }
// 获取当前(视频或音频)帧数据的元数据KV信息
sp<AMessage> meta();
protected:
virtual ~ABuffer();
private:
// 将当前(视频或音频)帧数据的元数据KV信息放入一个AMessage中缓存
sp<AMessage> mMeta;
// 当前(视频或音频)帧数据,使用无类型数据指针来指向该数据内存
void *mData;
// Buffer容量大小【单位为byte即uint8_t】
size_t mCapacity;
// Buffer范围偏移量即相对于当前数据内存开始地址到开始读取数据内存地址的偏移量,
// 也就是 Buffer已读取数据大小【单位为byte即uint8_t】
size_t mRangeOffset;
// Buffer范围长度即数据可读总长度,也就是(可读取)有效数据大小【单位为byte即uint8_t】
size_t mRangeLength;
// 32位长度的int类型数据
int32_t mInt32Data;
// 是否是自身数据即该数据是否是自身控制的数据【自身分配的内存】
bool mOwnsData;
// 该宏定义其实在前面相关章节中已分析过,其实就是不允许当前类之间的直接赋值,例如=赋值运算符不可用
DISALLOW_EVIL_CONSTRUCTORS(ABuffer);
};
} // namespace android
ABuffer类构造函数:
// [frameworks/av/media/libstagefright/foundation/ABuffer.cpp]
namespace android {
ABuffer::ABuffer(size_t capacity)
// 初始化从头开始位置开始读取数据
: mRangeOffset(0),
mInt32Data(0),
// 标志该数据是自身控制的数据【自身分配的内存】
mOwnsData(true) {
// 为当前数据分配容量大小内存
mData = malloc(capacity);
if (mData == NULL) {
// 内存分配失败时
mCapacity = 0;
mRangeLength = 0;
} else {
// 内存分配成功则记录容量和数据大小
mCapacity = capacity;
mRangeLength = capacity;
}
}
ABuffer::ABuffer(void *data, size_t capacity)
: mData(data),
mCapacity(capacity),
mRangeOffset(0),
mRangeLength(capacity),
mInt32Data(0),
// 不是自身分配的数据
mOwnsData(false) {
}
// static
sp<ABuffer> ABuffer::CreateAsCopy(const void *data, size_t capacity)
{
sp<ABuffer> res = new ABuffer(capacity);
if (res->base() == NULL) {
// 如上构造函数分析,内存分配失败
return NULL;
}
// 深拷贝复制另一个buffer的数据内存,数据大小为capacity
memcpy(res->data(), data, capacity);
return res;
}
ABuffer::~ABuffer() {
if (mOwnsData) {
if (mData != NULL) {
// 析构函数释放自身分配的数据内存
free(mData);
mData = NULL;
}
}
}
// 设置数据可访问范围,即偏移量和(可读取)数据大小
void ABuffer::setRange(size_t offset, size_t size) {
// CHECK_LE该宏定义其实是一个 <= 运算符的计算,此处时计算这些值的有效性
CHECK_LE(offset, mCapacity);
CHECK_LE(offset + size, mCapacity);
mRangeOffset = offset;
mRangeLength = size;
}
// 获取当前(视频或音频)帧数据的元数据KV信息对象,
// 若为空则初始化一个空对象来存储元数据
sp<AMessage> ABuffer::meta() {
if (mMeta == NULL) {
mMeta = new AMessage;
}
return mMeta;
}
} // namespace android
3.8.3、mediaBufferToABuffer(mbuf, trackType)实现分析:
将底层buffer数据类型转换为本地缓冲区buffer数据类型
// [frameworks/av/media/libmediaplayerservice/nuplayer/GenericSource.cpp]
sp<ABuffer> NuPlayer::GenericSource::mediaBufferToABuffer(
MediaBufferBase* mb,
media_track_type trackType) {
bool audio = trackType == MEDIA_TRACK_TYPE_AUDIO;
// 可读取(输出)数据大小
size_t outLength = mb->range_length();
if (audio && mAudioIsVorbis) {
// 文件格式是"audio/vorbis"类型时,将数据大小再增加int32_t类型的数据大小
outLength += sizeof(int32_t);
}
sp<ABuffer> ab;
if (mIsDrmProtected) {
// 媒体文件数字版权保护时,不分析此处
// Modular DRM
// Enabled for both video/audio so 1) media buffer is reused without extra copying
// 2) meta data can be retrieved in onInputBufferFetched for calling queueSecureInputBuffer.
// data is already provided in the buffer
ab = new ABuffer(NULL, mb->range_length());
ab->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mb));
// Modular DRM: Required b/c of the above add_ref.
// If ref>0, there must be an observer, or it'll crash at release().
// TODO: MediaBuffer might need to be revised to ease such need.
mb->setObserver(this);
// Extra increment (since we want to keep mb alive and attached to ab beyond this function
// call. This is to counter the effect of mb->release() towards the end.
mb->add_ref();
} else {
// 创建ABuffer对象
ab = new ABuffer(outLength);
// 深拷贝可读大小长度的有效数据
memcpy(ab->data(),
(const uint8_t *)mb->data() + mb->range_offset(),
mb->range_length());
}
if (audio && mAudioIsVorbis) {
// 文件格式是"audio/vorbis"类型时,查询有效样本数,失败赋为-1
int32_t numPageSamples;
if (!mb->meta_data().findInt32(kKeyValidSamples, &numPageSamples)) {
numPageSamples = -1;
}
// 正如上面分析的mAudioIsVorbis为true时,
// 多分配了一个int32位数据大小的内存,因为用于存储此处的值
uint8_t* abEnd = ab->data() + mb->range_length();
memcpy(abEnd, &numPageSamples, sizeof(numPageSamples));
}
// 获取一个存储当前数据帧元数据信息的AMessage对象
sp<AMessage> meta = ab->meta();
// 存储当前buffer帧数据的媒体时间值
// 【也就是当前帧数据应该显示的媒体时间点】
int64_t timeUs;
CHECK(mb->meta_data().findInt64(kKeyTime, &timeUs));
meta->setInt64("timeUs", timeUs);
// 视频track数据时,获取temporal layer id:NALU的时域层级,根据temporalId可以确定图像的重要性
if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
int32_t layerId;
if (mb->meta_data().findInt32(kKeyTemporalLayerId, &layerId)) {
meta->setInt32("temporal-layer-id", layerId);
}
}
if (trackType == MEDIA_TRACK_TYPE_TIMEDTEXT) {
const char *mime;
CHECK(mTimedTextTrack.mSource != NULL
&& mTimedTextTrack.mSource->getFormat()->findCString(kKeyMIMEType, &mime));
meta->setString("mime", mime);
}
// 获取当前buffer数据的媒体时长【注意:只是单个buffer数据的时长,而非整个track数据的时长】
int64_t durationUs;
if (mb->meta_data().findInt64(kKeyDuration, &durationUs)) {
meta->setInt64("durationUs", durationUs);
}
// 设置当前buffer数据对应于字母流track的索引值
if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
meta->setInt32("track-index", mSubtitleTrack.mIndex);
}
uint32_t dataType; // unused
// 获取H264视频编码的SEI(视频附加增强信息)类型的NALU单元数据(h264原始数据)及其长度
const void *seiData;
size_t seiLength;
if (mb->meta_data().findData(kKeySEI, &dataType, &seiData, &seiLength)) {
// 将SEI类型数据缓存在ABuffer对象中,并存储在key值为"sei"字段媒体元数据中
sp<ABuffer> sei = ABuffer::CreateAsCopy(seiData, seiLength);;
meta->setBuffer("sei", sei);
}
// 类似上面的处理,获取MPEG编码格式的用户信息及其长度,然后存储在元数据中
const void *mpegUserDataPointer;
size_t mpegUserDataLength;
if (mb->meta_data().findData(
kKeyMpegUserData, &dataType, &mpegUserDataPointer, &mpegUserDataLength)) {
sp<ABuffer> mpegUserData = ABuffer::CreateAsCopy(mpegUserDataPointer, mpegUserDataLength);
meta->setBuffer("mpeg-user-data", mpegUserData);
}
// 同上分析,获取HDR相关原始信息及其长度,然后存储在元数据中。
// 备注:高动态范围图像(High-Dynamic Range,简称HDR),相比普通的图像,
// 可以提供更多的动态范围和图像细节,根据不同的曝光时间的LDR(Low-Dynamic Range)图像,
// 利用每个曝光时间相对应最佳细节的LDR图像来合成最终HDR图像,能够更好的反映出真实环境中的视觉效果。
const void *hdr10PlusInfo;
size_t hdr10PlusInfoLength;
if (mb->meta_data().findData(
kKeyHdr10PlusInfo, &dataType, &hdr10PlusInfo, &hdr10PlusInfoLength)) {
sp<ABuffer> hdr10PlusData = ABuffer::CreateAsCopy(hdr10PlusInfo, hdr10PlusInfoLength);
meta->setBuffer("hdr10-plus-info", hdr10PlusData);
}
// buffer数据类型转换完成后则直接释放底层数据内存
mb->release();
mb = NULL;
return ab;
}
3.8.4、track->mPackets->queueAccessUnit(buffer)实现分析:
添加buffer到本地已解复用数据缓存队列【访问单元】中。该buffer缓冲区实现机制是生产者-消费者模式
// [frameworks/av/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp]
void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
// 判断当前buffer是否已损坏,如果是损坏的buffer,则在底层解复用时将会添加该标记的媒体元数据,
// 此处则会丢弃该buffer,不会让它添加到访问单元队列中
int32_t damaged;
if (buffer->meta()->findInt32("damaged", &damaged) && damaged) {
// LOG(VERBOSE) << "discarding damaged AU";
return;
}
// 加锁添加数据
Mutex::Autolock autoLock(mLock);
// 往队列添加数据
mBuffers.push_back(buffer);
// 队列中添加新数据后,唤醒可能正在该条件变量上等待的线程即解码器获取解复用数据的调用线程。
mCondition.signal();
// 查找当前buffer数据中的"discontinuity"元数据的值类型
int32_t discontinuity;
if (buffer->meta()->findInt32("discontinuity", &discontinuity)){
ALOGV("queueing a discontinuity with queueAccessUnit");
// 根据3.8.2小节中的分析可知,当该类型值存在时表示当前buffer数据是
// 在seeking期间媒体格式发生变化时获取的buffer数据,并且已将其添加到解复用数据队列中。
// 因此此处不再将其放入队列,并且初始化这几个数据值
mLastQueuedTimeUs = 0LL;
mEOSResult = OK;
mLatestEnqueuedMeta = NULL;
// 再次加入一个数据非连续片段记录对象【默认值】到列表中
// 备注:其实根据3.8.2小节分析可知,该类型值存在时已经加入过一次该数据项了,待后续分析具体作用TODO
mDiscontinuitySegments.push_back(DiscontinuitySegment());
return;
}
// 未查找到该类型值即还未将该buffer加入到缓冲区队列中时
// 获取最后一 个/帧 buffer数据即本次buffer的媒体时间戳值并缓存到全局变量中
int64_t lastQueuedTimeUs;
CHECK(buffer->meta()->findInt64("timeUs", &lastQueuedTimeUs));
mLastQueuedTimeUs = lastQueuedTimeUs;
// 1E6表示10的6次方,此处将转换为秒单位
ALOGV("queueAccessUnit timeUs=%" PRIi64 " us (%.2f secs)",
mLastQueuedTimeUs, mLastQueuedTimeUs / 1E6);
// CHECK(!mDiscontinuitySegments.empty());
// 获取该非连接类型数据队列最后一个数据【注意根据前面分析可知该数据添加的是空对象】
DiscontinuitySegment &tailSeg = *(--mDiscontinuitySegments.end());
// 【注意根据前面分析可知mMaxEnqueTimeUs该数据默认值为-1,该数据记录buffer加入队列时buffer媒体时间】
if (lastQueuedTimeUs > tailSeg.mMaxEnqueTimeUs) {
// 记录最后一帧音频或视频buffer的媒体时间戳
tailSeg.mMaxEnqueTimeUs = lastQueuedTimeUs;
}
if (tailSeg.mMaxDequeTimeUs == -1) {
// 若该值无效时,则也设置buffer出队列时该值为buffer媒体时间
tailSeg.mMaxDequeTimeUs = lastQueuedTimeUs;
}
if (mLatestEnqueuedMeta == NULL) {
// 若最新(或最近)加入队列buffer媒体元数据信息为空,则复制一份当前buffer的
mLatestEnqueuedMeta = buffer->meta()->dup();
} else {
// 该值存在时
// 计算最新(一帧buffer)媒体时间戳
int64_t latestTimeUs = 0;
// 帧间增量时间戳即最新一帧buffer距离上一帧buffer的显示时间间隔
int64_t frameDeltaUs = 0;
// 此处获取的是上一个加入队列时的buffer的媒体时间戳
CHECK(mLatestEnqueuedMeta->findInt64("timeUs", &latestTimeUs));
// 比较本次buffer媒体时间戳和上次最后一个buffer时间戳值
if (lastQueuedTimeUs > latestTimeUs) {
// 本次buffer时间大时,对于视频帧其实就是处理I帧或P帧数据,音频帧都是顺序的。
// 更新最近记录的媒体元信息对象
// 注意:对于视频帧,该最新加入队列buffer媒体元数据信息只记录I帧或P帧数据的。
mLatestEnqueuedMeta = buffer->meta()->dup();
// 然后计算本帧buffer距离上一帧buffer的显示时间间隔,也就是计算帧间增量时间戳
frameDeltaUs = lastQueuedTimeUs - latestTimeUs;
// 缓存帧间增量时间戳在key为"durationUs"的buffer时长媒体元数据中
mLatestEnqueuedMeta->setInt64("durationUs", frameDeltaUs);
} else if (!mLatestEnqueuedMeta->findInt64("durationUs", &frameDeltaUs)) {
// 获取上一帧buffer的显示时长失败时即处理视频B帧数据,
// 因为只有B帧数据的媒体时间才可能会比上一帧buffer媒体时间小。
// 【I帧和P帧的时间戳都是递增的】
// For B frames
// 计算帧间增量时间戳并缓存
frameDeltaUs = latestTimeUs - lastQueuedTimeUs;
mLatestEnqueuedMeta->setInt64("durationUs", frameDeltaUs);
}
}
}
本系列章节内容分析结束。关于文章中提到的具体媒体提取器解复用模块的实现分析,后续有时间再分析,高通平台会使用【mm-parser】私有库实现。
后续start启动播放流程分析请见下一相关章节分析。