Android Audio and Video Development Practice Series-03-Android MediaCodec Documento API chino oficial "Traducción"

Tabla de contenido

1. ¿Qué es MediaCodec?

2. El resultado final de calidad mínima de codificación de video

3. Tipo de datos

búfer comprimido

búfer de audio sin procesar

búfer de video sin procesar

Acceda al búfer de bytes de video sin procesar en dispositivos más antiguos

4. Estado

5. Crear

Crear un decodificador seguro

6. Inicialización

datos específicos del códec

7. Tratamiento de datos

Procesamiento asíncrono usando buffers

Sincronización mediante búfer

Procesamiento síncrono mediante matrices de búfer (en desuso)

Procesamiento final de flujo

Usar la superficie de salida

Transformación al renderizar a una superficie

Usar la superficie de entrada

Soporte de búsqueda y reproducción adaptativa

Límites de flujo y fotogramas clave

Para códecs que no admiten la reproducción adaptable (incluso cuando no se decodifica a Surface)

Para decodificadores compatibles y configurados para reproducción adaptativa

manejo de errores


Fuente original: MediaCodec | Desarrolladores de Android

1. ¿Qué es MediaCodec?

La clase MediaCodec proporciona acceso a códecs de medios de bajo nivel, los componentes codificadores/descodificadores. Es parte de la infraestructura de soporte multimedia subyacente de Android (a menudo se usa con MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface y AudioTrack).

En términos generales, un códec procesa datos de entrada para generar datos de salida. Procesa datos de forma asíncrona y utiliza un conjunto de búferes de entrada y salida. En un nivel simple, solicita (o recibe) un búfer de entrada vacío, lo llena con datos y lo envía al códec para su procesamiento. El códec se quedó sin datos y los convirtió en uno de los búferes de salida vacíos. Finalmente, solicita (o recibe) un búfer de salida lleno, consume su contenido y lo libera de nuevo al códec.

2. El resultado final de calidad mínima de codificación de video

A partir de Build.VERSION_CODES.S, los Video MediaCodecs de Android imponen estándares mínimos de calidad. El propósito es eliminar la codificación de video de baja calidad. Esta línea base de calidad se aplica cuando el códec está en modo de tasa de bits variable (VBR); no se aplica cuando el códec está en modo de tasa de bits constante (CBR). La aplicación de la línea base de calidad también está limitada a un rango de tamaño específico; este rango de tamaño está actualmente disponible para resoluciones de video mayores de 320x240 a 1920x1080.

Cuando este piso de calidad está en vigor, el códec y el código del marco de soporte garantizarán que el video resultante sea al menos de calidad "aceptable" o "buena". La métrica utilizada para seleccionar estos objetivos es VMAF (Función de evaluación de métodos múltiples de video), con una puntuación objetivo de 70 para la secuencia de prueba seleccionada.

Un efecto típico es que algunos videos generan una tasa de bits más alta que la configuración original. Esto es más notorio para el video configurado a tasas de bits muy bajas; el códec usará la tasa de bits determinada como más probable para producir videos de calidad "regular" o "buena". Otra situación es que el video contiene contenido muy complejo (mucho movimiento y detalles); en esta configuración, el códec usará una tasa de bits adicional según sea necesario para evitar perder todos los detalles más finos del contenido.

Este piso de calidad no afecta el contenido capturado a altas tasas de bits (lo que ya debería dar al códec suficiente capacidad para codificar todos los detalles). Quality Bottom no funciona con la codificación CBR. Actualmente, Quality Baseline no está disponible para resoluciones de 320x240 o inferiores, ni para videos superiores a 1920x1080.

3. Tipo de datos

Los códecs funcionan con tres tipos de datos: datos comprimidos, datos de audio sin procesar y datos de video sin procesar. Los tres tipos de datos se pueden procesar con ByteBuffers, pero debe usar Surfaces para datos de video sin procesar para mejorar el rendimiento del códec. Surface usa búferes de video nativos sin mapearlos ni copiarlos en ByteBuffers; por lo tanto, es mucho más eficiente. Cuando usa una superficie, generalmente no tiene acceso a datos de video sin procesar, pero puede usar la clase ImageReader para acceder a fotogramas de video decodificados (sin procesar) no seguros. Esto aún puede ser más eficiente que usar ByteBuffers, ya que algunos búferes nativos pueden asignarse directamente a ByteBuffers. Al usar el modo ByteBuffer, puede usar la clase Image y getInput/OutputImage(int) para acceder a cuadros de video sin formato.

búfer comprimido

Los búferes de entrada (para decodificadores) y los búferes de salida (para codificadores) contienen datos comprimidos según el tipo de formato. Para los tipos de video, esto suele ser un solo cuadro de video comprimido. Para los datos de audio, suele ser una única unidad de acceso (un segmento de audio codificado, que normalmente contiene unos pocos milisegundos de audio según lo dicta el tipo de formato), pero este requisito se relaja un poco ya que un búfer puede contener múltiples unidades de acceso de audio codificado. . En cualquier caso, los búferes no comienzan ni terminan en límites de bytes arbitrarios, sino en los límites de la unidad de marco/acceso, a menos que estén marcados como BUFFER_FLAG_PARTIAL_FRAME.

búfer de audio sin procesar

Los búferes de audio sin procesar contienen fotogramas completos de datos de audio PCM, en orden de canal con una muestra por canal. Cada muestra de audio PCM es un número entero con signo de 16 bits o un número de punto flotante, en orden de bytes nativo. Los búferes de audio sin procesar en la codificación PCM de punto flotante solo son posibles si MediaFormat#KEY_PCM_FLOAT de MediaFormat se establece en AudioFormat#ENCODING_PCM_FLOAT durante la configuración de MediaCodec (…) y se confirma con getOutputFormat() del decodificador o getInputFormat() del codificador. Un método de ejemplo para verificar PCM flotante en MediaFormat es el siguiente:

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

Para extraer un canal de un búfer que contiene datos de audio enteros con signo de 16 bits en una matriz corta, se puede usar el siguiente código:

 // 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;
 }

búfer de video sin procesar

En el modo ByteBuffer, los búferes de video se distribuyen según su formato de color. Puede obtener los formatos de color admitidos como una matriz desde getCodecInfo().getCapabilitiesForType(...).colorFormats. Los códecs de video pueden admitir tres formatos de color:

  • Formato de video sin procesar nativo: está marcado por CodecCapabilities#COLOR_FormatSurface, que se puede usar con una superficie de entrada o salida.
  • Búferes YUV flexibles (p. ej., CodecCapabilities#COLOR_FormatYUV420Flexible): se pueden usar con los modos de superficie de entrada/salida y ByteBuffer usando getInput/OutputImage(int).
  • Otros formatos específicos: por lo general, solo se admiten en el modo ByteBuffer. Algunos formatos de color son específicos del proveedor. Otros se definen en CodecCapabilities. Para formatos de color equivalentes a formatos flexibles, aún puede usar getInput/OutputImage(int).

Todos los códecs de video desde Build.VERSION_CODES.LOLLIPOP_MR1 admiten un búfer YUV 4:2:0 flexible.

Acceda al búfer de bytes de video sin procesar en dispositivos más antiguos

Antes de la compatibilidad con Build.VERSION_CODES.LOLLIPOP e Image, necesitaba usar los valores de formato de salida MediaFormat#KEY_STRIDE y MediaFormat#KEY_SLICE_HEIGHT para conocer el diseño del búfer de salida sin procesar.

Tenga en cuenta que en algunos dispositivos se recomienda establecer la altura del mosaico en 0. Esto podría significar que la altura del segmento es la misma que la altura del marco, o que la altura del segmento es la altura del marco alineada con algún valor (generalmente 2). Desafortunadamente, no existe una manera estándar y fácil de saber la altura real del corte en este caso. Además, la zancada vertical del plano U en formato plano no se especifica ni define, aunque suele ser la mitad de la altura del corte.

Las claves MediaFormat#KEY_WIDTH y MediaFormat#KEY_HEIGHT especifican el tamaño del cuadro de video; sin embargo, para la mayoría de las codificaciones, el video (imagen) solo ocupa una parte del cuadro de video. Esto se representa mediante un "rectángulo de recorte".

Debe usar las siguientes claves para obtener el rectángulo de recorte de la imagen de salida original del formato de salida. Si estas teclas no existen, el video ocupa todo el cuadro de video. El rectángulo de recorte se entiende en el contexto del cuadro de salida, antes de que se aplique cualquier rotación.

claves de formato

tipo

ilustrar

"recortar-izquierda"

Entero

La coordenada izquierda (x) del rectángulo de recorte

"top corto"

Entero

La coordenada superior (y) del rectángulo de recorte

"recortar a la derecha"

Entero

La coordenada derecha del rectángulo de recorte (x) MENOS 1

"recortar a la derecha"

Entero

La coordenada inferior del rectángulo de recorte (y) MENOS 1

La coordenada inferior derecha puede entenderse como la coordenada de la columna válida más a la derecha/fila válida más inferior de la imagen de salida recortada.

El tamaño del cuadro de video (antes de la rotación) se puede calcular así:

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

También tenga en cuenta que el significado de BufferInfo.offset no es consistente en todos los dispositivos. En algunos dispositivos, el desplazamiento apunta al píxel superior izquierdo del rectángulo de recorte, mientras que en la mayoría de los dispositivos apunta al píxel superior izquierdo de todo el marco.

4. Estado

Durante su vida útil, un códec se encuentra conceptualmente en uno de tres estados: Detenido, Ejecutando o Liberado. El estado Detenido es en realidad una colección de tres estados: Sin inicializar, Configurado y Error, mientras que el estado Ejecutando se divide conceptualmente en tres subestados: Vaciado, En ejecución y Fin de flujo.

Cuando crea un códec utilizando uno de los métodos de fábrica, el códec se encuentra en un estado no inicializado. Primero, debe configurarlo a través de configure(...) para ponerlo en el estado Configurado, luego llame a start() para moverlo al estado Ejecutando. En este estado, puede procesar datos a través de las operaciones de cola de búfer descritas anteriormente.

El estado Executing tiene tres subestados: Flushed, Running y End-of-Stream. Inmediatamente después de start(), el códec se encuentra en el subestado Flushed, que contiene todos los búferes. Una vez que se elimina la cola del primer búfer de entrada, el códec pasa al subestado en ejecución, donde pasa la mayor parte del tiempo. Cuando pone en cola un búfer de entrada con un marcador de fin de flujo, el códec pasa al subestado de fin de flujo. En este estado, el códec no acepta más búferes de entrada, pero aún produce búferes de salida hasta que se alcanza el final de la transmisión en la salida. Puede usar flush() mientras se encuentra en el estado Ejecutando para volver al subestado Flushed en cualquier momento.

Llamar a stop() devuelve el códec a un estado no inicializado, después de lo cual se puede configurar nuevamente. Cuando haya terminado de usar el códec, debe liberarlo llamando a release().

En casos excepcionales, el códec puede encontrar un error y entrar en un estado de error. Esto se comunica utilizando valores de retorno no válidos de operaciones en cola o, a veces, a través de excepciones. Llame a reset() para que el códec se pueda volver a utilizar. Puede llamarlo desde cualquier estado para devolver el códec a un estado no inicializado. De lo contrario, llame a release() para pasar al estado Liberado del terminal.

5. Crear

Cree un MediaCodec para un MediaFormat específico utilizando una MediaCodecList. Al decodificar un archivo o transmisión, puede obtener el formato deseado de MediaExtractor.getTrackFormat. Use MediaFormat.setFeatureEnabled para inyectar cualquier característica específica que desee agregar, luego llame a MediaCodecList.findDecoderForFormat para obtener el nombre de un códec que pueda manejar ese formato de medios específico. Finalmente, el códec se crea usando createByCodecName(String).

Nota: en Build.VERSION_CODES.LOLLIPOP, el formato de MediaCodecList.findDecoder/EncoderForFormat no debe incluir la velocidad de fotogramas. Use format.setString(MediaFormat.KEY_FRAME_RATE, null) para borrar cualquier configuración de velocidad de fotogramas existente en el formato.

También puede crear un códec preferido para un tipo MIME específico utilizando createDecoder/EncoderByType(java.lang.String). Sin embargo, esto no se puede usar para inyectar funciones y puede crear códecs que no pueden manejar el formato de medios específico deseado.

Crear un decodificador seguro

En Build.VERSION_CODES.KITKAT_WATCH y versiones anteriores, es posible que los códecs seguros no aparezcan en MediaCodecList, pero aún pueden estar disponibles en el sistema. Los códecs seguros existentes solo se pueden instanciar por nombre, agregando ".secure" al nombre de un códec normal (todos los nombres de códecs seguros deben terminar con ".secure"). createByCodecName(String) arrojará una IOException si el códec no existe en el sistema.

Comenzando con Build.VERSION_CODES.LOLLIPOP, debe usar la función CodecCapabilities#FEATURE_SecurePlayback en el formato multimedia para crear un decodificador seguro.

6. Inicialización

Después de crear un códec, si desea procesar los datos de forma asíncrona, puede establecer una devolución de llamada con setCallback. Luego, configure el códec con un formato de medios específico. Aquí es cuando puede especificar una superficie de salida para el productor de video: el códec (como un códec de video) que produjo los datos de video sin procesar. Aquí también es cuando puede establecer parámetros de descifrado para códecs seguros (ver MediaCrypto). Finalmente, dado que algunos códecs pueden operar en más de un modo, debe especificar si desea que funcione como decodificador o codificador.

Debido a Build.VERSION_CODES.LOLLIPOP, los formatos de entrada y salida generados se pueden consultar en el estado Configurado. Puede usar esto para validar la configuración generada, como los formatos de color, antes de iniciar un códec.

Si desea usar un consumidor de video (un códec que maneja la entrada de video sin procesar, como un codificador de video) para procesar de forma nativa los búferes de entrada de video sin procesar, use createInputSurface() después de la configuración para crear una superficie de destino para sus datos de entrada. Como alternativa, configure el códec para usar una superficie de entrada persistente creada previamente llamando a setInputSurface(Surface).

datos específicos del códec

Algunos formatos, en particular el audio AAC y los formatos de video MPEG4, H.264 y H.265, requieren que los datos reales tengan como prefijo múltiples búferes que contengan datos de configuración o datos específicos del códec. Cuando se trata de tales formatos comprimidos, estos datos deben enviarse al códec después de start() y antes de cualquier dato de cuadro. Dichos datos deben marcarse con la bandera BUFFER_FLAG_CODEC_CONFIG al llamar a queueInputBuffer.

Los datos específicos del códec también se pueden incluir en el formato pasado a la entrada ByteBuffer configurada, con las claves "csd-0", "csd-1", etc. Estas claves siempre se incluyen en el MediaFormat de la pista obtenido de MediaExtractor. Los datos específicos del códec en el formato se asignan automáticamente al códec en start(); no debe confirmar explícitamente estos datos. Si el formato no contiene datos específicos del códec, puede optar por enviarlo en el orden correcto utilizando una cantidad específica de búferes, según lo requiera el formato. Para H.264 AVC, también puede concatenar todos los datos específicos del códec y enviarlos como un único búfer de configuración de códec.

Android usa los siguientes búferes de datos específicos del códec. Estos también deben establecerse en el formato de pista para una configuración de pista adecuada de MediaMuxer. Cada conjunto de parámetros y sección de datos específicos del códec marcados con (*) debe comenzar con un código de inicio de "\x00\x00\x00\x01".

Formato

Búfer CSD #0

Búfer CSD #1

Búfer CSD n.º 2

CAA

Información específica del decodificador de ESDS*

nunca usado

nunca usado

Vobbis

encabezado de identificación

establecer título

nunca usado

OPUS

encabezado de identificación

Presalto en nanosegundos ( orden nativo
de 64 bits sin firmar)

entero. )
Esto anula el valor de presalto en el encabezado de ID.

Buscar predesplazamiento en nanosegundos ( secuencial nativo
de 64 bits sin firmar)

entero. )

FLAC

"fLaC", un marcador de flujo FLAC en formato ASCII,
seguido de un fragmento STREAMINFO (fragmento de metadatos obligatorio),
seguido opcionalmente de cualquier número de otros fragmentos de metadatos

nunca usado

nunca usado

MPEG-4

Información específica del decodificador de ESDS*

nunca usado

nunca usado

H.264 AVC

SPS (Conjunto de parámetros de secuencia*)

PPS (Conjunto de parámetros de imagen*)

nunca usado

H.265 HEVC

VPS (Conjunto de parámetros de video*) +
SPS (Conjunto de parámetros de secuencia*) +
PPS (Conjunto de parámetros de imagen*)

nunca usado

nunca usado

VP9

Códec VP9 privado

datos (opcional)

nunca usado

nunca usado

Nota: Se debe tener cuidado si el códec se vacía inmediatamente antes de que se hayan devuelto los búferes de salida o los cambios de formato de salida, o poco después del inicio, ya que los datos específicos del códec pueden perderse durante el vaciado. Debe volver a enviar los datos con búferes marcados BUFFER_FLAG_CODEC_CONFIG después de dichos vaciados para garantizar el funcionamiento correcto del códec.

Un codificador (o un códec que produce datos comprimidos) creará y devolverá datos específicos del códec antes de cualquier búfer de salida válido en los búferes de salida marcados con el indicador codec-config . Los búferes que contienen datos específicos del códec no tienen marcas de tiempo significativas.

7. Tratamiento de datos

Cada códec mantiene un conjunto de búferes de entrada y salida a los que se hace referencia mediante ID de búfer en las llamadas a la API. Después de una llamada exitosa a start(), el cliente no "posee" el búfer de entrada ni el búfer de salida. En modo síncrono, llame a dequeueInput / OutputBuffer(...) para obtener (tomar posesión de) un búfer de entrada o salida del códec. En el modo asíncrono, recibirá automáticamente los búferes disponibles a través de las devoluciones de llamada de MediaCodec.Callback.onInput / OutputBufferAvailable(…) .

Una vez que tenga el búfer de entrada, llénelo con datos y envíelo al códec usando queueInputBuffer , o queueSecureInputBuffer si usa descifrado. No envíe varios búferes de entrada con la misma marca de tiempo (a menos que se trate de datos específicos del códec marcados como tales).

El códec, a su vez, devolverá un búfer de salida de solo lectura a través de la devolución de llamada onOutputBufferAvailable en modo asíncrono, o responderá a una llamada a dequeueOutputBuffer en modo síncrono. Después de procesar el búfer de salida, llame a uno de los métodos releaseOutputBuffer para devolver el búfer al códec.

Si bien no es necesario volver a enviar/liberar los búferes al códec de inmediato, retener los búferes de entrada y/o salida puede detener el códec, y este comportamiento depende del dispositivo. Específicamente, un códec puede diferir la generación de búferes de salida hasta que todos los búferes pendientes se hayan liberado/reenviado. Así que trate de mantener la menor cantidad posible de búferes libres.

Según la versión de la API, puede manejar los datos de tres maneras:

Métodos de procesamiento

Versión API <= 20
Jelly Bean/KitKat

Versión API >= 21
Lollipop y superior

API síncrona usando matrices de búfer

soportado

obsoleto

API síncrona usando búferes

No disponible

soportado

API asíncrona usando buffers

No disponible

soportado

Procesamiento asíncrono usando buffers

Debido a Build.VERSION_CODES.LOLLIPOP , el método preferido es procesar los datos de forma asíncrona configurando una devolución de llamada antes de llamar a configure . El modo asíncrono cambia ligeramente las transiciones de estado, ya que debe llamar a start() después de flush() para hacer la transición del códec al subestado En ejecución y comenzar a recibir búferes de entrada. De manera similar, en la llamada inicial al códec, start se moverá directamente al subestado En ejecución y comenzará a pasar los búferes de entrada disponibles a través de las devoluciones de llamada.

MediaCodec generalmente se usa así en modo asíncrono:

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();

Sincronización mediante búfer

Debido a Build.VERSION_CODES.LOLLIPOP , incluso cuando usa el códec en modo síncrono, debe recuperar los búferes de entrada y salida usando getInput / OutputBuffer(int) y/o getInput / OutputImage(int) . Esto permite que el marco realice ciertas optimizaciones, por ejemplo, cuando se trata de contenido dinámico. Esta optimización está deshabilitada si llama a getInput / OutputBuffers() .

Nota: No mezcle métodos de matriz de búfer y de búfer al mismo tiempo. Específicamente, llame a getInput/ solo después o directamente después de sacar de la cola un ID de búfer de salida con un valor de . OutputBuffers start () INFO_OUTPUT_FORMAT_CHANGED

MediaCodec generalmente se usa así en modo síncrono:

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();

Procesamiento síncrono mediante matrices de búfer (en desuso)

En la versión Build.VERSION_CODES.KITKAT_WATCH y anteriores, los conjuntos de búfer de entrada y salida estaban representados por matrices ByteBuffer[]. Después de una llamada exitosa a start() , la matriz de búferes se recupera usando getInput / OutputBuffers() . Utilice ID de búfer como índices en estas matrices (cuando no sean negativas), como en el ejemplo a continuación. Tenga en cuenta que no existe una correlación intrínseca entre el tamaño de la matriz y la cantidad de búferes de entrada y salida utilizados por el sistema, aunque el tamaño de la matriz proporciona un límite superior.

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();

Procesamiento final de flujo

Cuando llegue al final de los datos de entrada, debe enviarlos al códec queueInputBuffer especificando el indicador BUFFER_FLAG_END_OF_STREAM en la llamada . Puede hacer esto en el último búfer de entrada válido o enviando un búfer de entrada vacío adicional con el indicador de fin de transmisión establecido. Si se utiliza un búfer vacío, se ignorará la marca de tiempo.

El códec seguirá devolviendo búferes de salida hasta que finalmente señale el final del flujo de salida especificando el mismo indicador de flujo final en BufferInfo establecido en dequeueOutputBuffer o mediante return onOutputBufferAvailable . Esto se puede establecer en el último búfer de salida válido o en un búfer vacío después del último búfer de salida válido. Las marcas de tiempo para tales búferes vacíos deben ignorarse.

No confirme búferes de entrada adicionales después de señalar el final del flujo de entrada a menos que el códec se haya vaciado o detenido y reiniciado.

Usar la superficie de salida

Al usar una superficie de salida , el manejo de datos es casi el mismo que en el modo ByteBuffer; sin embargo, no se podrá acceder al búfer de salida y se representará como un valor nulo. Por ejemplo, getOutputBuffer / Image(int) devuelve nulo y getOutputBuffers() devolverá una matriz que contiene solo nulos.

Al utilizar una superficie de salida , puede elegir si renderizar cada búfer de salida en la superficie . Tienes tres opciones:

Debido a Build.VERSION_CODES.M , la marca de tiempo predeterminada es la marca de tiempo de procesamiento del búfer (convertida a nanosegundos). Antes eso no estaba definido.

Además de Build.VERSION_CODES.M , puede usar setOutputSurface .

Al representar la salida en una superficie , la superficie puede estar configurada para descartar demasiados fotogramas (fotogramas que la superficie no consume de manera oportuna). O se puede configurar para que no pierda demasiados fotogramas. En el último modo, si Surface no consume los cuadros de salida lo suficientemente rápido, eventualmente bloqueará el decodificador.

Hasta Build.VERSION_CODES.Q, el comportamiento exacto no está definido, excepto que la superficie de Vista (SurfaceView o TextureView) siempre pierde demasiados fotogramas. Debido a Build.VERSION_CODES.Q , el comportamiento predeterminado es descartar demasiados fotogramas. Las aplicaciones pueden optar por este comportamiento para las superficies que no son de vista (como ImageReaders o texturas de superficie) apuntando el SDK fuera de Build.VERSION_CODES.Q , configurando la clave MediaFormat#KEY_ALLOW_FRAME_DROP en 0 en su formato configurado.

Transformación al renderizar a una superficie

Si el códec está configurado en modo Superficie, cualquier modo de recorte de rectángulo, rotación y escalado de video se aplicará automáticamente , con una excepción:

Antes del lanzamiento de Build.VERSION_CODES.M , es posible que los códecs de software no tengan la rotación aplicada al renderizar en una superficie. Desafortunadamente, no existe una forma estándar y fácil de identificar los códecs de software, o si aplican la rotación además de intentarlo.

También hay algunas advertencias.

Tenga en cuenta que la relación de aspecto de píxeles no se tiene en cuenta al mostrar la salida en la superficie. Esto significa que si está utilizando el modo VIDEO_SCALING_MODE_SCALE_TO_FIT , debe colocar la superficie de salida para que tenga la relación de aspecto de visualización final correcta. En su lugar, solo puede usar el modo VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING para contenido con píxeles cuadrados (proporción de aspecto de píxeles o 1:1).

Tenga en cuenta también que el modo VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING puede no funcionar correctamente para videos girados 90 o 270 grados en el momento del lanzamiento de Build.VERSION_CODES.N .

Al configurar el modo de escalado de video, tenga en cuenta que debe restablecerse después de cada cambio de búfer de salida. Dado que el evento INFO_OUTPUT_BUFFERS_CHANGED está en desuso, puede hacerlo después de cada cambio de formato de salida.

Usar la superficie de entrada

Cuando se usa una superficie de entrada, no hay un búfer de entrada accesible porque el búfer pasa automáticamente de la superficie de entrada al códec. Llamar a dequeueInputBuffer arrojará una IllegalStateException, y getInputBuffers() devuelve una matriz ByteBuffer[] falsa que no se puede escribir.

Llame a signalEndOfInputStream() para señalar el final de la transmisión. La superficie de entrada dejará de enviar datos al códec inmediatamente después de esta llamada.

Soporte de búsqueda y reproducción adaptativa

Los decodificadores de video (y, en general, los códecs que usan datos de video comprimidos) se comportan de manera diferente con respecto a las búsquedas y los cambios de formato, ya sea que admitan o no y estén configurados para la reproducción adaptativa. Puede verificar si el códec es compatible con la reproducción adaptable a través de CodecCapabilities.isFeatureSupported(String) . La compatibilidad con reproducción adaptativa para códecs de video solo se activa cuando configura el códec para decodificar en Surface.

Límites de flujo y fotogramas clave

Los datos de entrada que comienzan() o se vacían() después del límite de transmisión apropiado son importantes: el primer cuadro debe ser un cuadro clave. Un fotograma clave se puede decodificar por sí mismo (para la mayoría de los códecs, este fotograma clave es un fotograma I) y no se muestra ningún otro fotograma antes del fotograma clave al que se hace referencia.

La siguiente tabla resume los fotogramas clave disponibles para varios formatos de video.

Formato

fotograma clave adecuado

VP9/VP8

Un intraframe adecuado en el que ningún fotograma posterior hace referencia a un fotograma anterior.
(Dichos fotogramas clave no tienen un nombre específico).

H.265 HEVC

IDR o CRA

H.264 AVC

IDR

MPEG-4
H.263
MPEG-2

Un fotograma I adecuado en el que ningún fotograma posterior hace referencia a un fotograma anterior.
(Dichos fotogramas clave no tienen un nombre específico).

Para códecs que no admiten la reproducción adaptable (incluso cuando no se decodifica a Surface)

Para comenzar a decodificar datos que no están adyacentes a datos confirmados anteriormente (es decir, después de una búsqueda), debe actualizar el decodificador. Dado que todos los búferes de salida se vacían inmediatamente cuando se vacían, es posible que desee señalar y luego esperar el final de la transmisión antes de llamar a la descarga. Es importante que los datos de entrada actualizados comiencen en un límite de flujo/fotograma clave adecuado.

注意: flush后提交的数据格式必须不能改变;flush()不支持不连的格式;为此,一个完整的stop()- configure(…)-start()生命周期是必要的。

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

对于支持并配置为自适应播放的解码器

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

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

您将在图片大小更改发生之后和具有新大小的任何帧返回之前收到INFO_OUTPUT_FORMAT_CHANGED来自dequeueOutputBufferonOutputFormatChanged回调的返回值。

注意:就像编解码器特定数据的情况一样,在调用flush()方法来改图片大小要小心 。如果您还没有收到更改图片大小的确认,您将需要重新请求新的图片大小。

错误处理

您必须捕获或声明放弃工厂方法createByCodecNamecreateDecoder/EncoderByType引发IOException失败。MediaCodec 会抛出IllegalStateException,当从不允许它的编解码器状态调用方法时;这通常是由于不正确的应用程序 API 使用造成的。涉及安全缓冲区的方法可能会抛出 CryptoException,其中包含可从CryptoException#getErrorCode.

内部编解码器错误会导致CodecException,这可能是由于媒体内容损坏、硬件故障、资源耗尽等,即使应用程序正确使用 API。当抛出CodecException 时,调用CodecException#isRecoverableCodecException#isTransient来确定推荐的操作:

  • 可恢复的错误:如果isRecoverable()返回true,则调用 stop()configure(…)以及start()恢复。
  • 瞬态错误:如果isTransient()返回 true,则资源暂时不可用,该方法可能会稍后重试。
  • Error fatal: si tanto isRecoverable() como isTransient() devuelven falso, CodecException es fatal y el códec debe restablecerse o liberarse .

Ni isRecoverable() ni isTransient() devuelven verdadero al mismo tiempo.

Supongo que te gusta

Origin blog.csdn.net/xiangang12202/article/details/122267523
Recomendado
Clasificación