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

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

0 NuPlayer简介

Android2.3时引入流媒体框架,而流媒体框架的核心是NuPlayer。在之前的版本中一般认为Local Playback就用Stagefrightplayer+Awesomeplayer,流媒体用NuPlayer。Android4.0之后HttpLive和RTSP协议开始使用NuPlayer播放器,Android5.0(L版本)之后本地播放也开始使用NuPlayer播放器。 Android7.0(N版本)则完全去掉了Awesomeplayer。

通俗点说,NuPlayer是AOSP中提供的多媒体播放框架,能够支持本地文件、HTTP(HLS)、RTSP等协议的播放,通常支持H.264、H.265/HEVC、AAC编码格式,支持MP4、MPEG-TS封装。

在实现上NuPlayer和Awesomeplayer不同,NuPlayer基于StagefrightPlayer的基础类构建,利用了更底层的ALooper/AHandler机制来异步地处理请求,ALooper列队消息请求,AHandler中去处理,所以有更少的Mutex/Lock在NuPlayer中。Awesomeplayer中利用了omxcodec而NuPlayer中利用了Acodec。

1 NuPlayer框架

下图是NuPlayer整体框架图

或者下图

Android层的多媒体框架,有多层实现,甚至有跨进程的调用。这里重点关注NuPlayerDriver之后的实现和相关逻辑。至于上层的调用逻辑,建议参考其他资料。

各部分功能如下:

  • NuPlayer::Source:解析模块(parser,功能类似FFmpeg的avformat)。其接口与MediaExtractor和MediaSource组合的接口差不多,同时提供了用于快速定位的seekTo接口。
  • NuPlayer::Decoder:解码模块(decoder,功能类似FFmpeg的avcodec),封装了用于AVC、AAC解码的接口,通过ACodec实现解码(包含OMX硬解码和软解码)。
  • NuPlayer::Render:渲染模块(render,功能类似声卡驱动和显卡驱动),主要用于音视频渲染和同步,与NativeWindow有关。

2 多媒体文件如何通过NuPlayer播放的

在AOSP中,通常将一个多媒体文件或者URL称为DataSource。通常多媒体文件中包含至少一个音频流、视频流或者字幕流,NuPlayer将这三种统称为Track,细分下也有AudioTrack、VideoTrack、SubtitleTrack。将一个多媒体文件解析之后就可以通过解码器还原为原始数据,然后渲染了。具体流程参考下图:

DataSource有两个概念:

  • 上图中的DataSourceInput(DataSource)指的是单纯的原始数据(容器格式,没有经过demuxer处理)。
  • 在后文中setDataSource中DataSource指的是从数据输入到demux输出的一个过程(即图中最外层的DataSource)。

VideoTrack与AudioTrack指的是Extractor(即demux)的两个通道,从这里输出的分别就是单纯的解复用后的Video和Audio流。再经过Decoder后输出的就是音、视频的输出了:

  • VideoRenderer + Surface即视频的输出;
  • AudioSink即音频的输出;

至于Android应用层如何调用MediaPlayer,建议参考我之前的文章MediaPlayer Interface&State

3 我个人对于AOSP的源码分析的方法

鉴于AOSP是一个操作系统,整体比较复杂,从实际出发,可以关注于某个点。比如我这里主要关注NuPlayer的框架,其内部实现逻辑。那么最终就落实到如何从一个类中提取出需要的框架及知识点。那么一个类的对外接口部分通常包括:

  • 构造函数和析构函数
  • 必须调用的接口
  • 可选的调用接口

在多媒体播放中,通过关注的点有:

  • 如何实现解复用,得到音频、视频、字幕等数据
  • 如何实现解码
  • 如何实现音视频同步
  • 如何渲染视频
  • 如何播放音频
  • 如何实现快速定位

4 NuPlayer接口实现分析(NuPlayerDriver)

NuPlayer框架中最顶层的类是NuPlayerDriver,继承自MediaPlayerInterface,主要提供一个状态转换机制,作为NuPlayer类的Wrapper。NuPlayerDriver类中最重要的成员是以下几个:

  • State mState 播放器状体标志
  • sp<ALooper> mLooper 内部消息驱动机制
  • sp<NuPlayer> mPlayer 真正完成播放器的类

先说明下我参考的是Android 7的源码,NuPlayerDriver.cpp (目录:./frameworks/av/media/libmediaplayerservice/nuplayer/)

4.1 构造函数&析构函数

从代码中可以看到,构造函数中最主要的作用是创建ALooper和NuPlayer实例,并将它们关联起来。

mLooper = (new ALooper);
mLooper->start(
false, /* runOnCallingThread */
true, /* canCallJava */
PRIORITY_AUDIO); mPlayer = new NuPlayer(pid);
mLooper->registerHandler(mPlayer); mPlayer->setDriver(this);

析构函数主要就是销毁创建的ALooper和NuPlayer,由于是智能指针,直接调用stop即可。

mLooper->stop();

4.2 SetDataSource

这个接口实现很简单,检查当前的播放状态,然后直接调用NuPlayer::setDataSourceAsync函数。

status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) {
ALOGV("setDataSource(%p) file(%d)", this, fd);
Mutex::Autolock autoLock(mLock); if (mState != STATE_IDLE) {
return INVALID_OPERATION;
} mState = STATE_SET_DATASOURCE_PENDING; mPlayer->setDataSourceAsync(fd, offset, length); while (mState == STATE_SET_DATASOURCE_PENDING) {
mCondition.wait(mLock);
} return mAsyncResult;
}

最后等待该函数返回,并调用NuPlayerDriver::notifySetDataSourceCompleted接口,改变播放器状态。

4.3 setVideoSurfaceTexture

这个接口的实现思路跟SetDataSource,不过调用的是NuPlayer::setVideoSurfaceTextureAsync,该请求处理完之后调用NuPlayerDriver::notifySetSurfaceComplete接口。

4.4 prepare/prepareAsync

这两个接口基本功能是一致的,只是第二个是异步的调用过程。第一个通过prepare_l接口实现,二者最终均调用NuPlayer::prepareAsync,请求处理完成之后调用NuPlayerDriver::notifyPrepareCompleted接口。不过这里面有大量的关于播放状态判断的代码。比如prepareAsync中代码

ALOGV("prepareAsync(%p)", this);
Mutex::Autolock autoLock(mLock); switch (mState) {
case STATE_UNPREPARED:
mState = STATE_PREPARING;
mIsAsyncPrepare = true;
mPlayer->prepareAsync();
return OK;
case STATE_STOPPED:
// this is really just paused. handle as seek to start
mAtEOS = false;
mState = STATE_STOPPED_AND_PREPARING;
mIsAsyncPrepare = true;
mPlayer->seekToAsync(0, true /* needNotify */);
return OK;
default:
return INVALID_OPERATION;
};

4.5 start/stop

这两个函数作为开始播放和停止播放的接口,主要涉及到播放器内部状态的切换和判断。最终功能实现通过调用NuPlayer::start和NuPlayer::pause接口。

下面是start函数实现代码(start_l),主要需要判断不同状态下的调用逻辑:

switch (mState) {
case STATE_UNPREPARED:
{
status_t err = prepare_l(); if (err != OK) {
return err;
} CHECK_EQ(mState, STATE_PREPARED); // fall through
} case STATE_PAUSED:
case STATE_STOPPED_AND_PREPARED:
case STATE_PREPARED:
{
mPlayer->start(); // fall through
} case STATE_RUNNING:
{
if (mAtEOS) {
mPlayer->seekToAsync(0);
mAtEOS = false;
mPositionUs = -1;
}
break;
} default:
return INVALID_OPERATION;
} mState = STATE_RUNNING;

stop接口实现则相对简单,主要是判断什么状态下可以调用stop接口,并上报MEDIA_STOPPED状态。代码如下:

switch (mState) {
case STATE_RUNNING:
mPlayer->pause();
// fall through case STATE_PAUSED:
mState = STATE_STOPPED;
notifyListener_l(MEDIA_STOPPED);
break; case STATE_PREPARED:
case STATE_STOPPED:
case STATE_STOPPED_AND_PREPARING:
case STATE_STOPPED_AND_PREPARED:
mState = STATE_STOPPED;
break; default:
return INVALID_OPERATION;
}

4.6 pause / reset

pause用于实现暂停,其实现比较简单,直接调用NuPlayer::puase实现,代码如下:

switch (mState) {
case STATE_PAUSED:
case STATE_PREPARED:
return OK; case STATE_RUNNING:
mState = STATE_PAUSED;
notifyListener_l(MEDIA_PAUSED);
mPlayer->pause();
break; default:
return INVALID_OPERATION;
}

reset重置播放器,这是一个同步调用的接口。最终实现通过调用NuPlayer::resetAsync接口,然后调用NuPlayerDriver::notifyResetComplete通知。

4.7 isPlaying / getDuration / getCurrentPosition

这几个接口主要用于获取播放器的状态。

isPlaying直接通过播放器状态判断,其实现如下:

bool NuPlayerDriver::isPlaying() {
return mState == STATE_RUNNING && !mAtEOS;
}

getDuration的实现也相对简单,代码如下。不过具体获取的mDurationUs需要通过NuPlayer上报或者定期查询更新下。

status_t NuPlayerDriver::getDuration(int *msec) {
Mutex::Autolock autoLock(mLock); if (mDurationUs < 0) {
return UNKNOWN_ERROR;
} *msec = (mDurationUs + 500ll) / 1000; return OK;
}

getCurrentPosition则是通过调用NuPlayer::getCurrentPosition获取。

4.8 getMetadata

这个接口主要是判断播放器的属性,比如是否支持暂停、seek、向前seek、向后seek等。

5 后续细节分析

本文主要分析了NuPlayerDriver的接口实现,接下来分析的部分包括:

  • ALooper机制
  • NuPlayer
  • NuPlayer::Decoder
  • NuPlayer::Source
  • NuPlayer::Render
  • ACodec

总结下来,NuPlayerDriver主要是接口层的一个衔接,并记录了播放器内部的状态数据,以保证其符合Android MediaPlayer状态调用逻辑。代码相对简单。

参考文献

  1. NuPlayer介绍
  2. NuPlayer for HTTP live streaming
  3. Stagefright框架中视频播放流程

①Android 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. ③NuPlayer播放框架之类NuPlayer源码分析

    [时间:2016-10] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架] 0 引言 差不多一个月了,继续分析AOSP的播放框架的源码.这次我们需要深入分析的是N ...

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

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

  5. ⑥NuPlayer播放源码分析之DecoderBase分析

    NuPlayer播放源码分析之DecoderBase分析 [时间:2017-02] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,DecoderBase,Med ...

  6. android 音乐播放器

    本章以音乐播放器为载体,介绍android开发中,通知模式Notification应用.主要涉及知识点Notification,seekbar,service. 1.功能需求 完善音乐播放器 有播放列 ...

  7. Android的多媒体框架OpenCore介绍

    网上资料很少, 不过还是找到一个比较详细的说明: 特地在此整理了下: 地址:http://blog.csdn.net/djy1992/article/details/9339787 分为几个阶段: 1 ...

  8. 使用Vitamio打造自己的Android万能播放器(1)——准备

    前言 虽然Android已经内置了VideoView组件和MediaPlayer类来支持开发视频播放器,但支持格式.性能等各方面都十分有限,这里与大家一起利用免费的Vitamio来打造属于自己的And ...

  9. Android图片载入框架最全解析(一),Glide的基本使用方法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53759439 本文同步发表于我的微信公众号.扫一扫文章底部的二维码或在微信搜索 郭 ...

随机推荐

  1. poj2230 Watchcow【欧拉回路】【输出路径】(遍历所有边的两个方向)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4392 题目大意: 一个图,要将每条边恰好遍历两遍,而且要以不同的方向,还要回到原点. dfs解法    ...

  2. 用js来实现那些数据结构03(数组篇03-排序及多维数组)

    终于,这是有关于数组的最后一篇,下一篇会真真切切给大家带来数据结构在js中的实现方式.那么这篇文章还是得啰嗦一下数组的相关知识,因为数组真的太重要了!不要怀疑数组在JS中的重要性与实用性.这篇文章分为 ...

  3. xss总结--2018自我整理

    0x00前言 因为ctf中xss的题目偏少(因为需要机器人在后台点选手的连接2333),所有写的比较少 这里推荐个环境http://test.xss.tv/ 0x01xss作用 常见的输出函数:pri ...

  4. vue那些事儿

    本篇文章基于vue2,vuejs不支持ie8,因为es5的Object.defineProperty特性不被ie8支持,而vue正是使用Object.defineProperty把遍历过的data属性 ...

  5. for循环的灵活性

      for循环把初始化.测试和更新组合在一起,其基本形式如下所示: for(初始化:测试条件:更新表达式) { //循环体 }   关键字for后面的圆括号中3个表达式,分别用两个分号隔开:   第一 ...

  6. BZOJ4888 [Tjoi2017]异或和 FFT或树状数组+二进制拆位

    题面 戳这里 简要题解 做法一 因为所有数的和才100w,所以我们可以直接求出所有区间和. 直接把前缀和存到一个权值数组,再倒着存一遍,大力卷积一波. 这样做在bzoj目前还过不了,但是luogu开O ...

  7. Python3练习题系列(01)

    2018-06-13 题目: 根据用户回答做出相应的判断,完成一个“回答-判断”的小游戏 Python3知识点: if, else, elif 实例代码: print("You enter ...

  8. python生成指定文件夹目录树

    # -*- coding: utf-8 -*- import sys from pathlib import Path class DirectionTree(object): "" ...

  9. [HDU5361]In Touch

    [HDU5361]In Touch 题目大意: 有\(n(n\le2\times10^5)\)个点,每个点有三个属性\(l_i,r_i,c_i\).表示若\(|i-j|\in[l_i,r_i]\),\ ...

  10. 学校的统一订书80%该烧掉——IT推荐书单

    学校的统一订书80%该烧掉——IT推荐书单 作者: 夏浅音.py 中文图书个人黑名单:清华大学出版社的国产作者,例如一段错n次的谭浩强.一页错n次的严蔚敏... 中文图书个人白名单:机械工业出版社.电 ...