Android MediaCodec

一、MediaCodec介绍

MediaCodec是Android音视频中相当重要的一个API。MediaCodec类可以用于使用一些基本的多媒体编解码器(音视频编解码组件),它是Android基本的多媒体支持基础架构的一部分通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack 一起使用。

广义而言,编解码器处理输入数据以生成输出数据。 它异步处理数据并使用一组输入和输出缓冲区。 在简单的层面上,您请求(或接收)一个空输入缓冲区,填充数据并将其发送到编解码器进行处理。 编解码器使用数据并将其转换为其空的输出缓冲区之一。 最后,您请求(或接收)一个填充的输出缓冲区,消耗其内容并将其释放回编解码器。

一个编解码器可以处理输入的数据来产生输出的数据,编解码器使用一组输入和输出缓冲器来异步处理数据。你可以创建一个空的输入缓冲区,填充数据后发送到编解码器进行处理。编解码器使用输入的数据进行转换,然后输出到一个空的输出缓冲区。最后你获取到输出缓冲区的数据,消耗掉里面的数据,释放回编解码器。如果后续还有数据需要继续处理,编解码器就会重复这些操作。

上图是一张MediaCodec编码器的数据流程图,1)对于提供数据的Client来说,首先是向Codec申请一组空buffers(图中的inputbuffers),然后将数据放入buffers中,最后将装满数据的buffers还给Codec;2)对于消耗数据的Client来说,先向Codec申请一组带处理好的数据的buffers(图中的outputbuffers),然后消费buffers中的数据,最后将buffers还给Codec。

MediaCodec的编解方式

MediaCodec可以通过同步和异步两种方式进行编解码。同步编解码是指在主线程中进行解码,这种方式简单易懂,但是会导致主线程阻塞,影响用户体验。而异步编解码则是在子线程中进行编解码,不会阻塞主线程,可以提高应用的响应速度和流畅度。在异步编解码中,需要开辟两个线程,分别用于视频和音频的编解码,通过线程间的通信来实现音视频的同步播放。

在Android 5.0 之后,google 建议使用异步解码的方式去使用 MediaCodec。

MediaCodec生命周期

MediaCodec 有三种状态,分别是执行(Executing)、停止(Stopped)和释放(Released),其中执行和停止分别有三个子状态,执行的三个字状态分别是 Flushed、Running 和 Stream-of-Stream,停止的三个子状态分别是 Uninitialized、Configured 和 Error,MediaCodec 生命周期示意图如下:

上图是Codec的生命周期示意图,从图中可以看出:

1)当创建编解码器的时候处于未初始化状态。首先你需要调用configure(…)方法让它处于Configured状态,然后调用start()方法让其处于Executing状态。在Executing状态下,你就可以使用上面提到的缓冲区来处理数据。

2)Executing的状态下也分为三种子状态:Flushed, Running、End-of-Stream。在start() 调用后,编解码器处于Flushed状态,这个状态下它保存着所有的缓冲区。一旦第一个输入buffer出现了,编解码器就会自动运行到Running的状态。当带有end-of-stream标志的buffer进去后,编解码器会进入End-of-Stream状态,这种状态下编解码器不在接受输入buffer,但是仍然在产生输出的buffer。此时你可以调用flush()方法,将编解码器重置于Flushed状态。

3)调用stop()将编解码器返回到未初始化状态,然后可以重新配置。 完成使用编解码器后,您必须通过调用release()来释放它。

4)在极少数情况下,编解码器可能会遇到错误并转到错误状态。 这是使用来自排队操作的无效返回值或有时通过异常来传达的。 调用reset()使编解码器再次可用。 您可以从任何状态调用它来将编解码器移回未初始化状态。 否则,调用 release()动到终端释放状态。

MediaCodec本身并不具备Codec能力,通过调动底层编解码组件获得了Codec的能力,底层的编解码组件包括ACodec(采用 OMX 框架的解码器实现)和CCodec(采用 Codec 2 框架的解码器实现),如下是MediaCodec与ACodec组件的关系图:

MediaCodec 使用异步消息处理机制(AMessage/ALooper/AHandler)

编解码器支持的数据类型

编解码器对三种数据进行操作:压缩数据,原始音频数据和原始视频数据。 所有三种数据都可以使用ByteBuffers进行处理,但您应该使用Surface作为原始视频数据以提高编解码器的性能。 Surface使用本地视频缓冲区而不映射或将它们复制到ByteBuffers; 因此,它更有效率。 使用Surface时通常无法访问原始视频数据,但可以使用ImageReader类来访问不安全的已解码(原始)视频帧。 这可能仍然比使用ByteBuffers更高效,因为某些本地缓冲区可能映射到direct ByteBuffers中。 当使用ByteBuffer的模式,您可以使用访问原始视频帧Image类和getInput / OutputImage(int) 。

Compressed Buffers

根据format's type,输入缓冲器(用于解码器)和输出缓冲器(用于编码器)包含压缩数据。 对于视频类型,这是一个单一的压缩视频帧。 对于音频数据,这通常是单个访问单元(编码音频段通常包含由格式类型指示的几毫秒的音频),但是由于缓冲器可能包含多个编码的音频存取单元,所以这个要求稍微宽松。 无论哪种情况,缓冲区都不会以任意字节边界开始或结束,而是在帧/访问单元边界上开始或结束。

Raw Audio Buffers

原始音频缓冲区包含整个PCM音频数据帧,这是通道顺序中每个通道的一个样本。 每个样本都是16-bit signed integer in native byte order 。

Raw Video Buffers

在ByteBuffer模式下,视频缓冲区按照其color format进行布局 。 您可以从getCodecInfo()  .  getCapabilitiesForType(…)  .  colorFormats获得支持的颜色格式。 视频编解码器可支持三种颜色格式:

  • native raw video format: This is marked by COLOR_FormatSurface and it can be used with an input or output Surface.

  • flexible YUV buffers (such as COLOR_FormatYUV420Flexible): These can be used with an input/output Surface, as well as in ByteBuffer mode, by using getInput/OutputImage(int).

  • other, specific formats: These are normally only supported in ByteBuffer mode. Some color formats are vendor specific. Others are defined in MediaCodecInfo.CodecCapabilities. For color formats that are equivalent to a flexible format, you can still use getInput/OutputImage(int).

自 LOLLIPOP_MR1以来,所有视频编解码器都支持灵活的YUV 4:2:0缓冲区。

特定编解码格式数据

特定编解格式数据,我们暂且将其理解为一些重要编解码信息的载体。在MediaCodec编解码过程中,当遇到AAC音频格式及MPEG-4、H.264和H.265视频格式时,需要设置大量的Buffer,或者将特定信息载体作为真实数据的开头,如H.264中就包括SPS、PPS等重要信息。在处理这样的压缩格式,进行编解码操作前,调用start方法后,这些载体是编解码的依据,所以要立刻传给MediaCodec。当调用MediaCodec的queueInputBuffer函数时,这些数据必须使用flag:BUFFER_FLAG_CODEC_CONFIG,表明已配置好,可以进行真正的编解码操作了。

特定编解码格式数据也可以包含在ButeBuffer条目的格式中传递给配置钥匙”csd-0"、“csd-1”等。这些钥匙总是包含从MediaExtractor类中获取视频源的格式。当调用start方法时,特定编解码格式数据格式自动提交给编解码器,如果不包含特定编码的数据格式,你可以按照格式要求选择以正确的顺序提交到指定的缓冲区。对于H.264(AVC),还可以连接所有信息载体数据并提交作为单个编解码配置缓冲区。

二、MediaCodec API

static MediaCodec createByCodecName(String name):如果您知道要实例化的组件的确切名称(如OMX.google.mp3.decoder),请使用此方法将其实例化。
static MediaCodec createDecoderByType(String type):实例化支持给定MIME类型(如"video/avc")的输入数据的首选解码器。
static MediaCodec createEncoderByType(String type):实例化支持给定MIME类型(如"video/avc")的输出数据的首选编码器。
void configure(@Nullable MediaFormat format, @Nullable Surface surface, @Nullable MediaCrypto crypto, @ConfigureFlag int flags) :Configures a component.
void configure(@Nullable MediaFormat format, @Nullable Surface surface, @ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) :Configure a component to be used with a descrambler.
void setCallback(MediaCodec.Callback cb, Handler handler):为可操作的MediaCodec事件设置异步回调。
void setCallback(MediaCodec.Callback cb):为默认循环中的可操作MediaCodec事件设置异步回调。
final void start():开始编解码器
final void stop():停止编解码器
final void flush():冲洗组件的输入和输出端口。
final void reset():将编解码器返回到其初始(未初始化)状态。
final void release():释放编解码器实例使用的资源。
MediaCodecInfo getCodecInfo():获取编解码器信息。
final void setParameters(Bundleparams):将其他参数更改传递给组件实例。
void setOutputSurface(Surface surface):动态设置编解码器的输出表面。
Image getInputImage(int index):返回出队输入缓冲区索引的可写Image对象,以包含原始输入视频帧。
Image getOutputImage(int index):返回包含原始视频帧的出列输出缓冲区索引的只读Image对象。
ByteBuffer getInputBuffer(int index):获取输入缓冲区
int dequeueInputBuffer (long timeoutUs):获取可用的输入缓冲区的索引
final void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags):将填满数据的inputBuffer提交到编码队列
ByteBuffer getOutputBuffer(int index):获取输出缓冲区
int dequeueOutputBuffer (MediaCodec.BufferInfo info, long timeoutUs):获取已成功编解码的输出缓冲区的索引
final void releaseOutputBuffer(int index, boolean render):释放输出缓冲区

三、MediaCodec Message

kWhatCodecNotify
kWhatError
kWhatComponentAllocated
kWhatComponentConfigured
kWhatInputSurfaceCreated
kWhatInputSurfaceAccepted
kWhatSignaledInputEOS
kWhatStartCompleted
kWhatOutputBuffersChanged
kWhatOutputFramesRendered
kWhatFirstTunnelFrameReady
kWhatFillThisBuffer
kWhatDrainThisBuffer
kWhatEOS
kWhatStopCompleted
kWhatReleaseCompleted
kWhatFlushCompleted
kWhatInit
kWhatSetNotification
kWhatSetCallback
kWhatConfigure
kWhatSetSurface
kWhatCreateInputSurface
kWhatSetInputSurface
kWhatStart
kWhatStop
kWhatRelease
kWhatDequeueInputBuffer
kWhatDequeueInputTimedOut
kWhatQueueInputBuffer
kWhatDequeueOutputBuffer
kWhatDequeueOutputTimedOut
kWhatReleaseOutputBuffer
kWhatSignalEndOfInputStream
kWhatGetBuffers
kWhatFlush
kWhatGetInputFormat
kWhatGetOutputFormat
kWhatRequestIDRFrame
kWhatRequestActivityNotification
kWhatGetName
kWhatGetCodecInfo
kWhatSetParameters
kWhatDrmReleaseCrypto
kWhatCheckBatteryStats

四、MediaCodec相关类

JAVA类

MediaCodec

MediaCodec 类可用于访问低级媒体编解码器,即编码器/解码器组件。

MediaCodec 代码位于:

frameworks/base/media/java/android/meida/MediaCodec.java

MediaCodec 的定义:

final public class MediaCodec {
public final static class BufferInfo {}
private class EventHandler extends Handler {}
public static abstract class Callback {}
public final static class CryptoInfo {}
}

MediaCodecInfo

提供有关设备上可用的给定媒体编解码器的信息。

MediaCodecInfo代码位于:

frameworks/base/media/java/android/meida/MediaCodecInfo.java

MediaCodecInfo的定义:

public final class MediaCodecInfo {
    public static final class AudioCapabilities {}
    public static final class VideoCapabilities {}
    public static final class EncoderCapabilities {}
    public static final class CodecProfileLevel {}
}

MediaCodecList

允许您枚举可用的编解码器,查找支持给定格式的编解码器以及查询给定编解码器的功能。

MediaCodecList代码位于:

frameworks/base/media/java/android/meida/MediaCodecList.java

MediaCodecList的定义:

final public class MediaCodecList {}

C++类

NuPlayer::DecoderBase

NuPlayer::DecoderBase代码位于:

frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoderBase.cpp

frameworks/av/media/libmediaplayerservice/nuplayer/include/nuplayer/NuPlayerDecoderBase.h

NuPlayer::DecoderBase的定义:

struct NuPlayer::DecoderBase : public AHandler {}

NuPlayer::Decoder

NuPlayer的解码器对象, 有NuPlayer::Decoder和NuPlayer::DecoderPassThrough两种实现。

NuPlayer::Decoder代码位于:

frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp

frameworks/av/media/libmediaplayerservice/nuplayer/include/nuplayer/NuPlayerDecoder.h

NuPlayer::Decoder的定义:

struct NuPlayer::Decoder : public DecoderBase {}

NuPlayer::DecoderPassThrough

NuPlayer::DecoderPassThrough代码位于:

frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoderPassThrough.cpp

frameworks/av/media/libmediaplayerservice/nuplayer/include/nuplayer/NuPlayerDecoderPassThrough.h

NuPlayer::DecoderPassThrough的定义:

struct NuPlayer::DecoderPassThrough : public DecoderBase {}

NuPlayer::CCDecoder

NuPlayer::CCDecoder代码位于:

frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp

frameworks/av/media/libmediaplayerservice/nuplayer/include/nuplayer/NuPlayerCCDecoder.h

NuPlayer::CCDecoder的定义:

struct NuPlayer::CCDecoder : public RefBase {}

MediaCodec

实际的解码器接口, 其负责将部分上层的同步操作转换为异步操作, 其引用一个CodecBase。

MediaCodec代码位于:

frameworks/av/media/libstagefright/MediaCodec.cpp

frameworks/av/media/libstagefright/include/media/stagefright/MediaCodec.h

MediaCodec的定义:

struct MediaCodec : public AHandler {}

MediaCodecInfo

MediaCodecInfo代码位于:

frameworks/av/media/libmedia/MediaCodecInfo.cpp

frameworks/av/media/libmedia/include/media/MediaCodecInfo.h

MediaCodecInfo的定义:

struct MediaCodecInfo : public RefBase {}
struct MediaCodecInfoWriter {}

IMediaCodecList

MediaCodecList 代码位于:

frameworks/av/media/libmedia/IMediaCodecList.cpp

frameworks/av/media/libmedia/include/media/IMediaCodecList.h

MediaCodecList 的定义:

class IMediaCodecList: public IInterface {}
class BnMediaCodecList: public BnInterface<IMediaCodecList> {}

MediaCodecList

MediaCodecList 代码位于:

frameworks/av/media/libstagefright/MediaCodecList.cpp

frameworks/av/media/libstagefright/include/media/stagefright/MediaCodecList.h

MediaCodecList 的定义:

struct MediaCodecList : public BnMediaCodecList {}

CodecBase

CodecBase代码位于:

frameworks/av/media/libstagefright/CodecBase.cpp

frameworks/av/media/libstagefright/include/media/stagefright/CodecBase.h

CodecBase的定义:

struct CodecBase : public AHandler, /* static */ ColorUtils {
class BufferCallback {} //MediaCodec 和 CodecBase 对象之间的通道,用于管理缓冲区传递。只有 MediaCodec 需要调用这些方法,并且底层 CodecBase 实现应单独定义自己的接口。 
class CodecCallback {}
}
class BufferChannelBase {}

ACodec

采用 OMX 框架的解码器实现

ACodec代码位于:

frameworks/av/media/libstagefright/ACodec.cpp

frameworks/av/media/libstagefright/include/media/stagefright/ACodec.cpp

ACodec的定义:

struct ACodec : public AHierarchicalStateMachine, public CodecBase {
struct MessageList : public RefBase {}
struct CodecObserver : public BnOMXObserver {} //编解码器观察者
struct ACodec::DeathNotifier : public IBinder::DeathRecipient, public ::android::hardware::hidl_death_recipient {} //死亡通知程序
struct ACodec::BaseState : public AState {}
struct ACodec::UninitializedState : public ACodec::BaseState {
struct ACodec::LoadedState : public ACodec::BaseState {}
struct ACodec::LoadedToIdleState : public ACodec::BaseState {}
struct ACodec::ExecutingState : public ACodec::BaseState {}
struct ACodec::OutputPortSettingsChangedState : public ACodec::BaseState {}
struct ACodec::ExecutingToIdleState : public ACodec::BaseState {}
struct ACodec::IdleToLoadedState : public ACodec::BaseState {}
struct ACodec::FlushingState : public ACodec::BaseState {}
}

ACodec中的CodecObserver是一个OMX回调接口,用于接收OMX组件的事件通知和状态变化。在ACodec中,当OMX组件状态发生变化时,会通过CodecObserver回调函数通知ACodec,从而触发ACodec中的状态变化。例如,在ACodec中,当OMX组件状态从LoadedState变为IdleState时,会通过CodecObserver回调函数通知ACodec,从而触发ACodec中的状态变化。此外,CodecObserver还可以用于接收OMX组件的事件通知,例如当OMX组件接收到EOS事件时,会通过CodecObserver回调函数通知ACodec。

ACodec中的DeathNotifier是一个回调函数,用于在Binder通信中检测到远程服务崩溃时通知客户端。在ACodec中,DeathNotifier的作用是在检测到MediaCodecServer进程崩溃时,通知客户端并清理相关资源,以避免资源泄漏和其他问题的发生。具体来说,当MediaCodecServer进程崩溃时,DeathNotifier会调用ACodec的signalError函数,该函数会向客户端发送OMX_ErrorUndefined错误信号,并清理相关资源。这样,客户端就可以在崩溃发生时及时处理,并避免对系统造成不必要的影响

ACodec中的BaseState是一个状态机,它定义了ACodec的基本状态。在BaseState中,ACodec可以处理一些通用的状态转换,例如从IdleState转换到ExecutingState或者从ExecutingState转换到IdleState。此外,BaseState还定义了一些通用的状态转换函数,例如RESUBMIT_BUFFERS和FREE_BUFFERS,这些函数可以在不同的状态下被调用,以便ACodec可以正确地处理buffer的分配和释放。因此,BaseState在ACodec中扮演着非常重要的角色,它为ACodec提供了一个通用的状态转换框架,使得ACodec可以更加灵活地处理不同的状态转换。

ACodec中的MessageList是一个消息列表,用于存储OMX消息。在ACodec中,OMX消息是通过AMessage进行处理的。当ACodec接收到OMX消息时,它会将OMX消息转换为AMessage,并将其添加到MessageList中。然后,ACodec会处理MessageList中的所有消息,并将它们发送到CodecObserver的mNotify中。这个过程中,MessageList的作用是将所有的OMX消息打包成一个AMessage,方便ACodec进行处理和发送。

ACodecBufferChannel

ACodec 的 BufferChannelBase 实现,用于管理缓冲区传递。

ACodecBufferChannel代码位于:

frameworks/av/media/libstagefright/ACodecBufferChannel.cpp

frameworks/av/media/libstagefright/include/ACodecBufferChannel.cpp

ACodecBufferChannel的定义:

class ACodecBufferChannel : public BufferChannelBase {}

CCodec

采用 Codec 2 框架的解码器实现

CCodec代码位于:

frameworks/av/media/code2/sfplugin/CCodec.cpp

frameworks/av/media/code2/sfplugin/include/media/stagefright/CCodec.h

CCodec的定义:

class CCodec : public CodecBase {}

DataConverter

DataConverter代码位于:

frameworks/av/media/libstagefright/DataConverter.cpp

frameworks/av/media/libstagefright/include/DataConverter.h

DataConverter的定义:

struct DataConverter : public RefBase {}
struct SampleConverterBase : public DataConverter {}
struct AudioConverter : public SampleConverterBase {}

MediaResource

MediaResource代码位于:

frameworks/av/media/libmedia/MediaResource.cpp

frameworks/av/media/libmedia/include/MediaResource.h

MediaResource的定义:

class MediaResource : public MediaResourceParcel {}

五、MediaCodec流程分析

MediaCodec的创建流程分析

MediaCodec configure流程分析

MediaCodec start流程分析

MediaCodec stop流程分析

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值