android.media.MediaCodec

public final class MediaCodec
extends Object
java.lang.Object
↳ android.media.MediaCodec


MediaCodec类可用于访问底层媒体编解码器,即编码器/解码器组件。 它是Android底层多媒体支持架构的一部分(通常结合 MediaExtractor,MediaSync,MediaMuxer,MediaCrypto,MediaDrm,Image,Surface 和 AudioTrack 一起使用)

android.media.MediaCodec_第1张图片

广义地说,编解码器是用于处理输入数据以产出输出数据。 它以异步方式使用一组输入、输出缓存区来处理数据。 简单地说,您请求(或接收)一个空的输入缓存区,向缓存区输入数据并将其交给编解码器进行处理。 编解码器处理数据并将其转换到一个空的输出缓存区。 最后,您请求(或接收)一个有数据的输出缓存区,消耗其内容并将该输出缓存区释放回编解码器。

Data Types 数据类型

编解码器可用于三种数据:压缩数据,原始音频数据和原始视频数据。 这三种数据都可以可以使用 ByteBuffers 来处理,但为了提高编解码器处理原始视频数据的性能,你应该使用 Surface。 Surface 使用本地视频缓存区而不映射或复制到 ByteBuffers; 因此,效率更高。 通常在使用 Surface 时无法访问原始视频数据,但您可以使用 ImageReader 类访问不安全的解码(原始)视频帧。 这可能仍然比使用 ByteBuffers 有效,就像一些本机缓存区可能被映射到 direct ByteBuffers。 当使用 ByteBuffer 模式时,您可以使用 Image 类和 getInputImage(int index)/getOutputImage(int index) 方法访问原始视频帧。

Compressed Buffers 压缩缓存区

输入缓存器(用于解码器)和输出缓存器(用于编码器)根据格式的类型包含压缩数据。 对于视频类型,这是一个单一的压缩视频帧。 对于音频数据,这通常是单个访问单元(通常包含由格式类型指定的几毫秒的音频的编码音频段),但是这个要求稍微放宽,因为缓存器可以包含多个编码的音频访问单元。 在任一情况下,缓存区不会以任意字节边界开始或结束,而是在帧/访问单元边界上。

Raw Audio Buffers 原始音频缓存区

原始音频缓存区包含PCM音频数据的整个帧,原始音频缓存区是频道序列中每个频道的一个样本。 每个样本在本地字节序列中是一个16位有符号整数。

short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
   ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
   MediaFormat format = codec.getOutputFormat(bufferId);
   ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
   int numChannels = formet.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
   if (channelIx < 0 || channelIx >= numChannels) {
     return null;
   }
   short[] res = new short[samples.remaining() / numChannels];
   for (int i = 0; i < res.length; ++i) {
     res[i] = samples.get(i * numChannels + channelIx);
   }
   return res;
}

Raw Video Buffers 原始视频缓存区

在 ByteBuffer 模式下,视频缓存区根据其颜色格式进行布局。 您可以以数组的形式从 getCodecInfo().getCapabilitiesForType(…).colorFormats 获取支持的颜色格式。 视频编解码器可能支持三种颜色格式:

  • 本地原始视频格式:由 COLOR_FormatSurface 标记,可以与输入或输出Surface一起使用。

  • 灵活的YUV缓存区(如 COLOR_FormatYUV420Flexible ):它们可以通过使用 getInputImage(int index)/getOutputImage(int index) 与输入/输出 Surface,就像在 ByteBuffer 模式中一样。

  • 其他,特定格式:通常只能在 ByteBuffer 模式下支持。一些颜色格式是供应商特定的。 其他的在 MediaCodecInfo.CodecCapabilities 中定义。对于等同于灵活格式的颜色格式,您仍然可以使用 getInputImage(int index)/getOutputImage(int index)。

从 API 22 开始所有视频编解码器都支持灵活的YUV 4:2:0缓存区。

Accessing Raw Video ByteBuffers on Older Devices

在旧设备上访问原始视频字节缓存区

在 API 21 和 Image 支持之前,您需要使用 KEY_STRIDE 和 KEY_SLICE_HEIGHT 输出格式值来了解原始输出缓存区的布局。

请注意,在某些设备上,切片高度被标示为0。这可能意味着切片高度与帧高度相同,或者切片高度与帧高度对齐为某个值(通常是 2)。 不幸的是,在这种情况下,没有标准和简单的方式来说明实际的切片高度。 此外,平面格式的U平面的垂直跨度也没有被指定或定义,尽管通常它是切片高度的一半。

KEY_WIDTH 和 KEY_HEIGHT 指定视频帧的大小(即:宽高); 然而,对于大多数 encondings,视频(图片)仅占据视频帧的一部分。 通过“裁剪矩形”表示。

您需要使用以下属性从输出格式获取原始输出图像的裁剪矩形。 如果这些属性不存在,则视频占据整个视频帧。剪裁矩阵应该放在旋转之前在输出帧的上下文中去理解。

Format Key Type Description
“crop-left” Integer The left-coordinate (x) of the crop rectangle
“crop-top” Integer The top-coordinate (y) of the crop rectangle
“crop-right” Integer The right-coordinate (x) MINUS 1 of the crop rectangle
“crop-bottom” Integer The bottom-coordinate (y) MINUS 1 of the crop rectangle

右和下坐标可以被理解为裁剪的输出图像的最右边有效列/最下有效行的坐标。

视频帧的大小(旋转之前)可以这样计算:

 MediaFormat format = decoder.getOutputFormat(…);
 int width = format.getInteger(MediaFormat.KEY_WIDTH);
 if (format.containsKey("crop-left") && format.containsKey("crop-right")) {
     width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left");
 }
 int height = format.getInteger(MediaFormat.KEY_HEIGHT);
 if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
     height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
 }

另请注意,BufferInfo.offset 的意义在不同设备间是不一致的。 在某些设备上,偏移指向裁剪矩形的左上角像素,而在大多数设备上,它指向整个帧的左上角像素。

States 状态

在其生命周期中,从概念上讲编解码器处于三种状态中的一种:停止执行释放停止 实际上是三个状态的集合:未初始化已配置错误,而从概念上讲 执行 贯穿三个子状态:已刷新运行中流末端

android.media.MediaCodec_第2张图片

当您使用某种工厂方法实例化编解码器时,编解码器处于 未初始化/Uninitialized 状态。 首先,您需要通过 configure(…) 进行配置,使其进入 已配置/Configured 状态,然后调用 start() 将其移动到 执行/Executing 状态。 在这种状态下,您可以通过上面描述的缓存区队列操作来处理数据。

执行 状态有三个子状态:已刷新运行中流末端。 在 start() 之后,编解码器立即处于 已刷新/Flushed 子状态,它保存所有的缓存区。 一旦第一个输入缓存区出现,编解码器就会进入到 运行/Running 子状态,这个状态占据了大部分的生命周期。 当您使用 流末端标记/end-of-stream marker 对输入缓存区进行排队时,编解码器将进入 流末端/End-of-Stream 子状态。 在这种状态下,编解码器不再接收输入缓存,但仍然产出输出缓存,直到读到 流末端/end-of-stream 的标记。 在 执行 状态下,您可以随时使用 flush() 来返回到 已刷新/Flushed 子状态。

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

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

Creation 创建

使用 MediaCodecList 为特定的 MediaFormat 创建一个 MediaCodec。 当解码文件或流的时候,可以使用 MediaExtractor.getTrackFormat 方法获取所需的格式。 使用 MediaFormat.setFeatureEnabled 方法设置要添加的任何特定功能,然后调用 MediaCodecList.findDecoderForFormat 方法来获取可以处理该特定媒体格式的编解码器的名称。 最后,使用 createByCodecName(String) 实例化这个编解码器。

注意:在 API 21 中,MediaCodecList.findDecoder/EncoderForFormat 的格式不包含帧率。 使用 format.setString(MediaFormat.KEY_FRAME_RATE,null) 清除所有当前帧率的设置。

您也可以使用 createDecoder/EncoderByType(String) 为特定的MIME类型创建首选编解码器。 然而,这不能用于注入特征,并且可能创建无法处理特定需求媒体格式的编解码器。

Creating secure decoders 创建安全解码器

On versions KITKAT_WATCH and earlier, secure codecs might not be listed in MediaCodecList, but may still be available on the system. Secure codecs that exist can be instantiated by name only, by appending “.secure” to the name of a regular codec (the name of all secure codecs must end in “.secure”.) createByCodecName(String) will throw an IOException if the codec is not present on the system.

From LOLLIPOP onwards, you should use the FEATURE_SecurePlayback feature in the media format to create a secure decoder.

在 API 20 及更早版本上,安全编解码器可能未列在 MediaCodecList 中,但仍然在系统上可用。 目前存在的安全编解码器仅可以通过名称实例化,常规编解码器的名称后需要追加“.secure”(所有安全编解码器的名称必须以“.secure”结尾),如果编解码器不存在于系统上,调用 createByCodecName(String) 时将抛出 IOException。

从 API 21 开始,您应该使用媒体格式的 FEATURE_SecurePlayback 功能来创建安全解码器。

Initialization 初始化

创建编解码器后,如果要异步处理数据,则可以使用 setCallback 设置回调。 然后,使用特定的媒体格式 配置/configure 编解码器。 这个时候您可以为 视频生产者 - 生成原始视频数据的编解码器(例如视频解码器) 指定输出 Sufface 。 同时您也可以设置安全编解码器的解密参数(详见 MediaCrypto )。 最后,由于某些编解码器有多种模式,因此您必须指定要将其用作解码器还是编码器。

从 API 21 开始,您可以在 已配置/Configured 状态下查询生成的输入和输出格式。 您可以使用它来验证结果的配置,例如 在开始编解码之前验证颜色格式。

如果您想要在本地通过 视频消费者 - 一个处理原始视频输入的编解码器 处理原始输入视频缓存区,例如一个视频编码器 - 可在配置后使用 createInputSurface() 给你的输入数据创建一个目标 Surface。 或者,通过调用 setInputSurface(Surface) 使用先前创建的 持久化输入surface/persistent input surface 来设置编解码器。

Codec-specific Data 特殊的编解码数据

某些格式,特别是AAC音频和MPEG4,H.264和H.265视频格式要求实际数据的前缀是包含设置参数的缓存区或包含编解码器特定数据的缓存区。当处理这种压缩格式时,必须在调用 start() 方法之后且在处理任何帧数据之前将这种数据提交给编解码器。 在调用 queueInputBuffer 时,必须使用 BUFFER_FLAG_CODEC_CONFIG 标记此类数据。

Codec-specific 数据也可以以传递给 配置/configure 的格式包含在带有“csd-0”,“csd-1”等字符串的ByteBuffer条目中。这些关键字一般包含在从 MediaExtractor 获取的 媒体格式/MediaFormat 轨道中。 这种格式的 Codec-specific 数据在调用 start() 方法时自动提交到编解码器; 您不能显示的提交这些数据。 如果格式中不包含编解码器特定数据,您可以根据格式要求,选择使用指定大小的缓存区以正确的顺序提交数据。 在H.264 AVC的情况下,您还可以将其作为单个 codec-config 缓存区 连接所有 codec-specific 数据并提交这些数据。

Android使用以下 codec-specific 数据缓存区。 合适的 MediaMuxer 轨道配置仍然需要用轨道格式进行设置。 每个参数集和标有(*)的 codec-specific-data段 必须以起始代码“\ x00 \ x00 \ x00 \ x01”开头。

Format CSD buffer #0 CSD buffer #1 CSD buffer #2
AAC Decoder-specific information from ESDS* Not Used Not Used
VORBIS Identification header Setup header Not Used
OPUS Identification header Pre-skip in nanosecs(unsigned 64-bit native-order integer.)This overrides the pre-skip value in the identification header. Seek Pre-roll in nanosecs(unsigned 64-bit native-order integer.)
MPEG-4 Decoder-specific information from ESDS* Not Used Not Used
H.264AVC SPS(Sequence Parameter Sets*) PPS(Picture Parameter Sets*) Not Used
H.265HEVC VPS (Video Parameter Sets*) +SPS(Sequence Parameter Sets*) +PPS(Picture Parameter Sets*) Not Used Not Used
VP9 VP9 CodecPrivate Data(optional) Not Used Not Used

注意: 在任何输出缓存区或输出格式更改返回之前,如果编解码器要立即被刷新或刚刚启动不就,此时必须小心,因为在刷新过程中可能会丢失编解码器特定数据。 您必须在这样的刷新之后使用标记为 BUFFER_FLAG_CODEC_CONFIG 的缓存区重新提交数据,以确保正确的编解码器操作。

编码器(或生成压缩数据的编解码器)将在标有 codec-config 标志 的输出缓存区中的任何有效输出缓存区之前创建并返回编解码器专用数据。 包含 codec-specific-data 的缓存区含有无意义的时间戳。

Data Processing 数据处理

你可能感兴趣的:(Android)