承接上一章节分析:【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 3】【01】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
【此章节小节编号就接着上一章节排列】
10.1.1、new Decoder(notify, mSource, mPID, mUID, mRenderer, mSurface, mCCDecoder)实现分析:
创建新的视频解码器。这几个参数前面章节已分析过。
Decoder类声明:【省略其他代码】
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h]
struct NuPlayer::Decoder : public DecoderBase {}
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoderBase.h]
struct NuPlayer::DecoderBase : public AHandler {}
Decoder类构造函数实现:
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp]
NuPlayer::Decoder::Decoder(
const sp<AMessage> ¬ify,
const sp<Source> &source,
pid_t pid,
uid_t uid,
const sp<Renderer> &renderer,
const sp<Surface> &surface,
const sp<CCDecoder> &ccDecoder)
// 调用父类构造函数
// 见下面的分析
: DecoderBase(notify),
mSurface(surface),
mSource(source),
mRenderer(renderer),
mCCDecoder(ccDecoder),
mPid(pid),
mUid(uid),
// 跳过渲染(视频帧)直到指定可渲染的媒体时间后方可渲染,根据变量名就很形象的知晓了。
// 备注:该值其实就是用来处理seek流程中seek模式为支持非关键帧seek时就会用到该值。
mSkipRenderingUntilMediaTimeUs(-1LL),
// 总帧数
mNumFramesTotal(0LL),
// 丢弃的总输入帧(buffer)数(即已解复用帧丢弃数)
mNumInputFramesDropped(0LL),
// 丢弃的总输出帧(buffer)数(即已解码帧丢弃数)
mNumOutputFramesDropped(0LL),
// 视频宽高
mVideoWidth(0),
mVideoHeight(0),
// 是否是音频解码器,默认true
mIsAudio(true),
// 视频编码格式是否是AVC即h264编码格式
mIsVideoAVC(false),
// 是否是(安全)加密编码方式
mIsSecure(false),
// 是否是加密编码
mIsEncrypted(false),
// 是否加密监测太早
mIsEncryptedObservedEarlier(false),
// 是否媒体格式改变事件待执行,其实也就是标记该事件正在执行中
mFormatChangePending(false),
// 是否媒体播放时间改变事件待执行
mTimeChangePending(false),
// 视频总帧率
// kDefaultVideoFrameRateTotal:值为30。
// 也就是说若视频帧率不能从媒体文件中获取的话,那么此处帧率将会默认设置为30。
mFrameRateTotal(kDefaultVideoFrameRateTotal),
// 播放速度/速率,默认为1 即 正常播放速率
mPlaybackSpeed(1.0f),
// 视频track数据时,获取总的temporal layer:NALU的时域层级总数,根据temporalLayerId可以确定图像的重要性。
// 值为1默认解码所有的层级
mNumVideoTemporalLayerTotal(1), // decode all layers
// 允许的视频NALU时域层级数
mNumVideoTemporalLayerAllowed(1),
// 当前最大视频时域层级ID
mCurrentMaxVideoTemporalLayerId(0),
// 标记resume流程正在执行中
mResumePending(false),
// 解码器组件名称默认值
mComponentName("decoder") {
// 此ALooper是为MediaCodec创建的。
mCodecLooper = new ALooper;
mCodecLooper->setName("NPDecoder-CL");
mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
// 视频时域层级每层对应的帧率集合数组,第一个元素存储视频总帧率。
// 默认该数组大小为32层级(kMaxNumVideoTemporalLayers)。
mVideoTemporalLayerAggregateFps[0] = mFrameRateTotal;
}
DecoderBase(notify)构造函数实现:
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoderBase.cpp]
NuPlayer::DecoderBase::DecoderBase(const sp<AMessage> ¬ify)
: mNotify(notify),
// 缓冲Buffer操作代数值,代数值的作用前面已介绍过很多次了,不再细说
mBufferGeneration(0),
// 请求缓冲输入buffer(即已解复用数据【待解码数据】)数据延迟时长(单位为us微秒),默认为10毫秒
mRequestInputBufferDelay(10 * 1000LL),
// 解码器是否暂停工作
mPaused(false),
// 使用AMessage对象来统计解码相关数据项值(KV存储),默认创建空对象
mStats(new AMessage),
// 是否正在等待请求输入buffer,默认false
mRequestInputBuffersPending(false) {
// Every decoder has its own looper because MediaCodec operations
// are blocking, but NuPlayer needs asynchronous operations.
// 根据英文注释可知,每个解码器都需要有自己的ALooper消息机制循环线程来处理MediaCodec的相关方法功能,
// 因为MediaCodec相关操作请求是同步执行即阻塞调用端执行的,但NuPlayer需要异步执行解码器的相关功能,
// 因此此处创建了ALooper消息机制独立线程。
// 关于ALooper消息机制实现原理请查看文中提到的链接。
// 此ALooper是为NuPlayerDecoder创建的。
mDecoderLooper = new ALooper;
mDecoderLooper->setName("NPDecoder");
// 启动ALooper消息循环线程
mDecoderLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
}
10.1.2、(*decoder)->init()实现分析:
解码器初始化工作。该方法是在父类中声明和实现的,如下
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoderBase.cpp]
void NuPlayer::DecoderBase::init() {
// 其实就是注册自身到该ALooper消息循环线程中接收消息事件处理。
mDecoderLooper->registerHandler(this);
}
10.1.3、(*decoder)->configure(format)实现分析:
解码器配置工作。
由于篇幅长度过长,因此放入另外单独章节分析,请查看:
【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 4】
10.1.4、(*decoder)->setParameters(params)实现分析:
向解码器设置参数。该方法也是在父类中声明和实现的,如下
注意:此方法执行完毕后,第10.1小节NuPlayer的执行将会和此处后续流程是异步同时执行的!因为它们已经属于两个不同的消息循环线程中执行了。
警告:但是注意该方法必现等待上面的configure事件消息执行完毕后才能执行,因此(在NuPlayerDecoder的onConfigure处理需要wait)它可能会被耗时即该消息不能被立即执行,直到前面的(onConfigure)消息被执行完毕后才能执行此消息事件。
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoderBase.cpp]
void NuPlayer::DecoderBase::setParameters(const sp<AMessage> ¶ms) {
// 发送【kWhatSetParameters】事件消息自身接收处理
sp<AMessage> msg = new AMessage(kWhatSetParameters, this);
msg->setMessage("params", params);
msg->post();
}
【kWhatSetParameters】事件消息接收处理:
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoderBase.cpp]
void NuPlayer::DecoderBase::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatSetParameters:
{
sp<AMessage> params;
CHECK(msg->findMessage("params", ¶ms));
// 见下面的分析
onSetParameters(params);
break;
}
}
}
onSetParameters(params)实现分析:
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp]
void NuPlayer::Decoder::onSetParameters(const sp<AMessage> ¶ms) {
bool needAdjustLayers = false;
float frameRateTotal;
// Decoder初始化时mFrameRateTotal 默认为30帧率
if (params->findFloat("frame-rate-total", &frameRateTotal)
&& mFrameRateTotal != frameRateTotal) {
// 若原始视频格式中视频帧率不为30时
// 标记需要适配视频层图
needAdjustLayers = true;
// 更新
mFrameRateTotal = frameRateTotal;
}
int32_t numVideoTemporalLayerTotal;
// 获取视频编码的时域层级数参数
// kMaxNumVideoTemporalLayers = 32
// mNumVideoTemporalLayerTotal默认为1
if (params->findInt32("temporal-layer-count", &numVideoTemporalLayerTotal)
&& numVideoTemporalLayerTotal >= 0
&& numVideoTemporalLayerTotal <= kMaxNumVideoTemporalLayers
&& mNumVideoTemporalLayerTotal != numVideoTemporalLayerTotal) {
// 标记需要适配视频层图
needAdjustLayers = true;
// 更新为最大值
mNumVideoTemporalLayerTotal = std::max(numVideoTemporalLayerTotal, 1);
}
if (needAdjustLayers && mNumVideoTemporalLayerTotal > 1) {
// 从注释可知:其实该视频图层视频计算处理,应该从流中提取的数据才这么做。
// 其实下面的事情就是将帧率除以视频时域层数得到矫正之后的帧率,
// 比如为层数为2时就相当于以前帧率30,现在变为Fps[0]为15,Fps[1]为30。
// 也就是说时域层级越多,那么越大的层级帧率会越大,它们的关系就是递归递增关系
// TODO: For now, layer fps is calculated for some specific architectures.
// But it really should be extracted from the stream.
mVideoTemporalLayerAggregateFps[0] =
mFrameRateTotal / (float)(1LL << (mNumVideoTemporalLayerTotal - 1));
for (int32_t i = 1; i < mNumVideoTemporalLayerTotal; ++i) {
// 递归递增处理下一层级帧率,它等于自身帧率加上前面所有层级帧率总和
mVideoTemporalLayerAggregateFps[i] =
mFrameRateTotal / (float)(1LL << (mNumVideoTemporalLayerTotal - i))
+ mVideoTemporalLayerAggregateFps[i - 1];
}
}
// mPlaybackSpeed 播放速率默认为1
float playbackSpeed;
if (params->findFloat("playback-speed", &playbackSpeed)
&& mPlaybackSpeed != playbackSpeed) {
// 播放速度不为1即快进或快退时
// 标记需要适配视频层图
needAdjustLayers = true;
// 更新
mPlaybackSpeed = playbackSpeed;
}
if (needAdjustLayers) {
// 需要调整适配视频层图时
// 当前解码帧率
float decodeFrameRate = mFrameRateTotal;
// 操作帧率
float operating_rate;
// enable temporal layering optimization only if we know the layering depth
// 只有当我们知道分层深度时,才能启用临时分层优化
if (mNumVideoTemporalLayerTotal > 1) {
int32_t layerId;
for (layerId = 0; layerId < mNumVideoTemporalLayerTotal - 1; ++layerId) {
// kDisplayRefreshingRate定义:也就是屏幕刷新率
// static float kDisplayRefreshingRate = 60.f; // TODO: get this from the display
// 判断每一个时域层级的帧率【必须乘以播放速率来比较】,若有一个层级大于等于54帧,
// 那么将不会刷新启用这种超出54帧帧率的时域层级ID
if (mVideoTemporalLayerAggregateFps[layerId] * mPlaybackSpeed
>= kDisplayRefreshingRate * 0.9) {
break;
}
}
// 此处将记录可以启用的时域层级数,帧率大于等于54帧的层级将不能支持
mNumVideoTemporalLayerAllowed = layerId + 1;
// 解码帧率将会是最后一个时域层级的帧率,也就是最大帧率
decodeFrameRate = mVideoTemporalLayerAggregateFps[layerId];
}
ALOGV("onSetParameters: allowed layers=%d, decodeFps=%g",
mNumVideoTemporalLayerAllowed, decodeFrameRate);
if (mCodec == NULL) {
ALOGW("onSetParameters called before codec is created.");
return;
}
sp<AMessage> codecParams = new AMessage();
// 计算操作帧率:解码帧率乘以播放速率
operating_rate = decodeFrameRate * mPlaybackSpeed;
// 重要处理:
if ((int)operating_rate > 100)
// 操作帧率大于100时
// mRequestInputBufferDelay(10 * 1000LL)单位为微秒,默认值为10毫秒,
// 它的作用就是在一次获取输入buffer不足够时,再次发起获取数据的延迟时长
// 即获取输出(待解码或待编码)Buffer延迟时长。
// 此处将会根据当前播放帧率来重新计算需要的估算延迟时长,其结果就是operating_rate越大,
// 那么延迟时长越小,也就是说在帧率过大时必须需要加快获取输入Buffer才能满足底层组件的编解码,
// 否则播放时会数据严重不足导致卡顿或编码时输入Buffer时常不足。
mRequestInputBufferDelay = (1000.f/operating_rate) * 1000LL;
// 设置该操作帧率
codecParams->setFloat("operating-rate", operating_rate);
// 设置该参数给底层组件
// 见10.1.4.1小节分析
mCodec->setParameters(codecParams);
}
}
10.1.4.1、mCodec->setParameters(codecParams)实现分析:
设置该参数给底层组件,发送【kWhatSetParameters】事件,并让调用端等待执行结果
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t MediaCodec::setParameters(const sp<AMessage> ¶ms) {
sp<AMessage> msg = new AMessage(kWhatSetParameters, this);
msg->setMessage("params", params);
sp<AMessage> response;
return PostAndAwaitResponse(msg, &response);
}
MediaCodec接收【kWhatSetParameters】事件处理:
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatSetParameters:
{
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
sp<AMessage> params;
CHECK(msg->findMessage("params", ¶ms));
status_t err = onSetParameters(params);
// 应答消息
PostReplyWithError(replyID, err);
break;
}
}
}
onSetParameters(params)实现:
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t MediaCodec::onSetParameters(const sp<AMessage> ¶ms) {
mCodec->signalSetParameters(params);
return OK;
}
mCodec->signalSetParameters(params)实现分析:
通知ACodec设置参数
// [frameworks/av/media/libstagefright/ACodec.cpp]
void ACodec::signalSetParameters(const sp<AMessage> ¶ms) {
sp<AMessage> msg = new AMessage(kWhatSetParameters, this);
msg->setMessage("params", params);
msg->post();
}
ACodec接收【kWhatSetParameters】事件处理(原理):
注意关于它的实现是在ACodec中,有三个状态机的状态实现者接收了它,有两个是将该事件消息仅仅只是加入到延迟执行的消息队列中,然后等待进入ExecutingState正在执行状态实现者时才将其执行【ACodec::setParameters】并设置给底层OMXNode节点实例。
而关于【ACodec::setParameters】的实现请查看此前流程中已有分析。
10.1.5、determineAudioModeChange(format)实现分析:
确定(检查)音频模式改变情况【即是否可以支持offload模式播放音频流程处理】
注意该流程分析时,其实在NuPlayer中已经完成视频解码器的实例化了【但注意此时视频解码器还在异步初始化过程中】,然后NuPlayer同步进行音频解码器的实例化流程,也就会执行到此处。
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::determineAudioModeChange(const sp<AMessage> &audioFormat) {
// 由早前章节分析可知,此2对象不为空,有一个为空则return
if (mSource == NULL || mAudioSink == NULL) {
return;
}
// 不能为空,否则记录为不支持offload模式音频播放比较安全
if (mRenderer == NULL) {
ALOGW("No renderer can be used to determine audio mode. Use non-offload for safety.");
mOffloadAudio = false;
return;
}
// 获取音频Track元数据格式信息对象
sp<MetaData> audioMeta = mSource->getFormatMeta(true /* audio */);
// 获取视频Track元数据格式信息
sp<AMessage> videoFormat = mSource->getFormat(false /* audio */);
// 获取AudioSink的音频流类型
// 备注:该值是在此前初始化流程中设置的,请见此前MediaPlayerService时的分析
audio_stream_type_t streamType = mAudioSink->getAudioStreamType();
const bool hasVideo = (videoFormat != NULL);
// 判断是否支持音频offload模式播放
// 该方法见此前onStart()中的分析
bool canOffload = canOffloadStream(
audioMeta, hasVideo, mSource->isStreaming(), streamType)
&& (mPlaybackSettings.mSpeed == 1.f && mPlaybackSettings.mPitch == 1.f);
// DRM加密音频流不支持Offload模式
// Modular DRM: Disabling audio offload if the source is protected
if (canOffload && mIsDrmProtected) {
canOffload = false;
ALOGV("determineAudioModeChange: Disabling mOffloadAudio b/c the source is protected.");
}
if (canOffload) {
// 支持offload时
if (!mOffloadAudio) {
// 若此前全局的offload标志为false,则重新通知允许Renderer启用Audio offload
// 该流程暂不关注,通常不会为false,只关注正常流程 TODO
// 实现结果就是:添加Renderer的标志位支持offload即mFlags |= FLAG_OFFLOAD_AUDIO;
// 然后更新音频消耗和开始渲染等几个代数值
mRenderer->signalEnableOffloadAudio();
}
// open audio sink early under offload mode.
// 注:在offload模式下尽可能早地尝试打开AudioSink
// 见10.1.5.1小节分析
tryOpenAudioSinkForOffload(audioFormat, audioMeta, hasVideo);
} else {
// 不支持offload时
if (mOffloadAudio) {
// 全局变量记录为true时,先disable该flag,在重置为false
// 该方法和上面的enable方法相似处理,不同之处在于去掉Renderer的mflags标志位不支持offload
// 即mFlags &= ~FLAG_OFFLOAD_AUDIO,也不再展开分析
mRenderer->signalDisableOffloadAudio();
mOffloadAudio = false;
}
}
}
10.1.5.1、tryOpenAudioSinkForOffload(audioFormat, audioMeta, hasVideo)实现分析:
在offload模式下尽可能早地尝试打开AudioSink
备注:AudioSink和AudioFlinger属于另外领域的技术点了,因此后面涉及相关功能分析也只会简单阐述下它们的工作原理,而不会深入该领域实现分析。
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::tryOpenAudioSinkForOffload(
const sp<AMessage> &format, const sp<MetaData> &audioMeta, bool hasVideo) {
// Note: This is called early in NuPlayer to determine whether offloading
// is possible; otherwise the decoders call the renderer openAudioSink directly.
// 打开AudioSink
// 备注:关于此方法功能,目前由于我们侧重点不再AudioSink内部实现的Offload模式,
// 该流程将在后续的音频软解码器准备播放音频时才会执行该功能,
// 因此放入后续音频软解码器输出音频解码数据处理流程中再次分析。
status_t err = mRenderer->openAudioSink(
format, true /* offloadOnly */, hasVideo,
AUDIO_OUTPUT_FLAG_NONE, &mOffloadAudio, mSource->isStreaming());
if (err != OK) {
// offload模式尝试打开AudioSink失败,则直接改变是否支持offload模式全局变量为false
// Any failure we turn off mOffloadAudio.
mOffloadAudio = false;
} else if (mOffloadAudio) {
// 成功时,并且是否支持offload模式全局变量为true时,进入
// 发送音频Track媒体元数据配置信息到HAL层
// 见下面分析
sendMetaDataToHal(mAudioSink, audioMeta);
}
}
sendMetaDataToHal(mAudioSink, audioMeta)实现分析:
支持音频Offload播放模式时,发送音频Track媒体元数据配置信息到HAL层
// [frameworks/av/media/libstagefright/Utils.cpp]
status_t sendMetaDataToHal(sp<MediaPlayerBase::AudioSink>& sink,
const sp<MetaData>& meta)
{
int32_t sampleRate = 0;
int32_t bitRate = 0;
int32_t channelMask = 0;
int32_t delaySamples = 0;
int32_t paddingSamples = 0;
// 下面处理很简单,也就是创建一个音频参数对象【AudioParameter】,
// 来缓存从当前音频Track中获取的相关信息:采样率、码率、声道数、编码器延迟样本数、编码器填充样本数
// 并设置该音频参数对象给AudioSink去接收处理关于offload模式时的功能
AudioParameter param = AudioParameter();
if (meta->findInt32(kKeySampleRate, &sampleRate)) {
param.addInt(String8(AUDIO_OFFLOAD_CODEC_SAMPLE_RATE), sampleRate);
}
if (meta->findInt32(kKeyChannelMask, &channelMask)) {
param.addInt(String8(AUDIO_OFFLOAD_CODEC_NUM_CHANNEL), channelMask);
}
if (meta->findInt32(kKeyBitRate, &bitRate)) {
param.addInt(String8(AUDIO_OFFLOAD_CODEC_AVG_BIT_RATE), bitRate);
}
if (meta->findInt32(kKeyEncoderDelay, &delaySamples)) {
param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), delaySamples);
}
if (meta->findInt32(kKeyEncoderPadding, &paddingSamples)) {
param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), paddingSamples);
}
#ifndef __NO_AVEXTENSIONS__
// 没有扩展API时
AVUtils::get()->sendMetaDataToHal(meta, ¶m);
#endif
ALOGV("sendMetaDataToHal: bitRate %d, sampleRate %d, chanMask %d,"
"delaySample %d, paddingSample %d", bitRate, sampleRate,
channelMask, delaySamples, paddingSamples);
// 设置参数到AudioSink
sink->setParameters(param.toString());
return OK;
}
10.1.6、AVNuFactory::get()->createPassThruDecoder(notify, mSource, mRenderer)实现分析:
音频使用offload模式播放的解码器创建,即它将创建 NuPlayerDecoderPassThrough 对象。
TODO 注意该流程的音频offload解码器NuPlayerDecoderPassThrough的创建和前面的NuPlayerDecoder流程其大部分交互处理流程是很相似的,因此可自行分析,目前暂时不考虑分析。
此处只简单阐述一下它的工作原理:
可以认为offload模式就是将音频Track已解析数据,直接递交给音频硬件解码器如Audio DSP模块,该模块接收音频原始编码数据去解码并直接将解码数据给到AudioSink去合成播放,不需要像音频软解码器默认需要在Renderer中将解码数据写入AudioSink【即AudioSink.write()方法】。
另外从该音频解码器NuPlayerDecoderPassThrough类名称就可知晓,“PassThrough”就是代表着这只是个“穿过/通过”解码器直接递交原始音频编码数据的实现。其并不真正实现解码功能,只是获取到音频原始数据进行转移。
其比较具体的工作原理为:NuPlayerDecoderPassThrough该音频解码器在工作时,从Audio Track中获取到audio原始编码数据后,将会把该数据通过【NuPlayer::Renderer::queueBuffer】方法添加到Renderer内部维护的音频输出队列中,然后AudioSink将会通过Callback的方式来回调【NuPlayer::Renderer::AudioSinkCallback】方法来获取该输出队列Buffer,然后给到音频硬解码器DSP进行解码。
区别于音频软解码器的另一点不同是,音频软解码器默认是【推送音频已解码数据】到AudioSink中的,而offload模式播放音频处理时是由AudioSink通过Callback回调方式来拉取【音频已解析但未解码数据】的。
关于AudioSink、AudioTrack、AudioFlinger相关功能,目前暂不会源码分析,它属于另外的技术领域了,因此只会阐述它的大致工作原理
AudioSink由此前prepare章节可知,它是在MediaPlayerService中实现的,但它其实最终也只是个代理类实现,代理【AudioTrack】该类的实现,而AudioTrack又将会最终和AudioFlinger模块交互,最终AudioFlinger控制和管理所有需要发出声音的音频数据进行混音处理后发送给扬声器等硬件。
注意:默认非offload模式时不使用callback方式。另外注意,软音频解码也可以支持AudioSink通过回调callback方式来主动获取音频渲染队列数据或者渲染器来推送音频数据到AudioSink中去播放,该控制是通过一个系统属性值来控制的。
备注:AudioSink和AudioFlinger的通力合作完成了系统中所有音频流的合成播放,但它们已属于另外一个领域的技术点了。暂不展开分析
10.2、isVideoSampleReceived()实现分析:
内部处理其实际控制变量是未加锁的,也就是在(实测)实际运行过程中,Renderer内部的该实际控制变量的状态可能不对时将会造成此处if内部进行了pause后,后面没有流程再来唤醒Renderer进行resume流程了,即极小概率发生视频并未开始播放的黑屏状态现象。当然我们也可以有解决方案,比如在真正执行Renderer的暂停功能时判断是否出现了该问题,若出现了则不再执行pause流程即可。
备注:注意就算该标志位加锁了也不能加锁上面描述的这个问题,因为这个处理流程的唤醒renderer操作和修改该变量的操作不再同一个方法中,也就是说还是可以造成Renderer的pause流程在收到第一帧数据后还是执行了。。。
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
bool NuPlayer::Renderer::isVideoSampleReceived() const {
return mVideoSampleReceived;
}
10.3、NuPlayer接收【kWhatWakeupRendererFromPreroll】该事件消息处理:
超时唤醒Renderer事件消息。
这个超时时长唤醒事件到达后,在处理时又判断了【isVideoPrerollCompleted()】这里的条件,因此由于这是两个异步流程,判断未加锁变量,那么将会在这超时事件内,Renderer接收到了第一帧,将会改变该状态变量,那么也就会导致该超时唤醒机制失效,即将会无人唤醒Renderer造成视频不会播放状态。
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatWakeupRendererFromPreroll:
{
// 检查用户是否要求暂停了
// don't break pause if client requested renderer to pause too.
// isVideoPrerollCompleted()见下面实现
if (!mPaused && mRenderer != NULL && !mRenderer->isVideoPrerollCompleted()) {
// 视频预滚超时,恢复Renderer
ALOGI("NOTE: Video preroll timed out, resume renderer");
mRenderer->resume();
}
break;
}
}
}
isVideoPrerollCompleted()实现:由前面可知,基本上执行该判断时mPaused状态均为true
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
bool NuPlayer::Renderer::isVideoPrerollCompleted() const {
// 渲染器视频样本数据即视频帧已接收到时,或者Renderer当前未暂停状态时,返回true。
return mVideoSampleReceived || !mPaused;
}
本章结束