How to create a video from an array of images in Android?

后端 未结 9 882
时光说笑
时光说笑 2020-12-04 10:32

I want to call a function and build a video out of list of images, and then save it locally on the device:

public void CreateAndSaveVideoFile(List

        
相关标签:
9条回答
  • 2020-12-04 11:16

    You can use ffmpeg library to make video from array of image. FFMPEG Library is very useful to make video. Maybe the following link will help you. http://osric.com/chris/accidental-developer/2012/04/using-ffmpeg-to-programmatically-slice-and-splice-video/ https://groups.google.com/forum/#!topic/android-ndk/sxDYlGYK-Xg

    0 讨论(0)
  • 2020-12-04 11:19

    You can use jcodec SequenceEncoder to convert sequence of images to MP4 file.

    Sample code :

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

    It's a java library so it's easy to import it into Android project, you don't have to use NDK unlike ffmpeg.

    Refer http://jcodec.org/ for sample code & downloads.

    0 讨论(0)
  • 2020-12-04 11:20

    Using JCodec as demonstrated by Stanislav Vitvitskyy here.

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

    Now to convert your Bitmap to BufferedImage you can use this class:

    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;
        }
    }
    }
    
    0 讨论(0)
  • 2020-12-04 11:23

    you have bitmaps you can flip it into video using JCodec

    Here's a sample image sequence encoder:

    • https://github.com/jcodec/jcodec/blob/master/src/main/java/org/jcodec/api/SequenceEncoder.java

    You can modify it for your purposes by replacing BufferedImage with Bitmap.

    Use these methods according your need.

    public static Picture fromBitmap(Bitmap src) {
      Picture dst = Picture.create((int)src.getWidth(), (int)src.getHeight(), RGB);
      fromBitmap(src, dst);
      return dst;
    }
    
    public static void fromBitmap(Bitmap src, Picture dst) {
      int[] dstData = dst.getPlaneData(0);
      int[] packed = new int[src.getWidth() * src.getHeight()];
    
      src.getPixels(packed, 0, src.getWidth(), 0, 0, src.getWidth(), src.getHeight());
    
      for (int i = 0, srcOff = 0, dstOff = 0; i < src.getHeight(); i++) {
        for (int j = 0; j < src.getWidth(); j++, srcOff++, dstOff += 3) {
          int rgb = packed[srcOff];
          dstData[dstOff]     = (rgb >> 16) & 0xff;
          dstData[dstOff + 1] = (rgb >> 8) & 0xff;
          dstData[dstOff + 2] = rgb & 0xff;
        }
      }
    }
    
    public static Bitmap toBitmap(Picture src) {
      Bitmap dst = Bitmap.create(pic.getWidth(), pic.getHeight(), ARGB_8888);
      toBitmap(src, dst);
      return dst;
    }
    
    public static void toBitmap(Picture src, Bitmap dst) {
      int[] srcData = src.getPlaneData(0);
      int[] packed = new int[src.getWidth() * src.getHeight()];
    
      for (int i = 0, dstOff = 0, srcOff = 0; i < src.getHeight(); i++) {
        for (int j = 0; j < src.getWidth(); j++, dstOff++, srcOff += 3) {
          packed[dstOff] = (srcData[srcOff] << 16) | (srcData[srcOff + 1] << 8) | srcData[srcOff + 2];
        }
      }
      dst.setPixels(packed, 0, src.getWidth(), 0, 0, src.getWidth(), src.getHeight());
    }
    
    0 讨论(0)
  • 2020-12-04 11:33

    jCodec has added Android support.

    You need to add these to your gradle...

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

    ...and

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

    I can confirm this works as expected, but with caveats. First being I tried some full size images and the file wrote, but gave an error on playback. When I scaled down, I would get an error if the width or height of the image was not even because it requires a multiple of 2 for YUV420J colorspace.

    Also worthy of note, this makes your package HEAVY, heavy. My small project went over the dex limit by adding this and required enabling 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);
    }
    
    0 讨论(0)
  • 2020-12-04 11:33

    You can use Bitmp4 to convert sequence of images to MP4 file.

    Sample code :

    ...

    val encoder = MP4Encoder()
         encoder.setFrameDelay(50)
         encoder.setOutputFilePath(exportedFile.path)
         encoder.setOutputSize(width, width)
    
     startExport()
    
     stopExport()
    
     addFrame(bitmap) //called intervally
    

    It's a java library so it's easy to import it into Android project, you don't have to use NDK unlike ffmpeg.

    Refer https://github.com/dbof10/Bitmp4 for sample code & downloads.

    0 讨论(0)
提交回复
热议问题