技术标签: Android7.1-media
[时间:2016-09] [状态:Open]
[关键词:android,nuplayer,开源播放器,播放框架,nuplayerdriver]
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。
下图是NuPlayer整体框架图
或者下图
Android层的多媒体框架,有多层实现,甚至有跨进程的调用。这里重点关注NuPlayerDriver之后的实现和相关逻辑。至于上层的调用逻辑,建议参考其他资料。
各部分功能如下:
在AOSP中,通常将一个多媒体文件或者URL称为DataSource。通常多媒体文件中包含至少一个音频流、视频流或者字幕流,NuPlayer将这三种统称为Track,细分下也有AudioTrack、VideoTrack、SubtitleTrack。将一个多媒体文件解析之后就可以通过解码器还原为原始数据,然后渲染了。具体流程参考下图:
DataSource有两个概念:
VideoTrack与AudioTrack指的是Extractor(即demux)的两个通道,从这里输出的分别就是单纯的解复用后的Video和Audio流。再经过Decoder后输出的就是音、视频的输出了:
至于Android应用层如何调用MediaPlayer,建议参考我之前的文章MediaPlayer Interface&State。
鉴于AOSP是一个操作系统,整体比较复杂,从实际出发,可以关注于某个点。比如我这里主要关注NuPlayer的框架,其内部实现逻辑。那么最终就落实到如何从一个类中提取出需要的框架及知识点。那么一个类的对外接口部分通常包括:
在多媒体播放中,通过关注的点有:
NuPlayer框架中最顶层的类是NuPlayerDriver,继承自MediaPlayerInterface,主要提供一个状态转换机制,作为NuPlayer类的Wrapper。NuPlayerDriver类中最重要的成员是以下几个:
State mState
播放器状体标志sp<ALooper> mLooper
内部消息驱动机制sp<NuPlayer> mPlayer
真正完成播放器的类先说明下我参考的是Android 7的源码,NuPlayerDriver.cpp (目录:./frameworks/av/media/libmediaplayerservice/nuplayer/)
从代码中可以看到,构造函数中最主要的作用是创建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();
这个接口实现很简单,检查当前的播放状态,然后直接调用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接口,改变播放器状态。
这个接口的实现思路跟SetDataSource,不过调用的是NuPlayer::setVideoSurfaceTextureAsync,该请求处理完之后调用NuPlayerDriver::notifySetSurfaceComplete接口。
这两个接口基本功能是一致的,只是第二个是异步的调用过程。第一个通过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;
};
这两个函数作为开始播放和停止播放的接口,主要涉及到播放器内部状态的切换和判断。最终功能实现通过调用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;
}
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通知。
这几个接口主要用于获取播放器的状态。
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获取。
这个接口主要是判断播放器的属性,比如是否支持暂停、seek、向前seek、向后seek等。
本文主要分析了NuPlayerDriver的接口实现,接下来分析的部分包括:
总结下来,NuPlayerDriver主要是接口层的一个衔接,并记录了播放器内部的状态数据,以保证其符合Android MediaPlayer状态调用逻辑。代码相对简单。
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf