Android源码版本:android-12.0.0_r3Android源码来源:https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifestAndroid源码编译配置: aosp_crosshatch-userdebugGoogle Pixel 3 XL (crosshatch)SP1A.210812.016.A1Ubuntu 22.04 LTSCodeless表示尽可能少的代码.
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链接NuCachedSource2MediaExtractor: 表示一个解封装器, 其通过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用于描述一个解码器使用的缓冲区, 其有几种扩展实现
Codec2Buffer Codec 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中的TrackHandlemedia.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 实现的软解组件, 简单列出几种实现:
C2SoftAvcDecC2SoftHevcDecC2SoftVpxDecC2SoftFlacDecC2SoftAacDecSampleToneMappingFilter: ??WrappedDecoder: ??android::hardware::media::c1::V1_2::utils::ComponentInterface: 组件接口类, 负责描述一个 Codec 2 组件的各种信息, 其通过IComponentInterface向对端(Codec2Client)提供查询服务, 该接口类也被Component所引用, 后端实现为C2ComponentInterfaceC2Component::Listener: 为组件中, 客户端的回调实现, 其持有一个IComponentListener接口, 用于通知客户端组件的消息, 其有一个实现: Component::Listener, 持有父类的IComponentListener接口C2Work: 表示一个解码工作(播放器中)C2FrameData: 表示一个帧的数据, 其中有一组C2BufferC2Buffer: 一个缓冲区的描述, 其包含其数据的描述C2BufferDataC2BufferData: 描述一个缓冲区的数据, 以及它包含的块C2Block[1|2]DC2Block1D: 描述一个一维的缓冲区, 有如下实现:
C2LinearBlock: 可写一个线性缓冲区C2ConstLinearBlock: 只读线性缓冲区C2CircularBlock: 环形缓冲区(环形缓冲区是"首尾相接"的线性缓冲区)C2Block2D: 描述一个二维的缓冲区有如下实现:
C2GraphicBlock: 描述一个二维图形缓冲区C2ConstGraphicBlock: 描述一个只读的图形缓冲区(本例不涉及)MediaPlayerMediaPlayer(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.
MediaPlayerServicemediaserver创建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将收到的通知类型).NuPlayerClient负责响应来自MediaPlayer的请请求, 现在Client已经创建, MediaPlayer该通过IMediaPlayer接口通过它发起setDataSource()操作了, 这里分两个步骤:
NuPlayerDriverNuPlayerDriver执行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 -> NuPlayerNuPlayer -> [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还有几种主要类型:
StreamingSourceHTTPLiveSourceRTSPSourceRTPSourceGenericSourceGenericSource创建后直接配置数据源就可以了, 数据源被创建后是否开始解析数据文件呢? 没有, 这部分工作将在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 -> TinyDataSourceMetaData的获取解封装器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()完成请求, 这里略去.
IMediaSourceTrack的获取通过如下过程: 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: MediaBufferHelper
newHelper->mBuffer: CMediaBuffer
newHelper->mBuffer->handle: MediaBufferBase -> MediaBuffer
对于上述过程的最后一个函数, 也就是MediaBufferGroup::acquire_buffer()中, 只有for (auto it = mInternal->mBuffers.begin(); it != mInternal->mBuffers.end(); ++it)没有找到合适的buffer, 才会申请新的buffer
至此, 可以知道mediaserver所获取到的数据结构即MediaBufferBase
BnMediaSource::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 => BufferQueueConsumerupdateSurface()过程, 通过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()实例化解码器, 如果是音频解码器, 分两种情况:
DecoderPassThroughDecoderDecoderDecoder被创建后, 其init()和configure()方法被分别调用DecoderBase::configure()是DecoderBase通过异步消息kWhatConfigure调用到子类Decoder的onConfigure(), Decoder需要创建实际的解码器, 因此通过MediaCodec::CreateByType()创建MediaCodec, MediaCodecList::findMatchingCodecs()负责查找支持当前解码格式解码器的名字, 其定义在MediaCodecList.cpp, 如果找到解码器则创建MediaCodec, 创建MediaCodec时, 其mGetCodecBase被初始化为一个std::function<>对象, 后文的MediaCodec::init()会调用此lambada.创建完成后通过init()调用上文的mGetCodecBase也就是MediaCodec::GetCodecBase()创建更底层的CodecBase, CodecBase`的实现有多种:CCodecACodecMediaFilterCodec 2解码框架解码器CCodecCCodec的创建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实例, 返回给了Codec2ClientCCodec视频解码组件Component的查找Codec2Client在通过createComponent()方法创建组件时, ComponentStore首先找到匹配的ComponentLoader, 在Loader的初始化过程中欧给你, 将创建ComponentModule对象ComponentLoader对象从对应的libcodec2_soft_vp9dec.so中查找CreateCodec2Factory符号CreateCodec2Factory符号将返回C2ComponentFactory类型, 其实现为C2SoftVpxFactorycreateInterface方法, 返回一个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 => C2Block1D
mImpl: _C2Block1DImpl => C2Block1D::Impl
mAllocation: C2LinearAllocation => C2AllocationIon
mImpl: 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[] -> WorkBundle
C2Work -> Work
C2FrameData -> FrameData
C2InfoBuffer -> InfoBufferC2Buffer -> Buffer
C2Block -> BlockC2Worklet -> Worklet
C2FrameData -> FrameData
C2InfoBuffer -> InfoBufferC2Buffer -> Buffer
C2Block[1|2]D -> Blockmedia.swcodec对解码工作的接收以及解码数据的获取mediaserver通过IComponent::queue()发送C2Work到media.swcodec, 在服务端, objcpy()负责WorkBundle中的Work到C2Work的转换, 大概的层级关系:
WorkBundle -> C2Work[]Work -> C2Work
FrameData -> C2FrameData
InfoBuffer -> C2InfoBufferBuffer -> C2Buffer
Block -> C2BlockWorklet -> C2Worklet
FrameData -> C2FrameData
InfoBuffer -> C2InfoBufferBuffer -> C2Buffer
Block -> C2Block[1|2]DC2Block1D的实现是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_codec_decode()完成解码工作, 前提是输入数据长度有效.
C2GraphicBlock的创建解码完成后解码器从C2BlockPool中通过fetchGraphicBlock()拉取一个C2GraphicBlock, 此时将触发GraphicBuffer的创建. 这里C2BlockPool的实现是BlockingBlockPool, 通过mImpl引用C2BufferQueueBlockPool::Impl, 从这个实现开始:
android::hardware::graphics::bufferqueue::V2_0::IBufferQueueProducer(实现为BpHwBufferQueueProducer)获取一个HardwareBufferh2b()将HardwareBuffer通过AHardwareBuffer_createFromHandle()将HardwareBuffer转化为AHardwareBufferGraphicBuffer::fromAHardwareBuffer()通过AHardwareBuffer创建GraphicBufferGraphicBuffer是通过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_type
MEDIA_ERROR_UNKNOWN: 1, 具体的类型关注ext2:
ERROR_ALREADY_CONNECTEDERROR_NOT_CONNECTEDMEDIA_ERROR_SERVER_DIED: 100MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: 200MEDIA_INFO: ext1类型为media_info_type
MEDIA_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_ERRORMEDIA_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": ABufferSource::kWhatTimedMetaData: 元数据
"buffer": ABufferSource::kWhatTimedTextData: 文本数据?
"generation""buffer": ABuffer
"timeUs"Source::kWhatQueueDecoderShutdown
"audio""video""reply"Source::kWhatDrmNoLicense: 没有DRM的LicenseSource::kWhatIMSRxNotice
AMessagekWhatRendererNotify: 来自渲染器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类型报给上层IMediaExtractorMediaExtractorService::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 ................
可自行对比.
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我一直在寻找一种以编程方式或通过命令行将mp3转换为aac的方法,但没有成功。理想情况下,我有一段代码可以从我的Rails应用程序中调用,将mp3转换为aac。我安装了ffmpeg和libfaac,并能够使用以下命令创建aac文件:ffmpeg-itest.mp3-acodeclibfaac-ab163840dest.aac当我将输出文件的名称更改为dest.m4a时,它无法在iTunes中播放。谢谢! 最佳答案 FFmpeg提供AAC编码功能(如果您已编译它们)。如果您使用的是Windows,则可以从here获取完整的二进制文件。
深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG
目录SpringBootStarter是什么?以前传统的做法使用SpringBootStarter之后starter的理念:starter的实现: 创建SpringBootStarter步骤在idea新建一个starter项目、直接执行下一步即可生成项目。 在xml中加入如下配置文件:创建proterties类来保存配置信息创建业务类:创建AutoConfiguration测试如下:SpringBootStarter是什么? SpringBootStarter是在SpringBoot组件中被提出来的一种概念、简化了很多烦琐的配置、通过引入各种SpringBootStarter包可以快速搭建出一
这个问题在这里已经有了答案:Unabletoinstallgem-Failedtobuildgemnativeextension-cannotloadsuchfile--mkmf(LoadError)(17个答案)关闭9年前。嘿,我正在尝试在一台新的ubuntu机器上安装rails。我安装了ruby和rvm,但出现“无法构建gemnative扩展”错误。这是什么意思?$sudogeminstallrails-v3.2.9(没有sudo表示我没有权限)然后它会输出很多“获取”命令,最终会出现这个错误:Buildingnativeextensions.Thiscouldtakeawhi
我如何用ruby编写一个脚本,当从命令行执行时播放mp3文件(背景音乐)?我试过了run="mplayer#{"/Users/bhushan/resume/m.mp3"}-aosdl-vox11-framedrop-cache16384-cache-min20/100"system(run)但它也不起作用,以上是播放器特定的。如果用户没有安装mplayer怎么办。有没有更好的办法? 最佳答案 我一般都是这样pid=fork{exec'mpg123','-q',file} 关于ruby
我想知道如何从Apple.p12文件中提取key。根据我有限的理解,.p12文件是X504证书和私钥的组合。我看到我遇到的每个.p12文件都有一个X504证书和至少一个key,在某些情况下有两个key。这是因为每个.p12都有一个Apple开发人员key,有些还有一个额外的key(可能是Appleroot授权key)。我只考虑那些具有两个key的.p12文件是有效的。我的目标是区分具有一个key的.p12文件和具有两个key的.p12文件。到目前为止,我已经使用OpenSSL来检查X504文件和任何.p12的key。例如,我有这段代码可以检查目录中的所有.p12文件:Dir.glob(
require'openssl'ifARGV.length==2pkcs12=OpenSSL::PKCS12.new(File.read(ARGV[0]),ARGV[1])ppkcs12.certificateelseputs"Usage:load_cert.rb"end运行它会在Windows上产生错误,但在Linux上不会。错误:OpenSSL::PKCS12::PKCS12Error:PKCS12_parse:macverifyfailurefrom(irb):21:ininitializefrom(irb):21:innewfrom(irb):21fromC:/Ruby192/
我有一个使用postgresql的Rails4应用程序。我还有一个backbone.js应用程序,可将JSON推送到Rails4应用程序。这是我的Controller:defcreate@product=Product.new(ActiveSupport::JSON.decodeproduct_params)respond_todo|format|if@product.saveformat.json{renderaction:'show',status::created,location:@product}elseformat.json{renderjson:@product.erro
Ignoringbinding_of_caller-0.7.2becauseitsextensionsarenotbuilt.Try:gempristinebinding_of_caller--version0.7.2Ignoringbyebug-9.0.6becauseitsextensionsarenotbuilt.Try:gempristinebyebug--version9.0.6Ignoringcapybara-webkit-1.11.1becauseitsextensionsarenotbuilt.Try:gempristinecapybara-webkit--versio