¿Cómo crear un video a partir de una matriz de imágenes en Android?

11 minutos de lectura

avatar de usuario
Jalil Khalaf

Quiero llamar a una función y crear un video a partir de una lista de imágenes, y luego guardarlo localmente en el dispositivo:

public void CreateAndSaveVideoFile(List<Bitmap> MyBitmapArray)
{
   // ..
}

Ensayos:

  • Siguiendo java/xuggle: codifique una matriz de imágenes en una película, el enlace en la respuesta es un enlace muerto

  • Siguiendo ¿Cómo codificar imágenes en un archivo de video en Java a través de la programación?, La biblioteca sugerida en la respuesta aceptada no es compatible con Android.

  • La siguiente respuesta en lo anterior tiene un enfoque para los usuarios de Android, sin embargo, no me queda claro la entrada y la salida de esa función (¿dónde entregó las imágenes? ¿Y dónde obtuvo el video?) – Dejé un comentario de pregunta

  • La siguiente respuesta en lo anterior proporciona una clase completa, sin embargo, la biblioteca requerida que se incluirá tiene un archivo dañado (cuando intento descargarlo desde el enlace provisto): dejé un comentario de pregunta

  • Siguiendo a Java: ¿Cómo creo una película a partir de una matriz de imágenes?, la biblioteca sugerida en la respuesta superior usa comandos con los que no estoy familiarizado y ni siquiera sé cómo usarlos. Me gusta:

Crear un archivo MPEG-4 a partir de todos los archivos JPEG en el directorio actual:

mencoder mf://*.jpg -mf w=800:h=600:fps=25:type=jpg -ovc lavc \
-lavcopts vcodec=mpeg4:mbd=2:trell -oac copy -o output.avi

No sé cómo puedo usar lo anterior en un proyecto Java/Android.

¿Alguien puede ayudarme a guiarme o proporcionarme un enfoque para mi tarea? Gracias por adelantado.

  • No haga mapas de bits de los marcos primero. Y no los ponga en una lista ya que pronto se quedará sin memoria. Simplemente escriba sus marcos para archivar directamente.

    – aplicaciones verdes

    29 de octubre de 2016 a las 9:02


  • Compare el tamaño/longitud del marco con el tamaño de memoria necesario del mapa de bits y el informe. Si desea utilizar una lista, mejor coloque los marcos en ella. Simplemente compare los recuerdos necesarios.

    – aplicaciones verdes

    29 de octubre de 2016 a las 9:06


  • @greenapps en el botón, haga clic en Grabar video, desea que comience a guardar las imágenes recibidas localmente en el dispositivo. ¿Recomendaría esta forma de guardar Guardar y leer mapas de bits/imágenes? Sigo adelante e implemento para guardar una sola imagen y luego le informo el tamaño de la misma.

    – Khalil Khalaf

    29/10/2016 a las 17:43


  • @greenapps Hola, todavía no logro guardar una imagen. ¿Ayudaría esto en su lugar? s11.postimg.org/4a1kdregz/Capture.png

    – Khalil Khalaf

    29/10/2016 a las 20:36


  • alojar un servicio cuyas entradas son salidas zip (fotos): nombre de archivo de video para mp4. Cuando se le llama, desempaqueta las fotos de los zips, llamando a ffmpeg o cualquier otra cosa con medios desempaquetados como entradas. El archivo de salida es un MP4. Este es un servicio genérico de muxing de medios que probablemente se puede instalar como una biblioteca de nodo con revisiones mínimas. wud no tiene 2 escribirlo. Cuando mp4 está listo en el servidor, se envía por POST a su CDN (sirviendo medios mp4). Luego el cliente solicita el mp4 del CDN. Razón para el arquitecto alternativo: un servidor es un lugar mucho mejor para crear/alojar/servir el mp4 desde los medios capturados en el dispositivo móvil

    – Robert Rowntree

    31/10/2016 a las 18:36

Puedes usar jcodec SequenceEncoder para convertir una secuencia de imágenes a un archivo MP4.

Código de muestra :

import org.jcodec.api.awt.SequenceEncoder;
...
SequenceEncoder enc = new SequenceEncoder(new File("filename"));
// GOP size will be supported in 0.2
// enc.getEncoder().setKeyInterval(25);
for(...) {
    BufferedImage image = ... // Obtain an image to encode
    enc.encodeImage(image);
}
enc.finish();

Es una biblioteca de Java, por lo que es fácil importarla al proyecto de Android, no tiene que usar NDK a diferencia de ffmpeg.

Referirse http://jcodec.org/ para código de muestra y descargas.

  • Impresionante, se parece a lo que estoy buscando. Voy a darle una oportunidad, gracias!

    – Khalil Khalaf

    2 de noviembre de 2016 a las 18:49

  • @KhalilKhalaf, ¿dónde pudiste hacerlo funcionar? Estoy probando el mismo código, genera un archivo .mp4 corrupto.

    – nishant1000

    22 de marzo de 2018 a las 15:03

  • @ nishant1000 Esta función se eliminó y me redireccionaron para trabajar en otra cosa. ¿Probó el método “Bitmap to BufferedImage” en la otra respuesta antes de ejecutar JCodec en las imágenes?

    – Khalil Khalaf

    23 de marzo de 2018 a las 16:12

  • @Abhishek, ¿podemos agregar audio al video y animaciones de transición mientras cambiamos los cuadros en el video?

    – Mayank Pandya

    5 de agosto de 2018 a las 10:20

  • Parece que java awt no está disponible en Android, por lo que no sé si BufferedImage es una solución viable aquí.

    – caitcoo0odes

    9 de octubre de 2019 a las 3:38

avatar de usuario
Vinicius ADSL

Usando JCodec como lo demuestra Stanislav Vitvitskyy aquí.

public static void main(String[] args) throws IOException {
    SequenceEncoder encoder = new SequenceEncoder(new File("video.mp4"));
    for (int i = 1; i < 100; i++) {
        BufferedImage bi = ImageIO.read(new File(String.format("img%08d.png", i)));
        encoder.encodeImage(bi);
        }
    encoder.finish();}

Ahora, para convertir su mapa de bits a BufferedImage, puede usar esta clase:

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.io.IOException;
import java.io.InputStream;

/**
  * Utility class for loading windows bitmap files
  * <p>
  * Based on code from author Abdul Bezrati and Pepijn Van Eeckhoudt
  */
public class BitmapLoader {

/**
 * Static method to load a bitmap file based on the filename passed in.
 * Based on the bit count, this method will either call the 8 or 24 bit
 * bitmap reader methods
 *
 * @param file The name of the bitmap file to read
 * @throws IOException
 * @return A BufferedImage of the bitmap
 */
public static BufferedImage loadBitmap(String file) throws IOException {
    BufferedImage image;
    InputStream input = null;
    try {
        input = ResourceRetriever.getResourceAsStream(file);

        int bitmapFileHeaderLength = 14;
        int bitmapInfoHeaderLength = 40;

        byte bitmapFileHeader[] = new byte[bitmapFileHeaderLength];
        byte bitmapInfoHeader[] = new byte[bitmapInfoHeaderLength];

        input.read(bitmapFileHeader, 0, bitmapFileHeaderLength);
        input.read(bitmapInfoHeader, 0, bitmapInfoHeaderLength);

        int nSize = bytesToInt(bitmapFileHeader, 2);
        int nWidth = bytesToInt(bitmapInfoHeader, 4);
        int nHeight = bytesToInt(bitmapInfoHeader, 8);
        int nBiSize = bytesToInt(bitmapInfoHeader, 0);
        int nPlanes = bytesToShort(bitmapInfoHeader, 12);
        int nBitCount = bytesToShort(bitmapInfoHeader, 14);
        int nSizeImage = bytesToInt(bitmapInfoHeader, 20);
        int nCompression = bytesToInt(bitmapInfoHeader, 16);
        int nColoursUsed = bytesToInt(bitmapInfoHeader, 32);
        int nXPixelsMeter = bytesToInt(bitmapInfoHeader, 24);
        int nYPixelsMeter = bytesToInt(bitmapInfoHeader, 28);
        int nImportantColours = bytesToInt(bitmapInfoHeader, 36);

        if (nBitCount == 24) {
            image = read24BitBitmap(nSizeImage, nHeight, nWidth, input);
        } else if (nBitCount == 8) {
            image = read8BitBitmap(nColoursUsed, nBitCount, nSizeImage, nWidth, nHeight, input);
        } else {
            System.out.println("Not a 24-bit or 8-bit Windows Bitmap, aborting...");
            image = null;
        }
    } finally {
        try {
            if (input != null)
                input.close();
        } catch (IOException e) {
        }
    }
    return image;
}

/**
 * Static method to read a 8 bit bitmap
 *
 * @param nColoursUsed Number of colors used
 * @param nBitCount The bit count
 * @param nSizeImage The size of the image in bytes
 * @param nWidth The width of the image
 * @param input The input stream corresponding to the image
 * @throws IOException
 * @return A BufferedImage of the bitmap
 */
private static BufferedImage read8BitBitmap(int nColoursUsed, int nBitCount, int nSizeImage, int nWidth, int nHeight, InputStream input) throws IOException {
    int nNumColors = (nColoursUsed > 0) ? nColoursUsed : (1 & 0xff) << nBitCount;

    if (nSizeImage == 0) {
        nSizeImage = ((((nWidth * nBitCount) + 31) & ~31) >> 3);
        nSizeImage *= nHeight;
    }

    int npalette[] = new int[nNumColors];
    byte bpalette[] = new byte[nNumColors * 4];
    readBuffer(input, bpalette);
    int nindex8 = 0;

    for (int n = 0; n < nNumColors; n++) {
        npalette[n] = (255 & 0xff) << 24 |
                (bpalette[nindex8 + 2] & 0xff) << 16 |
                (bpalette[nindex8 + 1] & 0xff) << 8 |
                (bpalette[nindex8 + 0] & 0xff);

        nindex8 += 4;
    }

    int npad8 = (nSizeImage / nHeight) - nWidth;
    BufferedImage bufferedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);
    DataBufferInt dataBufferByte = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer());
    int[][] bankData = dataBufferByte.getBankData();
    byte bdata[] = new byte[(nWidth + npad8) * nHeight];

    readBuffer(input, bdata);
    nindex8 = 0;

    for (int j8 = nHeight - 1; j8 >= 0; j8--) {
        for (int i8 = 0; i8 < nWidth; i8++) {
            bankData[0][j8 * nWidth + i8] = npalette[((int) bdata[nindex8] & 0xff)];
            nindex8++;
        }
        nindex8 += npad8;
    }

    return bufferedImage;
}

/**
 * Static method to read a 24 bit bitmap
 *
 * @param nSizeImage size of the image  in bytes
 * @param nHeight The height of the image
 * @param nWidth The width of the image
 * @param input The input stream corresponding to the image
 * @throws IOException
 * @return A BufferedImage of the bitmap
 */
private static BufferedImage read24BitBitmap(int nSizeImage, int nHeight, int nWidth, InputStream input) throws IOException {
    int npad = (nSizeImage / nHeight) - nWidth * 3;
    if (npad == 4 || npad < 0)
        npad = 0;
    int nindex = 0;
    BufferedImage bufferedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_4BYTE_ABGR);
    DataBufferByte dataBufferByte = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer());
    byte[][] bankData = dataBufferByte.getBankData();
    byte brgb[] = new byte[(nWidth + npad) * 3 * nHeight];

    readBuffer(input, brgb);

    for (int j = nHeight - 1; j >= 0; j--) {
        for (int i = 0; i < nWidth; i++) {
            int base = (j * nWidth + i) * 4;
            bankData[0][base] = (byte) 255;
            bankData[0][base + 1] = brgb[nindex];
            bankData[0][base + 2] = brgb[nindex + 1];
            bankData[0][base + 3] = brgb[nindex + 2];
            nindex += 3;
        }
        nindex += npad;
    }

    return bufferedImage;
}

/**
 * Converts bytes to an int
 *
 * @param bytes An array of bytes
 * @param index
 * @returns A int representation of the bytes
 */
private static int bytesToInt(byte[] bytes, int index) {
    return (bytes[index + 3] & 0xff) << 24 |
            (bytes[index + 2] & 0xff) << 16 |
            (bytes[index + 1] & 0xff) << 8 |
            bytes[index + 0] & 0xff;
}

/**
 * Converts bytes to a short
 *
 * @param bytes An array of bytes
 * @param index
 * @returns A short representation of the bytes
 */
private static short bytesToShort(byte[] bytes, int index) {
    return (short) (((bytes[index + 1] & 0xff) << 8) |
            (bytes[index + 0] & 0xff));
}

/**
 * Reads the buffer
 *
 * @param in An InputStream
 * @param buffer An array of bytes
 * @throws IOException
 */
private static void readBuffer(InputStream in, byte[] buffer) throws IOException {
    int bytesRead = 0;
    int bytesToRead = buffer.length;
    while (bytesToRead > 0) {
        int read = in.read(buffer, bytesRead, bytesToRead);
        bytesRead += read;
        bytesToRead -= read;
    }
}
}

avatar de usuario
AndreyICE

Si la versión mínima de su aplicación Android SDK es mayor o igual a dieciséis (Android 4.1) la mejor forma de codificación de video es usar API de códec multimedia de Android.

De API de Android 4.3.

Al codificar video, Android 4.1 (SDK 16) requería que proporcionara los medios con una matriz ByteBuffer, pero Android 4.3 (SDK 18) ahora le permite usar una superficie como entrada para un codificador. Por ejemplo, esto le permite codificar la entrada de un archivo de video existente o usar marcos generados desde OpenGL ES.

multiplexor de medios agregado en Android 4.3 (SDK 18), por lo que para una forma conveniente de escribir archivos mp4 con Media Muxer, debe tener SDK>=18.

Al utilizar la API de Media Codec, obtendrá una codificación acelerada por hardware y podrá codificar fácilmente hasta 60 FPS.

Puedes empezar desde 1) ¿Cómo codificar mapas de bits en un video usando MediaCodec? o usar 2) Google Grafica o 3) Copo grande.

A partir de Grafika RecordFBOActivity.java. Reemplace el evento Choreographer con su propio mapa de bits que contiene para codificar, elimine el dibujo en pantalla, cargue su mapa de bits como Open GL Texture y dibújelo en la superficie de entrada de Media Codec.

  • Hola y gracias por los enlaces. ¿Podría ayudar a escribir los códigos completos?

    – Khalil Khalaf

    7 de noviembre de 2016 a las 0:41

  • Ver código en stackoverflow.com/questions/20343534/…

    – AndreyICE

    7 de noviembre de 2016 a las 17:41

avatar de usuario
a54studio

jCodec ha agregado soporte para Android.

Necesita agregar estos a su gradle …

implementation 'org.jcodec:jcodec:0.2.3'
implementation 'org.jcodec:jcodec-android:0.2.3'

…y

android {
    ...
    configurations.all {
        resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.2'
    }
}

Puedo confirmar que esto funciona como se esperaba, pero con advertencias. Primero, probé algunas imágenes de tamaño completo y el archivo se escribió, pero dio un error en la reproducción. Cuando reduje la escala, obtendría un error si el ancho o el alto de la imagen no eran iguales porque requiere un múltiplo de 2 para el espacio de color YUV420J.

También vale la pena señalar que esto hace que su paquete sea PESADO, pesado. Mi pequeño proyecto superó el límite de dex al agregar esto y requirió habilitar multidex.

FileChannelWrapper out = null;
File dir = what ever directory you use...
File file = new File(dir, "test.mp4");

try { out = NIOUtils.writableFileChannel(file.getAbsolutePath());
      AndroidSequenceEncoder encoder = new AndroidSequenceEncoder(out, Rational.R(15, 1));
      for (Bitmap bitmap : bitmaps) {
          encoder.encodeImage(bitmap);
      }
      encoder.finish();
} finally {
    NIOUtils.closeQuietly(out);
}

Puede usar Bitmp4 para convertir una secuencia de imágenes a un archivo MP4.

Código de muestra :

val encoder = MP4Encoder()
     encoder.setFrameDelay(50)
     encoder.setOutputFilePath(exportedFile.path)
     encoder.setOutputSize(width, width)

 startExport()

 stopExport()

 addFrame(bitmap) //called intervally

Es una biblioteca de Java, por lo que es fácil importarla al proyecto de Android, no tiene que usar NDK a diferencia de ffmpeg.

Referirse https://github.com/dbof10/Bitmp4 para código de muestra y descargas.

Creé un proyecto que debería ser capaz de manejar esto. El código es ligero y bastante sencillo.

https://github.com/dburckh/bitmap2video

avatar de usuario
Comunidad

Abhishek V tenía razón, más información sobre codificador de secuencia jcodec: ver Android hacer video animado de la lista de imágenes

Recientemente, construí un sistema de video en tiempo real usando raspberry pi y dispositivos Android, encontré el mismo problema que el suyo. En lugar de guardar una lista de archivos de imagen, utilicé algunos protocolos de transmisión en tiempo real como RTP/RTCP para transferir el flujo de datos al usuario. Si su requerimiento es algo como esto, tal vez podría cambiar sus estrategias.

Otra sugerencia es que puede explorar algunas bibliotecas C/C++, usando NDK/JNI para romper la limitación de Java.

Espero que las sugerencias tengan sentido para ti 🙂

  • Sí, tenía sentido para mí 🙂 Gracias por sus sugerencias y por el enlace. ¿Podríamos ir a una sala de chat, tal vez podamos hablar más sobre la estrategia?

    – Khalil Khalaf

    3 de noviembre de 2016 a las 17:28

  • Los contenidos de esta página me ayudaron mucho, el ejemplo ahí es totalmente práctico: Android-streaming-live-camera-video-to-web. Si esta estrategia se ajusta a sus requisitos y tiene más preguntas sobre la implementación, podemos continuar chateando.

    – JY0284

    4 de noviembre de 2016 a las 6:52

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad