MediaCode专题

MediaCodec专题(一):简介

2020-07-17  本文已影响0人  yzzCool

前言

MediaCodec大坑绝对是大坑,坑的很直溜。
本系列是参考 [奇卓社]的文章,喜欢的小伙伴可以直接去看[奇卓社]。

参考文章

  1. 官方MediaCodec
  2. Android视频处理之MediaCodec-1-简介

MediaCodec是什么?

   从API 16(Android 4.1)开始,Android提供了MediaCodec类以便开发者更加灵活的处理音视频的编解码,MediaCodec类可以访问底层媒体编解码器框架(StageFright或openMAX),即编码器/解码器组件。这是Android low-level多媒体支持基础设施的一部分(通常与MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack.一起使用))。

MediaCodec 大纲

一图胜千言::


MediaCodec工作原理.png

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

数据类型

编解码器对三种数据进行操作:compressed data 压缩数据(即为经过H264. H265等编码的视频数据或AAC等编码的音频数据),raw audio data 原始音频数据和raw video data 原始视频数据。
三种类型的数据均可以利用ByteBuffers进行处理,但是对于原始视频数据应提供一个Surface以提高编解码器的性能。
Surface直接使用本地视频数据缓存(native video buffers),而没有映射或复制数据到ByteBuffers,因此,这种方式会更加高效。在使用Surface的时候,通常不能直接访问原始视频数据,但是可以使用ImageReader类来访问非安全的解码(原始)视频帧。这仍然比使用ByteBuffers更加高效,因为一些本地缓存(native buffer)可以被映射到 direct ByteBuffers。当使用ByteBuffer模式,你可以利用Image类和getInput/OutputImage(int)方法来访问到原始视频数据帧。

Compressed Buffers 压缩缓冲区

输入缓冲区(用于解码器)和输出缓冲区(用于编码器)根据MediaFormat#KEY_MIME包含压缩数据。
对于视频类型,通常是单个压缩视频帧。
对于音频数据,这通常是一个访问单元(一个编码的音频段,通常包含几毫秒的音频,这由格式类型决定),但是由于缓冲区中可能包含多个编码的访问单元,因此这一要求稍微有所放松。
无论哪种情况,缓冲区都不会在任意字节边界处开始或结束,而不会在帧/访问单元边界处开始或结束,除非使用BUFFER_FLAG_PARTIAL_FRAME对其进行了标记。

Raw Audio Buffers 原始音频缓冲区

原始的音频数据缓存包含完整的PCM(脉冲编码调制)音频数据帧,这是每一个通道按照通道顺序的一个样本。
每一个PCM音频样本都是一个按照本机字节顺序的16位带符号整数或浮点数(16 bit signed integer or a float, in native byte order.)。

使用浮点PCM编码的原始音频缓冲区 :仅当在MediaCodec configure(…)期间将MediaFormat的MediaFormat#KEY_PCM_ENCODING设置为AudioFormat#ENCODING_PCM_FLOAT并由解码器的getOutputFormat()或编码器的getInputFormat()确认时,才可以使用浮点PCM编码的原始音频缓冲区。

检查MediaFormat中的float PCM的示例方法如下:

static boolean isPcmFloat(MediaFormat format) {
 return format.getInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
     == AudioFormat.ENCODING_PCM_FLOAT;
}

为了在短数组中提取包含16位带符号整数音频数据的缓冲区的一个通道,可以使用以下代码:

// Assumes the buffer PCM encoding is 16 bit. 假定缓冲区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 = format.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模式下,视频缓冲区根据他们的MediaFormat#KEY_COLOR_FORMAT进行布局。

你可以通过调用getCodecInfo().MediaCodecInfo#getCapabilitiesForType.CodecCapabilities#colorFormats方法获得编解码器支持的颜色格式数组。
视频编解码器可以支持三种类型的颜色格式:

从Android 5.1(API 22)开始,所有的视频编解码器都支持灵活的YUV4:2:0缓存(flexible YUV420 buffers)。

\color{red}{注意:在较旧的设备上访问原始视频字节缓冲区。 以下内容可以略过::}

在支持Build.VERSION_CODES.LOLLIPOPImage之前,您需要使用MediaFormat#KEY_STRIDE和MediaFormat#KEY_SLICE_HEIGHT输出格式值来了解原始输出缓冲区的布局。

MediaFormat#KEY_WIDTHMediaFormat#KEY_HEIGHT键指定视频帧的大小; 但是,在大多数情况下,视频(图片)仅占据视频帧的一部分。
这由“裁剪矩形”表示。 这由“crop rectangle 剪裁矩形”表示。

您需要使用以下键从输出格式获取原始输出图像的裁剪矩形。如果不存在这些键,则视频将占据整个视频帧。在应用任何MediaFormat#KEY_ROTATION之前,应在输出帧的上下文中了解裁剪矩形。

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

The right and bottom coordinates can be understood as the coordinates of the right-most valid column/bottom-most valid row of the cropped output image.
右坐标和底坐标可以理解为裁剪后的输出图像的最右边的有效列/最下面的有效行的坐标。

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

 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");
 }

生命周期

在编解码器的生命周期内有三种理论状态:停止态-Stopped、执行态-Executing、释放态-Released。

上一篇 下一篇

猜你喜欢

热点阅读