版本及环境说明
Android源码版本:android-12.0.0_r3Android源码来源:https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifestAndroid源码编译配置:aosp_crosshatch-userdebug- 设备型号:
Google Pixel 3 XL (crosshatch) - 设备驱动:
SP1A.210812.016.A1 - 主机环境:
Ubuntu 22.04 LTS
Codeless表示尽可能少的代码.
声明
- 本位所有的内容均为个人理解, 由于能力一般, 水平有限, 文章中难免有疏漏及错误, 望体谅
- 为了求证大部分流程的正确性, 本文的撰写进行了一些繁琐的调试工作, 可以在部分举证中看到这部分内容, 但受限于篇幅, 本文未列出所有的调试细节
- 由于本文的所有的内容均来自
Android Open Source Project的开源代码, 因此本文的内容也可随意分发 - 本人不对文章的内容承担任何保证或责任
概念
媒体文件解码流程:
媒体文件 --> |--> 视频流 -> 解码 --| | --> 视频渲染
| |--> 同步 --|
└--> 音频流 -> 解码 --| | --> 音频渲染
媒体文件在 Android 系统回放时设计的进程:
media.extractor -> mediaserver --| --> |media.swcodec
| <-- |
| --> surfaceflinger
| --> audioserver
至此已经掌握音视频所有的媒体文件播放流程了, 为方便理解本文的内容,提供两张图片供参考。
MediaPlayer开始播放时的基本流程:
MediaPlayer开始播放时的数据流向:
加粗的线条为数据在各类对象、进程见的流向。注: 由于水平有限,时序图和类图可能存在小的错误或缺失,请谅解。
部分类的简要说明
- 运行于应用进程中:
MediaPlayer: 播放器, 泛指Java层和Native(C++)层的播放器
- 运行于
media.extractor中MediaExtractorService: 实现IMediaExtractorService接口, 对外提供创建解封装的接口DataSource: 描述一个数据源头, 一个文件, 一个流等, 通常以IDataSource向外提供数据源的操作接口,有以有以下几种实现:DataURISourceFileSource: 数据来自文件HTTPBase: 数据来自HTTP链接NuCachedSource2
MediaExtractor: 表示一个解封装器, 其通过IMediaExtractor的实现RemoteMediaExtractor对外提供服务, 其子类实现为MediaExtractorCUnwrapper, 这是一个CMediaExtractor的Wrapper, 该类其实是MPEG4Extractor对外的抽象MediaTrack: 表示一个未解码的流, 通常以IMediaSource的实现RemoteMediaSource对外提供接口, 其子类实现为MediaTrackCUnwrapper, 这是一个CMediaTrack的Wrapper, 该类其实是MPEG4Source对外的抽象
- 运行于
mediaserver进程中MediaPlayerService: 播放器服务, 运行在mediaserver中, 以IMediaPlayerService接口提供服务MediaPlayerService::Client: 每个请求播放器服务的进程在MediaPlayerService中的描述MediaPlayerFactory: 用于创建播放器实例的工厂类, 它负责通过实现IFactory接口的工厂类创建播放器NuPlayerFactory:NuPlayerDriver的工厂类NuPlayerDriver: 播放器实例的驱动类, 负责部分同步操作到NuPlayer异步的转换NuPlayer:MediaPlayerService中实际的播放器实例, 负责完成播放相关的大部分异步操作NuPlayer::Source: 表示一个来源, 来源可能有很多种:NuPlayer::GenericSource: 来源是本地文件, 其引用:TinyCacheSource: 通过TinyCacheSource(DataSource)::mSource->CallbackDataSource(DataSource)访问IDataSource接口IMediaSource: 被成员mVideoSource和mAudioSource所引用
NuPlayer::StreamingSource: 来源是一个流NuPlayer::HTTPLiveSource: 来源是一个HTTP直播流NuPlayer::RTSPSource: 来源是一个RTSP流NuPlayer::RTPSource: 来源是一个RTP流
NuPlayer::Decoder:NuPlayer的解码器对象, 有NuPlayer::Decoder和NuPlayer::DecoderPassThrough两种实现, 本例只关注前者MediaCodec: 实际的解码器接口, 其负责将部分上层的同步操作转换为异步操作, 其引用一个CodecBaseCodecBase: 表示一个解码器实例, 其引用一个CodecBase, 通常有ACodec,CCodec,MediaFilter几种实现:CCodec: 采用 Codec 2 框架的解码器实现ACodec: 采用 OMX 框架的解码器实现
CodecCallback: 用于响应解码器的消息, 由``CodecBas`的实现BufferCallback: 用于响应缓冲区队列的消息, 由BufferChannelBase的实现回调该接口BufferChannelBase: 负责处理所有解码相关的输入输出缓冲区队列管理, 其有两个实现CCodecBufferChannel: Codec 2 框架实现缓冲区管理的实现ACodecBufferChannel: OMX 框架实现缓冲区管理的实现
MediaCodecBuffer用于描述一个解码器使用的缓冲区, 其有几种扩展实现Codec2BufferCodec 2 框架下的缓冲区描述, 该类有多种实现:LocalLinearBuffer: 本地线性缓存, 可写入DummyContainerBuffer: 空缓存, 在解码器没有配置Surface且应用试图获取数据时返回空缓存LinearBlockBuffer: 可写入的线性块缓存, 一般提供一个写入视图C2WriteView, 同样引用一个线性数据块C2LinearBlock(父类为C2Block1D)ConstLinearBlockBuffer: 只读的线性块缓存, 一般提供一个读取试图C2ReadView, 同样引用一个C2Buffer(子类为LinearBuffer)作为缓冲区的描述GraphicBlockBuffer: 图形数据块缓存描述, 一般提供一个视图C2GraphicView(用于写入), 同样引用一个C2GraphicBlock(父类为C2Block2D)GraphicMetadataBuffer: 图形元数据数据块,ConstGraphicBlockBuffer: 只读图形数据块, 其提供一个视图C2GraphicView(用于读取), 同样引用一个C2Buffer或ABufferEncryptedLinearBlockBuffer: 加密线性数据块
SharedMemoryBuffer: 共享内存方式SecureBuffer: 安全缓冲区
Codec2Client: 负责通过HIDL完成到 Codec 2 解码组件库所在进程meida.codec或media.swcodec的各种请求Codec2Client::Component: 负责通过HIDL完成到 Codec 2 解码组件库所在进程meida.codec或media.swcodec的各种请求Codec2Client::Interface: 为组建接口, 其引用一个远程的IComponentInterface接口, 它集成自Codec2Client::Configurable, 意为可配置Codec2Client::Listener: 负责监听来自组件HIDL接口的消息, 其有一个实现CCodec::ClientListener, 负责通知CCodecComponent::HidlListener: 负责监听来自组件的消息, 其实现了IComponentListener接口, 有消息产生后通过Codec2Client::Listener通知CCodecRenderer: 渲染器NuPlayerAudioSink: 音频输出, 其实现:AudioOutputAudioTrack: 音频输出流, 通过IAudioTrack访问audioserver中的TrackHandle
- 运行于
media.swcodec中(本文以软解为例)ComponentStore: 组件库, 运行与media.codec和media.swcodec两个进程, 通过IComponentStore接口提供 Codec 2 框架的解码器实现android::hardware::media::c1::V1_2::Component为 CCodec 2 框架解码器组件对于HIDL的实现, 其通过IComponent向其它进程的Codec2Client::Component提供服务, 后端实现为C2ComponentC2Component为 CCodec 2 框架解码器组的实现, 其有多种具体的实现:V4L2DecodeComponent:V4L2解码组件V4L2EncodeComponent:V4L2编码组件SimpleC2Component: Google 实现的软解组件, 简单列出几种实现:- 视频
C2SoftAvcDecC2SoftHevcDecC2SoftVpxDec
- 音频
C2SoftFlacDecC2SoftAacDec
- 视频
SampleToneMappingFilter: ??WrappedDecoder: ??
android::hardware::media::c1::V1_2::utils::ComponentInterface: 组件接口类, 负责描述一个 Codec 2 组件的各种信息, 其通过IComponentInterface向对端(Codec2Client)提供查询服务, 该接口类也被Component所引用, 后端实现为C2ComponentInterfaceC2Component::Listener: 为组件中, 客户端的回调实现, 其持有一个IComponentListener接口, 用于通知客户端组件的消息, 其有一个实现:Component::Listener, 持有父类的IComponentListener接口
- Codec 2 框架中的工作类
C2Work: 表示一个解码工作(播放器中)C2FrameData: 表示一个帧的数据, 其中有一组C2BufferC2Buffer: 一个缓冲区的描述, 其包含其数据的描述C2BufferDataC2BufferData: 描述一个缓冲区的数据, 以及它包含的块C2Block[1|2]DC2Block1D: 描述一个一维的缓冲区, 有如下实现:C2LinearBlock: 可写一个线性缓冲区C2ConstLinearBlock: 只读线性缓冲区C2CircularBlock: 环形缓冲区(环形缓冲区是"首尾相接"的线性缓冲区)
C2Block2D: 描述一个二维的缓冲区有如下实现:C2GraphicBlock: 描述一个二维图形缓冲区C2ConstGraphicBlock: 描述一个只读的图形缓冲区(本例不涉及)
应用层的播放器MediaPlayer
初始化
MediaPlayer(Java)对象有自己的本地方法, 其位于frameworks/base/media/jni/android_media_MediaPlayer.cpp中, 这些方法均以android_media_MediaPlayer_开头, 因此"native_init"对应android_media_MediaPlayer_native_init().MediaPlayer在构造时会做两件事情:
- 在加载
libmedia_jni.so并执行native_init(), 这个步骤只获取MediaPlayer类相关的一些信息, 并不会初始化 C++ 对象 - 其
native方法native_setup()接下来被调用, 这个步骤负责实例化一个MediaPlayer(C++)类, 并生成一个集成自MediaPlayerListener的JNIMediaPlayerListener用于监听来自播放器的消息. 新创建的MediaPlayer(C++)对象将被保存在MediaPlayer(Java)的mNativeContext中用于后续的下行调用.MediaPlayer的初始化比较简单, 只有设置数据源之后才能开始 解封装 / 解码 / 渲染 等的工作.
设置数据源
数据源是媒体的数据来源, 可能来自一个文件, 可能来自一个网络流. 媒体源是数据源中的一个未解码的流, 例如视频流 / 音频流 / 字幕流等.
在 Android Multimedia中主要以IMediaSource接口体现(和MediaSource不同, MediaSource用于描述一个未编码的媒体流). 该类别通常针对一个具体的类型, 比如一个符合VP9编码规范的数据源, 从该数据源读取的数据应是编码过的数据.
通常一个媒体文件中会包含很多个部分:
- 视频: 通常是指定的编码格式, 如:
VP9,H264,H265等 - 音频: 可能存在多条音轨, 每条音轨的编码不同, 可能的有
PCM,G711,FLAC,APE,AAC等 - 字幕: 多语言字母等
以上信息都会经过具体的封装格式进行封装, 例如常见的MP4, 本文的视频封装以及音视频编码参考信息:
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf58.29.100
Duration: 00:00:01.75, start: 0.000000, bitrate: 291 kb/s
Stream #0:0(eng): Video: vp9 (Profile 0) (vp09 / 0x39307076), yuv420p(tv, progressive), 1080x1920, 216 kb/s, 30.13 fps, 30.13 tbr, 90k tbn, 90k tbc (default)
Metadata:
handler_name : VideoHandle
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, mono, fltp, 73 kb/s (default)
Metadata:
handler_name : SoundHandle
接下来播放器要通过MeidaExtractor(最终的工作位于media.extractor中)找到响应的数据源. 那么首先从封装信息中确定音视频Track数量, 其对应的编码格式, 然后再根据每条具体的Track构造IMediaSource(媒体源).MediaPlayer.setDataSource的Java部分过程比较复杂, 涉及ContentResolver, 本文不讨论, 对于本地文件, 其最终配置到底层的android_media_MediaPlayer_setDataSourceFD() -> MediaPlayer::setDataSource(int fd,...), 此时一个问题出现了:MediaPlayer是本地播放的么? 并不是, 它请求远程服务完成播放, 执行播放任务的是MediaPlayerService, 该服务运行在mediaserver中, 那么mediaserver中构造的播放器是什么? 接下来一起看看MediaPlayer::setDataSource(int fd,...)时发生了什么? 首先我们需要了解一个重要的服务: MediaPlayerService.
播放服务MediaPlayerService
mediaserver创建IMediaPlayerService接口的MediaPlayerService实现, 该类将处理来自MediaPlayer的请求. 在MediaPlayerService创建时, 会注册多个MediaPlayerFactory::IFactory实现, 目前主要有两种:
NuPlayerFactory: 主要的工厂类, 用于创建播放器实例:NuPlayerDriver, 其更底层的实现是NuPlayerTestPlayerFactory: 测试目的, 不关注
注:: 以前的版本还有一种AwesomePlayer(被stagefrightplayer封装), 已经过时了.MediaPlayer设置数据源之前要先完成实际的播放器实例的创建, 它通过IMediaPlayerService接口向MediaPlayerService服务申请创建播放器, 创建播放器后, 本地的MediaPlayer将通过IMediaPlayer接口引用服务器为其创建的播放器实例. 显然该Client实现了BnMediaPlayer并在创建后返回给应用, 它将作为一个引用传递给MediaPlayer并作为后续所有的请求的代理,setDataSource()也在其中.MediaPlayerService要具备通知MediaPlayer的能力才行, 后者实现了BnMediaPlayerClient, 将通过IMediaPlayerClient在创建Client时被设置在Client的mClient中
在创建Client是也创建了MediaPlayerService::Listener, 该类是继承自MediaPlayerBase::Listener, 显然该Listener将负责从底层的MediaPlayerBase监听播放时的各种消息, 从这里, 也知道了在MediaPlayerService中, 负责播放任务的实现是集成自MediaPlayerBase的, 本例中的继承:MediaPlayerBase->MediaPlayerInterface->NuPlayerDriver,Listener本身持有了Client的引用, 因此Listener::notify()将通知到Client::notify(), 而这时调用上文的MediaPlayerService::Listener的notify()将完成通过IMediaPlayerClient完成对对端MediaPlayer的通知(见附录:MediaPlayer将收到的通知类型).
播放服务中的播放器NuPlayer
Client负责响应来自MediaPlayer的请请求, 现在Client已经创建, MediaPlayer该通过IMediaPlayer接口通过它发起setDataSource()操作了, 这里分两个步骤:
- 设置数据源需要创建实际的播放器:
NuPlayerDriver - 对
NuPlayerDriver执行setDataSource()
创建播放器的实例NuPlayerDriver, 这将在MediaPlayerService::Client响应createPlayer()消息时通过MediaPlayerFactory::createPlayer()静态方法从NuPlayerFactory构建.
此时创建的是NuPlayerDriver, 但该类会马上创建NuPlayer类.NuPlayer后续则会通过MediaPlayerService::Client->NuPlayerDriver响应来自应用中MediaPlayer的很多事件.
因此NuPlayer最终完成所有播放请求, 请求的类型很多,我们只讨论传统本地视频文件的播放,关注以下几种类型: kWhatSetDataSource: 设置数据源头
对应MediaPlayer.setDataSource(), 支持各种各样的数据源, 例如: URI/文件/数据源等等kWhatPrepare: 准备播放器
对应MediaPlayer.prepare()kWhatSetVideoSurface: 设置视频输出
对应MediaPlayer.setDisplay()或者MediaPlayer.setSurface(), 它们的参数不同kWhatStart: 开始播放
对应MediaPlayer.start()kWhatSeek: seek操作- 对应
MediaPlayer.seekTo(), 该方法可以设置seek(跳转到)的方式,seek时需要的参数:"seekTimeUs":seek的目标事件, 单位us"mode":seek的模式(向前/向后/最近等)"needNotify": 是否需要通知上层, 如果需要,NuPlayer将通过父类MediaPlayerInterface的sendEvent()方法通知上层.
kWhatPause: 暂停操作kWhatResume: 恢复播放NuPlayer不但需要负责完成各种下行的控制, 还要通过AMessage响应来自底层的一系列消息(见附录).NuPlayer在创建完成后会保存在NuPlayerDriver的mPlayer中, 而NuPlayerDriver作为MediaPlayerInterface(父类MediaPlayerBase)被Client的mPlayer引用, 因此总结
下行整体的调用流程:MediaPlayer(Java) ->MediaPlayer(C++) --[binder]–> [IMediaPlayer=>MediaPlayerService::Client] ->NuPlayerDriver->NuPlayer
上行消息流程:NuPlayer-> [MediaPlayerBase::Listener=>MediaPlayerService::Client::Listener] ->MediaPlayerService::Client--[binder]–> [IMediaPlayerClient=>MediaPlayer] -> [MediaPlayerListener=>JNIMediaPlayerListener] ->MediaPlayer(Java)
后续所有的流程将按照总结的过程默认, 有特殊情况再进行标记.NuPlayer::setDataSourceAsync(int fd, ...)在(NuPlayer::setDataSourceAsync(int fd, ...)被转换为异步处理)如何处理接下来的工作呢?, 数据类型如果是文件文件描述符, 则创建GenericSource(实现自:NuPlayer::Source), 除了该类型, 对于NuPlayer::Source还有几种主要类型:StreamingSourceHTTPLiveSourceRTSPSourceRTPSourceGenericSource
对于本地文件的简单情形,GenericSource创建后直接配置数据源就可以了, 数据源被创建后是否开始解析数据文件呢? 没有, 这部分工作将在MediaPlayer.prepare()时开始.
播放器准备工作
MediaPlayer.prepare(...)最终都是通过MediaPlayer::prepare()完成工作的, 而最后也都是通过MediaPlayer::prepareAsync_l() --[Binder]–> Client::prepareAsync() -> NuPlayerDriver::prepareAsync() -> NuPlayer::prepareAsync(), 既然是异步, 所以NuPlayer给自己的异步线程发送了kWhatPrepare消息, 上文说到, GenericSource不会开始解析文件, 知道prepare()开始, 此处NuPlayer也确实在prepare()时只调用了GenericSource::prepareAsync(), 同样GenericSource通过kWhatPrepareAsync异步处理这个消息.
MediaPlayerService中数据源IDataSource的创建
Android 中, 原则上都是通过MediaExtractorService处理, MediaExtractorService运行在media.extractor进程中, 其通过IMediaExtractorService为其它进程提供服务.
需求方GenericSource通过IMediaExtractorService::makeIDataSource()请求创建数据源, 提供了文件描述符, MediaExtractorService通过工厂类DataSourceFactory完成从文件描述符到DataSource的创建, 但DataSource本身不是继承自IDataSource接口, 无法为需求方提供服务, 因此DataSource最终还是要通过RemoteDataSource, 而RemoteDataSource继承自BnDataSource响应后续对端的请求. 对于本地文件DataSourceFactory创建的DataSource是FileSource.IDataSource接口通过Binder从MediaExtractorService返回给应用后通过TinyCacheSource(DataSource)::mSource -> CallbackDataSource(DataSource)::mIDataSource引用.
MediaExtractorService中数据源探测前的准备
数据源有了, 那么需要从数据源中接封装出媒体流, 它可能是音频/视频/字幕等, 这个过程需要 对数据源中的数据进行解封装, 找到各种媒体数据所对应的流, MediaExtractorFactory::Create()仍然是本地工作的, 它负责通过IMediaExtractorService::makeExtractor()向MediaExtractorService请求创建IMediaExtractor过程, 对应的服务端实现是:MediaExtractorService::makeExtractor().
但是这这个过程中, 上文的TinyCacheDataSource作为DataSource通过CreateIDataSourceFromDataSource()转换成了IDataSource接口的RemoteDataSource又发回给MediaExtracotrService了?
不是的, 在RemoteDataSource::wrap()不一定创建实现新的IDataSource的RemoeDataSource, 如果传入的DataSource本身及持有IDataSource, 那就直接返回了, 没有重新创建的必要, 所以返回的仍然是TinyCacheSource(DataSource)::mSource -> CallbackDataSource(DataSource)::mIDataSource所保存的来自MediaExtractor的IMediaSource.
请求发给服务端MediaExtractorService, 又会被如何处理呢? 这里仍然是通过CreateDataSourceFromIDataSource()创建了本地的DataSource, 这和上文应用中的操作完全一样? 是的, 完全一样, 最后本地曾经创建过的RemoteDataSource(IDataSource接口)也是被MediaExtractorService本地的TinyCacheSource(DataSource)::mSource -> CallbackDataSource(DataSource)::mIDataSource所引用.MediaExtractorService将通过MediaExtractorFactory的CreateFromService()方法完成MediaExtractor的创建, 从名字可以看到创建自服务端, 和上文MediaExtractorFactory::Create()不一样了.
创建具体的MediaExtractor之前, 需要从DataSource中读取一些数据, 然后对读取到的数据机型探测. 在继续之前先了解Extractor的插件加载.
数据源探测插件的加载
media.extractor在启动时, 创建MediaExtractorService服务, MediaExtractorService实例化是通过MediaExtractorFactory::LoadExtractors()装载插件.MediaExtractorFactory首先通过RegisterExtractors(), 它完成单个路径下的所有插件的加载, 例如"/apex/com.android.media/lib64/extractors/", 通常情况下形如lib[aac|amr|flac||midi|mkv|mp3|mp4|mpeg2|ogg|wav]extractor.so, 对于本例, 关注libmp4extractor.so, 首先从动态库中寻找"GETEXTRACTORDEF"符号, 因此getDef()就是GETEXTRACTORDEF函数, 函数被调用后, 返回一个ExtractorDef被用于构造ExtractorPlugin, ExtractorPlugin::def将在上文提到的sniff()函数中被获取. 而RegisterExtractor()为插件的注册. 最终插件的MPEG4Extractor.cpp:Sniff()函数将保存在:MediaService::gPlugins[...].m_ptr.def.u.v3.sniff中等待后续被调用.
数据源的探测
MediaExtractorFactory::CreateFromService()通过sniff()函数主要完成DataSource中流媒体文件格式的探测工作, 这将调用上文的MPEG4Extractor.cpp:Sniff(), 如果MPEG4Extractor.cpp:Sniff()判定为是自己能解析的格式, 则返回MPEG4Extractor.cpp:CreateExtractor()用于后续接封装器的创建. MediaExtractorFactory::CreateFromService()中, ((CreatorFunc)creator)(source->wrap(), meta)将调用该函数. 该函数创建解封装器之前, TinyCacheSource通过其父类DataSource被wrap()成了CDataSource, 其被DataSourceHelper引用, 供MPEG4Extractor创建时使用. MPEG4Extractor在被构造后, 也通过父类MediaExtractorPluginHelper的wrap()包装为CMediaExtractor给MediaExtractorFactory进一步封装为MediaExtractorCUnwrapper(父类MediaExtractor), 而MediaExtractorCUnwrapper最终通过RemoteMediaExtractor包装, 最后作为IMediaExtractor返回给mediaserver
总结:TinyDataSource被设置到了:MediaExtractorService::makeExtractor()中的extractor->mExtractor->plugin->data->mDataSource->mSource->handle
其中:
extractor:IMediaExtractor->RemoteMediaExtractorextractor->mExtractor:MediaExtractor->MediaExtractorCUnwrapperextractor->mExtractor->plugin:CMediaExtractorextractor->mExtractor->plugin->data:void *->MediaExtractorPluginHelper->MPEG4Extractorextractor->mExtractor->plugin->data->mDataSource:DataSourceHelperextractor->mExtractor->plugin->data->mDataSource->mSource:CDataSourceextractor->mExtractor->plugin->data->mDataSource->mSource->handle:DataSource->TinyDataSource
媒体文件元数据信息MetaData的获取
解封装器IMediaExtractor返回给MediaPlayerService后, 可以开始获取元数据了, 包括有多少条Track等等, IMediaExtractor::getMetaData()负责完成到RemoteMediaExtractor的请求.MPEG4Extractor::readMetaData()比较复杂, 放弃分析, 该函数负责将获取到的元数据保存在MPEG4Extracotr的mFileMetaData(类型为AMediaFormat)成员中, 该信息将在MPEG4Extractor::getMetaData)函数中通过AMediaFormat_copy()方法拷贝到调用方MediaExtractorCUnwrapper::getMetaData()的临时变量format中.
在MediaExtractorCUnwrapper::getMetaData()函数中, 获取到的AMediaFormat需要通过convertMessageToMetaData()函数转化到MetaData类型, 此处过程较长, 本文不分析.MetaData通过binder返回给mediaserver时是通过``MetaDataBase::writeToParcel()完成序列化的, 不文也不分析该过程.
获取元数据后, 获取Track的数量, 通过接口IMediaExtractor::countTracks()完成请求, 这里略去.
媒体源IMediaSource
Track的获取通过如下过程: IMediaExtractor::getTrack(size_t index) --[Binder]–> RemoteMediaExtractor::getTrack() -> MediaExtractorCUnwrapper::getTrack() -> MPEG4Extractor::getTrack(), 此时MPEG4Source被创建, 其实现是MediaTrackHelper, 类似的, 它也通过MediaTrackHelper::wrap()被包装为CMediaTrack, 由MediaTrackCUnwrapper引用, 而MediaTrackCUnwrapper被RemoteMediaSource引用, RemoteMediaSource作为IMediaSource返回给MediaPlayerService, 该过程和上文返回IMediaExtractor的过程是一样的.
总结:MPEG4Source作为MediaTrackHelper被设置在: RemoteMediaSource.mTrack->wrapper->data, 其中:
RemoteMediaSource.mTrack:MediaTrackCUnwrapperRemoteMediaSource.mTrack->wrapper:CMediaTrackRemoteMediaSource.mTrack->wrapper->data:MediaTrackHelper->MPEG4Source
媒体源元数据的获取
媒体元也有源数据信息, 标记了该媒体源的编码类型等: 通过接口IMediaExtractor::getTrackMetaData()完成请求.
最后IMediaSource被保存到GenericSource的mVideoSource或者mAudioSource(类型为GenericSource::Tracks)的mSource成员中, 后续将用于音频/视频流数据的获取.
媒体源数据组的创建
当GenericSource的准备工作完成后, 相应的媒体源也已经获取到, 则开始这些媒体源的工作, 这是会创建一个BufferGroup, 用户缓冲数据等, 调用的顺序: GenericSource::startSources() --[Binder]-> IMediaSource::startSources() => RemoteMediaSource::start() -> MediaTrackCUnwrapper::start() -> MediaBufferGroup::MediaBufferGroup() -> CMediaBufferGroup::CMediaBufferGroup(), 在媒体源开始后, CMediaBufferGroup完成对MediaBufferGroupHelper的创建.
总结:
MPEG4Source.mBufferGroup:MediaBufferGroupHelperMPEG4Source.mBufferGroup->mGroup:CMediaBufferGroupMPEG4Source.mBufferGroup->mGroup->handle:MediaBufferGroup
媒体源数据的读取
媒体源开始工作后, GenericSource即刻开始从媒体源读取数据.该读取过程是异步的, GenericSource给其异步线程发送了kWhatReadBuffer消息, 异步线程读取数据的调用过程为: GenericSource::onReadBuffer() -> GenericSource::readBuffer() -> IMediaSource::readMultiple() --[Binder]–> BnMediaSource::onTransact() => RemoteMediaSource::read() -> MediaTrackCUnwrapper::read() -> MPEG4Source::read() -> MediaBufferGroupHelper::acquire_buffer() -> MediaBufferGroup::acquire_buffer() -> MediaBuffer::MediaBuffer() -> MemoryDealer::allocate().
上述过程, MediaBuffer根据其size的要求, 自行确定了是否使用共享内存的方式创建, 创建完成后, 数据指针被保存到其自身的mData成员中, 创建完成后MediaBuffer被封装到newHelper->mBuffer->handle中返回给上层
在CMediaBufferGroup::acquire_buffer()中, newHelper:newHelper: MediaBufferHelpernewHelper->mBuffer: CMediaBuffernewHelper->mBuffer->handle: MediaBufferBase -> MediaBuffer
对于上述过程的最后一个函数, 也就是MediaBufferGroup::acquire_buffer()中, 只有for (auto it = mInternal->mBuffers.begin(); it != mInternal->mBuffers.end(); ++it)没有找到合适的buffer, 才会申请新的buffer
至此, 可以知道mediaserver所获取到的数据结构即MediaBufferBaseBnMediaSource::onTransact()是循环通过RemoteMediaSource::read()读取到MediaBuffer的, 读取后判断解析出来的MediaBuffer, 分两种情况:
MediaBuffer能用binder传递, 直接到最后一个else的位置通过reply->writeByteArray()写入数据到binderMediaBuffer不能通过binder传递, 这里又分两种情况:- 返回的
MediaBuffer未使用共享内存, 此时抱怨一下, 然后从RemoteMediaSource的父类BnMediaSource所持有的MediaBufferGroup中分配一个共享内存的MediaBuffer, 然后获取解码器返回的数据, 拷贝到新分配的共享内存中 - 返回的
MediaBuffer使用的为共享内存, 则直接向后传递, 传递到后面, 如果是共享内存还分两种情况:- 共享内存形式的
MediaBuffer中的IMemory是否有缓存在BnMediaSource的mIndexCache(类型为IndexCache)中, 如果没有,mIndexCache.lookup()返回的index就是0, 所以插入到缓存当中, 等待后续获取.
所以, 最终返回给MediaPlayerService的数据可能是ABuffer也可能是IMemory所创建的ABuffer, 那我们看看MediaPlayerService读取数据完成后, 是如何通过IMediaSource的实现BpMediaSource处理的.BpMediaSource根据返回的类型判断, 如果是IMemory的缓冲, 则构造了RemoteMediaBufferWrapper(其继承关系:RemoteMediaBufferWrapper->MediaBuffer->MediaBufferBase), 如果是ABuffer的类型, 那就直接构造一个ABuffer.
但是最终NuPlayer::GenericSource::readBuffer()将通过mediaBufferToABuffer()从MediaBufferBase(类型可能为RemoteMediaBufferWrapper或者MediaBuffer)的data()返回的指针, 然后构造(注意不是拷贝)一个新的ABuffer, 并将ABuffer插入GenericSource的track->mPackets(音频/视频).
那么这些从IMediaSource中读取到的数据合适被读取呢? 它们将在NuPlayer::Decoder::fetchInputData()是,NuPlayer::Decoder通过GenericSource::dequeueAccessUnit()被提取.
- 共享内存形式的
- 返回的
播放器显示的设置
系统相册在播放视频时会创建一个SurfaceView, 该类在构造是通过其Java层: updateSurface() -> createBlastSurfaceControls()构造了BLASTBufferQueue类, 此时会触发Native层构造BLASTBufferQueue, 该过程将创建一对消费这和生产者:
IGraphicBufferProducer=>BufferQueueProducer=>BBQBufferQueueProducerIGraphicBufferConsumer=>BufferQueueConsumer=>BufferQueueConsumer
然后在上层updateSurface()过程, 通过copySurfac()方法构造Surface(Java层), 构造的方式是:Surface.copyFrom(), 这将通过底层的BLASTBufferQueue::getSurface()获取一个Native的Surface, 而BLASTBufferQueue的生产者将被记录在这个Surfac中.MediaPlayer.setDisplay()->MediaPlayer._setVideoSurface()->android_media_MediaPlayer_setVideoSurface()->MediaPlayer::setVideoSurfaceTexture().
通过android_view_Surface_getSurface()将上层的Surface(Java)转换为底层的Surface(Native), 然后将该Surface(Native)指针记录在MediaPlayer.mNativeSurfaceTexture(Java)中, 最后通过mp->setVideoSurfaceTexture()也就是MediaPlayer::setVideoSurfaceTexture()设置从Surface(Native)调用getIGraphicBufferProducer()获得的IGraphicBufferProducer, 这个IGraphicBufferProducer正是上文BLASTBufferQueue中的, 该接口最终配置给底层的MediaPlayer(Native).mPlayer->setVideoSurfaceTexture()通过Binder调用到MediaPlayerService::Client::setVideoSurfaceTexture(), 通过上层传递的bufferProducer创建了新的Surface, 又通过disconnectNativeWindow_l()断开了bufferProducer与应用持有的Surface(Native)的联系, 然后将新创建的Surface保存到Client::mConnectedWindow, 这意味着,mediaserver直接负责获取并填充GraphicBuffer给原本属于应用持有的Surface. 进一步, 将Surface配置给NuPlayerDriver,NuPlayerDriver通过kWhatSetVideoSurface将Surface发个给异步线程.NuPlayer保存上层的Surface即mediaserver使用应用传递的IGraphicBufferProducer所创建的Surface到mSurface, 并调用NuPlayerDriver::notifySetSurfaceComplete()告知NuPlayerDriver::setVideoSurfaceTexture()可以返回.
播放的开始
开始过程和上文的几个操作类似, 受限于篇幅, 仅给出简化的流程MediaPlayer.start() -> MediaPlayer._start() -> android_media_MediaPlayer_start() -> MediaPlayer::start() --[Binder]–> NuPlayerDriver::start() -> NuPlayerDriver::start_l() -> NuPlayer::start().NuPlayer::start()通过kWhatStart通知异步线程启动, NuPlayer::onStart()负责相应kWhatStart消息, 其创建了NuPlayer::Rennderer, 但并没有设置给mVideoDecoder(类型为NuPlayer::Decoder), 因为此时还没有创建mVideoDecoder和mAudioDecoder.
这个Renderer后续通过其queueBuffer()接受MediaCodecBuffer, 它完成处理后, 通过kWhatRenderBuffer通知NuPlayer::Decoder进行MediaCodecBuffer的释放.
MediaCodec解码器的创建及初始化
在NuPlayer中, 解码器由NuPlayer::Decoder进行抽象. 在NuPlayer开始后, 如上文所述, 其首先完成了IMediaSource的开始, 然后通过置身的postScanSources()异步发出了kWhatScanSources消息, 该消息被异步线程收到后, 开始执行NuPlayer::instantiateDecoder()实例化解码器, 如果是音频解码器, 分两种情况:
DecoderPassThroughDecoder
如果是视频解码器则只创建:DecoderDecoder被创建后, 其init()和configure()方法被分别调用
初始化没有太多内容, 略去. 在DecoderBase::configure()是DecoderBase通过异步消息kWhatConfigure调用到子类Decoder的onConfigure(),Decoder需要创建实际的解码器, 因此通过MediaCodec::CreateByType()创建MediaCodec,MediaCodecList::findMatchingCodecs()负责查找支持当前解码格式解码器的名字, 其定义在MediaCodecList.cpp, 如果找到解码器则创建MediaCodec, 创建MediaCodec时, 其mGetCodecBase被初始化为一个std::function<>对象, 后文的MediaCodec::init()会调用此lambada.
MediaCodec创建完成后通过init()调用上文的mGetCodecBase也就是MediaCodec::GetCodecBase()创建更底层的CodecBase,CodecBase`的实现有多种:CCodecACodecMediaFilter
Codec 2解码框架解码器CCodec
CCodec的创建
Android Q以后的版本采用CCodec的方式加载解码插件, 此处仅仅是创建了Codecbase(这里是CCodec), 确定了解码器的名字, 但还没有初始化CCodec.
CCodec事件监听的注册
而MediaCodec在初始化完CCodec(CodecBase)后:
- 构造了
CodecCallback, 其实现了CodecBase::CodecCallback接口, 而CCodec::setCallback()是在父类CodecBase实现的 - 构造了
BufferCallback, 其实现了CodecBase::BufferCallback接口, 用于监听来自CCodecBufferChannel的消息. 而mBufferChannel的类型是CCodecBufferChannel, 其setCallback()是在父类BufferChannelBase实现的, 最后MediaCodec::BufferCallback作为CodecBase::BufferCallback设置在了CCodecBufferChannel的mCallback方法
CCodec的实例化
初始化过程仍在MediaCodec::init()中继续, 该函数后续发出了kWhatInit消息, 并传递了解码器的名字给异步线程,kWhatInit由CodecBase::initiateAllocateComponent()响应, 其对解码器进行实例化. 在CCodec创建时CCodecBufferChannel也被创建, 其继承自BufferChannelBase, 并设置在CCodec的mChannel中CCodec再次发出异步消息kWhatAllocate, 由CCodec::allocate()响应. CCodec通过Codec2Client::CreateFromService()创建了Codec2Client, Codec2Client持有IComponentStore接口, 并通过其访问media.swcodec的ComponnetStore.CCodec后续通过Codec2Client::CreateComponentByName()创建了Codec2Client::Component, 大体的过程是: Codec2Client::CreateComponentByName() -> Codec2Client::createComponent() --[Binder]–> [IComponentStore::createComponent_1_2() => ComponentStore::createComponent_1_2()]. 该过程涉及解码器插件的加载和解码器组件的查找, 先了解接加载过程.
CCodec视频解码插件加载
C2SoftVpxDec的加载过程:
libcodec2_soft_vp9dec.so对应的ComponentModule创建media.swcodec启动时, 通过RegisterCodecServices注册ComponentStore服务, 此时会创建C2PlatformComponentStore, 其集成关系:C2PlatformComponentStore->C2ComponentStoreC2PlatformComponentStore将创建mLibPath为libcodec2_soft_vp9dec.so的ComponentLoader类型- 最后通过
C2ComponentStore创建实现了IComponentStore的V1_2::utils::ComponentStore实例, 返回给了Codec2Client
CCodec视频解码组件Component的查找
Codec2Client在通过createComponent()方法创建组件时,ComponentStore首先找到匹配的ComponentLoader, 在Loader的初始化过程中欧给你, 将创建ComponentModule对象ComponentLoader对象从对应的libcodec2_soft_vp9dec.so中查找CreateCodec2Factory符号- 调用
CreateCodec2Factory符号将返回C2ComponentFactory类型, 其实现为C2SoftVpxFactory - 然后调用工厂类的
createInterface方法, 返回一个C2ComponentInterface接口, 其实现为SimpleC2Interface模板类 - 调用
C2ComponentFactory的createInterface方法, 也就是C2SoftVpxFactory::createInterface, 这将欻功能键一个C2ComponentInterface接口, 实现为SimpleC2Interface模板类, 对于Vpx9该类的实现为C2SoftVpxDec::IntfImpl, 其将被记录在C2Component::Traits中 - 组件的创建
- 查找组件的工作完成后,
ComponentModule组件的createComponent方法被调用, 该方法将调用上文CreateCodec2Factory的对应方法, 而CreateCodec2Factory::createComponent负责创建C2SoftVpxDec, 继承关系:C2SoftVpxDec->SimpleC2Component->C2Component, 而该C2Component最后由ComponentStore创建的Component对象持有, 而Component对象实现了IComponent, 其后续将被返回给Codec2Client.
此时IComponent被设置在Codec2Client::Component后续被设置给上文CCodec的CCodecBufferChannel中.
- 查找组件的工作完成后,
MediaCodec的配置
MediaCodec通过kWhatConfigure通知异步线程执行配置, 该消息由CCodec::initiateConfigureComponent()负责响应, 该方法继续发出kWhatConfigure消息给CCodec的异步线程, 并由CCodec::configure()响应.doConfig是个非常复杂的lambada, 作为std::fucntion传递给tryAndReportOnError(), 该部分代码做了大量配置工作, 完成配置后, mCallback->onComponentConfigured()回调到上文设置的MediaCodec::CodecCallback::onComponentConfigured()
MediaCodec的启动
Decoder::onConfigure()最后负责启动MediaCodec, MediaCodec通过kWhatStart通知异步线程执行配置, 该消息由CCodec::initiateStart()负责响应.
CCodec的启动
该方法继续发出kWhatStart消息给CCodec的异步线程, 并由CCodec::start()响应. 而CCodec::start()也调用了CCodecBufferChannel::start(), 上文说到CCodecBufferChannel保存了Codec2Client::Component, 此处Conponent::setOutputSurface()被调用. mOutputBufferQueue的类型是OutputBufferQueue, 因此不管那个分支, 都调用了OutputBufferQueue::configure(), 因此IGraphicBufferProducer被设置到了OutputBufferQueue的mIgbp, 在后文OutputBufferQueue::outputBuffer()时会用到. OutputBufferQueue是视频解码器的输出队列, 当解码器有GraphicBuffer通过C2Block2D描述返回给CCodecBufferChannel, 会注册到Codec2Client::Component的OutputBufferQueue中, 等待后续渲染时提取并送出.postPendingRepliesAndDeferredMessages("kWhatStartCompleted")完成后, MediaCodec::start()返回
CCodec解码
CCodec在启动CCodecBufferChannel后立刻调用其requestInitialInputBuffers()开始从数据源读取数据. 该方法从当前类的input->buffers中请求缓冲, 其类型为LinearInputBuffers, 继承关系: LinearInputBuffers -> InputBuffers -> CCodecBuffers, requestNewBuffer()正是由InputBuffers提供. 在请求时, 如果缓冲区没有申请过, 则通过LinearInputBuffers::createNewBuffer() -> LinearInputBuffers::Alloc()进行申请, 申请的类型为Codec2Buffer(父类MediaCodecBuffer), 其实现是LinearBlockBuffer, 在LinearBlockBuffer::Allocate()创建LinearBlockBuffer时, 首先从C2LinearBlock::map()获取一个写入视图C2WriteView, 该试图的data()将返回C2LinearBlock底层对应的ION缓冲区的指针, 该指针在创建LinearBlockBuffer时直接构造了ABuffer并保存到了LinearBlockBuffer父类Codec2Buffer的父类MediaCodecBuffer的mBuffer成员中用于后续写入未解码数据时引用.
编码数据缓存准备C2LinearBlock
对于编码数据, 其用线性数据块C2LinearBlock(实现自C2Block1D), 底层的实现是ION, 其引用关系:
C2LinearBlock=>C2Block1DmImpl:_C2Block1DImpl=>C2Block1D::ImplmAllocation:C2LinearAllocation=>C2AllocationIonmImpl:C2AllocationIon::ImplC2Block1D是从C2BlockPool分配的, 其引用关系:
C2BlockPool::mBase:C2PooledBlockPool::ImplmBufferPoolManager.mImpl:ClientManager::ImplmClients[x].mImpl:BufferPoolClient::ImplmLocalConnection:ConnectoinmAccessor.mImpl:Accessor::ImplmAllocator:_C2BufferPoolAllocator=>BufferPoolAllocatormAllocator:C2Allocator=>C2AllocatorIonmImpl:C2AllocationIon::Implion_alloc()C2AllocatorIon::newLinearAllocation()创建了上文的C2AllocationIon极其实现C2AllocationIon::Impl, 创建完成后进行的分配.
编码数据缓存的填充
Codec2Buffer申请完成后保存到mImpl(也就是BuffersArrayImpl), 最后作为MediaCodecBuffer(父类)返回. 请求成功之后立马通知上层, 输入缓冲可用, 该时间是通过BufferCallback::onInputBufferAvailable(), 上文提到BufferCallback是MediaCodec用来监听BufferChannelBase(也就是CCodecBufferChannel)消息的, 所以, BufferCallback会通过kWhatCodecNotify的AMessaage通知通知MediaCodec, 具体通知的消息为kWhatFillThisBuffer.kWhatFillThisBuffer消息由MediaCodec::onInputBufferAvailable()响应, MediaBuffer继续通过mCallback(类型为AMessage)通知上层的NuPlayer::Decoder, 具体的消息类型为MediaCodec::CB_INPUT_AVAILABLE, 播放器在得知底层输入缓冲可用时, 试图提取一段输入数据.NuPlayer::Decoder通过基类NuPlayer::DecoderBase的onRequestInputBuffers()去拉取数据, 这个过程将通过GenericSource的dequeueAccessUnit()方法完成, 注意: 此处dequeueAccessUnit()需要一个判断读取音频还是视频的参数, 继而判断通过mAudioTrack还是mVideoTrack来获取数据, 这两个成员上文已经介绍过. GenericSource::dequeueAccessUnit()上文已经讲过. 当该函数无法从缓冲区读取到数据时会通过postReadBuffer()从拉流, 该函数调用的GenericSource::readBuffer()上文已经讲过, 此处略去.dequeueAccessUnit得到ABuffer是上文GenericSource给出的, 其所有权属于media.extractor, 其指针指向的是该进程中的共享内存, 但MediaCodecBuffer才是解码器需要的缓冲区描述, 上面说到, 该缓存其实是ION缓冲区, 已经通过写入视图(C2WriteView)映射到ABuffer, 那么什么何时从ABuffer拷贝到MediaCodecBuffer::mBuffer的ABuffer中的呢? 是在DecoderBase::onRequestInputBuffers() -> Decoder::doRequestBuffers() -> Decoder::onInputBufferFetched()完成GenericSource::dequeueAccessUnit()后执行的. 至此编码数据已经填充到LinearBlockBuffer的C2Block1D(也就是C2LinearBlock)中.
解码工作描述C2Work以及编码数据描述C2Buffer的创建
通过objcpy()完成C2Work到Work的转换, 后者支持序列化, 便于通过Binder发送:
C2Work[]->WorkBundleC2Work->WorkC2FrameData->FrameDataC2InfoBuffer->InfoBufferC2Buffer->BufferC2Block->Block
C2Worklet->WorkletC2FrameData->FrameDataC2InfoBuffer->InfoBufferC2Buffer->BufferC2Block[1|2]D->Block
media.swcodec对解码工作的接收以及解码数据的获取
mediaserver通过IComponent::queue()发送C2Work到media.swcodec, 在服务端, objcpy()负责WorkBundle中的Work到C2Work的转换, 大概的层级关系:
WorkBundle->C2Work[]
*Work->C2WorkFrameData->C2FrameDataInfoBuffer->C2InfoBufferBuffer->C2BufferBlock->C2Block
Worklet->C2WorkletFrameData->C2FrameDataInfoBuffer->C2InfoBufferBuffer->C2BufferBlock->C2Block[1|2]D
这里C2Block1D的实现是C2LinearBlock, 通过底层的C2AllocationIon::Impl::map()可完成对ION缓存的映射, 获取待解码的数据.
解码器所在进程通过SimpleC2Component::queue_nb()响应binder请求, 并获取C2Block1D描述后, 发送kWhatProcess消息, 该消息由SimpleC2Component::processQueue()响应, 该方法直接调用子类的实现, 本文视频采用VP9的编码, 因此子类实现为C2SoftVpxDec::process(), 其同构work->input.buffers[0]->data().linearBlocks().front().map().get()获取输入数据, 这个调用可分如下步骤看待:
work->input.buffers[0]->data()返回C2BufferData类型C2ConstLinearBlock::linearBlocks()返回C2ConstLinearBlock类型, 该类型本质上是C2Block1DC2ConstLinearBlock::map()返回C2ReadView, 此时C2ConstLinearBlock通过实现C2Block1D::Impl所保存的C2LinearAllocation对ION的缓存进行映射, 映射完成后创建AcquirableReadViewBuddy(父类为C2ReadView)并将数据保存到它的mImpl(类型为:ReadViewBuddy::Impl, 实际上就是C2ReadView::Impl)中.
接下来uint8_t *bitstream = const_cast<uint8_t *>(rView.data() + inOffset, 是通过C2ReadView::data()获取数据指针, 正式从上面的C2ReadView::Impl.mData获取的.
VPx视频解码
vpx_codec_decode()完成解码工作, 前提是输入数据长度有效.
视频解码图形缓存描述C2GraphicBlock的创建
解码完成后解码器从C2BlockPool中通过fetchGraphicBlock()拉取一个C2GraphicBlock, 此时将触发GraphicBuffer的创建. 这里C2BlockPool的实现是BlockingBlockPool, 通过mImpl引用C2BufferQueueBlockPool::Impl, 从这个实现开始:
- 通过
android::hardware::graphics::bufferqueue::V2_0::IBufferQueueProducer(实现为BpHwBufferQueueProducer)获取一个HardwareBuffer - 使用
h2b()将HardwareBuffer通过AHardwareBuffer_createFromHandle()将HardwareBuffer转化为AHardwareBuffer - 最后通过
GraphicBuffer::fromAHardwareBuffer()通过AHardwareBuffer创建GraphicBuffer - 此时创建
GraphicBuffer是通过native_handle_t创建的, 那么将涉及GraphicBuffer的导入,GraphicBuffer通过GraphicBufferMapper::importBuffer()(后端实现是Gralloc2Mapper)完成导入.
这里的android::hardware::graphics::bufferqueue::V2_0::IBufferQueueProducer实现是BpHwGraphicBufferProducer, 该方法的对端为mediaserver进程中的BnHwGraphicBufferProducer, 最后处理消息的类为B2HGraphicBufferProducer, 而该方法中的mBase类型为android::IGraphicBufferProducer, 其实现为android::BpGraphicBufferProducer. 该方法将跨进程从应用系统相册一侧的BnGraphicBufferProducer=>BufferQueueProducer=>BBQBufferQueueProducer提取一个GraphicBuffer, 该对象将通过b2h()转换为一个IGraphicBufferProducer::QueueBufferOutput, 该类继承自Flattenable<QueueBufferOutput>是可序列化的, 最终b2h()转化GraphicBuffer得到的HardwareBuffer将通过Binder传递给media.swcodec的解码器.GraphicBuffer创建后被C2Handle所引用,C2Handle通过WrapNativeCodec2GrallocHandle()创建, 在为视频时, 实现为C2HandleGralloc, 通过C2Handle进一步分配了C2GraphicAllocation, 此时C2BufferQueueBlockPoolData被创建, 主要保存GraphicBuffer的信息._C2BlockFactory::CreateGraphicBlock()则负责创建C2GraphicBlock, 上文的创建的C2GraphicAllocation(子类C2AllocationGralloc)和C2BufferQueueBlockPoolData(类型为_C2BlockPoolData)保存到C2GraphicBlock的父类C2Block2D的mImpl(类型为C2Block2D::Impl)中. 直到此时GraphicBuffer中的数据指针还没有被获取. 但是,C2Block2D已经被创建.
那么解码器是如何通过C2Block2D获取到GraphicBuffer中的数据指针呢? - 首先
block->map().get()通过C2Block2D::Impl, 也就是_C2MappingBlock2DImpl创建一个Mapped, 这个Mapped通过C2GraphicAllocation执行映射, 这个过程中mHidlHandle.getNativeHandle()将获得native_handle_t(其中mHidlHandle的类型是上文创建的C2Handle). 只要有native_handle_t就可以通过GraphicBufferMapper::lockYCbCr()去锁定HardwareBuffer中的数据, 将获取PixelFormat4::YV12格式的GraphicBuffer中各数据分量的布局地址信息, 这些地址信息会保存到mOffsetData, 后面会通过_C2MappingBlock2DImpl::Mapped::data()获取. 最后C2GraphicBlock::map()返回的是C2Acquirable<C2GraphicView>, 而C2Acquirable<>返回的是C2GraphicView. - 然后
wView.data()获取数据指针, 该过程通过C2GraphicView:mImpl:_C2MappedBlock2DImpl_C2MappedBlock2DImpl::mapping():MappedMapped::data(): 为上文保存的各数据分量的地址.
最后通过copyOutputBufferToYuvPlanarFrame()完成解码后数据到GraphicBuffer的拷贝.createGraphicBuffer()负责从填充过的包含GraphicBuffer的C2GraphicBlock包装为C2Buffer, 然后通过fillWork代码块将C2Buffer打包到C2Work中, 等待返回. (举证请参见附件C2GraphicBlock)
视频解码返回图形缓存
解码完成后, 解码器填充数据到C2Buffer, 该结构将被描述到C2Work -> C2Worklet -> C2FrameData -> C2Buffer(以及C2BufferInfo)中, 按照上文的描述通过Binder返回给mediaserver, 该过程将通过objcpy()完成从C2Work[]到WorkBundle的转换, 该过程略去.
图形缓存的接收
mediaserver通过HidlListener(实现了接口IComponentListener)来接收WorkBundle(也就是Work[]), 上文提到objcpy()可完成Work到C2Work的转换, 该过程同样包含了各个阶段相应对象的创建, 这里只提及几个地方:
CreateGraphicBlock()负责创建C2GraphicBlock, 并配置给了dBaseBlocks[i](类型为C2BaseBlock)的graphic成员createGraphicBuffer()负责从C2ConstGraphicBlock到GraphicBuffer的转化(导入), 并保存到OutputBufferQueue的mBuffers[oldSlot]. 而C2ConstGraphicBlock来子上文转化后的C2FrameData::buffers(C2Buffer)::mImpl(C2Buffer::Impl)::mData(BufferDataBuddy)::mImpl(C2BufferData::Impl)::mGraphicBlocks(C2ConstGraphicBlock)
至此,GraphicBuffer已经完成从IComponent到mediaserver的传递.
继续向上层通知, 通知路径有两条:HidlListener::onWorkDone()->Codec2Client::Component::handleOnWorkDone(), 这条路径未执行Codec2Client::Listener=>CCodec::ClientListener::onWorkDone()->CCodec->CCodecBufferChannel::onWorkDone()->BufferCallback::onOutputBufferAvailable()Buffercallback上文提到过是MediaCodec用来监听BufferChannelBase(也就是CCodecBufferChannel)的, 所以这里BufferCallback通过kWhatDrainThisBuffer通知MediaCodec,MediaCodec::onOutputBufferAvailable()负责响应该消息, 该方法有进一步通过CB_OUTPUT_AVAILABLE消息, 通知到NuPlayer::Decoder,NuPlayer::Decoder::handleAnOutputBuffer()需要处理返回的视频帧, 解码器收到视频帧后推如渲染器, 也就是NuPlayer::Renderer, 这个过程会对Renderer发出kWhatQueueBuffer消息,Renderer::onQueueBuffer()负责响应该消息.
音频解码与视频解码的差异
音频解码输入的部分与视频解码并没有特别大的不同, 但输出的缓冲区类型也是C2LinearBlock, 且该输出缓存的来源和上文在解码数据的分配过程是一样的.
音画同步
暂时不讨论该话题.
视频渲染
mediaserver中的队列
上文讲过, GraphicBuffer的跨进程经历了很多步骤, 它通过BnHwGraphicBufferProducer::dequeueBuffer()响应media.swcodec被转化为HardwareBuffer通过Binder获取, 通过h2b()转化为GraphicBuffer, 在数据填充完成后, 又通过Binder传回, 在mediaserver中通过h2b转化回GraphicBuffer, 并通过Codec2Buffer作为MediaCodecBuffer给到MediaCodec通知上层同步, 同步完成后它最终将由MediaCodec触发渲染, NuPlayer::Renderer确定可以渲染时, 将通过kWhatReleaseOutputBuffer消息告知MediaCodec渲染, 响应该消息的是:MediaCodec::onReleaseOutputBuffer(), 显然MediaCodec将调用CCodecBufferChannel执行Codec2Buffer的渲染, Codec2Buffer原本是保存在CCodecBufferChannel的OutputBufferQueue中, 在渲染时GraphicBuffer将通过BpGraphicBufferProducer::queueBuffer()被推出.
应用中的队列BLASTBufferQueue
上文说到GraphicBuffer通过CCodecBufferChannel的OutputBufferQueue由IGraphicBufferProducer::queueBuffer()被推送到系统相册里, Surface表面内嵌缓冲队列BLASTBufferQueue的生产者BBQBufferQueueProducer, 然后BLASTBufferQueue的消费者BufferQueueConsumer将通过父类接口ConsumerBase::FrameAvailableListener()通知对应的实现为:BLASTBufferQueue::onFrameAvailable()进行 进一步处理. BLASTBufferQueue在processNextBufferLocked()中进一步处理收到的GraphicBuffer, 其此时构造一个SurfaceComposerClient::Transaction请求, 准备提交给SurfaceFlinger
附录
MediaPlayer将收到的通知类型
MEDIA_NOPMEDIA_PREPARED: 来自Source::kWhatPreparedMEDIA_PLAYBACK_COMPLETE:kWhatScanSources操作完成后MEDIA_BUFFERING_UPDATE: 来自Source::kWhatBufferingUpdateMEDIA_SEEK_COMPLETE:kWhatSeek完成后MEDIA_SET_VIDEO_SIZE: 来自DecoderBase::kWhatVideoSizeChanged或者Source::kWhatVideoSizeChangedMEDIA_STARTED: 来自Renderer::kWhatMediaRenderingStartMEDIA_PAUSED: 主要来自pause()和seekTo()两个操作MEDIA_STOPPED: 主要来来自stop()操作MEDIA_SKIPPED: 未使用MEDIA_NOTIFY_TIME: 主要来自MediaClock回调MEDIA_TIMED_TEXT: 来自Source::kWhatTimedTextDataMEDIA_ERROR:ext1类型为media_error_typeMEDIA_ERROR_UNKNOWN: 1, 具体的类型关注ext2:ERROR_ALREADY_CONNECTEDERROR_NOT_CONNECTED- …
MEDIA_ERROR_SERVER_DIED: 100MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: 200
MEDIA_INFO:ext1类型为media_info_typeMEDIA_INFO_UNKNOWN= 1MEDIA_INFO_STARTED_AS_NEXT: 准备用于下一个的播放MEDIA_INFO_RENDERING_START: 首帧已播放, 来自Renderer::kWhatVideoRenderingStartMEDIA_INFO_VIDEO_TRACK_LAGGING: 待解码的数据过于复杂可能会延迟较大MEDIA_INFO_BUFFERING_START: 播放器内部已暂停, 等待更多的数据, 来自Source::kWhatPauseOnBufferingStartMEDIA_INFO_BUFFERING_END: 播放器等待足够多的数据后恢复播放, 来自Source::kWhatResumeOnBufferingEndMEDIA_INFO_NETWORK_BANDWIDTH: 网络带宽统计信息, 来自Source::kWhatCacheStatsMEDIA_INFO_BAD_INTERLEAVING: 错误的数据插入?MEDIA_INFO_NOT_SEEKABLE: 媒体不允许SEEK操作MEDIA_INFO_METADATA_UPDATE: 有新的原数据, 来自Source::kWhatTimedMetaDataMEDIA_INFO_PLAY_AUDIO_ERROR: 无法播放音频数据, 来自DecoderBase::kWhatErrorMEDIA_INFO_PLAY_VIDEO_ERROR: 无法播放视频数据, 来自DecoderBase::kWhatErrorMEDIA_INFO_TIMED_TEXT_ERROR
MEDIA_SUBTITLE_DATA: 来自Source::kWhatSubtitleDataMEDIA_META_DATA: 来自Source::kWhatTimedMetaDataMEDIA_DRM_INFO: 来自Source::kWhatDrmInfoMEDIA_TIME_DISCONTINUITY: 来自kWhatMediaClockNotify,MediaClock回调MEDIA_IMS_RX_NOTICE: 来自RTP流的Source::kWhatIMSRxNoticeMEDIA_AUDIO_ROUTING_CHANGED: 在音频通路输出切换时, 将通过该消息通知上层
NuPlayer接受底层的消息主要有以下集中类型
kWhatMediaClockNotify: 负责监听来自MediaClock的消息, 消息中有如下三种信息:"anchor-media-us""anchor-real-us""playback-rate"
kWhatSourceNotify: 负责处理来自NuPlayer::Source的消息, 有以下信息:"what": 具体的数据源消息类型, 又分以下几种类型:Source::kWhatInstantiateSecureDecoders: 数据源是安全类型, 通知创建安全类型的解码器"reply": 等待消息的AMesage对象, 用于通知数据源安全解码器的创建完成
kWhatPrepared: 数据源已准备好, 可以读取数据"err": 准备结束不代表成功, 该字段返回具体的状态码
Source::kWhatDrmInfo: 有关于DRM信息"drmInfo"一个类型为ABuffer的对象
kWhatFlagsChanged播放标识改变"flags"改变的标识
Source::kWhatVideoSizeChanged: 播放源表示播放的解码信息发生改变"format"为具体改变的格式信息, 一AMessage体现, 其中主要的信息有"width""height"
可能包含的信息:"sar-width""sar-height""display-width""display-height""rotation-degrees"
Source::kWhatBufferingUpdate"percentage"
Source::kWhatPauseOnBufferingStartSource::kWhatResumeOnBufferingEndSource::kWhatCacheStats"bandwidth"
Source::kWhatSubtitleData: 字幕数据"buffer":ABuffer
Source::kWhatTimedMetaData: 元数据"buffer":ABuffer
Source::kWhatTimedTextData: 文本数据?"generation""buffer":ABuffer"timeUs"
Source::kWhatQueueDecoderShutdown"audio""video""reply"
Source::kWhatDrmNoLicense: 没有DRM的LicenseSource::kWhatIMSRxNotice- “message”:
AMessage
- “message”:
kWhatRendererNotify: 来自渲染器ALooper的消息, 提供以下信息:"generation""what": 有多种类型:Renderer::kWhatEOS"audio""finalResult"
Renderer::kWhatFlushComplete"audio"
Renderer::kWhatVideoRenderingStartRenderer::kWhatMediaRenderingStart"audio"
Renderer::kWhatAudioTearDown"reason": 原因"positionUs": 多久
kWhatClosedCaptionNotifykWhatAudioNotify和kWhatVideoNotify负责相应来自音视频NuPlayer::Decoder的消息, 主要有以下信息:"generation""reply""what", 有以下类型:DecoderBase::kWhatInputDiscontinuity: 输入不连续, 可能需要重新扫描数据源DecoderBase::kWhatEOS: 码流结束"err"
DecoderBase::kWhatFlushCompleted: Flush操作结束DecoderBase::kWhatVideoSizeChanged: 解码时格式改变"format":AMessage形式的格式描述
DecoderBase::kWhatShutdownCompleted: 停止播放DecoderBase::kWhatResumeCompleted: 恢复播放DecoderBase::kWhatError: 解码遇到错误"err": 错误原因, 错误码将通过MEDIA_INFO类型报给上层
部分举证
IMediaExtractor
MediaExtractorService::makeExtractor()下断点:
p *((TinyCacheSource *)((RemoteMediaExtractor *)0x0000007d68639df0)->mSource.m_ptr->mWrapper->handle)
warning: `this' is not accessible (substituting 0). Couldn't load 'this' because its value couldn't be evaluated
(android::TinyCacheSource) $35 = {
android::DataSource = {
mWrapper = 0x0000007d4864b410
}
mSource = {
m_ptr = 0x0000007d58647640
}
mCache = "..."...
mCachedOffset = 405144
mCachedSize = 2048
mName = (mString = "TinyCacheSource(CallbackDataSource(4894->4875, RemoteDataSource(FileSource(fd(/storage/emulated/0/Movies/VID_20220317_221515.mp4), 0, 4948142))))")
}
对于地址0x0000007d58647640, 已经知道其类型为CallbackDataSource, 因此:
p *(CallbackDataSource *)0x0000007d58647640
warning: `this' is not accessible (substituting 0). Couldn't load 'this' because its value couldn't be evaluated
(android::CallbackDataSource) $37 = {
android::DataSource = {
mWrapper = nullptr
}
mIDataSource = (m_ptr = 0x0000007d88645ab0)
mMemory = (m_ptr = 0x0000007d68639cd0)
mIsClosed = false
mName = (mString = "CallbackDataSource(4894->4875, RemoteDataSource(FileSource(fd(/storage/emulated/0/Movies/VID_20220317_221515.mp4), 0, 4948142)))")
}
通过mName确认到以上所有类型行的总结都是正确的
IMediaSource
为了验证该总结的正确性, 通过调试器:
p track
(const android::sp<android::IMediaSource>) $79 = (m_ptr = 0x0000007d98639b10)
得到的 track 的类型应为 RemoteMediaSource, 因此:
p *(android::RemoteMediaSource *)0x0000007d98639b10
(android::RemoteMediaSource) $80 = {
mExtractor = {
m_ptr = 0x0000007d68639fd0
}
mTrack = 0x0000007d3863c290
mExtractorPlugin = (m_ptr = 0x0000007d7863d9b0)
}
得到的 mTrack 类型应为 MediaTrackCUnwrapper, 因此
p *(android::MediaTrackCUnwrapper *)0x0000007d3863c290
(android::MediaTrackCUnwrapper) $81 = {
wrapper = 0x0000007d586463d0
bufferGroup = nullptr
}
得到的 wrapper 类型应为 CMediaTrack, 因此
p *(CMediaTrack *)0x0000007d586463d0
(CMediaTrack) $83 = {
data = 0x0000007de8638ed0
free = 0x0000007d143696b8 (libmp4extractor.so`android::wrap(android::MediaExtractorPluginHelper*)::'lambda'(void*)::__invoke(void*) + 4)
start = 0x0000007d143696bc (libmp4extractor.so`android::wrap(android::MediaTrackHelper*)::'lambda'(void*)::__invoke(void*) + 4)
stop = 0x0000007d143696c0 (libmp4extractor.so`android::wrap(android::MediaTrackHelper*)::'lambda0'(void*)::__invoke(void*))
getFormat = 0x0000007d143696c8 (libmp4extractor.so`android::wrap(android::MediaExtractorPluginHelper*)::'lambda'(void*, AMediaFormat*)::__invoke(void*, AMediaFormat*) + 4)
read = 0x0000007d143696cc (libmp4extractor.so`android::wrap(android::MediaTrackHelper*)::'lambda'(void*, AMediaFormat*)::__invoke(void*, AMediaFormat*) + 4)
supportsNonBlockingRead = 0x0000007d143696d0 (libmp4extractor.so`__typeid__ZTSFbPvE_global_addr)
}
得到的 data 的类型为: MPEG4Source, 因此:
p *((android::MPEG4Source *)0x0000007de8638ed0)
(android::MPEG4Source) $67 = {
android::MediaTrackHelper = {
mBufferGroup = nullptr
}
mLock = {
mMutex = {
__private = ([0] = 0, [1] = 0, [2] = 0, [3] = 0, [4] = 0, [5] = 0, [6] = 0, [7] = 0, [8] = 0, [9] = 0)
}
}
mFormat = 0x0000007d586489a0
mDataSource = 0x0000007d2863b850
mTimescale = 90000
...
}
此时关注 mFormat 我们打印其内容:
p *(AMediaFormat *)0x0000007d586489a0
(AMediaFormat) $87 = {
mFormat = {
m_ptr = 0x0000007d686398b0
}
mDebug = (mString = "")
}
此处 mFormat 的类型为 android::AMessage, 因此:
p *(android::AMessage *)0x0000007d686398b0
(android::AMessage) $89 = {
android::RefBase = {
mRefs = 0x0000007d3863c230
}
mWhat = 0
mTarget = 0
mHandler = {
m_ptr = nullptr
m_refs = nullptr
}
mLooper = {
m_ptr = nullptr
m_refs = nullptr
}
mItems = size=16 {
[0] = {
u = {
int32Value = 946061584
int64Value = 537816973584
sizeValue = 537816973584
floatValue = 0.0000543008209
doubleValue = 2.6571689039816359E-312
ptrValue = 0x0000007d3863c110
refValue = 0x0000007d3863c110
stringValue = 0x0000007d3863c110
rectValue = (mLeft = 946061584, mTop = 125, mRight = 0, mBottom = 0)
}
mName = 0x0000007d2863c270 "mime"
mNameLength = 4
mType = kTypeString
}
...
}
...
}
AMessage 中, mItems 的第一个 Item 类型中的 stringValue 类型为: AString *, 因此可以求 “mime” 的值:
p *(android::AString *)0x0000007d3863c110
(android::AString) $91 = (mData = "video/avc", mSize = 9, mAllocSize = 32)
可以清晰的看到, 有一个Track的mime类型为"video/avc", 而另一个通过同样的方法可得知为: "audio/mp4a-latm".
C2GraphicBlock
断点:C2BufferQueueBlockPool::Impl::fetchFromIgbp_l()(C2BqBuffer.cpp:463:13):
(lldb) p *block
(std::shared_ptr<C2GraphicBlock>) $36 = std::__1::shared_ptr<C2GraphicBlock>::element_type @ 0x0000006fa2dbbcd0 strong=1 weak=1 {
__ptr_ = 0x0000006fa2dbbcd0
}
(lldb) p *(C2GraphicBlock *)0x0000006fa2dbbcd0
(C2GraphicBlock) $38 = {
C2Block2D = {
_C2PlanarSectionAspect = {
_C2PlanarCapacityAspect = (_mWidth = 320, _mHeight = 240)
mCrop = (width = 320, height = 240, left = 0, top = 0)
}
mImpl = std::__1::shared_ptr<C2Block2D::Impl>::element_type @ 0x0000006ff2dbc5e8 strong=1 weak=2 {
__ptr_ = 0x0000006ff2dbc5e8
}
}
}
(lldb) p *(C2Block2D::Impl *)0x0000006ff2dbc5e8
(C2Block2D::Impl) $39 = {
_C2MappingBlock2DImpl = {
_C2Block2DImpl = {
_C2PlanarSectionAspect = {
_C2PlanarCapacityAspect = (_mWidth = 320, _mHeight = 240)
mCrop = (width = 320, height = 240, left = 0, top = 0)
}
mAllocation = std::__1::shared_ptr<C2GraphicAllocation>::element_type @ 0x0000006ff2db7cf0 strong=2 weak=1 {
__ptr_ = 0x0000006ff2db7cf0
}
mPoolData = std::__1::shared_ptr<_C2BlockPoolData>::element_type @ 0x0000006ff2dbabc8 strong=2 weak=2 {
__ptr_ = 0x0000006ff2dbabc8
}
}
... ...
// C2GraphicAllocation 的类型实际上是 C2AllocationGralloc
(lldb) p *(android::C2AllocationGralloc *)0x0000006ff2db7cf0
(android::C2AllocationGralloc) $40 = {
C2GraphicAllocation = {
_C2PlanarCapacityAspect = (_mWidth = 320, _mHeight = 240)
}
mWidth = 320
mHeight = 240
mFormat = 842094169
mLayerCount = 1
mGrallocUsage = 2355
mStride = 320
mHidlHandle = {
mHandle = {
= {
mPointer = 0x0000006fe2db5b40
_pad = 480547396416
}
}
...
// mHidlHandle.mHandle 的类型是 native_handle_t
(lldb) p *((android::C2AllocationGralloc *)0x0000006ff2db7cf0)->mHidlHandle.mHandle.mPointer
(const native_handle) $64 = (version = 12, numFds = 2, numInts = 22, data = int [] @ 0x0000000008a61a0c)
这里多扯一句, native_handl(也就是natvie_handle_t)其实是高通的私有的private_handle_t, 该数据的幻数是'msmg', 其也保存了宽高, 其定义在hardware/qcom/sdm845/display/gralloc/gr_priv_handle.h文件中, 名称为:private_handle_t, 其数据:
(lldb) x -c64 0x0000006fe2db5b40
0x6fe2db5b40: 0c 00 00 00 02 00 00 00 16 00 00 00 3e 00 00 00 ............>...
0x6fe2db5b50: 3f 00 00 00 6d 73 6d 67 08 02 10 14 40 01 00 00 ?...msmg....@...
0x6fe2db5b60: f0 00 00 00 40 01 00 00 f0 00 00 00 59 56 31 32 ....@.......YV12
0x6fe2db5b70: 01 00 00 00 01 00 00 00 84 05 00 00 00 00 00 00 ................
可自行对比.
---------------------
作者:nickliyz
来源:CSDN
原文:https://blog.csdn.net/liyangzmx/article/details/124582754
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

1392

被折叠的 条评论
为什么被折叠?



