目录
Android多媒体框架 & OMX
视频源播放。一个视频播放器播放视频文件需要经过的步骤流程。首先经过解协议,解协议通常是指解流媒体协议 HTTP、RTSP、MMS 等,解析为标准封装格式数据输出。我们日常说的 MP4、mkv、avi 等就是封装格式,封装格式的作用就是将已经压缩编码的视频数据和音频数据按照一定的格式组织到一起,因此解封装之后的输出就是音频压缩数据如 aac、ac3 编码,视频压缩数据如 h264 h265 编码等。我们常常把解封装的输出叫做音频轨和视频轨。有了音视频压缩编码文件,下一步就是通过解码 将压缩编码的数据解码输出为原始数据,经过音视频时间戳的同步,音频原始数据PCM通过音频驱动外放,视频原始数据YUV通过显示渲染处理,就完成了一个视频源文件的播放。
安卓最上层的java应用接口是 mediaplayer 和 mediacodec。
mediaplayer 和 mediacodec 是 Google 为多媒体应用开发设计的两套开发接口,函数调用都相对简洁明了。mediaplayer 是纯粹的播放器接口,承担的角色是上图中的所有包含解协议 解封装 解码 音视频同步 外放等。
mediaplayer 提供了一种简单的方式来播放音频和视频。它完全屏蔽掉解码通路过程中的细节。所以如果只是需要播放一个本地或服务器上的音视频文件,可以毫不犹豫的选择 MediaPlayer 接口。
mediacodec 只承担上图中解码的角色,他是多媒体的 “编解码器”,是 Android 底层多媒体支持基础框架的一部分.与 MediaPlayer prepare\start\pause\stop 的使用方式相比较,它的使用方式更为繁琐,但同时为开发者带来了更多的创造性。用户可以根据自己的环境视情况选择这两套接口。
mediaplayer 的分支,从应用接口下来通过 JNI 走到本地框架 native 层,也是通过安卓的 binder 机制,连接到 MediaPlayerService服务,service 进行播放器类型的策略选择,google 原生的 mediaplayer 实现是 nuplayer,视频解码角色由 codec2 承担,codec2 是
android12 硬解的新框架,承担的功能与之前 OMX 一致。
mediaplayerService 另外衍生出来一套 rk mediaplayer 接口实现分支,叫做RockitPlayer,Rockit 是平台开发的多媒体插件系统,向上封装了多套接口,安卓使用的 mediaplayer 插件实现,相比google原生的nuplayer,rockitplayer 支持更全的封装格式解析,rockitPlayer 的视频解码角色由一个系统服务 rockit hw-service 承担。
转到另一边从mediacodec 的分支下来,经过 JNI,mediacodec native 层的接口实现也有两条分支,一条是 ACodec + OMX 的旧分支,另一条则是codec2 新实现,android 12 硬解废弃了旧的 OMX 实现,转由 codec2 实现。
Android多媒体框架
MediaServer:MediaServer进程用于驱动playback和record,例如在组件和进程之间传递和同步缓冲区。流程通过标准的Binder机制进行通信
Application Framework
At the application framework level is application code that utilizes android.media APIs to interact with the multimedia hardware.
Binder IPC
The Binder IPC proxies facilitate communication over process boundaries.
Native Multimedia Framework
At the native level, Android provides MediaPlayerService for application layer services, each mediaplayer has a corresponding client in it and provides a state mechanism. Android provides a multimedia framework that utilizes the Stagefright engine for audio and video recording and playback. Stagefright comes with a default list of supported software codecs and you can implement your own hardware codec by using the OpenMax integration layer standard.
OpenMAX Integration Layer (IL)
The OpenMAX IL provides a standardized way for Stagefright to recognize and use custom hardware-based multimedia codecs called components.
A normal player playback architecture
parser模型
encode模型
Nuplayer
Source:数据的来源不仅是本地文件,而且是Internet上的各种协议,如:http、rtsp等。Source的任务是对数据源进行抽象,为Demux模块提供稳定的数据流,主要功能有:多媒体文件的格式检测、读取和解析文件
Parser/Demux:视频文件通常由音频流和视频流通过一些规则编织。这种规则就是容器规则。现在有许多不同的容器格式。如ts、mp4、flv、mkv、avi、rmvb等。demux的功能是将音频和视频流从容器中剥离出来,并将其发送到不同的解码器。Demux将为解码器提供解码数据流。
Decoder:player的核心模块。分为音频和视频解码器。音频和视频解码器的作用是恢复这些压缩数据(包括MPEG1 (VCD)、MPEG2 (DVD)、MPEG4、H.264等)。得到原始音频和视频数据。
Renderer:从功能上看,Renderer主要有几个功能:音频和视频原始数据的缓冲操作、音频回放(到声卡)、视频显示(到显卡)、音频和视频同步以及其他辅助回放控制操作。
在这个播放框架中,NuPlayer是Source, Demux, Decoder和Renderer之间的链接。
MediaCodec
MediaCodec是一种通过硬件加速解码和编码的编解码器。它为芯片供应商和应用程序开发人员建立了统一的接口。MediaCodec几乎是所有Android播放器的标准。
APP开发者可以采用两种多媒体JAVA库开发多媒体播放服务,分别为MediaPlayer,MediaCodec。
MediaCodec的C++层是对ACodec的封装;
ACodec完成的工作是向下调用OpenMAX的IL接口,实现音视频的编解码模块管理工作,作为媒体中间层的中间力量;
OpenMAX IL层统一了音视频编解码的调用接口,所有的芯片厂商都实现一套标准的接口,以供不同的上层调用,包括ACodec,OMXCodec。
MediaCodec与MediaPlayer/VideoView等high-level APIs相比,MediaCodec是low-level APIs,因此它提供了更加完善、灵活、丰富的接口,开发者可以实现更加灵活的功能。从API 16开始,Android提供了Mediacodec类以便开发者更加灵活的处理音视频的编解码。
Android提供了一个MediaCodecList,用于枚举设备支持的编解码器的名称和功能,以查找适当的编解码器
异步播放的方法是注册一个回调到MediaCodec,它被通知 MediaCodec:当输入缓冲区可用时,输出缓冲区可用,格式改变时。 我们需要做的是,当输入缓冲区可用时,通过MediaExtractor将数据填充到指定的缓冲区;当输出缓冲区可用时,决定是否显示帧。
使用MediaCodec解码usecase
MediaCodec的状态转换
MediaCodec提供给应用层的主要操作方法
在一个简单的APK DEMO中,一般需要包括这些主要操作方法。创建一个MediaCodec解码器实例可以采用两种方法,一种是CreateByType,一种是CreateByComponentName。
dequeueInputBuffer是从MediaCodec解码器取得一个空的Buffer,queueInputBuffer是将一个填充好码流数据的Buffer送给MediaCodec解码器,dequeueOutputBuffer是从MediaCodec解码器取得一个存放一帧图像的Buffer,renderOutputBufferAndRelease是将一个存放有一帧图像的Buffer送给GPU显示并且在显示完毕后还给MediaCodec解码器。
当应用层将最后一笔码流准备送给MediaCodec解码器后,必须再送一个空的Input Buffer并带上一个码流结束标志BUFFER_FLAG_END_OF_STREAM,这种通常用于解码。signalEndOfInputStream的作用类似于前述的操作,但通常用于录像(Record)的情况,并且录像的码流输入采用Surface的方式,当录像结束时,调用该方法告诉编码器,码流结束了。
MediaCodec提供给应用层的其他操作方法
requestActivityNotification的意思是,应用层调用该方法以得知MediaCodec解码器拥有多少个已经解码好的Output Buffer,它的一个意义在于,应用层应当尽快地取走解码好的图像数据,不要慢吞吞的,以致于有可能出现可用于解码的Buffer数量不够而使内部解码引擎解码效率降低。
releaseOutputBuffer的作用在于,取到的这个Output Buffer不送去显示而直接还给MediaCodec解码器。
MediaCodec的代码设计架构
NuPlayerDecoder和MediaCodec的交互
MediaCodec和ACodec的交互
MediaCodec实质是对ACodec的封装与管理,MediaCodec需要与ACodec进行通讯交互,包括MediaCodec直接调用ACodec方法,而ACodec采用AMessage向MediaCodec进行状态反馈与通知。
MediaCodec调用ACodec的initateConfigureComponent方法,ACodec完成OMX的组件配置工作后,将一个kWhatComponentAllocated事件放在一个AMessage消息中,即notify对象,并将该消息post出去,post的“目的地”为CodecBase(具体可见ACodec的onAllocateComponent函数),而CodecBase是继承于AHandler的虚类,它会将消息“转发”给MediaCodec
CodecBase基于AHandler构造,而MediaCodec亦基于AHandler构造,该消息将在MediaCodec的onMessageReceived中接收到并进行处理。这样的消息反馈机制
状态机MediaCodec & Acodec & OMX Plugin
OMX插件buffer sequence
EIT造型 & 依赖倒置 & 组合
三种设计原则:
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
「针对接口编程」真正的意思是「针对超类型(supertype)编程」。这里所谓的「接口」有多个含义,接口是一个「概念」,也是一种 Java 的 interface 构造。你可以在不涉及 Java interface 的情况下「针对接口编程」,关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为。
EIT造型
ALooper-AHandler-AMessage mechanism
NuPlayer是在Stagefright类的基础上构建的,使用了一个较低级的alooper - ahandler - message机制来异步处理消息。
AMessage作为消息的载体,并保存相关的信息;
ALooper是一个循环,它运行一个后台线程来循环处理接收到的消息(将信息传递给AHandler进行处理,相当于一个中继站); AHandler是一个handler,是消息的最终处理。
在NuPlayer中大量使用异步模式,因为在媒体相关的地方,很多操作都是耗时的操作,但用户对画面平滑度的容忍度很低。
流水线:Thread 包裹:AMessage
皮带:MessageQueue
转运洞口:Handler
包裹分拣器:Looper
buffer sequence
buffer轮转模型
Codec2.0是什么
在Android Q之前,Android的两套多媒体框架分别为MediaPlayer与MediaCodec,后者只负责解码与渲染工作,解封装工作由MediaExtractor代劳,MediaCodec经由ACodec层调用第三方编解码标准接口OpenMAX IL,实现硬件编解码。芯片厂商只需要支持上Khronos 制定的OpenMAX接口,就可以实现MediaCodec的硬件编解码。谷歌在Android Q上推出了Codec2.0,旨在于取代ACodec与OpenMAX,它可以看作是一套新的对接MediaCodec的中间件,往上对接MediaCodec Native层,往下提供新的API标准供编解码使用,相当于ACodec 2.0。
Codec2.0目标(Codec 2.0 Goals)
Enable new use cases, such as pre/post processing
新增 预处理/后处理
Improved performance by on-demand buffer allocation and buffer sharing
按需分配内存和内存共享改善性能
Simpler states and simpler processing
简化状态和处理过程
More precise (frame-based) processing and parameter descriptions (dependencies and supported values)
更精确的(基于单帧)处理过程和参数描述(依赖关系和支持的值)
More flexible buffers and configuration (best-effort ⇒ always valid)
更灵活的缓冲区和配置
Standard way of exposing vendor-specific extensions (settings and codecs) without modifying the media framework
标准化供应商的扩展(设置和编解码器)方法而不用修改媒体框架代码
Codec2.0框架
Codec2.0和ACodec OMX的关系如下图所示:
Codec2.0的代码目录位于/frameworks/av/media/codec2。目录结构如下:
sfplugin/CCodec.cpp是顶层实现,它提供的接口为MediaCodec Native层所调用,与libstagefright/ACodec接口一致,都继承自CodecBase,如下所示:
virtual std::shared_ptr<BufferChannelBase> getBufferChannel() override;
virtual void initiateAllocateComponent(const sp<AMessage> &msg) override;
virtual void initiateConfigureComponent(const sp<AMessage> &msg) override;
virtual void initiateCreateInputSurface() override;
virtual void initiateSetInputSurface(const sp<PersistentSurface> &surface) override;
virtual void initiateStart() override;
virtual void initiateShutdown(bool keepComponentAllocated = false) override;
virtual status_t setSurface(const sp<Surface> &surface) override;
virtual void signalFlush() override;
virtual void signalResume() override;
virtual void signalSetParameters(const sp<AMessage> ¶ms) override;
virtual void signalEndOfInputStream() override;
virtual void signalRequestIDRFrame() override;
virtual status_t querySupportedParameters(std::vector<std::string> *names) override;
virtual status_t describeParameter(
const std::string &name, CodecParameterDescriptor *desc) override;
virtual status_t subscribeToParameters(const std::vector<std::string> &names) override;
virtual status_t unsubscribeFromParameters(const std::vector<std::string> &names) override;
RK Codec2.0插件
RK Codec2.0的代码目录位于vendor/rockchip/hardware/interfaces/codec2。目录结构如下:
1)自定义系统服务创建,用于应用程序和框架之间的交互
2)开发HIDL接口
3)通过HIDL接口创建与系统服务交互的供应商服务
4) SE政策变化
关键点
理解代码中这些关键点,能大体知道codec2.0硬件插件是如何运行的。
C2Work
struct C2Work {
//链信息,插件中暂未使用
//additional work chain info not part of this work
std::shared_ptr<C2WorkChainInfo> chainInfo;
//输入待处理数据:解码输入pkt/编码输入frm
C2FrameData input;
//组件传递链路,里面包含输出缓存池的id,编解码的输出数据通过此链路往上传递
std::list<std::unique_ptr<C2Worklet>> worklets;
//处理完毕的工作片数目
uint32_t workletsProcessed;
//work处理结果,正确处理返回0
c2_status_t result;
};
C2Work类图
C2BlockPool
//获取缓存池Id,判断是否bufferqueue类型,如果是bufferqueue类型,说明上层有创建surface,否则没有创建
getLocalId()
//解码输出buffer申请
fetchGraphicBlock
//编码输出buffer申请
fetchLinearBlock
Codec2类图
组件初始化流程
组件启动流程
input Buffer的回调
Output Buffer的回调
解码
解码输入
uint8_t *inData = NULL;
size_t inSize = 0u;
C2ReadView rView = mDummyReadView;
if (!work->input.buffers.empty()) {
rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
inSize = rView.capacity();
}
inData = const_cast<uint8_t *>(rView.data());
...
//获取到输入数据后送进mpp
// may block, quit util enqueue success.
err = sendpacket(inData, inSize, frameIndex, timestamp, flags);
解码输出
//C2GraphicBlock block是由fetchGraphicBlock申请的,其中包含mpp解码输出buffer对应fd
std::shared_ptr<C2Buffer> buffer
= createGraphicBuffer(std::move(block),
C2Rect(mWidth, mHeight).at(left, top));
mOutBlock = nullptr;
{
if (mCodingType == MPP_VIDEO_CodingAVC ||
mCodingType == MPP_VIDEO_CodingHEVC ||
mCodingType == MPP_VIDEO_CodingMPEG2) {
IntfImpl::Lock lock = mIntf->lock();
buffer->setInfo(mIntf->getColorAspects_l());//将color信息设置到buffer中
}
}
...
//buffer打包好后,最终会push进work中返回给上层送显或传递
work->worklets.front()->output.buffers.push_back(mBuffer);
零拷贝
上层如果有创建surface,申请外部buffer时会默认使用BUFFERQUEUE类型的buffer pool,这个是一个环形缓存池,可以将pool中每一个buffer对应的fd送到mpp中,实现buffer的环形轮转,整个过程没有发生拷贝动作。
有关 BufferQueue 通信过程,请参见下图。
BufferQueue 包含将图像流生产方与图像流消耗方结合在一起的逻辑。本文图像生产方Codec2.0,图像来源于mpp解码输出,图像消耗方 SurfaceFlinger。下图可以清楚展示数据流方向:
关于BufferQueue详细可浏览图形 | Android 开源项目 | Android Open Source Project学习。
一次拷贝
......
编码
编码输入
......
编码输出
......
扩展
......
参考链接:
从使用Handler致内存泄漏角度源码追踪Handler工作机制(配动画)_亦游的博客-CSDN博客
BufferQueue详解 原理_王瑞-程序员资料_bufferqueue - 程序员资料
Codec2入门:框架解析_Kayson12345的博客-CSDN博客_android codec2
Codec2入门:解码组件_Kayson12345的博客-CSDN博客_codec2.0
http://www.cjcbill.com/2022/03/12/codec2-analyse/
Android OMX介绍(总括)_Android系统攻城狮的博客-CSDN博客_omx解码