Android媒体播放框架
Android媒体播放框架
安卓有许多媒体播放框架,这里讲的是MediaPlayer和NuPlayer及其相关的部分。
多媒体基础概念
音视频概念
视频分辨率
- 标清:意思就是“标准清晰度”,是物理分辨率在720p以下的视频格式。
- 高清:而物理分辨率达到720p以上的格式则称作为高清,简称HD。
- FHD:即是1080P。
- UHD:即是4k分辨率。
视频编码
- H.264:
是MPEG-4(MPEG-4是MPEG格式的一个压缩标准)第十部分,是由ITU-T视频编码专家组(VCEG)和ISO/IEC动态图像专家组(MPEG)联合组成的联合视频组(JVT,Joint
Video
Team)提出的高度压缩数字视频编解码器标准。这个标准通常被称之为H.264/AVC(或者AVC/H.264或者H.264/MPEG-4
AVC或MPEG-4/H.264 AVC)而明确的说明它两方面的开发者,优点:
- 低码率
- 高质量的图像
- 容错能力强
- 网络适应性强
- H.265: H.265是ITU-T VCEG继H.264之后所制定的新的视频编码标准。H.265标准围绕着现有的视频编码标准H.264,保留原来的某些技术,同时对一些相关的技术加以改进。新技术使用先进的技术用以改善码流、编码质量、延时和算法复杂度之间的关系,达到最优化设置。
- H.264:
是MPEG-4(MPEG-4是MPEG格式的一个压缩标准)第十部分,是由ITU-T视频编码专家组(VCEG)和ISO/IEC动态图像专家组(MPEG)联合组成的联合视频组(JVT,Joint
Video
Team)提出的高度压缩数字视频编解码器标准。这个标准通常被称之为H.264/AVC(或者AVC/H.264或者H.264/MPEG-4
AVC或MPEG-4/H.264 AVC)而明确的说明它两方面的开发者,优点:
音频编码
- AAC:
一种专为声音数据设计的文件压缩格式,与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的“性价比”。利用AAC格式,可使人感觉声音质量没有明显降低的前提下,更加小巧。
优点:相对于mp3,AAC格式的音质更佳,文件更小。不足:AAC是损压缩的格式。 - MP3: MP3是一种音频压缩技术,其全称是动态影像专家压缩标准音频层面3(Moving Picture Experts Group Audio Layer III),简称为MP3。MP3是利用人耳对高频声音信号不敏感的特性,将时域波形信号转换成频域信号,并划分成多个频段,对不同的频段使用不同的压缩率,对高频加大压缩比(甚至忽略信号)对低频信号使用小压缩比,保证信号不失真。
- AAC:
一种专为声音数据设计的文件压缩格式,与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的“性价比”。利用AAC格式,可使人感觉声音质量没有明显降低的前提下,更加小巧。
HLS流媒体协议
HTTP Live Streaming是一个由苹果公司提出的基于HTTP的流媒体网络传输协议。格式要求:
- 视频的封装格式TS(流媒体文件)
- 保存TS索引的M3U8文件
- 视频的编码格式:H264(只要MPEG-TS支持,基本都可以,音频类似)
- 音频的编码格式:AAC、MP3、AC-3
HLS优势:
- 使用标准HTTP传输数据,具有较好的网络穿透及防屏蔽性,易于内容分发网络传输
- HLS协议本身是支持码率自适应的,客户端可以根据实际网络状况切换合适的码率
- HLS内容发布服务更简单,对系统设备要求较低,易实现负载均衡,并且HLS是无状态协议的 HTTP,客户端只需要下载即可
HLS劣势:
- 延时较大,尤其是在直播的情况下,很难做到10s以内的延时
- 内容生成时对编码端性能要求较高
视频相关概念
- 分辨率
- 一帧视频的大小,表示长宽像素个数(720x576, 1280x720, 1920x1080 …)
- 屏幕图像的精密度,是指一帧视频图像所能显示的像素有多少
- 帧率
- 每秒钟视频帧数(24/25/30/48/60 FPS)
- 由于人类眼睛的特殊生理结构,如果所看画面之帧率高于24的时候,就会认为是连贯的,此现象称之为视觉暂留。
- 每秒的帧数(fps)或者说帧率表示图形处理器处理场时每秒钟能够更新的次数。帧率超过屏幕刷新率只会浪费图形处理的能力。
- 刷新率
- 刷新频率:即屏幕刷新的速度。
- 刷新频率越低,图像闪烁、停顿和抖动的就越厉害,眼睛疲劳得就越快。
- 编码格式
- 编码:目的是压缩数据量,采用编码算法压缩冗余数据,常用的有:
- MPEG(MPEG-2, MPEG-4)
- H.26X(H.263, H.264/AVC, H.265/HEVC)
- 封装格式
把编码后的音、视频数据以一定格式封装到一个容器,如MKV/AVI/TS … - 码率
每秒钟视频数据的比特位数(bps)
文件大小(b) = 码率(b/s) * 时长(s) - 画质vs码率
视频质量和码率、编码算法都有关系
音频相关概念
- 编码格式
- 有损编码: AAC/MP3/AMR/WMA
- 无损编码: WAV/FLAC/APE/ALAC
- 补充:用无损编码的数据是可以完全恢复的,解码后的数据与原始数据完全一致,有损编码在编码过程中要丢失不易察觉的信息,且丢失的数据不可恢复。
- 量化精度
- 可以将模拟信号分成多少个等级,量化精度越高,音乐的声压振幅越接近原音乐.
- 量化精度的单位是Bit,CD标准的量化精度是16Bit,DVD标准的量化精度是24Bit。
- 也可理解为一个采样点用多少bit表示(8/16/24/32bit).
- 采样率
- 每秒钟音频采样点个数(8000/44100Hz)
- 采样率单位用Hz(赫兹)表示
- 声道数目
- 立体声,5.1,7.1声道
- 立体声:声音在录制过程中被分配到两个独立的声道,从而达到了很好的声音定位效果。这种技术在音乐欣赏中显得尤为有用,听众可以清晰地分辨出各种乐器来自的方向,从而使音乐更富想象力,更加接近于临场感受。立体声技术广泛运用于自Sound Blaster Pro以后的大量声卡,成为了影响深远的一个音频标准。
- 5.1声道:其实5.1声音系统来源于4.1环绕,不同之处在于它增加了一个中置单元。这个中置单元负责传送低于80Hz的声音信号,在欣赏影片时有利于加强人声,把对话集中在整个声场的中部,以增加整体效果。杜比AC-3(Dolby Digital)、DTS等都是以5.1声音系统为技术蓝本的。相信每一个真正体验过Dolby AC-3音效的朋友都会为5.1声道所折服。现在广泛用于传统影院和家庭影院。
- 音频帧
- 一定数目的采样点数的集合
- 编码: 基本编码单元
- 举例: 音频帧的播放时间 =
一个AAC帧对应的采样样本的个数/采样频率(单位为s)。
采样率(samplerate)为 44100Hz,表示每秒 44100个采样点,
所以,根据公式,
音频帧的播放时长 = 一个AAC帧对应的采样点个数 / 采样频率
则,当前一帧的播放时间 = 1024 * 1000000/44100= 22.32ms(单位为ms)
48kHz采样率:
则,当前一帧的播放时间 = 1024 * 1000000/48000= 21.32ms(单位为ms)
22.05kHz采样率:
则,当前一帧的播放时间 = 1024 * 1000000/22050= 46.43ms(单位为ms)
视频播放流程
视音频技术主要包含以下几点:封装技术,视频压缩编码技术以及音频压缩编码技术。如果考虑到网络传输的话,还包括流媒体协议技术。视频播放器播放一个互联网上的视频文件,需要经过以下几个步骤:解协议,解封装,解码视音频,视音频同步。如果播放本地文件则不需要解协议。流程如下:
解协议:将流媒体协议(如HTTP、RTSP+RTP)的数据,解析为标准的相应的封装格式数据。这些协议在传输视音频数据的同时,也会传输一些信令数据,包括对播放的控制(播放,暂停,停止),或者对网络状态的描述等。解协议的过程中会去除掉信令数据而只保留视音频数据。
解封装:将封装格式数据(如MP4,MKV,RMVB,TS,FLV,AVI),分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式数据将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如,FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
解码:将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含AAC,MP3,AC-3等等,视频的压缩编码标准则包含H.265/HEVC,H.264/AVC,MPEG-2,VC-1等等。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV,RGB等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如PCM数据。
视音频同步:根据解封装模块处理过程中获取到的参数信息,同步解码出来的视频和音频数据,并将视频音频数据送至系统的显卡和声卡播放。
Android总体播放框架如图:
NuPlayer框架
Android2.3时引入流媒体框架,而流媒体框架的核心是NuPlayer。Android4.0之前的版本中本地播放用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机制来异步地处理请求,所以有更少的Mutex/Lock在NuPlayer中。Awesomeplayer中利用了omxcodec而NuPlayer中利用了Acodec。
MediaPlayer播放框架
从系统层面看,媒体模块可以分为Java层、JNI层和Native层。Android在Java层中提供了一个MediaPlayer的类来作为播放媒体资源的接口,具体请查阅"MediaPlayer的API使用及源码分析"文章。
MediaPlayer.java中的方法通过JNI层中的android_media_mediaPlayer.cpp来调用Native层中的接口。在Native层,最上层是mediaplayer.cpp,它是对媒体播放器基本功能的封装,提供播放音视频的各种接口。Mediaplayer.cpp的setaDataSource()中会获取系统中的MediaPlayerService服务,调用该Service中的create()创建一个MediaPlayerService::Client类对象,再调用Client的setDatasource(),其中又会通过MediaPlayerFactory.cpp获取和创建NuPlayerDriver对象。NuPlayerDriver包含sp<NuPlayer>成员对象mPlayer和sp<ALooper>成员对象mLooper,NuPlayer继承自AHandler,NuPlayerDriverde构造函数中会创建NuPlayer和ALooper对象,注册mLooper并关联mPlayer,因此NuPlayer中可以利用ALooper/AHandler机制处理上层的setDataSource()、prepare()、start()等方法对其的调用。NuPlayer中主要包含三种类的成员变量,sp<Source>mSource、sp<DecoderBase> mVideoDecoder、sp<DecoderBase>mAudioDecoder、sp<Renderer> mRenderer。
Source负责音视频文件的连接、获取、装载、解析和提取(音视频分离),它有三个子类GenericSource负责本地音视频文件,HTTPLiveSource负责HLS协议的流媒体,RTSPSource负责RTSP协议的流媒体。DecorderBase有一个子类NuPlayer::Decorder,它负责音视频文件的解码,NuPlayer::Renderer负责将解码后的原始音视频信号同步和送往对应驱动。
MediaPlayer模块的框架和文件结构如下图所示。
NuPlayer播放框架
- Source:数据源,数据的来源不一定都是本地文件,也可能是网上的各种协议。
- Parser/Demux:解析/解复用,有很多不同的容器格式。如ts、mp4、mkv、avi等等。demux的功能就是把音视频的ES流从容器中剥离出来,然后分别送到不同的解码器中。
- Decoder:解码器,播放器的核心模块。分为音频和视频解码器。
- Renderer:渲染器,从功能上看,其主要有几个功能:音视频原始数据缓存操作,音频播放到声卡,视频显示到显卡,音视频同步,其他辅助播放控制的操作。
NuPlayer是这个播放框架中联系Source,Decoder,Renderer的纽带。
播放音视频NuPlayer中主要函数的调用过程如下:
- NuPlayer() 初始化成员变量。
- setDataSourceAsync() 通过URL前缀判断媒体类型,然后创建相应的Source子类对象,调用其setDataSource()函数,最后发送消息,在onMessageReceived中进行处理。
- prepareAsync() 创建、开始和注册mLooper,将其关联GenericSource(继承自AHandler)实例的这个AHandler,然后发送消息到onMessageReceived()。
- start() start()中发送消息到onMessageReceived(),onMessageReceived根据当前状态调用onResunme()或onStart()进行处理。第一次start()时mStarted为false,因此会调用onStart()。
NuPlayer简化调用流程
简化播放流程如上图所示,最开始NuPlayerDriver调用setDataSource函数让NuPlayer去调用setDataSourceAsync,NuPlayer确定并创建对应source类型,类型包括StreamingSource,Generic Source和HTTPLive、RTSP Source。然后进入prepare状态,driver调用prepareAsync让Nuplayer调用prepareAsync,source此时会创建对应的解析器extractor,功能是将封装好的数据源分开成video track和audio track。之后开始创建解码器流程,nuplayer会调用instantiateDecoder创建NuPlayerDecoder,Decoder调用onConfigure函数中的CreateByType函数创建MediaCodec,mediaCodec在初始化过程中调用GetCodecBase创建ACodec,ACodec是实际调用OpenMax的模块,开始ACodec处于Uninitialized状态,初始化时调用onAllocateComponent创建OMX,实现对底层的调用。在完成解码后通过fillBuffer将数据放入缓冲区,ACodec用消息通知MediaCodec回调,source用dequeueAccessUnit取走数据,然后decoder调用queueInputBuffer将解码前数据放入缓冲区,通知后面模块解码,形成循环。
NuPlayer状态转换
NuPlayerDriver由MediaPlayer创建,重点是包含NuPlayer和ALooper,driver本身整体可以看做一个状态机,为上层实现调用NuPlayer的接口,状态调转和主要函数如下图:
NuPlayer是处理消息实现功能的模块,通过onMessageReceived函数接收来自其他模块和自身的AMessage,进入不同case,调用对应内容:
- kWhatSetDataSource:创建msource对象,完成后通知driver
- kWhatPrepare:调用msource的prepareAsync
- kWhatSetVideoSurface:创建surface并设定
- kWhatSetAudioSink:创建mAudioSink
- kWhatStart:调用msource的start,设定播放初始状态,创建渲染器renderer,结束后发出scanSource消息
- kWhatScanSources:调用instantiateDecoder函数创建Decoder实例,先创建Video的再创建Audio的
- kWhatAudio/VideoNotify:取得一些跟renderer及buffer相关的消息,按照消息分开处理
- kWhatSeek:改变播放的进度,调用onstart(time_offset)发出seekComplete消息
- kWhatSourceNotify:直接调用onSourceNotify函数处理source相关消息
NuPlayer相关部分
GenericSource: 包含音频流mAudioTrack,视频流mVideoTrack,字幕流mSubtitleTrack,NuPlayer构造之后通过setDataSource根据参数不同设定数据源,通过prepareAsync/stop/start/pause/resume/seekTo/disconnect控制播放,同样通过message调用实际函数功能。
onPrepareAsync: 函数中会对文件格式进行试探,主要是在initFromDataSource函数里面创建extractor后利用extractor的sniff方法确定,然后从注册好的extractor获得track数量、metaData,根据track的mimeType确定是音频还是视频,然后把音视频流加入到msources。
NuPlayerDecoder和NuPlayerDecoderBase是一个组合,Base用于处理decoder不处理的msg,在nuplayer调用onStart创建decoder的同时创建base,创建renderer让它和decoder对应上。
Decoder在instantiateDecoder时被创建和初始化,先创建的是video的decoder,然后创建audio的decoder。
decoder在onConfigure函数中调用createByType创建MediaCodec,此时已经知道文件格式,所以根据格式创建mediaCodec,decoder也是通过AMessage消息触发函数的调用。kWhatCodecNotify-MediaCodec::CB_OUTPUT_AVAILABLE:
简单处理buffer情况之后调用renderer处理buffer,最后调用mediacodec的renderOutputBufferAndRelease kWhatCodecNotify-MediaCodec::CB_INPUT_AVAILABLE:
从mediacodec获取buffer信息,调用onInputBufferFetched函数取数据,最后调用mediacodec的queueInputBuffer完成工作。然后doRequestBuffers调用fetchInputData--dequeueAccessUnit函数,与上层的genericSource通信。
Renderer在NuPlayer的onStart中被创建,当decoder收到来自mediaCodec的onOutputBufferAvailable知道有可用的解码后数据在buffer中,调用handleAnOutputBuffer里面queueBuffer给renderer,调用onQueueBuffer处理数据,onDrainAudioQueue将queue中数据取出写入mAudioSink,在处理video数据时,onDrainVideoQueue会通过比较mediaTime和realTime计算是否还需要渲染或者放弃,时间固定为40ms,在这过程中也计算出了视频实际的播放时间,实现与音频的对齐,在提前两倍vsync的时候开始处理videoBuffer.
MediaCodec
MediaCodec在初始化init过程中调用createByType根据mime创建格式的ACodec,然后判断是不是video的codec是的话需要创建一个新的ALooper,不是的话就跟上层共用looper,之后调用start启动codec。
mediaCodec通过byteBuffer操作输入视频数据、输入音频数据和输出数据。
mediaCodec也是通过AMessage和onMessageReceived函数结合来完成数据的转移和状态的转换。
获得一个可填充的buffer之后调用queueInputBuffer传送给编解码器,dequeueOutputBuffer获取解码输出的数据。
MediaExtractor
Extractor: 通过createFromService确定多媒体文件格式,首先registerDefaltSniffers注册默认的sniffer,包括MPEG4/wav/MP3/AAC/...其中调用getExtendedSniffer设定额外的默认sniffer,之后sniff遍历所有可能,每个格式生成自信值和meta,自信值最高则认定文件为该格式,返回之后正式注册该格式的extractor。
ACodec
ACodec开始创建时状态为Uninitialized,初始化会调用onAllocateComponent创建OMXClient,绑定OMX实例和OMXNode,然后状态转移到Loaded,loadded状态主要处理播放相关配置,onStart转移到loadedToIdle状态,处理OMXEvent的消息,结束后转移到IdleToExcuting状态,主要等待开始播放转移到executing状态,进入播放。
ACodec对应两个消息处理体系,onMessageReceived处理来自mediaCodec的消息,基状态的onOMXMessage处理来自OMX的消息,主要是处理OMX来的数据和需要数据的请求,OMX来数据了调用onOMXFillBufferDone,mediacodec来数据了要给OMX调用OMXNode的emptyBuffer。
AVSync
NuplayerRenderer.cpp 这个文件主要负责音视频同步、音视频数据输出和视频数据输出。里面有两个消息循环分别对于音频数据的输出以及视频数据的输出:
postDrainAudioQueue_l -> onDrainAudioQueue
postDrainVideoQueue -> onDrainVideoQueue
时间戳
mediaTime 是指这帧数据的显示时间戳,比如一个30fps的视频,第一帧的mediaTime是0s,然后是33ms、66ms......。播放的时候首先会获取到这一帧的MediaTime。SurfaceFlinger那边显示每帧数据也是利用时间戳,但是它使用的是realTime。所以如果我们要让画面正常显示,就必须把mediaTime转化成realTime。如果mFlags标志有FLAG_REAL_TIME,说明timeUs存放的就是realTime,无需经过转换,现在基本这个标志位都不会被置起来,因此走的是else。通过getRealTimeUs转换成realTime。realTime 是系统时间。
基准时间的更新
由于人对音频要比视频敏感,因此我们都是用音频的时间戳要更新基准时间。
网络上的安卓媒体框架资料
资料来自CSDN,中文资料在CSDN上比较多。
总览:
- Android 多媒体(VNanyesheshou)
- Android MultiMedia框架完全解析(yanbixing123)
- MultiMediaFramework(码农突围)
- Android 9.0 multimedia(小黄鸡#)
Android MultiMedia框架完全解析:
- Android MultiMedia框架完全解析 - 从开机到MediaServer的注册过程
- Android MultiMedia框架完全解析 - MediaPlayer的C/S架构与Binder机制实现
- Android MultiMedia框架完全解析 - setDataSource继续分析
- Android MultiMedia框架完全解析 - MediaPlayerFactory中OMX_Player的实现
- Android MultiMedia框架完全解析 - 再谈Playback框架及一些学习方法的讨论
- Android MultiMedia框架完全解析 - NuPlayerDriver与NuPlayer的通信
- Android MultiMedia框架完全解析 - ALooper-AHandler-AMessage机制分析
- Android MultiMedia框架完全解析 - prepareAsync的过程分析
- Android MultiMedia框架完全解析 - MediaExtractor和MediaMuxer介绍
- Android MultiMedia框架完全解析 - MediaExtractor::Create函数的解析和FslExtractor分析
- Android MultiMedia框架完全解析 - start流程分析
- Android MultiMedia框架完全解析 - 从NuPlayer到MediaCodec到ACodec到OMX的整体流程与状态转换
- Android MultiMedia框架完全解析 - MediaCodec解析
- Android MultiMedia框架完全解析 - NuPlayerDecoder与MediaCodec的交互
- Android MultiMedia框架完全解析 - Render流程分析
- Android MultiMedia框架完全解析 - ACodec详细解析
- Android MultiMedia框架完全解析 - MediaClock分析与音视频同步
Android 9.0 multimedia框架解析: