Android 12 原生播放器的编解码 Codec 2

版本及环境说明

  • Android源码版本:android-12.0.0_r3
  • Android源码来源:https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest
  • Android源码编译配置: 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开始播放时的基本流程:
Android 12 原生播放器的编解码 Codec 2_第1张图片
MediaPlayer开始播放时的数据流向:
Android 12 原生播放器的编解码 Codec 2_第2张图片
加粗的线条为数据在各类对象、进程见的流向。注: 由于水平有限,时序图和类图可能存在小的错误或缺失,请谅解。

部分类的简要说明

  • 运行于应用进程中:
    • MediaPlayer: 播放器, 泛指Java层和Native(C++)层的播放器
  • 运行于media.extractor
    • MediaExtractorService: 实现IMediaExtractorService接口, 对外提供创建解封装的接口
    • DataSource: 描述一个数据源头, 一个文件, 一个流等, 通常以IDataSource向外提供数据源的操作接口,有以有以下几种实现:
      • DataURISource
      • FileSource: 数据来自文件
      • HTTPBase: 数据来自HTTP链接
      • NuCachedSource2
    • MediaExtractor: 表示一个解封装器, 其通过IMediaExtractor的实现RemoteMediaExtractor对外提供服务, 其子类实现为MediaExtractorCUnwrapper, 这是一个CMediaExtractorWrapper, 该类其实是MPEG4Extractor对外的抽象
    • MediaTrack: 表示一个未解码的流, 通常以IMediaSource的实现RemoteMediaSource对外提供接口, 其子类实现为MediaTrackCUnwrapper, 这是一个CMediaTrackWrapper, 该类其实是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: 被成员mVideoSourcemAudioSource所引用
      • NuPlayer::StreamingSource: 来源是一个流
      • NuPlayer::HTTPLiveSource: 来源是一个HTTP直播流
      • NuPlayer::RTSPSource: 来源是一个RTSP
      • NuPlayer::RTPSource: 来源是一个RTP
    • NuPlayer::Decoder: NuPlayer的解码器对象, 有NuPlayer::DecoderNuPlayer::DecoderPassThrough两种实现, 本例只关注前者
    • MediaCodec: 实际的解码器接口, 其负责将部分上层的同步操作转换为异步操作, 其引用一个CodecBase
    • CodecBase: 表示一个解码器实例, 其引用一个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(用于读取), 同样引用一个C2BufferABuffer
        • EncryptedLinearBlockBuffer: 加密线性数据块
      • SharedMemoryBuffer: 共享内存方式
      • SecureBuffer: 安全缓冲区
    • Codec2Client: 负责通过HIDL完成到 Codec 2 解码组件库所在进程meida.codecmedia.swcodec的各种请求
    • Codec2Client::Component: 负责通过HIDL完成到 Codec 2 解码组件库所在进程meida.codecmedia.swcodec的各种请求
    • Codec2Client::Interface: 为组建接口, 其引用一个远程的IComponentInterface接口, 它集成自Codec2Client::Configurable, 意为可配置
    • Codec2Client::Listener: 负责监听来自组件HIDL接口的消息, 其有一个实现CCodec::ClientListener, 负责通知CCodec
    • Component::HidlListener: 负责监听来自组件的消息, 其实现了IComponentListener接口, 有消息产生后通过Codec2Client::Listener通知CCodec
    • Renderer: 渲染器
    • NuPlayerAudioSink: 音频输出, 其实现: AudioOutput
    • AudioTrack: 音频输出流, 通过IAudioTrack访问audioserver中的TrackHandle
  • 运行于media.swcodec中(本文以软解为例)
    • ComponentStore: 组件库, 运行与media.codecmedia.swcodec两个进程, 通过IComponentStore接口提供 Codec 2 框架的解码器实现
    • android::hardware::media::c1::V1_2::Component为 CCodec 2 框架解码器组件对于HIDL的实现, 其通过IComponent向其它进程的Codec2Client::Component提供服务, 后端实现为C2Component
    • C2Component为 CCodec 2 框架解码器组的实现, 其有多种具体的实现:
      • V4L2DecodeComponent: V4L2解码组件
      • V4L2EncodeComponent: V4L2编码组件
      • SimpleC2Component: Google 实现的软解组件, 简单列出几种实现:
        • 视频
          • C2SoftAvcDec
          • C2SoftHevcDec
          • C2SoftVpxDec
        • 音频
          • C2SoftFlacDec
          • C2SoftAacDec
      • SampleToneMappingFilter: ??
      • WrappedDecoder: ??
    • android::hardware::media::c1::V1_2::utils::ComponentInterface: 组件接口类, 负责描述一个 Codec 2 组件的各种信息, 其通过IComponentInterface向对端(Codec2Client)提供查询服务, 该接口类也被Component所引用, 后端实现为C2ComponentInterface
    • C2Component::Listener: 为组件中, 客户端的回调实现, 其持有一个IComponentListener接口, 用于通知客户端组件的消息, 其有一个实现: Component::Listener, 持有父类的IComponentListener接口
  • Codec 2 框架中的工作类
    • C2Work: 表示一个解码工作(播放器中)
    • C2FrameData: 表示一个帧的数据, 其中有一组C2Buffer
    • C2Buffer: 一个缓冲区的描述, 其包含其数据的描述C2BufferData
    • C2BufferData: 描述一个缓冲区的数据, 以及它包含的块C2Block[1|2]D
    • C2Block1D: 描述一个一维的缓冲区, 有如下实现:
      • 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++)类, 并生成一个集成自MediaPlayerListenerJNIMediaPlayerListener用于监听来自播放器的消息. 新创建的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, 其更底层的实现是NuPlayer
  • TestPlayerFactory: 测试目的, 不关注
    注:: 以前的版本还有一种AwesomePlayer(被stagefrightplayer封装), 已经过时了.
    MediaPlayer设置数据源之前要先完成实际的播放器实例的创建, 它通过IMediaPlayerService接口向MediaPlayerService服务申请创建播放器, 创建播放器后, 本地的MediaPlayer将通过IMediaPlayer接口引用服务器为其创建的播放器实例. 显然该Client实现了BnMediaPlayer并在创建后返回给应用, 它将作为一个引用传递给MediaPlayer并作为后续所有的请求的代理, setDataSource()也在其中.
    MediaPlayerService要具备通知MediaPlayer的能力才行, 后者实现了BnMediaPlayerClient, 将通过IMediaPlayerClient在创建Client时被设置在ClientmClient
    在创建Client是也创建了MediaPlayerService::Listener, 该类是继承自MediaPlayerBase::Listener, 显然该Listener将负责从底层的MediaPlayerBase监听播放时的各种消息, 从这里, 也知道了在MediaPlayerService中, 负责播放任务的实现是集成自MediaPlayerBase的, 本例中的继承: MediaPlayerBase -> MediaPlayerInterface -> NuPlayerDriver, Listener本身持有了Client的引用, 因此Listener::notify()将通知到Client::notify(), 而这时调用上文的MediaPlayerService::Listenernotify()将完成通过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将通过父类MediaPlayerInterfacesendEvent()方法通知上层.
  • kWhatPause: 暂停操作
  • kWhatResume: 恢复播放
    NuPlayer不但需要负责完成各种下行的控制, 还要通过AMessage响应来自底层的一系列消息(见附录).
    NuPlayer在创建完成后会保存在NuPlayerDrivermPlayer中, 而NuPlayerDriver作为MediaPlayerInterface(父类MediaPlayerBase)被ClientmPlayer引用, 因此总结
    下行整体的调用流程:
    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还有几种主要类型:
    • StreamingSource
    • HTTPLiveSource
    • RTSPSource
    • RTPSource
    • GenericSource
      对于本地文件的简单情形, 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创建的DataSourceFileSource.
IDataSource接口通过BinderMediaExtractorService返回给应用后通过TinyCacheSource(DataSource)::mSource -> CallbackDataSource(DataSource)::mIDataSource引用.

MediaExtractorService中数据源探测前的准备

数据源有了, 那么需要从数据源中接封装出媒体流, 它可能是音频/视频/字幕等, 这个过程需要 对数据源中的数据进行解封装, 找到各种媒体数据所对应的流, MediaExtractorFactory::Create()仍然是本地工作的, 它负责通过IMediaExtractorService::makeExtractor()MediaExtractorService请求创建IMediaExtractor过程, 对应的服务端实现是:MediaExtractorService::makeExtractor().
但是这这个过程中, 上文的TinyCacheDataSource作为DataSource通过CreateIDataSourceFromDataSource()转换成了IDataSource接口的RemoteDataSource又发回给MediaExtracotrService了?
不是的, 在RemoteDataSource::wrap()不一定创建实现新的IDataSourceRemoeDataSource, 如果传入的DataSource本身及持有IDataSource, 那就直接返回了, 没有重新创建的必要, 所以返回的仍然是TinyCacheSource(DataSource)::mSource -> CallbackDataSource(DataSource)::mIDataSource所保存的来自MediaExtractorIMediaSource.
请求发给服务端MediaExtractorService, 又会被如何处理呢? 这里仍然是通过CreateDataSourceFromIDataSource()创建了本地的DataSource, 这和上文应用中的操作完全一样? 是的, 完全一样, 最后本地曾经创建过的RemoteDataSource(IDataSource接口)也是被MediaExtractorService本地的TinyCacheSource(DataSource)::mSource -> CallbackDataSource(DataSource)::mIDataSource所引用.
MediaExtractorService将通过MediaExtractorFactoryCreateFromService()方法完成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通过其父类DataSourcewrap()成了CDataSource, 其被DataSourceHelper引用, 供MPEG4Extractor创建时使用. MPEG4Extractor在被构造后, 也通过父类MediaExtractorPluginHelperwrap()包装为CMediaExtractorMediaExtractorFactory进一步封装为MediaExtractorCUnwrapper(父类MediaExtractor), 而MediaExtractorCUnwrapper最终通过RemoteMediaExtractor包装, 最后作为IMediaExtractor返回给mediaserver
总结:
TinyDataSource被设置到了:MediaExtractorService::makeExtractor()中的extractor->mExtractor->plugin->data->mDataSource->mSource->handle
其中:

  • extractor: IMediaExtractor -> RemoteMediaExtractor
  • extractor->mExtractor: MediaExtractor -> MediaExtractorCUnwrapper
  • extractor->mExtractor->plugin: CMediaExtractor
  • extractor->mExtractor->plugin->data: void * -> MediaExtractorPluginHelper -> MPEG4Extractor
  • extractor->mExtractor->plugin->data->mDataSource: DataSourceHelper
  • extractor->mExtractor->plugin->data->mDataSource->mSource: CDataSource
  • extractor->mExtractor->plugin->data->mDataSource->mSource->handle: DataSource -> TinyDataSource

媒体文件元数据信息MetaData的获取

解封装器IMediaExtractor返回给MediaPlayerService后, 可以开始获取元数据了, 包括有多少条Track等等, IMediaExtractor::getMetaData()负责完成到RemoteMediaExtractor的请求.
MPEG4Extractor::readMetaData()比较复杂, 放弃分析, 该函数负责将获取到的元数据保存在MPEG4ExtracotrmFileMetaData(类型为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引用, 而MediaTrackCUnwrapperRemoteMediaSource引用, RemoteMediaSource作为IMediaSource返回给MediaPlayerService, 该过程和上文返回IMediaExtractor的过程是一样的.
总结:
MPEG4Source作为MediaTrackHelper被设置在: RemoteMediaSource.mTrack->wrapper->data, 其中:

  • RemoteMediaSource.mTrack: MediaTrackCUnwrapper
  • RemoteMediaSource.mTrack->wrapper: CMediaTrack
  • RemoteMediaSource.mTrack->wrapper->data: MediaTrackHelper -> MPEG4Source

媒体源元数据的获取

媒体元也有源数据信息, 标记了该媒体源的编码类型等: 通过接口IMediaExtractor::getTrackMetaData()完成请求.
最后IMediaSource被保存到GenericSourcemVideoSource或者mAudioSource(类型为GenericSource::Tracks)的mSource成员中, 后续将用于音频/视频流数据的获取.

媒体源数据组的创建

GenericSource的准备工作完成后, 相应的媒体源也已经获取到, 则开始这些媒体源的工作, 这是会创建一个BufferGroup, 用户缓冲数据等, 调用的顺序: GenericSource::startSources() --[Binder]-> IMediaSource::startSources() => RemoteMediaSource::start() -> MediaTrackCUnwrapper::start() -> MediaBufferGroup::MediaBufferGroup() -> CMediaBufferGroup::CMediaBufferGroup(), 在媒体源开始后, CMediaBufferGroup完成对MediaBufferGroupHelper的创建.
总结:

  • MPEG4Source.mBufferGroup: MediaBufferGroupHelper
  • MPEG4Source.mBufferGroup->mGroup: CMediaBufferGroup
  • MPEG4Source.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()写入数据到binder
  • MediaBuffer不能通过binder传递, 这里又分两种情况:
    • 返回的MediaBuffer未使用共享内存, 此时抱怨一下, 然后从RemoteMediaSource的父类BnMediaSource所持有的MediaBufferGroup中分配一个共享内存的MediaBuffer, 然后获取解码器返回的数据, 拷贝到新分配的共享内存中
    • 返回的MediaBuffer使用的为共享内存, 则直接向后传递, 传递到后面, 如果是共享内存还分两种情况:
      • 共享内存形式的MediaBuffer中的IMemory是否有缓存在BnMediaSourcemIndexCache(类型为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插入GenericSourcetrack->mPackets(音频/视频).
        那么这些从IMediaSource中读取到的数据合适被读取呢? 它们将在NuPlayer::Decoder::fetchInputData()是, NuPlayer::Decoder通过GenericSource::dequeueAccessUnit()被提取.

播放器显示的设置

系统相册在播放视频时会创建一个SurfaceView, 该类在构造是通过其Java层: updateSurface() -> createBlastSurfaceControls()构造了BLASTBufferQueue类, 此时会触发Native层构造BLASTBufferQueue, 该过程将创建一对消费这和生产者:

  • IGraphicBufferProducer => BufferQueueProducer => BBQBufferQueueProducer
  • IGraphicBufferConsumer => BufferQueueConsumer => BufferQueueConsumer
    然后在上层updateSurface()过程, 通过copySurfac()方法构造Surface(Java层), 构造的方式是:Surface.copyFrom(), 这将通过底层的BLASTBufferQueue::getSurface()获取一个NativeSurface, 而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通过kWhatSetVideoSurfaceSurface发个给异步线程.NuPlayer保存上层的Surfacemediaserver使用应用传递的IGraphicBufferProducer所创建的SurfacemSurface, 并调用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), 因为此时还没有创建mVideoDecodermAudioDecoder.
这个Renderer后续通过其queueBuffer()接受MediaCodecBuffer, 它完成处理后, 通过kWhatRenderBuffer通知NuPlayer::Decoder进行MediaCodecBuffer的释放.

MediaCodec解码器的创建及初始化

NuPlayer中, 解码器由NuPlayer::Decoder进行抽象. 在NuPlayer开始后, 如上文所述, 其首先完成了IMediaSource的开始, 然后通过置身的postScanSources()异步发出了kWhatScanSources消息, 该消息被异步线程收到后, 开始执行NuPlayer::instantiateDecoder()实例化解码器, 如果是音频解码器, 分两种情况:

  • DecoderPassThrough
  • Decoder
    如果是视频解码器则只创建: Decoder
    Decoder被创建后, 其init()configure()方法被分别调用
    初始化没有太多内容, 略去. 在DecoderBase::configure()DecoderBase通过异步消息kWhatConfigure调用到子类DecoderonConfigure(), Decoder需要创建实际的解码器, 因此通过MediaCodec::CreateByType()创建MediaCodec, MediaCodecList::findMatchingCodecs()负责查找支持当前解码格式解码器的名字, 其定义在MediaCodecList.cpp, 如果找到解码器则创建MediaCodec, 创建MediaCodec时, 其mGetCodecBase被初始化为一个std::function<>对象, 后文的MediaCodec::init()会调用此lambada.
    MediaCodec创建完成后通过init()调用上文的mGetCodecBase也就是MediaCodec::GetCodecBase()创建更底层的CodecBase, CodecBase`的实现有多种:
  • CCodec
  • ACodec
  • MediaFilter

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设置在了CCodecBufferChannelmCallback方法
CCodec的实例化

初始化过程仍在MediaCodec::init()中继续, 该函数后续发出了kWhatInit消息, 并传递了解码器的名字给异步线程,kWhatInitCodecBase::initiateAllocateComponent()响应, 其对解码器进行实例化. 在CCodec创建时CCodecBufferChannel也被创建, 其继承自BufferChannelBase, 并设置在CCodecmChannel
CCodec再次发出异步消息kWhatAllocate, 由CCodec::allocate()响应. CCodec通过Codec2Client::CreateFromService()创建了Codec2Client, Codec2Client持有IComponentStore接口, 并通过其访问media.swcodecComponnetStore.
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 -> C2ComponentStore
    • C2PlatformComponentStore将创建mLibPathlibcodec2_soft_vp9dec.soComponentLoader类型
    • 最后通过C2ComponentStore创建实现了IComponentStoreV1_2::utils::ComponentStore实例, 返回给了Codec2Client
CCodec视频解码组件Component的查找
  • Codec2Client在通过createComponent()方法创建组件时, ComponentStore首先找到匹配的ComponentLoader, 在Loader的初始化过程中欧给你, 将创建ComponentModule对象
  • ComponentLoader对象从对应的libcodec2_soft_vp9dec.so中查找CreateCodec2Factory符号
  • 调用CreateCodec2Factory符号将返回C2ComponentFactory类型, 其实现为C2SoftVpxFactory
  • 然后调用工厂类的createInterface方法, 返回一个C2ComponentInterface接口, 其实现为SimpleC2Interface模板类
  • 调用C2ComponentFactorycreateInterface方法, 也就是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后续被设置给上文CCodecCCodecBufferChannel中.

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被设置到了OutputBufferQueuemIgbp, 在后文OutputBufferQueue::outputBuffer()时会用到. OutputBufferQueue是视频解码器的输出队列, 当解码器有GraphicBuffer通过C2Block2D描述返回给CCodecBufferChannel, 会注册到Codec2Client::ComponentOutputBufferQueue中, 等待后续渲染时提取并送出.
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的父类MediaCodecBuffermBuffer成员中用于后续写入未解码数据时引用.

编码数据缓存准备C2LinearBlock

对于编码数据, 其用线性数据块C2LinearBlock(实现自C2Block1D), 底层的实现是ION, 其引用关系:

  • C2LinearBlock => C2Block1D
    • mImpl: _C2Block1DImpl => C2Block1D::Impl
      • mAllocation: C2LinearAllocation => C2AllocationIon
        • mImpl: C2AllocationIon::Impl
          C2Block1D是从C2BlockPool分配的, 其引用关系:
  • C2BlockPool::mBase: C2PooledBlockPool::Impl
  • mBufferPoolManager.mImpl: ClientManager::Impl
  • mClients[x].mImpl: BufferPoolClient::Impl
  • mLocalConnection: Connectoin
  • mAccessor.mImpl: Accessor::Impl
  • mAllocator: _C2BufferPoolAllocator => BufferPoolAllocator
  • mAllocator: C2Allocator => C2AllocatorIon
  • mImpl: C2AllocationIon::Impl
  • ion_alloc()
    C2AllocatorIon::newLinearAllocation() 创建了上文的C2AllocationIon极其实现C2AllocationIon::Impl, 创建完成后进行的分配.
编码数据缓存的填充

Codec2Buffer申请完成后保存到mImpl(也就是BuffersArrayImpl), 最后作为MediaCodecBuffer(父类)返回. 请求成功之后立马通知上层, 输入缓冲可用, 该时间是通过BufferCallback::onInputBufferAvailable(), 上文提到BufferCallbackMediaCodec用来监听BufferChannelBase(也就是CCodecBufferChannel)消息的, 所以, BufferCallback会通过kWhatCodecNotifyAMessaage通知通知MediaCodec, 具体通知的消息为kWhatFillThisBuffer.
kWhatFillThisBuffer消息由MediaCodec::onInputBufferAvailable()响应, MediaBuffer继续通过mCallback(类型为AMessage)通知上层的NuPlayer::Decoder, 具体的消息类型为MediaCodec::CB_INPUT_AVAILABLE, 播放器在得知底层输入缓冲可用时, 试图提取一段输入数据.
NuPlayer::Decoder通过基类NuPlayer::DecoderBaseonRequestInputBuffers()去拉取数据, 这个过程将通过GenericSourcedequeueAccessUnit()方法完成, 注意: 此处dequeueAccessUnit()需要一个判断读取音频还是视频的参数, 继而判断通过mAudioTrack还是mVideoTrack来获取数据, 这两个成员上文已经介绍过. GenericSource::dequeueAccessUnit()上文已经讲过. 当该函数无法从缓冲区读取到数据时会通过postReadBuffer()从拉流, 该函数调用的GenericSource::readBuffer()上文已经讲过, 此处略去.
dequeueAccessUnit得到ABuffer是上文GenericSource给出的, 其所有权属于media.extractor, 其指针指向的是该进程中的共享内存, 但MediaCodecBuffer才是解码器需要的缓冲区描述, 上面说到, 该缓存其实是ION缓冲区, 已经通过写入视图(C2WriteView)映射到ABuffer, 那么什么何时从ABuffer拷贝到MediaCodecBuffer::mBufferABuffer中的呢? 是在DecoderBase::onRequestInputBuffers() -> Decoder::doRequestBuffers() -> Decoder::onInputBufferFetched()完成GenericSource::dequeueAccessUnit()后执行的. 至此编码数据已经填充到LinearBlockBufferC2Block1D(也就是C2LinearBlock)中.

解码工作描述C2Work以及编码数据描述C2Buffer的创建

通过objcpy()完成C2WorkWork的转换, 后者支持序列化, 便于通过Binder发送:

  • C2Work[] -> WorkBundle
    • C2Work -> Work
      • C2FrameData -> FrameData
        • C2InfoBuffer -> InfoBuffer
        • C2Buffer -> Buffer
          • C2Block -> Block
      • C2Worklet -> Worklet
        • C2FrameData -> FrameData
          • C2InfoBuffer -> InfoBuffer
          • C2Buffer -> Buffer
            • C2Block[1|2]D -> Block
media.swcodec对解码工作的接收以及解码数据的获取

mediaserver通过IComponent::queue()发送C2Workmedia.swcodec, 在服务端, objcpy()负责WorkBundle中的WorkC2Work的转换, 大概的层级关系:

  • WorkBundle -> C2Work[]
    *Work -> C2Work
    • FrameData -> C2FrameData
      • InfoBuffer -> C2InfoBuffer
      • Buffer -> C2Buffer
        • Block -> C2Block
    • Worklet -> C2Worklet
      • FrameData -> C2FrameData
        • InfoBuffer -> C2InfoBuffer
        • Buffer -> C2Buffer
          • Block -> 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类型, 该类型本质上是C2Block1D
  • C2ConstLinearBlock::map()返回C2ReadView, 此时C2ConstLinearBlock通过实现C2Block1D::Impl所保存的C2LinearAllocationION的缓存进行映射, 映射完成后创建AcquirableReadViewBuddy(父类为C2ReadView)并将数据保存到它的mImpl(类型为: ReadViewBuddy::Impl, 实际上就是C2ReadView::Impl)中.
    接下来uint8_t *bitstream = const_cast(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是可序列化的, 最终b2h()转化GraphicBuffer得到的HardwareBuffer将通过Binder传递给media.swcodec的解码器.
    GraphicBuffer创建后被C2Handle所引用, C2Handle通过WrapNativeCodec2GrallocHandle()创建, 在为视频时, 实现为C2HandleGralloc, 通过C2Handle进一步分配了C2GraphicAllocation, 此时C2BufferQueueBlockPoolData被创建, 主要保存GraphicBuffer的信息.
    _C2BlockFactory::CreateGraphicBlock()则负责创建C2GraphicBlock, 上文的创建的C2GraphicAllocation(子类C2AllocationGralloc)和C2BufferQueueBlockPoolData(类型为_C2BlockPoolData)保存到C2GraphicBlock的父类C2Block2DmImpl(类型为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, 而C2Acquirable<>返回的是C2GraphicView.
  • 然后wView.data()获取数据指针, 该过程通过C2GraphicView:
    • mImpl: _C2MappedBlock2DImpl
    • _C2MappedBlock2DImpl::mapping(): Mapped
    • Mapped::data(): 为上文保存的各数据分量的地址.
      最后通过copyOutputBufferToYuvPlanarFrame()完成解码后数据到GraphicBuffer的拷贝. createGraphicBuffer()负责从填充过的包含GraphicBufferC2GraphicBlock包装为C2Buffer, 然后通过fillWork代码块将C2Buffer打包到C2Work中, 等待返回. (举证请参见附件 C2GraphicBlock)
视频解码返回图形缓存

解码完成后, 解码器填充数据到C2Buffer, 该结构将被描述到C2Work -> C2Worklet -> C2FrameData -> C2Buffer(以及C2BufferInfo)中, 按照上文的描述通过Binder返回给mediaserver, 该过程将通过objcpy()完成从C2Work[]WorkBundle的转换, 该过程略去.

图形缓存的接收

mediaserver通过HidlListener(实现了接口IComponentListener)来接收WorkBundle(也就是Work[]), 上文提到objcpy()可完成WorkC2Work的转换, 该过程同样包含了各个阶段相应对象的创建, 这里只提及几个地方:

  • CreateGraphicBlock()负责创建C2GraphicBlock, 并配置给了dBaseBlocks[i](类型为C2BaseBlock)的graphic成员
  • createGraphicBuffer()负责从C2ConstGraphicBlockGraphicBuffer的转化(导入), 并保存到OutputBufferQueuemBuffers[oldSlot]. 而C2ConstGraphicBlock来子上文转化后的C2FrameData::buffers(C2Buffer)::mImpl(C2Buffer::Impl)::mData(BufferDataBuddy)::mImpl(C2BufferData::Impl)::mGraphicBlocks(C2ConstGraphicBlock)
    至此, GraphicBuffer已经完成从IComponentmediaserver的传递.
    继续向上层通知, 通知路径有两条:
  • 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原本是保存在CCodecBufferChannelOutputBufferQueue中, 在渲染时GraphicBuffer将通过BpGraphicBufferProducer::queueBuffer()被推出.

应用中的队列BLASTBufferQueue

上文说到GraphicBuffer通过CCodecBufferChannelOutputBufferQueueIGraphicBufferProducer::queueBuffer()被推送到系统相册里, Surface表面内嵌缓冲队列BLASTBufferQueue的生产者BBQBufferQueueProducer, 然后BLASTBufferQueue的消费者BufferQueueConsumer将通过父类接口ConsumerBase::FrameAvailableListener()通知对应的实现为:BLASTBufferQueue::onFrameAvailable()进行 进一步处理. BLASTBufferQueueprocessNextBufferLocked()中进一步处理收到的GraphicBuffer, 其此时构造一个SurfaceComposerClient::Transaction请求, 准备提交给SurfaceFlinger

附录

MediaPlayer将收到的通知类型

  • MEDIA_NOP
  • MEDIA_PREPARED: 来自Source::kWhatPrepared
  • MEDIA_PLAYBACK_COMPLETE: kWhatScanSources操作完成后
  • MEDIA_BUFFERING_UPDATE: 来自Source::kWhatBufferingUpdate
  • MEDIA_SEEK_COMPLETE: kWhatSeek完成后
  • MEDIA_SET_VIDEO_SIZE: 来自DecoderBase::kWhatVideoSizeChanged或者Source::kWhatVideoSizeChanged
  • MEDIA_STARTED: 来自Renderer::kWhatMediaRenderingStart
  • MEDIA_PAUSED: 主要来自pause()seekTo()两个操作
  • MEDIA_STOPPED: 主要来来自stop()操作
  • MEDIA_SKIPPED: 未使用
  • MEDIA_NOTIFY_TIME: 主要来自MediaClock回调
  • MEDIA_TIMED_TEXT: 来自Source::kWhatTimedTextData
  • MEDIA_ERROR: ext1类型为media_error_type
    • MEDIA_ERROR_UNKNOWN: 1, 具体的类型关注ext2:
      • ERROR_ALREADY_CONNECTED
      • ERROR_NOT_CONNECTED
    • MEDIA_ERROR_SERVER_DIED: 100
    • MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: 200
  • MEDIA_INFO: ext1类型为media_info_type
    • MEDIA_INFO_UNKNOWN = 1
    • MEDIA_INFO_STARTED_AS_NEXT: 准备用于下一个的播放
    • MEDIA_INFO_RENDERING_START: 首帧已播放, 来自Renderer::kWhatVideoRenderingStart
    • MEDIA_INFO_VIDEO_TRACK_LAGGING: 待解码的数据过于复杂可能会延迟较大
    • MEDIA_INFO_BUFFERING_START: 播放器内部已暂停, 等待更多的数据, 来自Source::kWhatPauseOnBufferingStart
    • MEDIA_INFO_BUFFERING_END: 播放器等待足够多的数据后恢复播放, 来自Source::kWhatResumeOnBufferingEnd
    • MEDIA_INFO_NETWORK_BANDWIDTH: 网络带宽统计信息, 来自Source::kWhatCacheStats
    • MEDIA_INFO_BAD_INTERLEAVING: 错误的数据插入?
    • MEDIA_INFO_NOT_SEEKABLE: 媒体不允许SEEK操作
    • MEDIA_INFO_METADATA_UPDATE: 有新的原数据, 来自Source::kWhatTimedMetaData
    • MEDIA_INFO_PLAY_AUDIO_ERROR: 无法播放音频数据, 来自DecoderBase::kWhatError
    • MEDIA_INFO_PLAY_VIDEO_ERROR: 无法播放视频数据, 来自DecoderBase::kWhatError
    • MEDIA_INFO_TIMED_TEXT_ERROR
  • MEDIA_SUBTITLE_DATA: 来自Source::kWhatSubtitleData
  • MEDIA_META_DATA: 来自Source::kWhatTimedMetaData
  • MEDIA_DRM_INFO: 来自Source::kWhatDrmInfo
  • MEDIA_TIME_DISCONTINUITY: 来自kWhatMediaClockNotify, MediaClock回调
  • MEDIA_IMS_RX_NOTICE: 来自RTP流的Source::kWhatIMSRxNotice
  • MEDIA_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::kWhatPauseOnBufferingStart
      • Source::kWhatResumeOnBufferingEnd
      • Source::kWhatCacheStats
        • "bandwidth"
      • Source::kWhatSubtitleData: 字幕数据
        • "buffer": ABuffer
      • Source::kWhatTimedMetaData: 元数据
        • "buffer": ABuffer
      • Source::kWhatTimedTextData: 文本数据?
        • "generation"
        • "buffer": ABuffer
          • "timeUs"
      • Source::kWhatQueueDecoderShutdown
        • "audio"
        • "video"
        • "reply"
      • Source::kWhatDrmNoLicense: 没有DRM的License
      • Source::kWhatIMSRxNotice
        • “message”: AMessage
  • kWhatRendererNotify: 来自渲染器ALooper的消息, 提供以下信息:
    • "generation"
    • "what": 有多种类型:
      • Renderer::kWhatEOS
        • "audio"
        • "finalResult"
      • Renderer::kWhatFlushComplete
        • "audio"
      • Renderer::kWhatVideoRenderingStart
      • Renderer::kWhatMediaRenderingStart
        • "audio"
      • Renderer::kWhatAudioTearDown
        • "reason": 原因
        • "positionUs": 多久
  • kWhatClosedCaptionNotify
  • kWhatAudioNotifykWhatVideoNotify负责相应来自音视频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) $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)

可以清晰的看到, 有一个Trackmime类型为"video/avc", 而另一个通过同样的方法可得知为: "audio/mp4a-latm".

C2GraphicBlock

断点:C2BufferQueueBlockPool::Impl::fetchFromIgbp_l()(C2BqBuffer.cpp:463:13):

(lldb) p *block
(std::shared_ptr) $36 = std::__1::shared_ptr::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::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::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  [email protected]
0x6fe2db5b70: 01 00 00 00 01 00 00 00 84 05 00 00 00 00 00 00  ................

可自行对比.

你可能感兴趣的:(Android,android)