android 原生 MediaPlayer 和 MediaCodec 的区别和联系(三)
目录:
(4)Android 官方网站 对 MediaCodec的介绍
注:编解码器特定数据(Code-specific Data,简写为csd) 部分结合网上资料加入了补充和个人理解。请悉知。
正文:
MediaCodec类可用于访问底层媒体编解码器,例如:编码器/解码器组件。它是Android底层多媒体支持基础架构的一部分(通常与MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, 和 AudioTrack 一起使用。)
从广义上讲,编解码器处理输入数据生成输出数据。它异步处理数据并使用一组输入和输出缓冲区。简单来讲,你请求(或接受)一个空的输入缓冲区,填满数据将它发送给该编解码处理器处理。 编解码器使用数据并将其转换为其空输出缓冲区之一。 最后,您请求(或接收)填充的输出缓冲区,使用其内容并将其释放回编解码器。
数据类型
编解码器对三种数据进行操作:压缩数据、原始音频数据、原始视频数据。可以使用 ByteBuffers 处理所有这三种数据,但是你应该对原始视频数据使用一个 Surface 来提高编解码性能。Surface 使用本地视频缓冲而不映射或复制他们到 ByteBuffers;因此,效率更高。使用Surface时通常无法访问原始视频数据,但您可以使用 ImageReader 类访问不安全的解码(原始)视频帧。这可能仍然比使用 ByteBuffer 高效,因为一些本地缓冲区可能被映射到 direct ByteBuffers。使用 ByteBuffer 模式时,可以使用 Image 和 getInput/OutputImage(int) 来访问原始视频帧。
压缩缓冲区
输入缓冲区(用于解码器)和输出缓冲区(用于编码器)包含根据样式类型(according to the format's type)的压缩数据。对于视频类型,这通常是单个压缩视频帧。对于音频数据,这通常是单个访问单元(一个音频编码通常包含由格式类型指示的几毫秒的音频),但是该要求稍微放宽,一个缓冲区也可能包含多个编码的音频访问单元。在任何一种情况下,缓冲区不会在任意字节边界上开始或结束,而是在帧/访问单元边界上开始或结束,除非它们使用 BUFFER_FLAG_PARTIAL_FRAME标记。
原始音频缓冲区
原始音频缓冲区包含整个PCM音频数据帧,这是通道顺序中每个通道的一个样本。 每个样本都是本机字节顺序的16位有符号整数。
原始视频缓冲区
在ByteBuffer模式下,视频缓冲区根据它们的颜色格式来布局。你可以从 getCodecInfo().getCapabilitiesForType(…).colorFormats 获取到支持的颜色格式数组。视频编解码器可以支持三种颜色格式:
- native raw video format:这由 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface 标记,并且它可以和输入输出缓冲区一起使用。
- flexible YUV buffers(例如MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible):通过使用getInput/OutputImage(int) ,它们可以与输入/输出 Surface 一起使用,也可以在 ByteBuffer 模式下使用。
- other, specific formats:这些通常仅在 ByteBuffer 模式下受支持。某些颜色格式是供应商特定的。 其他在MediaCodecInfo.CodecCapabilities中定义。 对于等效于灵活格式的颜色格式,您仍然可以使用getInput/OutputImage(int)。
从 Build.VERSION_CODES.LOLLIPOP_MR1 起所有的视频编解码器均支持灵活的 YUV 4:2:0 缓冲器。
在旧设备上访问原始视频缓冲区
在Build.VERSION_CODES.LOLLIPOP 和 Image 支持之前,你需要使用 MediaFormat.KEY_STRIDE 和 MediaFormat.KEY_SLICE_HEIGHT 输出格式值去了解原始输出缓存的布局。
★ 请注意,在某些设备上,切片高度公布为0。这可能意味着切片高度与框架高度相同,或者切片高度是与某个值对齐的框架高度(通常是2)。 不幸的是,在这种情况下,没有标准和简单的方法来告诉实际的切片高度。 此外,平面格式的U平面的垂直步幅也未指定或定义,但通常它是切片高度的一半。
MediaFormat.KEY_WIDTH 和 MediaFormat.KEY_HEIGHT 键指定视频帧的大小;然而,对于大多数内容,视频(图片)仅占据视频帧的一部分。这由‘裁剪矩形’表示。
你需要使用如下键从 输出格式 (output format)获取原始输出图像裁剪矩形。如果这些键不存在,视频占据整个视频帧。在应用任何旋转(rotation)之前结合输出帧的上下文理解裁剪矩形。
注:1). MINUS 翻译为“减”
2). 右侧和底部坐标可以理解为裁剪输出图像的最右侧有效列/最底部有效行的坐标。
视频帧(旋转前)的大小可以这样计算:
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的含义在各设备之间并不一致。 在某些设备上,偏移指向裁剪矩形的左上角像素,而在大多数设备上,它指向整个帧的左上角像素。
状态
在其生命周期中,编解码器在概念上存在以下三种状态之一:停止、执行或释放。停止集合状态实际上是三个状态的集合:未初始化、已配置以及错误,而执行状态通过三个子状态进行:刷新、运行和流结束。
当你使用其中一种工厂方法创建编解码器时,该编解码器处于未初始化状态。首先,你需要通过 configure(…) 进行配置,将其置于已配置状态,然后调用 start() 将其移至执行状态。在此状态下,您可以通过上述缓冲区队列操作处理数据。
执行状态有三个子状态:Flushed,Running和 End-of-Stream。在 start() 之后,编解码器立即处于 Flushed子状态,该状态持有所有缓冲区。一旦第一个输入缓冲区出列,编解码器就会移动到(变为是否更合适?)Running子状态,在该状态下会花费生命周期中绝大部分的时间。使用 end-of-stream marker 对输入缓冲区进行排队时,编解码器转换为 End-Of-Stream子状态。在此状态下,编解码器不再接受其他输入缓冲区,但仍会生成输出缓冲区直到 end-of-steam 到达输出端。在执行状态时使用 flush() ,您可以随时返回到 Flushed 子状态。
在极少数情况下,编解码器可能会遇到错误并移动到 错误 状态。这是使用来自排队操作的无效返回值来传达的,或者有时是通过异常。调用 reset() 以使得编解码器再次可用。您可以从任何状态调用它以将编解码器移回到 未初始化状态。否则,调用 release() 移动到终端 释放状态。
创建
使用 MediaCodecList 为特定的 MediaFormat 创建一个 MediaCodec。解码文件或流时,您可以从 MediaExtractor.getTrackFormat 获取所需的格式。使用 MediaFormat.setFeatureEnabled 注入您想添加的任何特定功能,然后调用 MediaCodecList.findDecoderForFormat 以获取可以处理该特定媒体格式的编解码器的名称。最后,使用 createByCodecName(String) 创建 编解码器(the codec)。
★ 注:在 Build.VERSION_CODES.LOLLIPOP上,MediaCodecList.findDecoder/EncoderForFormat 的格式不得包含 frame rate 。使用 format.setString(MediaFormat.KEY_FRAME_RATE, null) 清除格式中的任何现有帧率设置。
您还可以使用 createDecoder/EncoderByType(String) 为特定 MIME 类型创建首选编解码器/但是,这不能用于注入功能,并且可能会创建无法处理特定所需媒体格式的编解码器。
创建安全解码器
在Build.VERSION_CODES.KITKAT_WATCH 以及更早版本中,安全编解码器可能未在 MediaCodecList 中列出,但可能仍在系统可用。存在的安全编解码器只能通过名称实例化,方法是通过给常规编解码器的名称追加“.secure”后缀(所有安全编解码器的名称必须以“.secure”结尾。)。如果该编解码器不存在于系统中,createByCodecName(String) 将抛出 IOException。
从 Build.VERSION_CODES.LOLLIPOP 开始,你应该使用媒体格式中的 MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback 功能来创建安全的编解码器。
初始化
创建编解码器后,如果您想异步处理数据,你可以使用 setCallback 设置一个回调。然后,使用特定媒体格式配置( configure )该编解码器。这时您可以为视频生产者(生成原始视频数据的编解码器(例如:视频解码器))指定输出 Surface。这时您也可以为安全编解码器(请参阅MediaCrypto)设置解密参数。最后,由于某些编解码器可以在多种模式下运行,因此必须指定是否要将其用作解码器或编码器。
从Build.VERSION_CODES.LOLLIPOP 后,您可以在 已配置 状态下查询生成的输入和输出格式。你可以使用它来验证生成的配置,例如:颜色格式,在启动编解码器。
如果要使用视频消费者本地化地处理原始输入视频缓冲区——处理原始视频输入的编解码器,例如视频编码器——在配置后使用createInputSurface() 为输入数据创建一个目标Surface。或者,通过调用 setInputSurface(Surface) 设置编解码器以使用先前创建的持久输入surface(persistent input surface)。
编解码器特定数据(Code-specific Data,简写为csd)
某些格式,尤其 AAC音频 和 MPEG4, H.264以及H.265视频格式 要求 实际数据 以包含设置数据或编解码器特定数据的多个缓冲区 来做前缀(为辅助理解,附上相关示例:请参阅图一、二、三及图四)。(未完待续……,
(图一)
(图二)
(图三)
(图四)
继续接以上未完成翻译)处理此类压缩数据时,必须在 start() 之后 和 任何帧数据 之前将此数据提交给编解码器。在调用queueInputBuffer(源码)时*最后一位参数*必须赋值为标志 BUFFER_FLAG_CODEC_CONFIG 以标记此类数据。
特定编解码器数据也可以包含在通过关键字“csd-0”,“csd-1”,等的ByteBuffer条目中配置(configure)的格式中。这些键总是包含在从 MediaExtractor 获得的轨道 MediaFormat 中。格式中的编解码器特定数据在 start() 时自动提交给编解码器;你不能显示地提交这类数据。如果格式不包含特定编解码器数据,则可以根据根据格式要求选择使用指定数量的缓冲区以正确的顺序提交。对于 H.264 AVC,您还可以连接所有特定编解码器数据并将其作为单个编解码器配置缓冲区提交。
Android使用以下特定编解码器数据缓冲区。这些也需要以轨道格式设置,以便正确配置 MediaMuxer 轨道。每个参数集和标有(*)的编解码器特定数据部分必须以起始代码“\x00\x00\x00\x01”开头。
★ 注意:如果在返回任何输出缓冲区或输出格式更改之前立即或在启动后不久刷新编解码器,则必须小心,因为在刷新期间编解码器特定数据可能会丢失。在进行此类刷新后,必须使用标记为 BUFFER_FLAG_CODEC_CONFIG 的缓冲区重新提交数据,以确保正确的编解码器操作。
编码器(或生成压缩数据的编解码器)将在标记有codec-config flag标志的输出缓冲区中的任何有效输出缓冲区前创建并返回编解码器特定数据。包含编解码器特定数据的缓冲区没有有意义的时间戳。
数据处理
每一个编解码器都维护一组输入和输出缓冲区,这些缓冲区在API调用中由缓冲区ID引用。在成功调用start()之后,客户端“owns”既不拥有输入也不拥有输出缓冲区。在同步模式下,调用 dequeueInput/OutputBuffer(…) 获取(获得所有权)输入或输出缓冲区。在异步模式下,您将通过 MediaCodec.Callback.onInput/OutputBufferAvailable(…) 回调自动接收可用缓冲区。
获取输入缓冲区后,使用 queueInputBuffer – 或 queueSecureInputBuffer(如果使用解密)将其填入数据并将其提交给编解码器。不要提交具有相同时间戳的多个输入缓冲区(除非它是标记为此类编解码器特定数据)。
反过来,编解码器将通过异步模式下的onOutputBufferAvailable回调返回只读输出缓冲区,或者响应同步模式下的dequeuOutputBuffer 调用。处理完输出缓冲区后,调用releaseOutputBuffer方法之一将缓冲区返回给编解码器。
虽然您不需要立即向编解码器重新提交/释放缓冲区,但持有输入和/或输出缓冲区可能会使编解码器停止运行,并且此行为取决于设备。具体地说,编解码器可能会在生成输出缓冲区前暂停,直到所有未完成的缓冲区都被释放/重新提交。因此,尽可能少地保持可用的缓冲区。
根据API版本,您可以通过三种方式处理数据:
使用 Buffers 异步处理
从Build.VERSION_CODES.LOLLIPOP开始,首选方法是通过在调用 configure 之前设置回调来异步处理数据。异步模式稍微地改变了状态转换,因为必须在 flush() 之后调用start() 以将编解码器转换为 Running 子状态并开始接收输入缓冲区。类似地,在初始调用启动后,编解码器将直接移动到 Runnning 子状态并开始通过回调传递可用的输入缓冲区。
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();
使用 Buffers 同步处理
从 Build.VERSION_CODES.LOLLIPOP 开始,即使在同步模式下使用编解码器,也应使用 getInput/OutputBuffer(int) 和/或 getInput/OutputImage(int) 检索输入和输出缓冲区。这允许框架的某些优化,例如:处理动态内容时。如果调用 getInput/OutputBuffers(),则禁用此优化。
★ 不要同时混合使用缓冲区和缓冲区数组的方法。具体来说,只能在 start() 之后或在讲输出缓冲区ID以INFO_OUTPUT_FORMAT_CHANGED 值出列后直接调用 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();
使用 Buffer Arrays 同步处理(舍弃)
版本Build.VERSION_CODES.KITKAT_WATCH以及之前,输入和输出缓冲区的集合由 ByteBuffer[] 数组表示。 成功调用start()后,使用 getInput/OutputBuffers() 检索缓冲区数组。 使用缓冲区 ID-s 作为这些数组的索引(当为非负数时),如下面的示例所示。 请注意,尽管数组大小提供了上限,但数组的大小与系统使用的输入和输出缓冲区的数量之间没有固有的相关性。
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 标志来将其发送到编解码器。 您可以在最后一个有效的输入缓冲区上执行此操作,或者通过提交一个设置有 end-of-stream 标志的额外空输入缓冲区来执行此操作。 如果使用空缓冲区,则将忽略时间戳。
编解码器将继续返回输出缓冲区,直到它最终通过在 dequeueOutputBuffer 中设置的 MediaCodec.BufferInfo 中指定相同的 end-of-stream 标志或通过 onOutputBufferAvailable 返回来通知输出流的结尾。 这可以在最后一个有效输出缓冲区上设置,也可以在最后一个有效输出缓冲区后的空缓冲区上设置。 应忽略此类空缓冲区的时间戳。
除非编解码器已刷新,或已停止并重新启动,否则在发出输入流结束信号后,请勿提交额外输入缓冲区。
使用一个输出 Surface
使用输出Surface时,数据处理几乎与 ByteBuffer 模式相同; 但是,输出缓冲区将不可访问,并表示为空值。 例如。 getOutputBuffer/Image(int) 将返回 null,getOutputBuffers() 将返回仅包含nulll-s的数组。
使用输出Surface时,可以选择是否在曲面上渲染每个输出缓冲区。 你有三个选择:
- 不渲染缓冲区:调用releaseOutputBuffer(bufferId, false)。
- 使用默认时间戳呈现缓冲区:调用releaseOutputBuffer(bufferId, true)。
- 使用特定时间戳呈现缓冲区:调用releaseOutputBuffer(bufferId, timestamp)。
从Build.VERSION_CODES.M开始,默认时间戳是缓冲区的显示时间戳-presentation timestamp (转换为纳秒)。 之前没有定义。
此外,自Build.VERSION_CODES.M起,您可以使用setOutputSurface动态更改输出Surface。
渲染到曲面上的变换
如果编解码器配置为“曲面”模式,任何裁剪矩形,旋转(rotation)和视频缩放模式(video scaling mode)会自动应用,但有一个例外:
★ 在 Build.VERSION_CODES.M版本之前,软件解码器在渲染到Surface上时可能没有应用旋转。遗憾的是,没有标准和简单的方法来识别软件解码器,或者如果他们应用旋转而不是通过尝试它。
还有一些警告。
★ 请注意,在Surface上显示输出时不考虑像素长宽比。这意味着如果您使用的是VIDEO_SCALING_MODE_SCALE_TO_FIT模式,则必须定位输出Surface,使其具有正确的最终显示宽高比。相反,您只能对具有方形像素(像素长宽比或1:1)的内容使用VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING模式。
★ 另请注意,从Build.VERSION_CODES.N 版本开始,对于旋转90度或270度的视频,VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING模式可能无法正常工作。
★ 设置视频缩放模式时,请注意每次输出缓冲区更改后必须复位。由于不推荐使用INFO_OUTPUT_BUFFERS_CHANGED事件,因此可以在每次输出格式更改后执行此操作。
使用一个输入 Surface
使用输入Surface时,没有可访问的输入缓冲区,因为缓冲区会自动从输入 surface 传递到编解码器。 调用 dequeueInputBuffer 将抛出IllegalStateException,并且getInputBuffers()返回一个绝不能写入的伪造的ByteBuffer[]数组。
调用 signalEndOfInputStream() 来发信号给末端流(end-of-stream)。 在此调用之后,输入surface 将立即停止向编解码器提交数据。
寻址(seek) & 自适应回放支持
视频解码器(以及消费压缩视频数据的一般编解码器)在寻址和格式改变方面表现不同,无论它们是否支持并配置为自适应回放。 您可以通过CodecCapabilities.isFeatureSupported(String)检查解码器是否支持自适应播放(adaptive playback)。 只有将编解码器配置为在Surface上解码时,才会激活对视频解码器的自适应回放支持。
流边界和关键帧
重要的是start()或flush()之后的输入数据在合适的流边界处开始:第一帧必须是关键帧。 关键帧可以完全自己解码(对于大多数编解码器,这意味着I-帧(I-frame)),并且在关键帧之后没有要显示的帧指的是关键帧之前的帧。
下表总结了适用于各种视频格式的关键帧。
对于不支持自适应播放的解码器(包括不解码到Surface时)
为了开始解码与先前提交的数据不相邻的数据(即在寻址(seek)之后),你必须刷新解码器。由于所有输出缓冲区在刷新时立即被撤销,因此您可能需要首先发出信号,然后在调用flush之前等待末端流(end-of-steam)。重要的是,刷新之后的输入数据在合适的流边界/关键帧处开始。
★ 注意:刷新后提交的数据格式不得更改;flush() 不支持格式不连续;为此,一个完整的stop() - configure(…) - start() 循环是必要的。
★ 另请注意:如果在start() 后过早刷新编解码器 - 通常,在收到第一个输出缓冲区或输出格式更改之前 - 您需要将编解码器特定数据(codec-specific-data)重新提交给编解码器。有关详细信息,请参阅编解码器特定数据部分(codec-specific-data section)。
适用于支持和配置自适应播放的解码器
为了开始解码与先前提交的数据不相邻的数据(即,在寻址(seek)之后),不必刷新解码器;但是,不连续后的输入数据必须从合适的流边界/关键帧开始。
对于某些视频格式 - 即H.264,H.265,VP8和VP9 - 也可以在中途改变图像大小或配置mid-stream。 为此,必须将整个新的编解码器特定配置数据与关键帧一起打包到单个缓冲区(包括任何起始代码)中,并将其作为常规输入缓冲区提交。
在发生图片大小更改之后以及返回任何具有新大小的帧之前,您将从dequeueOutputBuffer或onOutputFormatChanged回调中收到INFO_OUTPUT_FORMAT_CHANGED返回值。
★ 注意:就像编解码器特定数据的情况一样,在更改图片大小后立即调用flush()时要小心。 如果您尚未收到图片尺寸更改的确认,则需要重复请求新图片尺寸。
异常处理
工厂方法 createByCodecName 和 createDecoder/EncoderByType 在失败时抛出IOException,您必须捕获或声明要传递。当从不允许它的编解码器状态调用方法时,MediaCodec 方法抛出 IllegalStateException;这通常是由于申请API用法不正确。涉及安全缓冲区的方法可能会抛出MediaCodec.CryptoException,它可以从MediaCodec.CryptoException.getErrorCode()获得更多错误信息。
内部编解码器错误导致MediaCodec.CodecException,这可能是由于媒体内容损坏,硬件故障,资源耗尽等等,即使应用程序正确使用API也是如此。接收到一个CodecException时建议的操作是可以通过调用MediaCodec.CodecException.isRecoverable()和MediaCodec.CodecException.isTransient()来确定:
- 可恢复的错误:如果isRecoverable()返回 true,则调用stop(),configure(…)和start()进行恢复。
- 瞬态错误:如果iisTransient()返回 true,则资源暂时不可用,并且可以在以后重试该方法。
- 致命错误:如果isRecoverable()和isTransient()都返回 false,则CodecException是致命的,并且必须重置(reset )或释放编解码器(released)。
isRecoverable()和isTransient()都不会同时返回 true。
写在文末:本文在对官方文档进行翻译时,一些地方结合网上资料加入了补充资料或自己的理解,譬如:编解码器特定数据(Code-specific Data,简写为csd) 部分。如有不当或影响阅读,请留言告知。先行谢过。