[时间:2016-10] [状态:Open]

[关键词:android,nuplayer,开源播放器,播放框架]

0 引言

差不多一个月了,继续分析AOSP的播放框架的源码。这次我们需要深入分析的是NuPlayer类,相比于NuPlayerDriver的接口功能,NuPlayer继承自AHandler类,是AOSP播放框架中连接Source、Decoder、Render的纽带。

我希望读完本文大家可以对NuPlayer的源码结构有一定了解。

本文是我的NuPlayer播放框架的第三篇。

1 主要接口和核心的类成员

NuPlayer类被NuPlayerDriver直接调用,其主要接口如下:

// code frome NuPlayer.h (~/frameworks/av/media/libmediaplayerservice/nuplayer/)
struct NuPlayer : public AHandler {
NuPlayer(pid_t pid);
void setUID(uid_t uid);
void setDriver(const wp<NuPlayerDriver> &driver);
void setDataSourceAsync(...);
void prepareAsync();
void setVideoSurfaceTextureAsync(const sp<IGraphicBufferProducer> &bufferProducer);
void start();
void pause(); // Will notify the driver through "notifyResetComplete" once finished.
void resetAsync(); // Will notify the driver through "notifySeekComplete" once finished
// and needNotify is true.
void seekToAsync(int64_t seekTimeUs, bool needNotify = false); status_t setVideoScalingMode(int32_t mode);
status_t getTrackInfo(Parcel* reply) const;
status_t getSelectedTrack(int32_t type, Parcel* reply) const;
status_t selectTrack(size_t trackIndex, bool select, int64_t timeUs);
status_t getCurrentPosition(int64_t *mediaUs); sp<MetaData> getFileMeta();
float getFrameRate(); protected:
virtual ~NuPlayer();
virtual void onMessageReceived(const sp<AMessage> &msg);
}

接口分类下,无外乎几个分类:

  • 用于初始化的(比如构造函数、setDriver/setDataSourceAsync/prepareAsync/setVideoSurfaceTextureAsync)
  • 用于销毁的(比如析构函数、resetAsync)
  • 用于播放控制的(比如start/pause/seekToAsync)
  • 用于状态获取的(比如getCurrentPosition/getFileMeta)

下面是主要的类成员部分

wp<NuPlayerDriver> mDriver; // 接口调用方
sp<Source> mSource; // 相当于FFmpeg中的demuxer
sp<Surface> mSurface; // 显示用的Surface
sp<DecoderBase> mVideoDecoder; // 视频解码器
sp<DecoderBase> mAudioDecoder; // 音频解码器
sp<CCDecoder> mCCDecoder;
sp<Renderer> mRenderer; // 渲染器
sp<ALooper> mRendererLooper;

2 setDataSourceAsync实现分析

这个函数有多重不同的重载形式,如下:

void setDataSourceAsync(const sp<IStreamSource> &source);
void setDataSourceAsync(const sp<IMediaHTTPService> &httpService, const char *url,
const KeyedVector<String8, String8> *headers);
void setDataSourceAsync(int fd, int64_t offset, int64_t length);
void setDataSourceAsync(const sp<DataSource> &source);

需要根据实际情况选择,这里以第三个接口为例,说明下多本地媒体文件是如何处理的。

下面是这个函数的实现代码:

void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) {
sp<AMessage> msg = new AMessage(kWhatSetDataSource, this); sp<AMessage> notify = new AMessage(kWhatSourceNotify, this);
// 创建对象用于读取本地文件
sp<GenericSource> source =
new GenericSource(notify, mUIDValid, mUID);
// 实际干活的的代码
status_t err = source->setDataSource(fd, offset, length); if (err != OK) {
ALOGE("Failed to set data source!");
source = NULL;
} msg->setObject("source", source);
msg->post();
}

看实现很简单,创建GenericSource对象,并调用其setDataSource接口,然后发送kWhatSetDataSource消息。

我们看看如何处理然后发送kWhatSetDataSource消息呢?代码如下:

case kWhatSetDataSource:
{
CHECK(mSource == NULL); status_t err = OK;
sp<RefBase> obj;
CHECK(msg->findObject("source", &obj));
if (obj != NULL) {
Mutex::Autolock autoLock(mSourceLock);
mSource = static_cast<Source *>(obj.get());
} else {
err = UNKNOWN_ERROR;
}
// 通知Driver函数调用完成
CHECK(mDriver != NULL);
sp<NuPlayerDriver> driver = mDriver.promote();
if (driver != NULL) {
driver->notifySetDataSourceCompleted(err);
}
break;
}

看到这里发现,其实没做什么就是直接通知NuPlayerDriver。我们还注意到这里构建了一个特殊消息(AMessage)notify,这个消息用于在Source和NuPlayer直接传递。下面这是消息循环中的处理函数:

case kWhatSourceNotify:
{
onSourceNotify(msg);
break;
}

在后续讨论Source的时候详细说明这个消息通知的意义。

3 prepareAsync

这个函数实现的功能对应于MediaPlayerBase::prepare/prepareAsync接口,实现异步的prepare功能,一般就是做一些额外的初始化工作。那么直接看一下实现:

void NuPlayer::prepareAsync() {
(new AMessage(kWhatPrepare, this))->post();
}

代码就是发了一个kWhatPrepare的消息。接下来是如何处理这个消息。

case kWhatPrepare:
{
mSource->prepareAsync();
break;
}

最终还是调用了Source::prepareAsync接口。后面会解释其功能。(这里面可能会解析下码流,读取音频、视频、字幕流信息,读取时长、元数据等)。

4 setVideoSurfaceTextureAsync

调用这个接口主要为了设置视频渲染窗口。其实现相对简单,创建一个Surface,然后发送异步的kWhatSetVideoSurface消息。代码如下:

void NuPlayer::setVideoSurfaceTextureAsync( const sp<IGraphicBufferProducer> &bufferProducer) {
sp<AMessage> msg = new AMessage(kWhatSetVideoSurface, this); if (bufferProducer == NULL) {
msg->setObject("surface", NULL);
} else {
msg->setObject("surface", new Surface(bufferProducer, true /* controlledByApp */));
} msg->post();
}

那么看看如何处理kWhatSetVideoSurface消息呢?

case kWhatSetVideoSurface: {
sp<RefBase> obj;
CHECK(msg->findObject("surface", &obj));
sp<Surface> surface = static_cast<Surface *>(obj.get()); // Need to check mStarted before calling mSource->getFormat because NuPlayer might
// be in preparing state and it could take long time.
// When mStarted is true, mSource must have been set.
if (mSource == NULL || !mStarted || mSource->getFormat(false /* audio */) == NULL
// NOTE: mVideoDecoder's mSurface is always non-null
|| (mVideoDecoder != NULL && mVideoDecoder->setVideoSurface(surface) == OK)) {
performSetSurface(surface); // 通知NuPlayerDriver设置完成
break;
}
// 清空音频、视频缓冲
mDeferredActions.push_back(
new FlushDecoderAction(FLUSH_CMD_FLUSH /* audio */,FLUSH_CMD_SHUTDOWN /* video */));
// 最终调用NuPlayer::performSetSurface接口
mDeferredActions.push_back(new SetSurfaceAction(surface)); if (obj != NULL || mAudioDecoder != NULL) {
if (mStarted) {
// Issue a seek to refresh the video screen only if started otherwise
// the extractor may not yet be started and will assert.
// If the video decoder is not set (perhaps audio only in this case)
// do not perform a seek as it is not needed.
int64_t currentPositionUs = 0;
if (getCurrentPosition(&currentPositionUs) == OK) {
mDeferredActions.push_back(
new SeekAction(currentPositionUs));
}
} // 对于新的surface设置,重置下解码器
mDeferredActions.push_back(new SimpleAction(&NuPlayer::performScanSources));
} // After a flush without shutdown, decoder is paused.
// Don't resume it until source seek is done, otherwise it could
// start pulling stale data too soon.
mDeferredActions.push_back(
new ResumeDecoderAction(false /* needNotify */));
// 把上面mDeferredActions中缓存的所有Action处理下,并清空
processDeferredActions();
break;
}

这里的代码相对复杂点,涉及到很多,其实主要是为了设置Surface之后,可以正常解码显示,因为某些情况下解码器初始化需要依赖于具体的Surface。当然,里边还涉及到NuPlayer状态及初始化判断。

5 start/pause

start函数实现很简单,实际就发送了kWhatStart消息。

void NuPlayer::start() {
(new AMessage(kWhatStart, this))->post();
}

在消息处理函数中的处理如下:

case kWhatStart:
{
if (mStarted) {
// do not resume yet if the source is still buffering
if (!mPausedForBuffering) {
onResume();
}
} else {
onStart();
}
mPausedByClient = false;
break;
}

直接调用了OnStart/OnResume函数。

pause函数实现类似,只是发送的是kWhatPause消息。在消息处理函数中的代码如下:

case kWhatPause:
{
onPause();
mPausedByClient = true;
break;
}

直接调用的onPause函数。下面单独分析下这三个函数。先从简单的函数开始OnPause/onResume

NuPlayer::onPause

这个函数实现暂停功能,总体来说就是把Source和Render暂停就可以了,代码如下:

void NuPlayer::onPause() {
if (mPaused) {
return;
}
mPaused = true;
if (mSource != NULL) {
mSource->pause();
}
if (mRenderer != NULL) {
mRenderer->pause();
}
}

NuPlayer::onResume

这个函数实现恢复功能,代码逻辑跟onPause差不多,把Source和Render恢复,还可能涉及其它操作。代码如下:

void NuPlayer::onResume() {
if (!mPaused || mResetting) {
return;
}
mPaused = false;
if (mSource != NULL) {
mSource->resume();
}
// |mAudioDecoder| may have been released due to the pause timeout, so re-create it if
// needed.
if (audioDecoderStillNeeded() && mAudioDecoder == NULL) {
instantiateDecoder(true /* audio */, &mAudioDecoder);
}
if (mRenderer != NULL) {
mRenderer->resume();
}
}

NuPlayer::onStart

这个接口实现启动的操作,相对复杂点,需要初始化解码器、初始化Render、设置Source状态,并将三者关联起来。代码如下:

void NuPlayer::onStart(int64_t startPositionUs) {
if (!mSourceStarted) {
mSourceStarted = true;
mSource->start(); // 设置Source状态
} // ... (省略部分代码) sp<AMessage> notify = new AMessage(kWhatRendererNotify, this);
++mRendererGeneration; // 创建Render和RenderLooper,属性设置、与解码器关联
notify->setInt32("generation", mRendererGeneration);
mRenderer = new Renderer(mAudioSink, notify, flags);
mRendererLooper = new ALooper;
mRendererLooper->setName("NuPlayerRenderer");
mRendererLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
mRendererLooper->registerHandler(mRenderer); status_t err = mRenderer->setPlaybackSettings(mPlaybackSettings); float rate = getFrameRate();
if (rate > 0) {
mRenderer->setVideoFrameRate(rate);
} if (mVideoDecoder != NULL) {
mVideoDecoder->setRenderer(mRenderer);
}
if (mAudioDecoder != NULL) {
mAudioDecoder->setRenderer(mRenderer);
} postScanSources();
}

上面代码中没有解码器的初始化,那只能继续看看postScanSources代码了。看实现发现就是发送了kWhatScanSources消息。那么消息循环里边是怎么处理的呢?

case kWhatScanSources:
{
int32_t generation;
CHECK(msg->findInt32("generation", &generation));
if (generation != mScanSourcesGeneration) {
// Drop obsolete msg.
break;
} mScanSourcesPending = false;
bool mHadAnySourcesBefore = (mAudioDecoder != NULL) || (mVideoDecoder != NULL);
bool rescan = false; // initialize video before audio because successful initialization of
// video may change deep buffer mode of audio.
if (mSurface != NULL) { // 初始化视频解码器
if (instantiateDecoder(false, &mVideoDecoder) == -EWOULDBLOCK) {
rescan = true;
}
} // Don't try to re-open audio sink if there's an existing decoder.
if (mAudioSink != NULL && mAudioDecoder == NULL) { // 初始化音频解码器
if (instantiateDecoder(true, &mAudioDecoder) == -EWOULDBLOCK) {
rescan = true;
}
} if (!mHadAnySourcesBefore && (mAudioDecoder != NULL || mVideoDecoder != NULL)) {
// This is the first time we've found anything playable.
// 设置定期查询时长
if (mSourceFlags & Source::FLAG_DYNAMIC_DURATION) {
schedulePollDuration();
}
} status_t err; // 一些异常处理逻辑
if ((err = mSource->feedMoreTSData()) != OK) {
if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
// We're not currently decoding anything (no audio or
// video tracks found) and we just ran out of input data. if (err == ERROR_END_OF_STREAM) {
notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0);
} else {
notifyListener(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
}
}
break;
}
// 如果需要的话,重新扫描Source
if (rescan) {
msg->post(100000ll);
mScanSourcesPending = true;
}
break;
}

6 seekToAsync

这个函数完成seek操作,其实现比较简单直接发送kWhatSeek消息,代码如下:

void NuPlayer::seekToAsync(int64_t seekTimeUs, bool needNotify) {
sp<AMessage> msg = new AMessage(kWhatSeek, this);
msg->setInt64("seekTimeUs", seekTimeUs);
msg->setInt32("needNotify", needNotify);
msg->post();
}

在消息循环里边的处理代码如下:

case kWhatSeek:
{
int64_t seekTimeUs;
int32_t needNotify;
if (!mStarted) {
// Seek before the player is started. In order to preview video,
// need to start the player and pause it. This branch is called
// only once if needed. After the player is started, any seek
// operation will go through normal path.
// Audio-only cases are handled separately.
onStart(seekTimeUs);
if (mStarted) {
onPause();
mPausedByClient = true;
}
if (needNotify) {
notifyDriverSeekComplete();
}
break;
} mDeferredActions.push_back(
new FlushDecoderAction(FLUSH_CMD_FLUSH /* audio */,
FLUSH_CMD_FLUSH /* video */));
// 真正做seek事情的在这里
mDeferredActions.push_back(new SeekAction(seekTimeUs)); // After a flush without shutdown, decoder is paused.
// Don't resume it until source seek is done, otherwise it could
// start pulling stale data too soon.
mDeferredActions.push_back(new ResumeDecoderAction(needNotify)); processDeferredActions();
break;
}

实际代码中SeekAction最终调用performSeek接口,其实现如下:

void NuPlayer::performSeek(int64_t seekTimeUs) {
if (mSource == NULL) {
// This happens when reset occurs right before the loop mode
// asynchronously seeks to the start of the stream.
LOG_ALWAYS_FATAL_IF(mAudioDecoder != NULL || mVideoDecoder != NULL,
"mSource is NULL and decoders not NULL audio(%p) video(%p)",
mAudioDecoder.get(), mVideoDecoder.get());
return;
}
mPreviousSeekTimeUs = seekTimeUs;
mSource->seekTo(seekTimeUs); // 直接调用Source对应接口
++mTimedTextGeneration; // everything's flushed, continue playback.
}

7 resetAsync

重置函数实现逻辑相对简单,直接重置下,代码如下:

void NuPlayer::resetAsync() {
sp<Source> source;
{
Mutex::Autolock autoLock(mSourceLock);
source = mSource;
} if (source != NULL) {
// During a reset, the data source might be unresponsive already, we need to
// disconnect explicitly so that reads exit promptly.
// We can't queue the disconnect request to the looper, as it might be
// queued behind a stuck read and never gets processed.
// Doing a disconnect outside the looper to allows the pending reads to exit
// (either successfully or with error).
source->disconnect();
} (new AMessage(kWhatReset, this))->post();
}

消息循环中对于kWhatReset处理如下:

case kWhatReset:
{
mResetting = true; mDeferredActions.push_back(
new FlushDecoderAction(
FLUSH_CMD_SHUTDOWN /* audio */,
FLUSH_CMD_SHUTDOWN /* video */)); mDeferredActions.push_back(new SimpleAction(&NuPlayer::performReset)); processDeferredActions();
break;
}

上面的SimpleAction是直接调用接口的,其实现如下:

void NuPlayer::performReset() {
cancelPollDuration(); ++mScanSourcesGeneration;
mScanSourcesPending = false;
// 销毁Render
if (mRendererLooper != NULL) {
if (mRenderer != NULL) {
mRendererLooper->unregisterHandler(mRenderer->id());
}
mRendererLooper->stop();
mRendererLooper.clear();
}
mRenderer.clear();
++mRendererGeneration;
// 销毁Source
if (mSource != NULL) {
mSource->stop(); Mutex::Autolock autoLock(mSourceLock);
mSource.clear();
}
// 通知Reset完成
if (mDriver != NULL) {
sp<NuPlayerDriver> driver = mDriver.promote();
if (driver != NULL) {
driver->notifyResetComplete();
}
} mStarted = false;
mPrepared = false;
mResetting = false;
mSourceStarted = false;
}

8 getCurrentPosition/getFileMeta

getCurrentPosition用于获取当前播放位置,直接通过Render的对应接口获取的。实现代码如下:

status_t NuPlayer::getCurrentPosition(int64_t *mediaUs) {
sp<Renderer> renderer = mRenderer;
if (renderer == NULL) {
return NO_INIT;
} return renderer->getCurrentPosition(mediaUs);
}

getFileMeta获取媒体的元数据信息,直接通过Source的对应接口获取。实现代码如下:

sp<MetaData> NuPlayer::getFileMeta() {
return mSource->getFileFormatMeta();
}

9 总结和疑问

到这里,我们已经把NuPlayer主要的函数分析完了,但是问题依旧在。比如下面几个:

  1. 不同格式的多媒体文件如何探测并解析的?音视频数据缓冲区在哪里?(Source)
  2. 视频如何显示的?音频如何播放的?音视频同步在哪里?(Renderer)
  3. 音频解码线程、视频解码线程在哪里? (DecoderBase)

我想接下来几个主题就是解决这些疑问的。

当然总结下本文的内容。

主要参考AOSP 7.0的源码,结合代码分析了NuPlayer主要对外接口的实现,并简单总结了各部分的功能。

③NuPlayer播放框架之类NuPlayer源码分析的更多相关文章

  1. ⑤NuPlayer播放框架之GenericSource源码分析

    [时间:2017-01] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,GenericSource] 0 导读 GenericSource是NuPlayer:: ...

  2. ④NuPlayer播放框架之Renderer源码分析

    [时间:2016-11] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,渲染器,render] 0 导读 之前我们分析了NuPlayer的实现代码,本文将重点聚 ...

  3. DotNetty网络通信框架学习之源码分析

    DotNetty网络通信框架学习之源码分析 有关DotNetty框架,网上的详细资料不是很多,有不多的几个博友做了简单的介绍,也没有做深入的探究,我也根据源码中提供的demo做一下记录,方便后期查阅. ...

  4. 深入理解分布式调度框架TBSchedule及源码分析

    简介 由于最近工作比较忙,前前后后花了两个月的时间把TBSchedule的源码翻了个底朝天.关于TBSchedule的使用,网上也有很多参考资料,这里不做过多的阐述.本文着重介绍TBSchedule的 ...

  5. $Django cbv源码分析 djangorestframework框架之APIView源码分析

    1 CBV的源码分析 #视图 class login (View): pass #路由 url(r'^books/$', views.login.as_view()) #阅读源码: #左侧工程栏--- ...

  6. 设计模式(十五)——命令模式(Spring框架的JdbcTemplate源码分析)

    1 智能生活项目需求 看一个具体的需求 1) 我们买了一套智能家电,有照明灯.风扇.冰箱.洗衣机,我们只要在手机上安装 app 就可以控制对这些家电工作. 2) 这些智能家电来自不同的厂家,我们不想针 ...

  7. 设计模式(二十一)——解释器模式(Spring 框架中SpelExpressionParser源码分析)

    1 四则运算问题 通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求 1) 先输入表达式的形式,比如 a+b+c-d+e,  要求表达式的字母不能重复 2) 在分别输入 a ,b, c, ...

  8. ②NuPlayer播放框架之ALooper-AHandler-AMessage底层机制分析

    [时间:2016-09] [状态:Open] [关键词:android,NuPlayer,开源播放器,播放框架,ALooper,AHandler,AMessage] 前文中提到过NuPlayer基于S ...

  9. Android 应用框架层 SQLite 源码分析

    概述   Android 在应用框架层为开发者提供了 SQLite 相关操作接口,其归属于android.database.sqlite包底下,主要包含SQLiteProgram, SQLiteDat ...

随机推荐

  1. mac配置php7运行环境

    不用mac自带的apache和php,安装自己想要的版本.配置过程一直采坑,需要有闲时间和好的心理素质才行,哈哈,因为网上很教程都有纰漏之处,所以先把采坑无数后发现的个人认为最好的一个教程链接放在这里 ...

  2. hdu 3065 病毒侵袭持续中【AC自动机】

    <题目链接> 题目大意: 小t非常感谢大家帮忙解决了他的上一个问题.然而病毒侵袭持续中.在小t的不懈努力下,他发现了网路中的“万恶之源”.这是一个庞大的病毒网站,他有着好多好多的病毒,但是 ...

  3. hdu 2167 方格取数 【状压dp】(经典)

    <题目链接> 题目大意: 给出一些数字组成的n*n阶矩阵,这些数字都在[10,99]内,并且这个矩阵的  3<=n<=15,从这个矩阵中随机取出一些数字,在取完某个数字后,该数 ...

  4. SQL server学习(四)T-SQL编程之事务、索引和视图

    今天来分享下T-SQL高级编程中的事务.索引.视图,可以和之前的SQL server系列文章结合起来. 一.事务 事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个 ...

  5. 【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-3 蒙特卡罗 (三)

    开学人倍忙,趁着第二周周末,我们继续图形相关的博客  Preface 今天我们来介绍一些理论方面的东西,为Monte Carlo 应用到我们的光线追踪器做铺垫 我们今天会介绍两章的东西,因为有一章内容 ...

  6. Servlet与HTTP介绍学习

    http介绍:http是一套规范,一种网络数据交互的标准协议,不同的语言,不同的数据想要实现合理的数据交互(例如:浏览器和服务器数据交互),就得按照他所规定的协议来,这样就会形成标准的(大家都认识的) ...

  7. win10 图标异常 ,重命名后,图标不显示,名字错乱。

    win10 图标异常 ,重命名后,图标不显示,名字错乱. 按下快捷键 Win+R,在打开的运行窗口中输入 %localappdata%,回车. 在打开的文件夹中,找到 IconCache.db,将其删 ...

  8. 10.29 正睿停课训练 Day11

    目录 2018.10.29 正睿停课训练 Day11 A 线段树什么的最讨厌了(思路 DFS) B 已经没有什么好害怕的了(差分 前缀和) C 我才不是萝莉控呢(DP 贪心 哈夫曼树) 考试代码 A ...

  9. BZOJ.3720.Gty的妹子树(树分块)

    题目链接 洛谷上惨遭爆零是为什么.. 另外这个树分块算法是假的. /* 插入删除只涉及一个数,故每次可以枚举一遍,而不是重构完后sort */ #include<cmath> #inclu ...

  10. PyPDF2详解

    工作中可能会涉及处理pdf文件,PyPDF2就是这样一个库, 使用它可以轻松的处理pdf文件,它提供了读.写.分割.合并.文件转换等多种操作.官方地址:http://mstamy2.github.io ...