android - Usando MediaCodec para guardar series de imágenes como video
(1)
Estoy tratando de usar MediaCodec
para guardar una serie de Imágenes, guardadas como Byte Arrays en un archivo, en un archivo de video. He probado estas imágenes en un SurfaceView
(reproduciéndolas en serie) y puedo verlas bien. He visto muchos ejemplos usando MediaCodec
, y esto es lo que entiendo (corríjame si me equivoco):
Obtenga InputBuffers del objeto MediaCodec -> llénelo con los datos de imagen de su marco -> ponga en cola el búfer de entrada -> obtenga el búfer de salida codificado -> escríbalo en un archivo -> aumente el tiempo de presentación y repita
Sin embargo, he probado esto mucho y termino con uno de dos casos:
- Todos los proyectos de muestra que intenté imitar causaron la muerte del servidor de medios al llamar a
queueInputBuffer
por segunda vez. - Intenté llamar a
codec.flush()
al final (después de guardar el búfer de salida en el archivo, aunque ninguno de los ejemplos que vi hizo esto) y el servidor de medios no murió, sin embargo, no puedo abrir el archivo de video de salida con Cualquier reproductor multimedia, por lo que algo está mal.
Aquí está mi código:
MediaCodec codec = MediaCodec.createEncoderByType(MIMETYPE);
MediaFormat mediaFormat = null;
if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)){
mediaFormat = MediaFormat.createVideoFormat(MIMETYPE, 1280 , 720);
} else {
mediaFormat = MediaFormat.createVideoFormat(MIMETYPE, 720, 480);
}
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 700000);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 10);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
codec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
codec.start();
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
boolean sawInputEOS = false;
int inputBufferIndex=-1,outputBufferIndex=-1;
BufferInfo info=null;
//loop to read YUV byte array from file
inputBufferIndex = codec.dequeueInputBuffer(WAITTIME);
if(bytesread<=0)sawInputEOS=true;
if(inputBufferIndex >= 0){
if(!sawInputEOS){
int samplesiz=dat.length;
inputBuffers[inputBufferIndex].put(dat);
codec.queueInputBuffer(inputBufferIndex, 0, samplesiz, presentationTime, 0);
presentationTime += 100;
info = new BufferInfo();
outputBufferIndex = codec.dequeueOutputBuffer(info, WAITTIME);
Log.i("BATA", "outputBufferIndex="+outputBufferIndex);
if(outputBufferIndex >= 0){
byte[] array = new byte[info.size];
outputBuffers[outputBufferIndex].get(array);
if(array != null){
try {
dos.write(array);
} catch (IOException e) {
e.printStackTrace();
}
}
codec.releaseOutputBuffer(outputBufferIndex, false);
inputBuffers[inputBufferIndex].clear();
outputBuffers[outputBufferIndex].clear();
if(sawInputEOS) break;
}
}else{
codec.queueInputBuffer(inputBufferIndex, 0, 0, presentationTime, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
info = new BufferInfo();
outputBufferIndex = codec.dequeueOutputBuffer(info, WAITTIME);
if(outputBufferIndex >= 0){
byte[] array = new byte[info.size];
outputBuffers[outputBufferIndex].get(array);
if(array != null){
try {
dos.write(array);
} catch (IOException e) {
e.printStackTrace();
}
}
codec.releaseOutputBuffer(outputBufferIndex, false);
inputBuffers[inputBufferIndex].clear();
outputBuffers[outputBufferIndex].clear();
break;
}
}
}
}
codec.flush();
try {
fstream2.close();
dos.flush();
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
codec.stop();
codec.release();
codec = null;
return true;
}
Mi pregunta es, ¿cómo puedo obtener un video de trabajo de una secuencia de imágenes usando MediaCodec? ¿Qué estoy haciendo mal?
Otra pregunta (si no soy demasiado codicioso), me gustaría agregar una pista de audio a este video, ¿se puede hacer con MediaCodec también, o debo usar FFmpeg?
Nota : sé sobre MediaMux
en Android 4.3, sin embargo, no es una opción para mí, ya que mi aplicación debe funcionar en Android 4.1+.
Actualización Gracias a la respuesta fadden, pude alcanzar EOS sin que el servidor de medios se muriera (el código anterior es posterior a la modificación). Sin embargo, el archivo que estoy obteniendo está produciendo galimatías. Aquí hay una instantánea del video que recibo (solo funciona como archivo .h264).
El formato de mi imagen de entrada es la imagen YUV (NV21 de la vista previa de la cámara). No puedo conseguir que sea un formato jugable. Probé todos los formatos COLOR_FormatYUV420 y la misma salida de galimatías. Y todavía no puedo encontrar (usando MediaCodec) para agregar audio.
Creo que tienes la idea general correcta. Algunas cosas a tener en cuenta:
- No todos los dispositivos son compatibles con
COLOR_FormatYUV420SemiPlanar
. Algunos solo aceptan planar. (Android 4.3 introdujo pruebas CTS para garantizar que el códec AVC sea compatible con uno u otro). - No es el caso que poner en cola un búfer de entrada dé como resultado inmediatamente la generación de un búfer de salida. Algunos códecs pueden acumular varios fotogramas de entrada antes de producir salida y pueden producir salida después de que su entrada haya finalizado. Asegúrese de que sus bucles tengan eso en cuenta (por ejemplo, sus
inputBuffers[].clear()
explotarán si aún es -1). - No intente enviar datos y enviar EOS con la misma llamada
queueInputBuffer
. Los datos en ese marco pueden ser descartados. Siempre envíe EOS con un búfer de longitud cero.
La salida de los códecs es generalmente bastante "en bruto", por ejemplo, el códec AVC emite un flujo elemental H.264 en lugar de un archivo .mp4 "cocinado". Muchos jugadores no aceptan este formato. Si no puede confiar en la presencia de MediaMuxer
, tendrá que encontrar otra forma de cocinar los datos (busque ideas en el ).
Ciertamente no se espera que el proceso de mediaserver se bloquee.
Puede encontrar algunos ejemplos y enlaces a las pruebas de 4.3 CTS here .
Actualización: A partir de Android 4.3, MediaCodec
y Camera
no tienen formatos de ByteBuffer en común, por lo que al menos deberá jugar con los planos cromáticos. Sin embargo, ese tipo de problema se manifiesta de manera muy diferente (como se muestra en las imágenes para esta pregunta ).
La imagen que agregó parece un video, pero con problemas de zancada y / o alineación. Asegúrese de que sus píxeles están dispuestos correctamente. En el CTS EncodeDecodeTest , el método generateFrame()
(línea 906) muestra cómo codificar YUV420 tanto planar como semiplanar para MediaCodec
.
La forma más fácil de evitar los problemas de formato es mover los marcos a través de una Surface
(como la muestra CameraToMpegTest), pero desafortunadamente eso no es posible en Android 4.1.