Share via


MediaCodec 类

定义

MediaCodec 类可用于访问低级别媒体编解码器 i.

[Android.Runtime.Register("android/media/MediaCodec", DoNotGenerateAcw=true)]
public sealed class MediaCodec : Java.Lang.Object
[<Android.Runtime.Register("android/media/MediaCodec", DoNotGenerateAcw=true)>]
type MediaCodec = class
    inherit Object
继承
MediaCodec
属性

注解

MediaCodec 类可用于访问低级别媒体编解码器,即编码器/解码器组件。 它是 Android 低级多媒体支持基础结构的一部分, (通常与 MediaExtractor、、MediaSyncMediaMuxerImageSurfaceMediaCryptoMediaDrm、 和 AudioTrack.)

<center><img src=“../../../images/media/mediacodec_buffers.svg“ style=”width: 540px;height: 205px“ alt=”MediaCodec 缓冲区流图“></center>

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

<h3 id=qualityFloor>“qualityFloor”>视频编码<的最低质量底限/h3>

android.os.Build.VERSION_CODES#S开始,Android 的视频 MediaCodecs 强制实施最低质量要求。 目的是消除劣质视频编码。 当编解码器处于可变比特率 (VBR) 模式时,将应用此质量最低值;当编解码器处于常量比特率 (CBR) 模式下时,不会应用它。 质量下限执法也仅限于特定的尺寸范围:此大小范围目前适用于大于 320x240 到 1920x1080 的视频分辨率。

当此质量下限生效时,编解码器和支持框架代码将起作用,以确保生成的视频至少具有“公平”或“良好”的质量。 用于选择这些目标的指标是 VMAF (视频多方法评估函数) ,所选测试序列的目标分数为 70。

典型的效果是,某些视频将生成比最初配置的更高的比特率。 对于配置了非常低比特率的视频,这是最值得注意的。编解码器将使用确定更有可能生成“公平”或“良好”视频的比特率。 另一种情况是,视频包含非常复杂的内容 (大量的动作和细节) :在此类配置中,编解码器将根据需要使用额外的比特率,以避免丢失所有内容的详细信息。

此质量下限不会影响以高比特率捕获的内容, (高比特率应该已经为编解码器提供了足够的容量来编码所有细节) 。 质量最低不对 CBR 编码进行操作。 目前,质量最低不对分辨率为 320x240 或更低,分辨率高于 1920x1080 的视频也不运行。

<h3>数据类型</h3>

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

<h4>压缩缓冲区</h4>

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

<h4>原始音频缓冲区</h4>

原始音频缓冲区包含 PCM 音频数据的整个帧,这是按通道顺序表示的每个通道的一个示例。 每个 PCM 音频样本都是 16 位带符号整数或浮点数,按本机字节顺序排列。 仅当 MediaFormat 的 MediaFormat#KEY_PCM_ENCODING 在 MediaCodec #configure configure(&hellip;) 期间设置为 AudioFormat#ENCODING_PCM_FLOAT 并且对于解码器或#getInputFormat编码器进行确认时,#getOutputFormat才能使用浮点 PCM 编码中的原始音频缓冲区。 为 MediaFormat 中的浮点 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 &lt; 0 || channelIx &gt;= numChannels) {
                 return null;
               }
               short[] res = new short[samples.remaining() / numChannels];
               for (int i = 0; i &lt; res.length; ++i) {
                 res[i] = samples.get(i * numChannels + channelIx);
               }
               return res;
             }

<h4>原始视频缓冲区</h4>

在 ByteBuffer 模式下,视频缓冲区根据其 MediaFormat#KEY_COLOR_FORMAT颜色格式进行布局。 可以从 获取支持的颜色格式作为数组#getCodecInfoCodecCapabilities#colorFormats colorFormats.MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType(&hellip;).。 视频编解码器可能支持三种颜色格式:ul>li strong 本机原始视频格式:</strong> 这是由 标记的CodecCapabilities#COLOR_FormatSurface,它可以与输入或输出 Surface 一起使用。<>><<</li>li strong flexible YUV buffers</strong> (,如 CodecCapabilities#COLOR_FormatYUV420Flexible) :这些缓冲区可用于输入/输出 Surface,也可在 ByteBuffer 模式下使用#getOutputImage OutputImage(int)#getInputImage getInput/ 。<>><</li><li><strong>other, specific formats:</strong> 这些通常仅在 ByteBuffer 模式下受支持。 某些颜色格式特定于供应商。 其他在 中 CodecCapabilities定义。 对于等效于灵活格式的颜色格式,仍 #getInputImage getInput/#getOutputImage OutputImage(int)可使用 。</li></ul>

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

<h4>在旧设备上<访问原始视频字节缓冲区/h4>

android.os.Build.VERSION_CODES#LOLLIPOP 支持 和 Image 之前,需要使用 MediaFormat#KEY_STRIDEMediaFormat#KEY_SLICE_HEIGHT 输出格式值来了解原始输出缓冲区的布局。 <p class=note> 请注意,在某些设备上,切片高度播发为 0。 这可能意味着切片高度与帧高度相同,或者切片高度是与某个值对齐的帧高度, (通常为 2) 的幂。 遗憾的是,在这种情况下,没有标准且简单的方法来判断实际切片高度。 此外,平面格式的 U 平面垂直步幅也未指定或定义,尽管它通常是切片高度的一半。

MediaFormat#KEY_WIDTHMediaFormat#KEY_HEIGHT 键指定视频帧的大小;但是,对于大多数连接,视频 (图片) 仅占用视频帧的一部分。 这由“裁剪矩形”表示。

需要使用以下键从 #getOutputFormat 输出格式获取原始输出图像的裁剪矩形。 如果这些键不存在,则视频将占用整个视频帧。在应用任何 MediaFormat#KEY_ROTATION旋转之前<,>在输出帧<的>上下文中理解裁剪矩形。 <table style=“width: 0%”><thead<>tr><th>Format Key</th>><Type</th<>>th Description/th/th></<tr></thead><tbody><tr><td>MediaFormat#KEY_CROP_LEFT</td><td>Integer</td><td>左坐标 (x) 裁剪矩形</td<>/tr><><tdMediaFormat#KEY_CROP_TOP<>/td td 整数><></Td><td>裁剪矩形</td/tr<><>td><<>>MediaFormat#KEY_CROP_RIGHT/td td>< 的顶坐标 (y) 整数</td><td>右坐标 (x) <强>减号 1</>裁剪矩形</td/tr><><td><><MediaFormat#KEY_CROP_BOTTOM/td td<>td>的>><<下坐标 (y) <强>减号 1</strong>裁剪矩形</td></tr<><>td colspan=3> 右侧坐标和底部坐标可以理解为裁剪输出图像的右有效列/最下有效行的坐标。 </td></tr></tbody></table>

旋转) 之前视频帧 (的大小可以按如下方式计算:

MediaFormat format = decoder.getOutputFormat(&hellip;);
             int width = format.getInteger(MediaFormat.KEY_WIDTH);
             if (format.containsKey(MediaFormat.KEY_CROP_LEFT)
                     && format.containsKey(MediaFormat.KEY_CROP_RIGHT)) {
                 width = format.getInteger(MediaFormat.KEY_CROP_RIGHT) + 1
                             - format.getInteger(MediaFormat.KEY_CROP_LEFT);
             }
             int height = format.getInteger(MediaFormat.KEY_HEIGHT);
             if (format.containsKey(MediaFormat.KEY_CROP_TOP)
                     && format.containsKey(MediaFormat.KEY_CROP_BOTTOM)) {
                 height = format.getInteger(MediaFormat.KEY_CROP_BOTTOM) + 1
                              - format.getInteger(MediaFormat.KEY_CROP_TOP);
             }

<p class=note> 另请注意, 的含义 BufferInfo#offset BufferInfo.offset 在设备之间不一致。 在某些设备上,偏移指向裁剪矩形的左上角像素,而在大多数设备上,它指向整个帧的左上角像素。

<h3>States</h3>

在编解码器的生命周期中,编解码器在概念上存在于以下三种状态之一:已停止、正在执行或释放。 “已停止”集体状态实际上是三种状态的聚合:“未初始化”、“已配置”和“错误”,而“正在执行”状态在概念上通过三个子状态进行:刷新、正在运行和流结束。

<center><img src=“../../../images/media/mediacodec_states.svg“ style=”width: 519px;height: 356px“ alt=”MediaCodec state diagram“></center>

使用工厂方法之一创建编解码器时,编解码器处于“未初始化”状态。 首先,需要通过 #configure configure(&hellip;)对其进行配置,这会使其进入“已配置”状态,然后调用 #start 以将其移动到“正在执行”状态。 在此状态下,可以通过上述缓冲区队列操作处理数据。

“正在执行”状态有三个子状态:“已刷新”、“正在运行”和“流结束”。 在编解码器处于 Flushed 子状态后 #start 立即保存所有缓冲区。 一旦取消第一个输入缓冲区的排队,编解码器就会移动到“正在运行”子状态,其中大部分时间都处于此状态。 使用 #BUFFER_FLAG_END_OF_STREAM 流结束标记将输入缓冲区排队时,编解码器将转换为“流结束”子状态。 在此状态下,编解码器不再接受进一步的输入缓冲区,但仍会生成输出缓冲区,直到在输出上到达流结束。 对于解码器,在处于“正在执行”状态时,可以使用 随时移回刷新的子状态 #flush。 <p class=note><strong>注意:</strong> 仅解码器支持返回到刷新状态,并且可能不适用于编码器, (行为未定义) 。

调用 #stop 以将编解码器返回到“未初始化”状态,因此可能会再次对其进行配置。 使用完编解码器后,必须通过调用 #release来释放它。

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

<h3>创建</h3>

使用 MediaCodecList 为特定 MediaFormat创建 MediaCodec。 解码文件或流时,可以从 获取所需的格式 MediaExtractor#getTrackFormat MediaExtractor.getTrackFormat。 使用 MediaFormat#setFeatureEnabled MediaFormat.setFeatureEnabled注入要添加的任何特定功能,然后调用 MediaCodecList#findDecoderForFormat MediaCodecList.findDecoderForFormat 以获取可处理该特定媒体格式的编解码器的名称。 最后,使用 #createByCodecName创建编解码器。 <p class=note><strong>Note:</strong> On android.os.Build.VERSION_CODES#LOLLIPOPMediaCodecList.findDecoder/EncoderForFormat 格式不得包含 MediaFormat#KEY_FRAME_RATE帧速率。 使用 format.setString(MediaFormat.KEY_FRAME_RATE, null) 清除格式中的任何现有帧速率设置。

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

<h4>创建安全解码器</h4>

在 版本和更早版本中 android.os.Build.VERSION_CODES#KITKAT_WATCH ,安全编解码器可能未在 中 MediaCodecList列出,但系统可能仍可用。 仅可通过名称实例化存在的安全编解码器,方法是将 追加 ".secure" 到常规编解码器的名称 (所有安全编解码器的名称必须以 结尾 ".secure"。) #createByCodecName 如果系统上不存在编解码器,将引发 IOException

android.os.Build.VERSION_CODES#LOLLIPOP 此以后,应使用 CodecCapabilities#FEATURE_SecurePlayback 媒体格式的功能来创建安全解码器。

<h3>初始化</h3>

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

由于 android.os.Build.VERSION_CODES#LOLLIPOP,可以在“已配置”状态下查询生成的输入和输出格式。 在启动编解码器之前,可以使用它来验证生成的配置,例如颜色格式。

如果要使用视频使用者以本机方式处理原始输入视频缓冲区,–处理原始视频输入的编解码器,例如视频编码器 –在配置后使用 #createInputSurface 为输入数据创建目标 Surface。 或者,通过调用 #setInputSurface将编解码器设置为使用以前创建的 #createPersistentInputSurface 持久输入图面。

<h4 id=CSD>“CSD”>Codec-specific Data</h4>

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

还可以将特定于编解码器的数据包含在 ByteBuffer 条目中传递给 #configure configure 的格式中,键为“csd-0”、“csd-1”等。这些键始终包含在从 获取的轨迹 MediaFormatMediaExtractor#getTrackFormat MediaExtractor。 格式的特定于编解码器的数据在 时#start自动提交到编解码器;强<>必须不要</强>显式提交此数据。 如果格式不包含编解码器特定的数据,则可以根据格式要求,选择使用指定数量的缓冲区以正确的顺序提交它。 对于 H.264 AVC,还可以连接所有特定于编解码器的数据,并将其作为单个编解码器配置缓冲区提交。

Android 使用以下特定于编解码器的数据缓冲区。 还需要以跟踪格式设置这些设置,以便进行正确的 MediaMuxer 跟踪配置。 每个参数集以及标记为 (<sup*</sup>>) 的编解码器特定数据部分必须以 的起始代码"\x00\x00\x00\x01"开头。

<style>td。NA { background: #ccc; } .mid > tr > td { vertical-align: middle; }</style><table><thead><th>Format</th><th CSD>buffer #0</th<>th CSD>buffer #1</th<>th>CSD buffer #2</th></thead<>tbody class=mid><tr<>td>AAC</td td<>>decoder-specific information from ESDS<sup>*</sup></td td<>class=NA>Not Used</td><td class=NA>Not Used</Td></tr>td><>VORBIS</td><td>Identification header</td><td>Setup header</td><td class=NA>Not Used</td></tr<>><td td>OPUS</td<>td>Identification header</td><td>Pre-skip in nanosecs<br> (unsigned 64 位 ByteOrder#nativeOrder native-order integer.) br><<这会替代标识标头中的跳过前值。</td><td>Seek Pre-roll in nanosecs<br> (无符号 64 位 ByteOrder#nativeOrder native-order integer.) </td></tr><td<>>FLAC</td<>td>“fLaC”, ASCII 中的 FLAC 流标记,<br> 后跟 STREAMINFO 块 (必需的元数据块) ,br><(可选)后跟任意数量的其他元数据块</td<>td class=NA>Not Used</td><td class=NA>Not Used</td></tr><<>td>MPEG-4</td td<>>decoder specific information from ESDS<sup>*</sup></td td><class=NA>Not Used</td<>td class=NA>Not Used</td<>/tr<>><td>H.264 AVC</td<>td>SPS (序列参数集<sup>*</sup>) </td<>td>PPS (图片参数集<sup>*</sup>) </td><td class=NA>Not Used</td></tr><<>td>H.265 HEVC</td td><>VPS (Video Parameter Sets<sup>*</sup>) +<br> SPS (序列参数集<sup>*</sup>) +br><PPS (图片参数集<sup>*</sup>) </td<>td class=NA>Not Used</td><td class=NA>Not Used</td<>/tr<<>>td>VP9</td><td>VP9 CodecPrivate Data (optional) </td><td td class=NA>Not Used</td><td class=NA>Not Used</td<>/tr><tr><td>AV1</td><td>AV1 AV1CodecConfigurationRecord Data (optional) </td<>td class=NA>Not Used</td><td class=NA>Not Used</td<>/tr></tbody></table>

<p class=note><strong>注意:</如果编解码器立即刷新,或者在启动后不久,在返回任何输出缓冲区或输出格式更改之前,必须采取 strong> 操作,因为编解码器特定的数据可能会在刷新期间丢失。 在此类刷新后,必须使用标记为 #BUFFER_FLAG_CODEC_CONFIG 的缓冲区重新提交数据,以确保编解码器操作正确。

) 生成压缩数据的编码器 (或编解码器将在标记有 #BUFFER_FLAG_CODEC_CONFIG 编解码器-config 标志的输出缓冲区中的任何有效输出缓冲区之前创建并返回编解码器特定的数据。 包含编解码器特定数据的缓冲区没有有意义的时间戳。

<h3>数据处理</h3>

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

获取输入缓冲区后,使用 &ndash 填充数据并将其提交到编解码器;如果使用#queueSecureInputBuffer queueSecureInputBuffer解密,则将其提交到编解码器#queueInputBuffer queueInputBuffer。 请勿提交时间戳相同的多个输入缓冲区 (,除非它是标记为此类) 的特定于编解码器的数据。

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

虽然不需要立即将缓冲区重新提交/释放到编解码器,但按住输入和/或输出缓冲区可能会停止编解码器,并且此行为取决于设备。 <具体而言>,在释放/重新提交所有</em> 未完成的缓冲区之前<>,编解码器可能会在生成输出缓冲区时暂停。</strong> 因此,请尽量少地保留可用缓冲区。

根据 API 版本,可以通过三种方式处理数据:table>thead><tr<>th>Processing Mode</th>><API version <= 20<br>Jelly Bean/KitKat</th><>API version >= 21<br>Lollipop and later</th></tr<>/thead<>tbody<>tr><td>Synchronous API using buffer arrays</td>><Supported</td<<><td>Deprecated</td></tr><<>td>同步 API using buffers</td><td class=NA>Not Available</td><td>Supported</td></tr><td><td>Asynchronous API using buffers</td><td class=NA>Not Available</td><td>Supported</td></tr></tbody></table>

<h4>使用 Buffers/h4 的<异步处理>

由于 android.os.Build.VERSION_CODES#LOLLIPOP,首选方法是通过在调用 #configure configure之前设置回调来异步处理数据。 异步模式会略微更改状态转换,因为必须在 之后#flush调用 #start ,才能将编解码器转换为“正在运行”子状态并开始接收输入缓冲区。 同样,在初始调用 start 编解码器时,将直接移动到“正在运行”子状态,并开始通过回调传递可用的输入缓冲区。

<center><img src=“../../../images/media/mediacodec_async_states.svg“ style=”width: 516px;height: 353px“ alt=”MediaCodec 异步操作状态图“></center>

MediaCodec 通常在异步模式下如下所示使用:

MediaCodec codec = MediaCodec.createByCodecName(name);
             MediaFormat mOutputFormat; // member variable
             codec.setCallback(new MediaCodec.Callback() {
               {@literal @Override}
               void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
                 ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
                 // fill inputBuffer with valid data
                 &hellip;
                 codec.queueInputBuffer(inputBufferId, &hellip;);
               }

               {@literal @Override}
               void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, &hellip;) {
                 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.
                 &hellip;
                 codec.releaseOutputBuffer(outputBufferId, &hellip;);
               }

               {@literal @Override}
               void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
                 // Subsequent data will conform to new format.
                 // Can ignore if using getOutputFormat(outputBufferId)
                 mOutputFormat = format; // option B
               }

               {@literal @Override}
               void onError(&hellip;) {
                 &hellip;
               }
               {@literal @Override}
               void onCryptoError(&hellip;) {
                 &hellip;
               }
             });
             codec.configure(format, &hellip;);
             mOutputFormat = codec.getOutputFormat(); // option B
             codec.start();
             // wait for processing to complete
             codec.stop();
             codec.release();

<使用 Buffers/h4>的< h4 同步处理>

由于 android.os.Build.VERSION_CODES#LOLLIPOP,应使用 #getInputBuffer getInput/#getOutputBuffer OutputBuffer(int) 和/甚至在 #getInputImage getInput/#getOutputImage OutputImage(int) 同步模式下使用编解码器时检索输入和输出缓冲区。 这允许框架进行某些优化,例如在处理动态内容时。 如果调用 #getInputBuffers getInput/#getOutputBuffers OutputBuffers(),则会禁用此优化。

<p class=note><strong>注意:</strong> 不会同时使用缓冲区和缓冲区数组的方法。 具体而言,仅在取消输出缓冲区 ID 的排队后或之后直接调用 getInput/OutputBuffers ,值为 #INFO_OUTPUT_FORMAT_CHANGED#start

MediaCodec 通常在同步模式下使用如下:

MediaCodec codec = MediaCodec.createByCodecName(name);
             codec.configure(format, &hellip;);
             MediaFormat outputFormat = codec.getOutputFormat(); // option B
             codec.start();
             for (;;) {
               int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
               if (inputBufferId &gt;= 0) {
                 ByteBuffer inputBuffer = codec.getInputBuffer(&hellip;);
                 // fill inputBuffer with valid data
                 &hellip;
                 codec.queueInputBuffer(inputBufferId, &hellip;);
               }
               int outputBufferId = codec.dequeueOutputBuffer(&hellip;);
               if (outputBufferId &gt;= 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.
                 &hellip;
                 codec.releaseOutputBuffer(outputBufferId, &hellip;);
               } 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();

<使用缓冲区数组的 h4>同步处理 (已弃用) </h4>

在版本和之前版本中 android.os.Build.VERSION_CODES#KITKAT_WATCH ,输入和输出缓冲区集由 ByteBuffer[] 数组表示。 成功调用 #start后,使用 #getInputBuffers getInput/#getOutputBuffers OutputBuffers()检索缓冲区数组。 当非负) 时,将缓冲区 ID-s 用作这些数组 (的索引,如下面的示例所示。 请注意,尽管数组大小提供了上限,但数组大小与系统使用的输入和输出缓冲区数之间没有内在关联。

MediaCodec codec = MediaCodec.createByCodecName(name);
             codec.configure(format, &hellip;);
             codec.start();
             ByteBuffer[] inputBuffers = codec.getInputBuffers();
             ByteBuffer[] outputBuffers = codec.getOutputBuffers();
             for (;;) {
               int inputBufferId = codec.dequeueInputBuffer(&hellip;);
               if (inputBufferId &gt;= 0) {
                 // fill inputBuffers[inputBufferId] with valid data
                 &hellip;
                 codec.queueInputBuffer(inputBufferId, &hellip;);
               }
               int outputBufferId = codec.dequeueOutputBuffer(&hellip;);
               if (outputBufferId &gt;= 0) {
                 // outputBuffers[outputBufferId] is ready to be processed or rendered.
                 &hellip;
                 codec.releaseOutputBuffer(outputBufferId, &hellip;);
               } 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();

<h4>流结束处理</h4>

到达输入数据的末尾时,必须通过在对 的调用#queueInputBuffer queueInputBuffer中指定 标志来向编解码器发出#BUFFER_FLAG_END_OF_STREAM信号。 可以在最后一个有效输入缓冲区上执行此操作,也可以通过提交设置了流结束标志的附加空输入缓冲区来执行此操作。 如果使用空缓冲区,则将忽略时间戳。

编解码器将继续返回输出缓冲区,直到它通过在 中#dequeueOutputBuffer dequeueOutputBuffer设置或通过 返回Callback#onOutputBufferAvailable onOutputBufferAvailable的指定相同的流结束标志BufferInfo,最终发出输出流的结束信号。 这可以在最后一个有效输出缓冲区上设置,也可以在最后一个有效输出缓冲区之后的空缓冲区上设置。 应忽略此类空缓冲区的时间戳。

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

<h4>使用输出 Surface</h4>

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

使用输出 Surface 时,可以选择是否在图面上呈现每个输出缓冲区。 你有三个选项: <ul><li><strong>不呈现 buffer:</strong> 调用 #releaseOutputBuffer(int, boolean) releaseOutputBuffer(bufferId, false)。</li><li><strong>使用默认时间戳呈现缓冲区:</strong> 调用 #releaseOutputBuffer(int, boolean) releaseOutputBuffer(bufferId, true)。</li><li><strong>呈现具有特定时间戳的缓冲区:</strong> 调用 #releaseOutputBuffer(int, long) releaseOutputBuffer(bufferId, timestamp)。</li></ul>

由于 android.os.Build.VERSION_CODES#M,默认时间戳是缓冲区的 BufferInfo#presentationTimeUs 表示时间戳, (转换为纳秒) 。 在此之前没有定义它。

此外,由于 android.os.Build.VERSION_CODES#M,你可以使用 动态 #setOutputSurface setOutputSurface更改输出 Surface。

将输出呈现到 Surface 时,可能会将 Surface 配置为在) 及时丢弃 Surface 未使用的过多帧 (。 或者,它可以配置为不丢弃过多的帧。 在后一种模式下,如果 Surface 未以足够快的速度消耗输出帧,它最终会阻止解码器。 在 android.os.Build.VERSION_CODES#Q 确切行为之前未定义,除了视图图面 (SurfaceView 或 TextureView) 始终丢弃过多帧。 由于 android.os.Build.VERSION_CODES#Q 默认行为是放置过多帧。 应用程序可以通过以 SDK android.os.Build.VERSION_CODES#Q 为目标并将密钥MediaFormat#KEY_ALLOW_FRAME_DROP0设置为 配置格式,选择对非视图图面 ((如 ImageReader 或 SurfaceTexture) )禁用此行为。

<呈现到 Surface</h4>时的 h4 转换>

如果编解码器配置为 Surface 模式,则将自动应用任何裁剪矩形、MediaFormat#KEY_ROTATION旋转和 #setVideoScalingMode 视频缩放模式,但有一个例外: <p class=note> 在发布之前 android.os.Build.VERSION_CODES#M ,软件解码器在呈现到 Surface 上时可能未应用旋转。 遗憾的是,没有标准且简单的方法来识别软件解码器,或者它们是否应用轮换,而不是通过尝试。

还有一些注意事项。 <p class=note> 请注意,在 Surface 上显示输出时,不考虑像素纵横比。 这意味着,如果使用的是 #VIDEO_SCALING_MODE_SCALE_TO_FIT 模式,则必须定位输出 Surface,使其具有适当的最终显示纵横比。 相反,只能 #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING 将模式用于具有方形像素 (像素纵横比或 1:1) 的内容。 <p class=note> 另请注意,截至 android.os.Build.VERSION_CODES#N 发布时, #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING 模式可能无法对旋转 90 度或 270 度的视频正常工作。 <p class=note> 设置视频缩放模式时,请注意,每次输出缓冲区更改后都必须重置它。 #INFO_OUTPUT_BUFFERS_CHANGED由于 事件已弃用,因此可以在每次输出格式更改后执行此操作。

<h4>使用输入图面</h4>

使用输入 Surface 时,没有可访问的输入缓冲区,因为缓冲区会自动从输入图面传递到编解码器。 调用 #dequeueInputBuffer dequeueInputBuffer 将引发 IllegalStateException,并#getInputBuffers返回一个将强>MUST NOT</strong> 写入到的虚假ByteBuffer[]数组<。

调用 #signalEndOfInputStream 以发出流结束信号。 在此调用后,输入图面将立即停止向编解码器提交数据。

<h3>查找 &自适应播放支持</h3>

视频解码器 (使用压缩视频数据的一般编解码器,) 在查找和格式更改方面的行为不同,无论它们是否支持自适应播放并配置为自适应播放。 如果解码器通过 支持 CodecCapabilities#FEATURE_AdaptivePlayback自适应播放CodecCapabilities#isFeatureSupported CodecCapabilities.isFeatureSupported(String),则可以检查。 仅当将编解码器配置为解码到 时 Surface,才会激活对视频解码器的自适应播放支持。

<h4 id=KeyFrames>“KeyFrames”>流边界和关键帧</h4>

在合适的流边界之后 #start#flush 开始输入数据非常重要:第一个帧必须是关键帧。 <>对于大多数编解码器来说,可以自行 (完全解码关键帧</>em,这意味着 I 帧) ,关键帧后不会显示任何帧引用关键帧之前的帧。

下表总结了各种视频格式的合适关键帧。 <table>thead><tr><th>Format</th>><th Suitable key frame</th></tr></thead><tbody class=mid><tr><td>VP9/VP8</td td>><a合适的内部帧,其中没有后续帧引用此帧之前的帧。<<br> (对于此类关键帧没有特定名称。) </td></tr>><<td>H.265 HEVC</td<>tdID>或 CRA</td></tr<>td>><H.264 AVC</td><tdID></td></tr>><<td>MPEG-4<br>H.263<br>MPEG-2</td><td td>适合的 I 帧,其中没有后续帧引用帧在此帧之前。<br> (此类关键 frame.) </td<>/tr></tbody></table 没有特定名称>

<h4>对于不支持自适应播放的解码器, (包括未解码到 Surface) </h4>

为了开始解码与之前提交的数据不相邻的数据 (即在查找) 强 <>MUST</strong> 刷新解码器。 由于所有输出缓冲区在刷新时会立即撤销,因此可能需要先发出信号,然后等待流结束,然后再调用 flush。 刷新后的输入数据必须从合适的流边界/关键帧开始。 <p class=note>strong 注意:</strong> 刷新后提交的数据的格式不得更改;#flush不支持格式不连续;为此,需要一个完整的#start - - #stop#configure configure(&hellip;)周期。><

<p class=note><strong>另请注意:</strong> 如果在 &ndash 后 #start 刷新编解码器太快;通常,在收到第一个输出缓冲区或输出格式更改之前,–需要将编解码器 specific-data 重新提交到编解码器。 有关详细信息,请参阅编解码器特定数据部分。

<h4>对于支持和配置为自适应播放<的解码器/h4>

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

对于某些视频格式(即 H.264、H.265、VP8 和 VP9),还可以更改图片大小或配置。 为此,必须将整个新的特定于编解码器的配置数据与关键帧打包到单个缓冲区中, (包括) 的任何开始代码,并将其作为 <强>常规</强> 输入缓冲区提交。

在发生图片大小更改之后以及返回具有新大小的帧之前,你将从 #dequeueOutputBuffer dequeueOutputBuffer#INFO_OUTPUT_FORMAT_CHANGED 收到一个Callback#onOutputBufferAvailable onOutputFormatChanged回调。 <p class=note><strong>Note:</strong> 就像特定于编解码器的数据一样,在更改图片大小后不久调用 #flush 时要小心。 如果尚未收到图片大小更改的确认,则需要重复请求新的图片大小。

<h3>错误处理</h3>

工厂方法和#createByCodecName createByCodecName#createEncoderByType EncoderByType#createDecoderByType createDecoder/在失败时引发IOException,你必须捕获或声明这些方法才能传递。 当从不允许该方法的编解码器状态调用该方法时,MediaCodec 方法会引发 IllegalStateException ;这通常是由于应用程序 API 使用不正确造成的。 涉及安全缓冲区的方法可能会引发 CryptoException,其中包含可从 CryptoException#getErrorCode获取的进一步错误信息。

内部编解码器错误会导致 CodecException,这可能是由于媒体内容损坏、硬件故障、资源耗尽等,即使应用程序正确使用 API 也是如此。 接收 CodecException 时的建议操作可以通过调用 CodecException#isRecoverableCodecException#isTransient来确定:<ul><li><strong>可恢复错误:</strong> 如果isRecoverable()返回 true,则调用 #stop#configure configure(&hellip;)#start 进行恢复。</li><li><强>暂时性错误:</strong> 如果isTransient()返回 true,则资源暂时不可用,稍后可能会重试方法。</li><li><strong>致命错误:</strong> 如果 和 isTransient()isRecoverable()返回 false,则 CodecException 为致命,并且编解码器必须 #reset 重置或 #release 释放。</li></ul>

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

<h2 id=History>“History”>有效的 API 调用和 API 历史记录</h2>

本部分汇总了每种状态中的有效 API 调用以及 MediaCodec 类的 API 历史记录。 有关 API 版本号,请参阅 android.os.Build.VERSION_CODES

<style> .api > tr > th, .api > tr > td { text-align: center; padding: 4px 4px; } .api > tr > th { vertical-align: bottom; } .api > tr > td { vertical-align: middle; } .sml > tr > th, .sml > tr > td { text-align: center; padding: 2px 4px; } .fn { text-align: left; } .fn > code > a { font: 14px/19px Roboto Condensed, sans-serif; } .deg45 { white-space: nowrap; background: none; border: none; vertical-align: bottom; width: 30px; height: 83px;} .deg45 > div { transform: skew (-45deg, 0deg) translate (1px, -67px) ; transform-origin: bottom left 0; width: 30px; height: 20px; } .deg45 > div > div { border: 1px solid #ddd; background: #999; height: 90px; width: 42px; } .deg45 > div > div > div { transform: skew (45deg, 0deg) translate (-55px, 55px) rotate (-45deg) ; }</风格>

<table align=“right” style=“width: 0%”>thead<>tr><th>Symbol</th><>Meaning</th></tr<>/thead<>tbody class=sml<>tr><td>●</td td>><Supported</td></tr><><td>⁕</td td><>semantics changed</td></tr><td>><○</td><td td>实验支持</td></tr><tr><td<>[ ]</td td>><已弃用</td></tr<>td><>⎋</td td>><Restricted to surface input mode</td></tr><><td>⎆</td><td>Restricted to surface output mode</td></tr<><>td>▧</td><td>Restricted to ByteBuffer input mode</td></tr><<>td>↩</td><td>Restricted to synchronous mode/<td></tr><><td>⇄</td td><>限制为异步模式</td></tr<>tr><td> ( ) </td td><td>可以调用,但不应<调用/td></tr<>/tbody></table>

<table style=“width: 100%;”><thead class=api><tr><th class=deg45><div><div style=“background:#4285f4”><div>Uninitialized</div></div></><th><class=deg45><div><div style=“background:#f4b400”><div>Configured</div></div></><th><class=deg45><div><div style=“background:#e67c73”><div>Flushed</div></div></div></th><class=deg45><div><div style=“background:#0f9d58”><div>Running</div></div></div></th<>class=deg45><div><div style=“background:#f7cb4d”><div>End of Stream</div></div/div><></th<>th class=deg45><div><div style=“background:#db4437”><div>Error</div></div></div/><th><th class=deg45><div><div style=“background:#666”div>><Released</div></div></><th<>></th/th<>colspan=“8”>SDK Version</th></tr<>th><colspan=“7”>State</th><th>Method</th<>th>16</th>><th 17</th>><18</th>><19</th><>20/th<>>21<</th><>22</th>><23</th></tr></thead><tbody class=api><tr><td></td><td></td<>td></td><td></td><td/td><td><></td><td></td td/td<>td class=fn>#createByCodecName createByCodecName</td><td>●</td><td>●</td><td>●</td<>td>●</td><td>●</td<>td>●</td td>><●</td<>td>●</td<>/tr<>><td></td><td></td><td></td><td></td td/td><td></td><td></td<><><> td class=fn>#createDecoderByType createDecoderByType</td td>●</td><td>●</td<>td>●</td td>><●</td td>><●</td><td>●</td td<>>●</td td><td>●</td></tr<>><td></td><td></td<<>><><> td><><></Td><td></td><td></td><td class=fn#createEncoderByType createEncoderByType></td<>td>●</td><td>●</td><td>●</td<>td>●</td td<>>●</td<>td>●</td<>td>●</td><td>●</td></tr><tr<>td></td><td></td><td></td><td></td><td></td><td></td><td></td><td class=fn<#createPersistentInputSurface createPersistentInputSurface>/td td<><>/td<>td/td><td><></td><td></td td/td<>td></td<>td></td td><></td><td>●</td<>/tr><td>16+</td<>td>-</td<>td>-</td<>td>-</td<>td>-</td td>><-</td td>-</td<<>>td class=fn<>#configure configure/td<>td>●</td<>td>●</td<>td>●</td<>td>●</td<>><td●/td><td>⁕</td><td>●</td><td>●</td<>/tr><td>><-</td><td>18+</td<>td>-</td><td>-</td<>td>-</td><td-/td td>-</td td>-</td><><td class=fn#createInputSurface createInputSurface><></Td><td></td><td></td><td>⎋</td><td>⎋</td><td>⎋</td<>td>⎋</td><td>⎋</td><td>⎋</td></tr>><<td>-</td<>td>-</td<>td>16+</td><td>16+</td td><td> (16+) </td<>td>-</td<><>td>< td class=fn#dequeueInputBuffer dequeueInputBuffer<>/td<>td>●</td><td>●</td><td>▧</td><td>▧</td<>td>▧</td td><>⁕▧↩</td><td>▧↩</td><td>▧↩</td></Tr><tr>td>-</td><td td<>>>< 16+</td<>td>16+</td td 16+/td><td>16+</td><td>-</td td<>>-</td><td class=fn>#dequeueOutputBuffer dequeueOutputBuffer</td><td>●</td><td>●</td td>><●</td<>td>●</td><<td>●</td<>td>⁕↩</td<>td>↩</td<>td>↩</td></tr<>td><>-</td td><td>-</td<>td>16+</td><td>16+</td><td>16+</td td>><-</td<>td>-</td><td class=fn>#flush flush</td><td>●</td<>td>●</td<>td>●</td><td>●</td td><>●</td td>><●</td td><>●</td td<>>●</td></tr<>td<>>18+</td><<>td>>18+<</Td><td>18+</td<>td>18+</td<>td>18+</td<>td>-</td<>td class=fn#getCodecInfo getCodecInfo<>/td td><></td<>td>><><●</td<>td>●</td><td>●</td><td>●</td><td>●</td<>td>●</td></tr><><td>-</td><td>-</td<>td> (21+) </td td<>>21+</td<>td> (21+) </td td><>-</td<>td td>-</td><td class=fn><#getInputBuffer getInputBuffer/td><td></td><td></td><><><><td></td><td>●</td<>td>●</td<>td>●</td<>/tr<>><td>-</td td>><-</td<>td>16+</td td><> (16+) </td><td> (16+) </td<>td>-</td<>td><td<>><class=fn><#getInputBuffers getInputBuffers/td<>td>●</td<>td>●</td<>td>●</td td>><●</td><td>●</td td>><[⁕↩]</td><td>[↩]</td><td>[↩]</td<>/tr><><td>-</td><td>21+</td><td> (21+) </td><td> (21+) </td td<>> (21+) </td<>td td>><><-</td<>td class=fn<>#getInputFormat getInputFormat/td td><></td><td></td<>td></td<>td></td td><></td><td>●</td<>td>●/<td><td>●</td<>/tr<>><td>-</td><td>-</td><td> (21+) </td td>><21+</td td<>> (21+) </td<>td>-</td<>td>-</td<>td class=fn<#getInputImage getInputImage>/td><td></td td><></Td><td></td><td></td><td><><>○</td<>td>●</td td<>>●</td<>/tr><<>td>18+</td td<>>18+</td td>><18+/td td 18+</td<>td>18+</td><td>18+</td td><>18+</td><td>-</td<>td class=fn><#getName getName/td><td></td><td>><><●</td><td>●</td<>td>●</td><td>●</td<>td>●</td<>td>●</td></tr><Tr><td>-</td><td>-</td><td> (21+) </td><td>21+</td><td>21+</td td>><-</td<>td>-</td<>td class=fn#getOutputBuffer getOutputBuffer></td<>td></td<>td/td<>td></td><><><><td></td><td>●</td><td>●</td td><>●</td<>/tr<>><td>-</td td><>-</td<>td>16+</td<>td>16+</td><td 16+/td td>16+</td td><>-</td td-><></Td><td class=fn>#getOutputBuffers getOutputBuffers</td><td>●</td<>td>●</td><td>●</td><td>●</td<>td>●</td><td>[⁕↩]</td><td>[↩]</td><td>[↩]</td></tr>><<td>-/<td td>21+</td><td>16+</td><td>16+</td<>td>16+</td td<>>-</td<>td>-</td<>td class=fn><#getOutputFormat()/td<>td>●</td><td>●</td<>td>●</td><td●>><</Td><td●/td<>td>●</td<>td>●</td td<>>●</td></tr<>td>-</td><td><<> td-/td<>td> (21+) </td<>td>21+</td<>td>21+</td td<>><<><>td>-</td><td class=fn#getOutputFormat(int)></td<>td></td<>td></td><td></td<>td></td td><td></td><td>●</td<>td>●</td><td>●</td<>/tr<>td<>>-</td><td-></Td><td> (21+) </td><td>21+</td><td>21+</td td><>-</td<>td>-</td<>td class=fn><#getOutputImage getOutputImage/td><td td></td<>td></td<>td></td<>td></td/td><<><> td>○</td><td●/td<>td>●</td<>/tr>><<td>-</td td>><-</td<>td td>-</td><td>16+</td><td> (16+) </td td<>>-</td<>td>-</td<>td class=fn#queueInputBuffer queueInputBuffer></td>><<td>●</td<>td>●</td<>td>●</td><td>●</td><td>●</td<>td>⁕</td<>td>●</td td><>●</td></tr><><td>-</td<>td><><td>-</td><td>16+</td td<>> (16+) </td td<>>-</td><td>-</td><td class=fn><#queueSecureInputBuffer queueSecureInputBuffer/td<>td>●</td><td>●</td<>td>●</td<>td>●</td><td●></Td><td>⁕</td<>td>●</td td<>>●</td></tr<>td><>16+</td<>td>16+</td<>td>16+</td<>td>16+</td><td>16+</td><td 16+/td><td>>16+<</td><td class=fn>#release release</td<>td>●</td td>><●</td<>td>●</td<>td>●</td td>><●</td><td>●</td td<>>●</td td<>>●</td<>/tr<>td<>>-</td><td>-</td><td>-</td><td>16+</td><td>16+</td td>><-</td<>td>-</td<>td class=fn>#releaseOutputBuffer(int, boolean)</td td>><●</td><td>●</td><td>●</td<>td>●</td><td>●</td<>td>⁕</td><td>●</td<>td>⁕</td<>/tr<>td>><-</td td>><-</td<>td>-</td><td>21+</td><td>21+</td td><><><td>-</td><td class=fn>#releaseOutputBuffer(int, long)</td<>td></td><td></td><td></td><td></td td><></td><td>⎆</td><td>⎆</td td>><⎆</td<>/tr><><td>21+</td><td td>21+</td><td>21+</td><td>21+</td><td>21+</td<>td>21+</td td><td>-</td<>td class=fn#reset reset<>/td><td>< td/td><td></td<>td></td><td></><td<>>< td●></Td><td>●</td><td>●</td></tr><td><>21+</td><td>-</td td><>-</td><td>-</td<>td>-/td td-</td<>td>-</td<>td>-</td<>td class=fn#setCallback(Callback) setCallback<>/td td><td></Td><td></td><td></td><td></td><<>td><>●</td<>td>●</td td/td<>>< td<>#setCallback(Callback, Handler) &#8277;/tr<><>td>-</td td>><23+</td td><>-</td td-/td><td>-/td td-<><></Td><td>-</td><td>-</td><td class=fn<#setInputSurface setInputSurface>/td<>td></td><td></td<>td></td td><<>/td<>td></td td/td><td></td<>td></td td<>>⎋</td></tr<>td><>23+</td><td>23+</td<>td>23+</td<>td>23+</td<>td>23+</td td<>> (23+) </td<>td> (23+) </td><td td class=fn<#setOnFrameRenderedListener setOnFrameRenderedListener>/td<>td></td>><<td></td td></td><><><><><td></td><td></td><td>○ ⎆</td></tr><<>td>-</td><td>23+</td><td>23+</td<>td>23+</td><td 23+/td td>23+</td><td>-</td td>><-</td><td class=fn>/td><td></td><td></td><td></td><td></td><td></td><td></td><td></td td><>⎆</td<>/tr<><>td>19+</td><td>19+</td<>td>19+/td<>td>19+<#setOutputSurface setOutputSurface<</Td><td>19+</td td>>< (19+) </td<>td>-</td<>td class=fn<>#setParameters setParameters/td td>><</td td></td><<>td<><>>●</td<>td>●</td<>td>●</td><td>●</td td><td><●/td></tr<><>td>-</td<>td> (16+) </td td>>< (16+) </td<>td>16+</td td><> (16+) </td<>td> (16+) </td td><td<>>< class=fn<>#setVideoScalingMode setVideoScalingMode/td td><td><⎆/td td<>>⎆</td<>td>⎆</td<>td>⎆</td><td>⎆</td<>td>⎆</td<>td>⎆</td td><>⎆</td<>/tr><><td> (29+) </td><td>29+</td><td>29+</td><td>29+</td<>td> (29+) </td td<>> (29+) </td td>><-</td<>td class=fn#setAudioPresentation setAudioPresentation<>/td td><td></td<>td></td><td></td><td></td><><><><><td></td><td></td></tr>><<td>-</td td>><-</td<>td>18+</td><td>18+</td td><td>-</td<>td>-</td td>-/td><td-</td><td class=fn><#signalEndOfInputStream signalEndOfInputStream/td td<>></td><td></td><td>⎋</td><td>⎋</td<>td>⎋</td><td>⎋</td><td>⎋</td><td>⎋</td<>/tr<>><td>-</td><td>16+</td<>td>21+ (⇄) </td><td>-</td><td>-</td><td>-</td><td>-</td><td class=fn#start start<>/td><td>●</td<>td>●</td><td>●</td<>td>●</td<>td>●</td><td⁕></Td><td>●</td><td>●</td></tr>><<td>-</td td><>-</td<>td>16+</td<>td>16+</td<>td>16+/td td 16+</td<>td>-</td<>td>-</td<>td class=fn#stop stop<>/td td><><●/td td>><●</td><td>●</td><td>●</td<>td>●</td<>td>●</td><td>●</td><td>●</td<>/tr<>/tbody></table>

android.media.MediaCodecJava 文档。

此页面的某些部分是基于 创建和共享的工作进行的修改,并根据 署名许可中所述的条款使用。

字段

BufferFlagCodecConfig
已过时.

这表示标记为此类的缓冲区包含编解码器初始化/编解码器特定的数据,而不是媒体数据。

BufferFlagDecodeOnly
已过时.

这表示缓冲区已解码并更新解码器的内部状态,但不生成任何输出缓冲区。

BufferFlagEndOfStream
已过时.

这表示流 i 的结束。

BufferFlagKeyFrame
已过时.

这表示 (编码) 缓冲区标记为此类缓冲区包含关键帧的数据。

BufferFlagPartialFrame
已过时.

这表示缓冲区仅包含帧的一部分,解码器应对数据进行批处理,直到在解码帧之前出现不带此标志的缓冲区为止。

BufferFlagSyncFrame
已过时.

这表示 (编码) 缓冲区标记为此类缓冲区包含关键帧的数据。

ConfigureFlagEncode
已过时.

如果此编解码器要用作编码器,请传递此标志。

ConfigureFlagUseBlockModel
已过时.

如果此编解码器要与 和/或 HardwareBuffer一起使用LinearBlock,请传递此标志。

ConfigureFlagUseCryptoAsync
已过时.

仅应在安全解码器上使用此标志。

CryptoModeAesCbc
已过时.

MediaCodec 类可用于访问低级别媒体编解码器 i.

CryptoModeAesCtr
CryptoModeUnencrypted
InfoOutputBuffersChanged
已过时.

输出缓冲区已更改,客户端必须引用从此点返回 #getOutputBuffers 的新输出缓冲区集。

InfoOutputFormatChanged
已过时.

输出格式已更改,后续数据将遵循新格式。

InfoTryAgainLater
已过时.

如果在对 的调用 #dequeueOutputBuffer中指定了非负超时,则指示调用超时。

ParameterKeyHdr10PlusInfo

在下一个排队输入帧上设置 HDR10+ 元数据。

ParameterKeyLowLatency

启用/禁用低延迟解码模式。

ParameterKeyOffsetTime

指定要在时间戳之上添加的偏移量 ((以微秒为单位) )。

ParameterKeyRequestSyncFrame

请求编码器“很快”生成同步帧。

ParameterKeySuspend

暂时暂停/恢复输入数据的编码。

ParameterKeySuspendTime

存在时 #PARAMETER_KEY_SUSPEND ,客户端还可以选择使用此键指定暂停/恢复操作生效的微秒) 时间戳 (。

ParameterKeyTunnelPeek

当编解码器配置为隧道模式 MediaFormat#KEY_AUDIO_SESSION_ID 时,在 暂停时 AudioTrack 控制第一帧的视频速览。

ParameterKeyVideoBitrate

动态更改视频编码器的目标比特率。

VideoScalingModeScaleToFit
已过时.

内容缩放到图面尺寸

VideoScalingModeScaleToFitWithCropping
已过时.

内容是缩放的,保持其纵横比,使用整个外围应用,内容可以裁剪。

属性

CanonicalName

检索基础编解码器名称。

Class

返回此 Object的运行时类。

(继承自 Object)
CodecInfo

获取编解码器信息。

Handle

基础 Android 实例的句柄。

(继承自 Object)
InputFormat

成功返回后 #configure 调用此项以获取编解码器接受的输入格式。

JniIdentityHashCode

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
JniPeerMembers

MediaCodec 类可用于访问低级别媒体编解码器 i.

Metrics

返回有关当前编解码器实例的指标数据。

Name

检索编解码器名称。

OutputFormat

在 dequeueOutputBuffer 通过返回 来发出格式更改的信号后调用 #INFO_OUTPUT_FORMAT_CHANGED

PeerReference

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
SupportedVendorParameters

返回供应商参数名称的列表。

ThresholdClass

此 API 支持 Mono for Android 基础结构,不应直接从代码使用。

(继承自 Object)
ThresholdType

此 API 支持 Mono for Android 基础结构,不应直接从代码使用。

(继承自 Object)

方法

Clone()

创建并返回此对象的副本。

(继承自 Object)
Configure(MediaFormat, Surface, MediaCodecConfigFlags, MediaDescrambler)

将组件配置为与解乱器一起使用。

Configure(MediaFormat, Surface, MediaCrypto, MediaCodecConfigFlags)

配置组件。

CreateByCodecName(String)

如果知道要实例化的组件的确切名称,请使用此方法将其实例化。

CreateDecoderByType(String)

实例化支持给定 mime 类型的输入数据的首选解码器。

CreateEncoderByType(String)

实例化支持给定 mime 类型的输出数据的首选编码器。

CreateInputSurface()

请求 Surface 用作编码器的输入,以取代输入缓冲区。

CreatePersistentInputSurface()

创建可与通常具有输入图面的编解码器(例如视频编码器)配合使用的永久性输入图面。

DequeueInputBuffer(Int64)

返回要用有效数据填充的输入缓冲区的索引;如果当前没有可用的此类缓冲区,则返回 -1。

DequeueOutputBuffer(MediaCodec+BufferInfo, Int64)

取消对输出缓冲区的排队,最多阻止“timeoutUs”微秒。

Dispose()

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
Dispose(Boolean)

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
Equals(Object)

指示其他某个对象是否“等于”此对象。

(继承自 Object)
Flush()

刷新组件的输入和输出端口。

GetHashCode()

返回对象的哈希代码值。

(继承自 Object)
GetInputBuffer(Int32)

为取消排队的输入缓冲区索引返回一个 java.nio.Buffer#clear cleared、可写的 ByteBuffer 对象,以包含输入数据。

GetInputBuffers()
已过时.

检索输入缓冲区集。

GetInputImage(Int32)

返回取消排队的输入缓冲区索引的可写 Image 对象,以包含原始输入视频帧。

GetOutputBuffer(Int32)

返回取消排队的输出缓冲区索引的只读 ByteBuffer。

GetOutputBuffers()
已过时.

检索输出缓冲区集。

GetOutputFormat(Int32)

返回特定输出缓冲区的输出格式。

GetOutputFrame(Int32)

返回一个 OutputFrame 对象。

GetOutputImage(Int32)

返回包含原始视频帧的取消排队输出缓冲区索引的只读 Image 对象。

GetParameterDescriptor(String)

描述名称为 的参数。

GetQueueRequest(Int32)

QueueRequest返回输入槽索引的 对象。

JavaFinalize()

当垃圾回收确定不再引用对象时,由对象上的垃圾回收器调用。

(继承自 Object)
MapHardwareBuffer(HardwareBuffer)

HardwareBuffer 对象映射到 Image,以便可访问缓冲区的内容。

Notify()

唤醒正在等待此对象的监视器的单个线程。

(继承自 Object)
NotifyAll()

唤醒正在等待此对象的监视器的所有线程。

(继承自 Object)
QueueInputBuffer(Int32, Int32, Int32, Int64, MediaCodecBufferFlags)

在指定索引处填充输入缓冲区的范围后,将其提交到组件。

QueueSecureInputBuffer(Int32, Int32, MediaCodec+CryptoInfo, Int64, MediaCodecBufferFlags)

类似于 #queueInputBuffer queueInputBuffer ,但会提交可能加密的缓冲区。

Release()

释放编解码器实例使用的资源。

ReleaseOutputBuffer(Int32, Boolean)

如果已完成缓冲区,请使用此调用将缓冲区返回到编解码器或将其呈现在输出图面上。

ReleaseOutputBuffer(Int32, Int64)

如果已完成缓冲区,请使用此调用更新其表面时间戳,并将其返回到编解码器以将其呈现在输出图面上。

Reset()

将编解码器返回到其初始 (未初始化) 状态。

SetAudioPresentation(AudioPresentation)

设置音频演示文稿。

SetCallback(MediaCodec+Callback)

为默认循环器上可操作的 MediaCodec 事件设置异步回调。

SetCallback(MediaCodec+Callback, Handler)

为默认循环器上可操作的 MediaCodec 事件设置异步回调。

SetHandle(IntPtr, JniHandleOwnership)

设置 Handle 属性。

(继承自 Object)
SetInputSurface(Surface)

配置编解码器 (e。

SetOnFirstTunnelFrameReadyListener(Handler, MediaCodec+IOnFirstTunnelFrameReadyListener)

注册一个回调,当第一个输出帧已解码并准备好在配置为使用 KEY_AUDIO_SESSION_ID的隧道模式的编解码器上呈现时要调用。

SetOnFrameRenderedListener(MediaCodec+IOnFrameRenderedListener, Handler)

注册在输出图面上呈现输出帧时要调用的回调。

SetOutputSurface(Surface)

动态设置编解码器的输出图面。

SetParameters(Bundle)

将其他参数更改传达给组件实例。

SetVideoScalingMode(VideoScalingMode)

如果在之前的调用中指定了图面, #configure 则指定要使用的缩放模式。

SignalEndOfInputStream()

输入时发出流结束信号。

Start()

成功配置组件后,调用 start

Stop()

完成解码/编码会话,请注意编解码器实例保持活动状态,并准备 #start再次进行。

SubscribeToVendorParameters(IList<String>)

订阅供应商参数,以便这些参数将存在于 中 #getOutputFormat ,对这些参数的更改将生成输出格式更改事件。

ToArray<T>()

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
ToString()

返回对象的字符串表示形式。

(继承自 Object)
UnregisterFromRuntime()

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
UnsubscribeFromVendorParameters(IList<String>)

取消订阅供应商参数,以便这些参数将不存在于中 #getOutputFormat ,并且对这些参数的更改不再生成输出格式更改事件。

Wait()

导致当前线程等待,直到它被唤醒,通常是通过 em <通知/em> 或 <em>interrupted</em>。<>

(继承自 Object)
Wait(Int64)

导致当前线程等待,直到它被唤醒,通常是通过 em <通知/em> 或 <em>interrupted</em>,或直到经过一定数量的实时。<>

(继承自 Object)
Wait(Int64, Int32)

导致当前线程等待,直到它被唤醒,通常是通过 em <通知/em> 或 <em>interrupted</em>,或直到经过一定数量的实时。<>

(继承自 Object)

显式接口实现

IJavaPeerable.Disposed()

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
IJavaPeerable.DisposeUnlessReferenced()

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
IJavaPeerable.Finalized()

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
IJavaPeerable.JniManagedPeerState

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
IJavaPeerable.SetJniIdentityHashCode(Int32)

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
IJavaPeerable.SetJniManagedPeerState(JniManagedPeerStates)

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)
IJavaPeerable.SetPeerReference(JniObjectReference)

MediaCodec 类可用于访问低级别媒体编解码器 i.

(继承自 Object)

扩展方法

JavaCast<TResult>(IJavaObject)

执行 Android 运行时检查的类型转换。

JavaCast<TResult>(IJavaObject)

MediaCodec 类可用于访问低级别媒体编解码器 i.

GetJniTypeName(IJavaPeerable)

MediaCodec 类可用于访问低级别媒体编解码器 i.

适用于