MediaCodec y cámara: los espacios de colores no coinciden

He intentado hacer que la encoding H264 funcione con la entrada capturada por la cámara en una tableta Android utilizando el nuevo MediaCodec de bajo nivel. He tenido algunas dificultades con esto, ya que el MediaCodecAPI está poco documentado, pero finalmente he conseguido algo para trabajar.

Estoy configurando la cámara de la siguiente manera:

Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewFormat(ImageFormat.YV12); //  parameters.setPreviewFpsRange(4000,60000); parameters.setPreviewSize(640, 480); mCamera.setParameters(parameters); 

Para la parte de encoding, estoy instanciando el objeto MediaCodec de la siguiente manera:

  mediaCodec = MediaCodec.createEncoderByType("video/avc"); MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 640, 480); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500000); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar); //  mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.start(); 

El objective final es crear un RTP-stream (y corresponder con Skype), pero hasta ahora solo estoy transmitiendo el H264 sin procesar directamente a mi escritorio. Ahí utilizo el siguiente GStreamer-pipeline para mostrar el resultado:

 gst-launch udpsrc port=5555 ! video/x-h264,width=640,height=480,framerate=15/1 ! ffdec_h264 ! autovideosink 

Todo funciona bien, excepto por los colores. Necesito establecer 2 formatos de color en la computadora: uno para la vista previa de la cámara (línea etiquetada con ) y otro para el objeto MediaCodec (etiquetado con )

Para determinar los valores aceptables para las líneas utilicé parameters.getSupportedPreviewFormats() . A partir de esto, sé que los únicos formatos admitidos en la cámara son ImageFormat.NV21 e ImageFormat.YV2 .

Para , recuperé MediaCodecInfo.CodecCapabilities -object para type video / avc , siendo los valores enteros 19 (correspondientes a MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar y 2130708361 (que no se corresponde con ningún valor de MediaCodecInfo.CodecCapabilities ).

Cualquier otro valor que no sea el anterior ocasionará un locking.

La combinación de estas configuraciones ofrece resultados diferentes, que mostraré a continuación. Aquí está la captura de pantalla en Android (es decir, los colores “reales”): Entrada en la tableta Android Estos son los resultados que muestra Gstreamer:

= NV21, = COLOR_FormatYUV420Planar Salida Gstreamer para NV21-COLOR_FormatYUV420Planar

= NV21, = 2130708361 Salida Gstreamer para NV21-2130708361

= YV2, = COLOR_FormatYUV420Planar Gstreamer-output para YV2-COLOR_FormatYUV420Planar

= YV2, = 2130708361 Gstreamer-output para YV2-2130708361

Como se puede ver, ninguno de estos es satisfactorio. El espacio de color YV2 parece el más prometedor, pero parece que el rojo (Cr) y el azul (Cb) están invertidos. El NV21 parece entrelazado, supongo (sin embargo, no soy un experto en este campo).

Dado que el propósito es comunicarse con Skype, supongo que no debería cambiar el decodificador (es decir, el comando Gstreamer), ¿verdad? ¿Esto se debe resolver en Android y, en caso afirmativo, cómo? ¿O se puede resolver esto agregando cierta información de carga RTP? ¿Alguna otra sugerencia?

Lo resolví intercambiando los byteplanes yo mismo en el nivel de Android, usando una función simple:

 public byte[] swapYV12toI420(byte[] yv12bytes, int width, int height) { byte[] i420bytes = new byte[yv12bytes.length]; for (int i = 0; i < width*height; i++) i420bytes[i] = yv12bytes[i]; for (int i = width*height; i < width*height + (width/2*height/2); i++) i420bytes[i] = yv12bytes[i + (width/2*height/2)]; for (int i = width*height + (width/2*height/2); i < width*height + 2*(width/2*height/2); i++) i420bytes[i] = yv12bytes[i - (width/2*height/2)]; return i420bytes; } 

Creo que es más eficiente cambiar los valores en su lugar.

  int wh4 = input.length/6; //wh4 = width*height/4 byte tmp; for (int i=wh4*4; i 

Tal vez incluso mejor, puedes reemplazar

  inputBuffer.put(input); 

Con las 3 rebanadas planas en el orden correcto

  inputBuffer.put(input, 0, wh4*4); inputBuffer.put(input, wh4*5, wh4); inputBuffer.put(input, wh4*4, wh4); 

Creo que solo debería tener una pequeña sobrecarga

Parece que Android está transmitiendo en YV12, pero el formato establecido en los encabezados H264 es YUV420. Estos formatos son iguales, excepto que los canales U y V están en orden diferente, lo que explica el intercambio de rojo y azul.

Lo mejor sería, por supuesto, arreglar la configuración en el lado de Android. Pero si no hay forma de establecer configuraciones compatibles para la cámara y el codificador, deberá forzar el formato en el lado de GStreamer.

Esto se puede hacer añadiendo el elemento capssetter después del ffdec_h264

... ! ffdec_h264 ! capssetter caps="video/x-raw-yuv, format=(fourcc)YV12" ! colorspace ! ...

Con ImageFormat.NV21 configurado en la cámara y COLOR_FormatYUV420Planar para el codificador, una sombra azul similar se superpone en mi caso. Como entiendo, la función de intercambio anterior no se puede utilizar en mi caso, ¿alguna sugerencia sobre un algoritmo que pueda usarse para esto? ps: es una pantalla negra completa en el decodificador cuando el formato de vista previa de la cámara está configurado como YV12