[译] Android MediaCodec 官方文档(2020-6-12)翻译

原文:https://developer.android.google.cn/reference/android/media/MediaCodec

翻译的原文时间戳是 2020-06-12,先使用谷歌翻译,然后进行手动修正和排版处理。

由于原文使用的SVG图片在CSDN里用不了,就使用了 https://blog.csdn.net/yssjz960427031/article/details/70050142 的图片。

转载请说明出处:https://blog.csdn.net/hegan2010/article/details/106690503

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

mediacodec_buffers.png

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

数据类型(Data Types)

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

压缩缓冲区(Compressed Buffers)

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

原始音频缓冲区(Raw Audio Buffers)

原始音频缓冲区包含 PCM 音频数据的整个帧,这是按通道顺序排列的每个通道的样本集合。 每个 PCM 音频样本都是16位带符号整数或浮点数(以本机字节顺序)。 仅当在 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.
 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 获取受支持的颜色格式数组。 视频编解码器可能支持三种颜色格式:

Build.VERSION_CODES.LOLLIPOP_MR1 起,所有视频编解码器均支持灵活的 YUV 4:2:0 缓冲区。

在较旧的设备上访问原始视频字节缓冲区(Accessing Raw Video ByteBuffers on Older Devices)

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

注意,在某些设备上,切片高度被标为0。这可能意味着切片高度与帧高度相同,或者切片高度对齐到帧高度的某个值(通常为 2 的幂)。 不幸的是,在这种情况下,没有标准和简单的方法可以知道实际的切片高度。 此外,虽然通常是切片高度的一半,但也未指定或定义平面格式的 U 平面的垂直跨度。

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

您需要使用以下键从输出格式获取原始输出图像的裁剪矩形。 如果这些键不存在,则视频将占据整个视频帧。在应用任何 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

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

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

 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)

在其生命周期内,编解码器从概念上讲处于以下三种状态之一:Stopped,Executing 或 Released。 Stopped 状态实际上是三个状态的集合:Uninitialized,Configured 和 Error,而 Executing 状态从概念上讲经过三个子状态:Flushed,Running 和 End-of-Stream。

mediacodec_states.png

使用工厂方法之一创建编解码器时,编解码器处于 Uninitialized 状态。 首先,您需要通过 configure(…) 对其进行配置,使其进入 Configured 状态,然后调用 start() 将其移至 Executing 状态。 在这种状态下,您可以通过上述缓冲区队列操作来处理数据。

Executing 状态具有三个子状态:Flushed,Running 和 End-of-Stream。 在 start() 之后,编解码器立即处于 Flushed 子状态,持有所有缓冲区。 一旦第一个输入缓冲区出队,编解码器将移至 Running 子状态,大部分时间都处于此状态。 当您将带有 end-of-stream marker 标记的输入缓冲区入队时,编解码器将转换为 End-of-Stream 子状态。 在这种状态下,编解码器将不再接受其他输入缓冲区,但仍会生产输出缓冲区,直到在输出端达到流末尾为止。 在 Executing 状态下,您随时可以使用 flush() 返回到 Flushed 子状态。

调用 stop() 将使编解码器返回到 Uninitialized 状态,随后可以再次对其进行配置。 使用编解码器完毕后,必须调用 release() 来释放它。

在极少数情况下,编解码器可能会遇到错误并进入 Error 状态。 使用来自入队操作的无效返回值或有时通过异常来传达此信息。 调用 reset() 使编解码器再次可用。 您可以从任何状态调用它,将编解码器移回到 Uninitialized 状态。 否则,请调用 release() 以移至终止 Released 状态。

创建(Creation)

使用 MediaCodecList 为特定的 MediaFormat 创建 MediaCodec。 解码文件或流时,可以从 MediaExtractor#getTrackFormat 获得所需的格式。 使用 MediaFormat#setFeatureEnabled 注入要添加的任何特性,然后调用 MediaCodecList#findDecoderForFormat 以获取可以处理该特定媒体格式的编解码器的名称。 最后,使用 createByCodecName(String) 创建编解码器。

注意:在 Build.VERSION_CODES.LOLLIPOP 上,MediaCodecList.findDecoder / EncoderForFormat 的格式不得包含 MediaFormat#KEY_FRAME_RATE 帧率。 使用 format.setString(MediaFormat.KEY_FRAME_RATE,null) 以清除格式中任何现有的帧频设置。

您还可以使用 createDecoder/EncoderByType(java.lang.String) 为特定的 MIME 类型创建首选编解码器。 但是,这不能用于注入特性,并且可能会创建无法处理特定所需媒体格式的编解码器。

创建安全的解码器(Creating secure decoders)

Build.VERSION_CODES.KITKAT_WATCH 和更低版本上,安全编解码器可能未在 MediaCodecList 中列出,但仍然在系统上可用。 存在的安全编解码器只能通过名称实例化,方法是在常规编解码器的名称后附加 ".secure"(所有安全编解码器的名称都必须以 ".secure" 结尾)。如果编解码器不存在于系统上,createByCodecName(String)  将抛出 IOException。

Build.VERSION_CODES.LOLLIPOP 开始,您应该使用媒体格式的 CodecCapabilities#FEATURE_SecurePlayback 功能来创建安全解码器。

初始化(Initialization)

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

Build.VERSION_CODES.LOLLIPOP 开始,您可以在 Configured 状态下查询生成的输入和输出格式。 您可以使用它来验证最终的配置,例如 颜色格式,然后再启动编解码器。

如果要使用视频使用者(处理视频输入的编解码器,例如视频编码器)本地处理原始输入视频缓冲区,请在配置后使用 createInputSurface() 为输入数据创建目标 Surface。 或者,通过调用 setInputSurface(Surface) 将编解码器设置为使用以前创建的 persistent input surface

编解码器特定数据(Codec-specific Data)

某些格式,尤其是 AAC 音频和 MPEG4,H.264 和 H.265 视频格式,要求实际数据以一些包含设置数据或编解码器特定数据的缓冲区为前缀。 处理此类压缩格式时,必须在 start() 之后和在任何帧数据之前将此数据提交给编解码器。 此类数据必须在对 queueInputBuffer 的调用中使用标志 BUFFER_FLAG_CODEC_CONFIG 进行标记。

编解码器特定数据也可以包含在传递给配置的格式中的 ByteBuffer 条目中,其中包含键 "csd-0", "csd-1" 等。这些键始终包含在从 MediaExtractor#getTrackFormat 获得的轨道 MediaFormat 中。 格式中的编解码器特定数据在 start() 时自动提交给编解码器; 您不得显式提交此数据。 如果格式不包含编解码器特定数据,则可以根据格式要求选择使用指定数量的缓冲区以正确的顺序提交它。 对于 H.264 AVC,您还可以连接所有编解码器特定数据,并将其作为单个编解码器配置缓冲区进行提交。

Android 使用以下编解码器特定数据缓冲区。 还需要将它们设置在轨道格式中,以进行正确的 MediaMuxer 轨道配置。 每个参数集和标有(*)的编解码器特定数据部分必须以 "\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 ByteOrder#nativeOrder integer.)
This overrides the pre-skip value in the identification header.
Seek Pre-roll in nanosecs
(unsigned 64-bit ByteOrder#nativeOrder integer.)
FLAC "fLaC", the FLAC stream marker in ASCII,
followed by the STREAMINFO block (the mandatory metadata block),
optionally followed by any number of other metadata blocks
Not Used Not Used
MPEG-4 Decoder-specific information from ESDS* Not Used Not Used
H.264 AVC SPS (Sequence Parameter Sets*) PPS (Picture Parameter Sets*) Not Used
H.265 HEVC 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 flag 标志的输出缓冲区中的任何有效输出缓冲区之前,创建并返回编解码器特定数据。 包含编解码器特定数据的缓冲区没有有意义的时间戳。(此处似乎意义不明,原文也是)

数据处理(Data Processing)

每个编解码器维护一组输入和输出缓冲区,这些输入和输出缓冲区由 API 调用中的缓冲区 ID 引用。 成功调用 start() 后,客户端“不拥有”输入缓冲区或输出缓冲区。 在同步模式下,调用 dequeueInput/OutputBuffer(…) 从编解码器获取(或拥有)输入或输出缓冲区。 在异步模式下,您将通过 Callback#onInputBufferAvailable/Callback#onOutputBufferAvailable 回调自动接收可用缓冲区。

获取输入缓冲区后,将其填充数据,然后使用 queueInputBuffer 或者 queueSecureInputBuffer (如果使用解密) 将其提交给编解码器。 不要提交带有相同时间戳的多个输入缓冲区(除非它被标记为编解码器特定数据)。

反过来,编解码器将通过 Callback#onOutputBufferAvailable 回调在异步模式下或响应于同步模式下的 dequeueOutputBuffer 调用返回只读输出缓冲区。 处理完输出缓冲区后,调用 releaseOutputBuffer 方法之一将缓冲区返回到编解码器。

尽管不需要立即将缓冲区重新提交/释放到编解码器,但持有输入和/或输出缓冲区可能会使编解码器停顿,并且此行为与设备有关。 特别是,编解码器可能会推迟生成输出缓冲区,直到所有未完成的缓冲区都已释放/重新提交。 因此,请尝试尽可能少地保留可用缓冲区。

根据 API 版本,您可以通过三种方式处理数据:

Processing Mode API version <= 20
Jelly Bean/KitKat
API version >= 21
Lollipop and later
Synchronous API using buffer arrays Supported Deprecated
Synchronous API using buffers Not Available Supported
Asynchronous API using buffers Not Available Supported

使用缓冲区异步处理(Asynchronous Processing using Buffers)

Build.VERSION_CODES.LOLLIPOP 开始,首选方法是在调用 configure 之前通过设置回调来异步处理数据。 异步模式会稍微改变状态转换,因为您必须在 flush() 之后调用 start() 才能将编解码器转换为 Running 子状态并开始接收输入缓冲区。 同样,在首次启动编解码器时,将直接移至 Running 子状态,并开始通过回调传递可用的输入缓冲区。

mediacodec_async_states.png

MediaCodec 在异步模式下通常像这样使用:

 MediaCodec codec = MediaCodec.createByCodecName(name);
 MediaFormat mOutputFormat; // member variable
 codec.setCallback(new MediaCodec.Callback() {
  @Override
  void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
 
  @Override
  void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is equivalent to mOutputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  }
 
  @Override
  void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    mOutputFormat = format; // option B
  }
 
  @Override
  void onError(…) {
    …
  }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); // option B
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();

使用缓冲区的同步处理(Synchronous Processing using Buffers)

Build.VERSION_CODES.LOLLIPOP 开始,即使在同步模式下使用编解码器,也应使用 getInput/OutputBuffer(int) 和/或 getInput/OutputImage(int) 来获取输入和输出缓冲区。 这允许框架进行某些优化,例如 处理动态内容时。 如果调用 getInput/OutputBuffers(),则会禁用此优化。

注意:请勿同时使用缓冲区和缓冲区数组的方法。 具体来说,仅在 start() 之后或在使用 INFO_OUTPUT_FORMAT_CHANGED 值出队一个输出缓冲区 ID 后才直接调用 getInput/OutputBuffers

MediaCodec 在同步模式下通常按以下方式使用:

 MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, …);
 MediaFormat outputFormat = codec.getOutputFormat(); // option B
 codec.start();
 for (;;) {
  int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
  if (inputBufferId >= 0) {
    ByteBuffer inputBuffer = codec.getInputBuffer(…);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
  int outputBufferId = codec.dequeueOutputBuffer(…);
  if (outputBufferId >= 0) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is identical to outputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    outputFormat = codec.getOutputFormat(); // option B
  }
 }
 codec.stop();
 codec.release();

使用缓冲区数组的同步处理 (已过时)(Synchronous Processing using Buffer Arrays (deprecated))

Build.VERSION_CODES.KITKAT_WATCH 及更低版本中,输入和输出缓冲区的集合由 ByteBuffer[] 数组表示。 在成功调用 start() 后,使用 getInput/OutputBuffers() 来获取缓冲区数组。 使用缓冲区 ID 作为这些数组的索引(非负数时),如以下示例所示。 请注意,尽管数组大小提供了上限,但数组大小与系统使用的输入和输出缓冲区的数量之间没有固有的相关性。

 MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, …);
 codec.start();
 ByteBuffer[] inputBuffers = codec.getInputBuffers();
 ByteBuffer[] outputBuffers = codec.getOutputBuffers();
 for (;;) {
  int inputBufferId = codec.dequeueInputBuffer(…);
  if (inputBufferId >= 0) {
    // fill inputBuffers[inputBufferId] with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
  int outputBufferId = codec.dequeueOutputBuffer(…);
  if (outputBufferId >= 0) {
    // outputBuffers[outputBufferId] is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    outputBuffers = codec.getOutputBuffers();
  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    // Subsequent data will conform to new format.
    MediaFormat format = codec.getOutputFormat();
  }
 }
 codec.stop();
 codec.release();

流末尾处理(End-of-stream Handling)

当到达输入数据的末尾时,必须在对 queueInputBuffer 的调用中指定 BUFFER_FLAG_END_OF_STREAM 标志,将其发送给编解码器。 您可以在最后一个有效的输入缓冲区上执行此操作,或者通过提交额外一个空输入缓冲区并设置流结束标志来执行此操作。 如果使用空缓冲区,则时间戳将被忽略。

编解码器将继续返回输出缓冲区,直到最终通过 dequeueOutputBuffer 或通过 Callback#onOutputBufferAvailable 返回的 BufferInfo 中指定相同的 end-of-stream 标志来指示输出流的结束为止。 这可以在最后一个有效的输出缓冲区上设置,也可以在最后一个有效的输出缓冲区后的空缓冲区上设置。 这种空缓冲区的时间戳应该被忽略。

除非已刷新,停止或重新启动编解码器,否则在发出输入流结束信号后不要提交其他输入缓冲区。

使用输出 Surface(Using an Output Surface)

当使用输出 Surface 时,数据处理几乎与 ByteBuffer 模式相同; 但是,输出缓冲区将不可访问,并表示为 null 值。 例如 getOutputBuffer/Image(int) 将返回 null, getOutputBuffers() 将返回仅包含 null-s 的数组。

当使用输出 Surface 时,可以选择是否在 surface 上渲染每个输出缓冲区。 您有三种选择:

Build.VERSION_CODES.M 开始,缓冲区的默认时间戳为 BufferInfo#presentationTimeUs (转换为纳秒)。 在此之前未定义。

此外,从 Build.VERSION_CODES.M 开始,您可以使用 setOutputSurface 来动态更改输出 Surface。

当将输出渲染到 Surface 时,Surface 可以配置为丢弃过多的帧(没有及时被 Surface 处理掉的帧)。 或者可以将其配置为不丢弃过多的帧。 在后一种模式下,如果 Surface 无法足够快地处理输出帧,则它将最终阻塞解码器。 在 Build.VERSION_CODES.Q 之前,确切的行为是不确定的,除了 View Surface(SurfaceView 或 TextureView)始终丢弃过多的帧。 从 Build.VERSION_CODES.Q 开始,默认行为是丢弃过多的帧。 应用程序可以选择通过设置目标 SDK 为 Build.VERSION_CODES.Q 并在其配置格式中将键 "allow-frame-drop" 设置为 0,将非 View Surface(例如 ImageReader 或 SurfaceTexture)的这种行为取消。

渲染到 Surface 时的变换(Transformations When Rendering onto Surface)

如果将编解码器配置为 Surface 模式,则将自动应用任何裁剪矩形,MediaFormat#KEY_ROTATION  旋转和视频缩放模式,但有一个例外:

Build.VERSION_CODES.M 之前,软件解码器在渲染到 Surface 时可能尚未应用旋转。 不幸的是,没有标准和简单的方法来识别软件解码器,或者是否应用了旋转,除了通过尝试之外。

还有一些警告:

请注意,在将输出显示到 Surface 时不考虑像素长宽比。 这意味着,如果您使用的是 VIDEO_SCALING_MODE_SCALE_TO_FIT 模式,则必须定位输出 Surface 以使其具有正确的最终显示长宽比。 相反,您只能将 VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING 模式用于具有正方形像素(像素长宽比或1:1)的内容。

另请注意,从 Build.VERSION_CODES.N 版本开始,VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING 模式可能无法正确工作于旋转了90度或270度的视频。

设置视频缩放模式时,请注意,每当更改输出缓冲区后必须将其重置。 由于已不推荐使用 INFO_OUTPUT_BUFFERS_CHANGED 事件,因此可以在每次输出格式更改后执行此(重置)操作。

使用输入 Surface(Using an Input Surface)

当使用输入Surface时,没有可供访问的输入缓冲区,因为缓冲区会自动从输入 surface 传递到编解码器。 调用 dequeueInputBuffer 会抛出 IllegalStateException,并且 getInputBuffers() 将返回一个不可写入的虚假的 ByteBuffer[] 数组。

调用 signalEndOfInputStream() 来发送 end-of-stream 信号。 调用后,输入 surface 将立即停止向编解码器提交数据。

寻道和自适应播放支持(Seeking & Adaptive Playback Support)

视频解码器(以及一般处理压缩视频数据的编解码器)在寻道和格式更改方面是否有所不同,无论它们是否支持并配置为自适应播放。 您可以通过 CodecCapabilities#isFeatureSupported 来检查解码器是否支持 CodecCapabilities#FEATURE_AdaptivePlayback 自适应播放。 仅当将编解码器配置为解码到 Surface 上时,才激活对视频解码器的自适应播放支持。

流边界和关键帧(Stream Boundary and Key Frames)

很重要的一点,在 start()flush() 之后的输入数据必须在合适的流边界处开始:第一帧必须是关键帧。 关键帧可以完全自行解码(对于大多数编解码器而言,这意味着是 I 帧),并且在关键帧之后要显示的任何帧都不参考该关键帧之前的帧。

下表总结了各种视频格式的合适关键帧。

Format Suitable key frame
VP9/VP8 a suitable intraframe where no subsequent frames refer to frames prior to this frame.
(There is no specific name for such key frame.)
H.265 HEVC IDR or CRA
H.264 AVC IDR
MPEG-4
H.263
MPEG-2
a suitable I-frame where no subsequent frames refer to frames prior to this frame.
(There is no specific name for such key frame.)

对于不支持自适应播放的解码器(包括不解码到 Surface 上的解码器)

(For decoders that do not support adaptive playback (including when not decoding onto a Surface))

为了开始解码与先前提交的数据不相邻的数据(即在寻道之后),您必须刷新解码器。 由于所有输出缓冲区在刷新时立即被撤销,因此您可能要先发出信号,然后在等待 end-of-stream 后再调用 flush。 刷新后的输入数据必须在合适的流边界/关键帧处开始,这一点很重要。

注意:刷新后提交的数据格式不得更改;flush() 不支持格式不连续; 为此,需要一个完整的 stop() - configure(…) - start() 周期。

另请注意:如果您在 start() 之后过早刷新编解码器(通常在接收到第一个输出缓冲区或输出格式更改之前),则需要将编解码器特定数据重新提交给编解码器。 有关更多信息,请参见编解码器特定数据部分

对于支持并配置为自适应播放的解码器(For decoders that support and are configured for adaptive playback)

为了开始解码与先前提交的数据不相邻的数据(即在寻道之后),没有必要刷新解码器。 但是,不连续之后的输入数据必须从合适的流边界/关键帧开始。

对于某些视频格式 - 即 H.264,H.265,VP8 和 VP9 - 也可以在流中间更改画面大小或配置。 为此,您必须将整个新的编解码器特定配置数据与关键帧一起打包到一个缓冲区(包括任何起始代码)中,并将其作为常规输入缓冲区提交。

在画面大小发生更改之后,在任何具有新大小的帧返回之前,您将从 dequeueOutputBufferCallback#onOutputBufferAvailable 回调中收到 INFO_OUTPUT_FORMAT_CHANGED 返回值。

注意:就像编解码器特定数据一样,在更改画面大小不久后,调用 flush() 时要小心。 如果您尚未收到画面尺寸更改的确认,则需要重新请求新的画面尺寸。

错误处理(Error handling)

工厂方法 createByCodecNamecreateDecoder/EncoderByType 在失败时会抛出 IOException,您必须捕获或声明向上传递该异常。 当从不允许的编解码器状态调用该方法时,MediaCodec 方法将抛出 IllegalStateException,这通常是由于不正确的应用程序 API 使用引起的。 涉及安全缓冲区的方法可能会抛出 CryptoException,该异常具有可从 CryptoException#getErrorCode 获得的更多错误信息。

内部编解码器错误会导致 CodecException,这可能是由于媒体内容损坏,硬件故障,资源耗尽等导致的,即使应用程序正确使用了 API 也是如此。 可以通过调用 CodecException#isRecoverableCodecException#isTransient 来确定收到 CodecException 时的建议操作:

  • recoverable errors: 如果 isRecoverable() 返回 true,则调用 stop(), configure(…), and start() 进行恢复。
  • transient errors: 如果 isTransient() 返回 true,则资源暂时不可用,可能会在稍后重试该方法。
  • fatal errors: 如果 isRecoverable()isTransient() 均返回 false,则 CodecException 是致命的,必须 resetreleased 编解码器。

isRecoverable()isTransient() 不会同时返回 true。

合法的API调用和API历史记录(Valid API Calls and API History)

本节总结了每种状态下的合法 API 调用以及 MediaCodec 类的 API 历史记录。 有关 API 版本号,请参见 Build.VERSION_CODES

Symbol Meaning
Supported
Semantics changed
Experimental support
[ ] Deprecated
Restricted to surface input mode
Restricted to surface output mode
Restricted to ByteBuffer input mode
Restricted to synchronous mode
Restricted to asynchronous mode
( ) Can be called, but shouldn't

Uninitialized

Configured

Flushed

Running

End of Stream

Error

Released

  SDK Version
State Method 16 17 18 19 20 21 22 23
              createByCodecName
              createDecoderByType
              createEncoderByType
              createPersistentInputSurface              
16+ - - - - - - configure
- 18+ - - - - - createInputSurface    
- - 16+ 16+ (16+) - - dequeueInputBuffer ⁕▧↩ ▧↩ ▧↩
- - 16+ 16+ 16+ - - dequeueOutputBuffer ⁕↩
- - 16+ 16+ 16+ - - flush
18+ 18+ 18+ 18+ 18+ 18+ - getCodecInfo    
- - (21+) 21+ (21+) - - getInputBuffer          
- - 16+ (16+) (16+) - - getInputBuffers [⁕↩] [↩] [↩]
- 21+ (21+) (21+) (21+) - - getInputFormat          
- - (21+) 21+ (21+) - - getInputImage          
18+ 18+ 18+ 18+ 18+ 18+ - getName    
- - (21+) 21+ 21+ - - getOutputBuffer          
- - 16+ 16+ 16+ - - getOutputBuffers [⁕↩] [↩] [↩]
- 21+ 16+ 16+ 16+ - - getOutputFormat()
- - (21+) 21+ 21+ - - getOutputFormat(int)          
- - (21+) 21+ 21+ - - getOutputImage          
- - - 16+ (16+) - - queueInputBuffer
- - - 16+ (16+) - - queueSecureInputBuffer
16+ 16+ 16+ 16+ 16+ 16+ 16+ release
- - - 16+ 16+ - - releaseOutputBuffer(int, boolean)
- - - 21+ 21+ - - releaseOutputBuffer(int, long)          
21+ 21+ 21+ 21+ 21+ 21+ - reset          
21+ - - - - - - setCallback          
- 23+ - - - - - setInputSurface              
23+ 23+ 23+ 23+ 23+ (23+) (23+) setOnFrameRenderedListener               ○ ⎆
- 23+ 23+ 23+ 23+ - - setOutputSurface              
19+ 19+ 19+ 19+ 19+ (19+) - setParameters      
- (16+) (16+) 16+ (16+) (16+) - setVideoScalingMode
(29+) 29+ 29+ 29+ (29+) (29+) - setAudioPresentation                
- - 18+ 18+ - - - signalEndOfInputStream    
- 16+ 21+(⇄) - - - - start
- - 16+ 16+ 16+ - - stop

一些更详细的 API 说明可以参见这篇博客:https://www.cnblogs.com/roger-yu/p/5635494.html

猜你喜欢

转载自blog.csdn.net/hegan2010/article/details/106690503