【问题标题】:How to create a video from an array of images in Android?如何从 Android 中的图像数组创建视频?
【发布时间】:2020-09-25 15:29:00
【问题描述】:

我想调用一个函数并从图像列表中构建一个视频,然后将其保存在设备本地:

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

试验:

从当前的所有 JPEG 文件创建一个 MPEG-4 文件 目录:

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

我不知道如何在 Java / Android 项目中使用上述内容..

任何人都可以帮助指导我或/和为我提供完成任务的方法吗?提前致谢。

【问题讨论】:

  • 不要先制作帧的位图。并且一旦内存不足,就不要将它们放在列表中。只需将帧直接写入文件即可。
  • 请将帧大小/长度与位图所需的内存大小进行比较并报告。如果您想使用列表,那么最好将框架放入其中。只需比较所需的记忆。
  • @greenapps 按钮单击 RecordVideo,您希望我开始在设备上本地保存 ReceivedImages。你会推荐这种保存Saving and Reading Bitmaps/Images的方法吗?我继续实施以保存单个图像,然后向您报告它的大小。
  • @greenapps 你好,我还是not succeeding in saving an image。这会有所帮助吗? s11.postimg.org/4a1kdregz/Capture.png
  • 托管服务,其输入为 zip(照片)输出 - mp4 的视频文件名。调用时,它会解压缩 zips 照片 - 调用 ffmpeg 或其他将解压缩媒体作为输入的东西。输出文件是 MP4。这是通用的媒体多路复用服务,可能可以安装为具有最小修订版的节点库。 wd没有2写它。当 mp4 在服务器上准备好时,它会 POST'd 到您的 CDN(服务 mp4 媒体)。然后客户端从 CDN 请求 mp4。替代架构师的原因 - 服务器是从移动设备上捕获的媒体创建/托管/提供 mp4 的更好地方

标签: android image video


【解决方案1】:

您可以使用 jcodec SequenceEncoder 将图像序列转换为 MP4 文件。

示例代码:

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

它是一个java库,因此很容易将其导入Android项目,您不必像ffmpeg那样使用NDK。

请参阅http://jcodec.org/ 以获取示例代码和下载。

【讨论】:

  • 太棒了,看起来像我要找的东西。我试试看,谢谢!
  • @KhalilKhalaf,你能在哪里让它工作?我正在尝试相同的代码,它会生成一个损坏的 .mp4 文件。
  • @nishant1000 此功能已被删除,我被重定向到其他工作。在对图像执行 JCodec 之前,您是否尝试过其他答案中的“Bitmap to BufferedImage”方法?
  • @Abhishek 我们可以在视频中更改帧的同时添加音频到视频和过渡动画吗?
  • java awt 在 Android 中似乎不可用,所以我不知道 BufferedImage 在这里是一个可行的解决方案
【解决方案2】:

使用 JCodec 由 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();}

现在要将您的位图转换为 BufferedImage,您可以使用此类:

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

【讨论】:

    【解决方案3】:

    如果您的应用 Android SDK 的最低版本大于或等于 16 (Android 4.1),则视频编码的最佳方式是使用 Android Media Codec API

    来自Android 4.3 APIs

    在编码视频时,Android 4.1 (SDK 16) 要求您提供 带有 ByteBuffer 数组的媒体,但 Android 4.3 (SDK 18) 现在允许 您可以使用 Surface 作为编码器的输入。例如,这个 允许您对来自现有视频文件或使用帧的输入进行编码 从 OpenGL ES 生成。

    Media Muxer 在 Android 4.3 (SDK 18) 中添加,因此为了方便使用 Media Muxer 编写 mp4 文件,您应该具有 SDK>=18。

    使用媒体编解码器 API 方式,您将获得硬件加速编码,并且您可以轻松编码高达 60 FPS。

    你可以从1) How to encode Bitmaps into a video using MediaCodec?开始 或使用 2) Google Grafika3) Bigflake

    从 Grafika RecordFBOActivity.java 开始。将 Choreographer 事件替换为您自己的包含要编码的位图,删除屏幕上的绘图,将您的位图加载为 Open GL 纹理并将其绘制在媒体编解码器输入表面上。

    【讨论】:

    【解决方案4】:

    jCodec 已添加 Android 支持。

    您需要将这些添加到您的 gradle...

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

    ...和

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

    我可以确认这按预期工作,但有一些警告。首先,我尝试了一些全尺寸图像并写入了文件,但在播放时出错。当我按比例缩小时,如果图像的宽度或高度不均匀,我会得到一个错误,因为它需要 YUV420J 颜色空间的 2 的倍数。

    另外值得注意的是,这会使您的包裹沉重、沉重。我的小项目通过添加这个超出了 dex 限制,需要启用 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);
    }
    

    【讨论】:

    • 我正在使用 CameraX 和 ImageAnalysis.Analyzer 分析图像,同时我想将用户看到的内容存储到视频文件中,该文件稍后将用于调试,这部分代码真的帮助。谢谢。
    • 这需要很多时间,在我的情况下,当使用 145 张图像(低质量图像 - 每张 200 kb)调用此函数时,大约需要 2 分钟。有什么办法可以提高吗??
    【解决方案5】:

    您可以使用 Bitmp4 将图像序列转换为 MP4 文件。

    示例代码:

    ...

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

    它是一个java库,因此很容易将其导入Android项目,您不必像ffmpeg那样使用NDK。

    请参阅https://github.com/dbof10/Bitmp4 以获取示例代码和下载。

    【讨论】:

      【解决方案6】:

      我创建了一个应该能够处理这个问题的项目。代码简洁明了。

      https://github.com/dburckh/bitmap2video

      【讨论】:

      • 谢谢!我可以确认我有这个工作:)
      【解决方案7】:

      Abhishek V 是对的,关于 jcodec SequenceEncoder 的更多信息: 见Android make animated video from list of images

      最近我使用树莓派和安卓设备构建了一个实时视频系统,遇到了和你一样的问题。我没有保存图像文件列表,而是使用了一些实时流协议,如 RTP/RTCP 将数据流传输给用户。如果你的要求是这样的,也许你可以改变你的策略。

      另外一个建议是你可以探索一些 C/C++ 库,使用 NDK/JNI 来打破 Java 的限制。

      希望这些建议对你有意义:)

      【讨论】:

      • 是的,这对我来说很有意义 :) 感谢您的建议和链接。我们可以去聊天室,也许我们可以更多地讨论一下策略吗?
      • 这个页面的内容对我帮助很大,里面的例子非常实用:android-streaming-live-camera-video-to-web。如果此策略符合您的要求,并且您对实施还有更多疑问,我们可能会继续聊天
      【解决方案8】:

      您可以使用 ffmpeg 库从图像数组制作视频。 FFMPEG 库对于制作视频非常有用。 也许以下链接会对您有所帮助。 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

      【讨论】:

        【解决方案9】:

        您有位图,您可以使用 JCodec 将其转换为视频

        这是一个示例图像序列编码器:

        您可以通过将 BufferedImage 替换为 Bitmap 来修改它。

        根据需要使用这些方法。

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

        【讨论】:

          猜你喜欢
          • 2013-01-03
          • 2019-04-12
          • 2012-02-17
          • 1970-01-01
          • 2016-05-01
          • 1970-01-01
          • 2012-08-15
          • 1970-01-01
          • 2010-09-20
          相关资源
          最近更新 更多