android - perfiles - srgb o adobe rgb video
MediaCodec y cámara: los espacios de colores no coinciden (4)
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
He intentado hacer que la codificación H264 funcione con la entrada capturada por la cámara en una tableta Android utilizando el nuevo MediaCodec 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); // <1>
parameters.setPreviewFpsRange(4000,60000);
parameters.setPreviewSize(640, 480);
mCamera.setParameters(parameters);
Para la parte de codificación, 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); // <2>
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
El objetivo 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 <1>
) y otro para el objeto MediaCodec (etiquetado con <2>
)
Para determinar los valores aceptables para las líneas <1>
utilicé parameters.getSupportedPreviewFormats()
. A partir de esto, sé que los únicos formatos admitidos en la cámara son ImageFormat.NV21 e ImageFormat.YV2 .
Para <2>
, 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 bloqueo.
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"): Estos son los resultados que muestra Gstreamer:
<1>
= NV21, <2>
= COLOR_FormatYUV420Planar
<1>
= NV21, <2>
= 2130708361
<1>
= YV2, <2>
= COLOR_FormatYUV420Planar
<1>
= YV2, <2>
= 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?
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<wh4*5; i++)
{
tmp = input[i];
input[i] = input[i+wh4];
input[i+wh4] = tmp;
}
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
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;
}
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 ! ...