Подтвердить что ты не робот

Как создать видео из массива изображений в Android?

Я хочу вызвать функцию и создать видео из списка изображений, а затем сохранить его локально на устройстве:

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

Trials:

  • Следуя java/xuggle - закодируйте массив изображений в фильм, ссылка в ответе - это мертвая ссылка

  • Следуя Как кодировать изображения в видеофайл в Java через программирование?, предлагаемая библиотека в принятом ответе не поддерживает Android.

  • Следующий ответ в этом случае имеет подход для пользователей Android, однако для меня непонятно ввод и вывод этой функции (где он дал изображения? и где он получил видео?) - Я оставил комментарий к вопросу

  • Следующий ответ в этом случае предоставляет целый класс, однако требуемая библиотека, которая будет включена, имеет поврежденный файл (когда я пытаюсь загрузить его из предоставленной ссылки) - я оставил комментарий вопроса

  • Следуя Java: как мне создать фильм из массива изображений?, предлагаемая библиотека в верхнем ответе использует команды, которые я не знаю и я даже не знаю, как их использовать. Как:

Создание файла MPEG-4 из всех файлов JPEG в текущем Каталог:

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.

Может ли кто-нибудь помочь в руководстве мной или/или предоставлении мне подхода к моей задаче? Спасибо заранее.

4b9b3361

Ответ 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, вам не нужно использовать NDK в отличие от ffmpeg.

Обратитесь http://jcodec.org/ для примера кода и загрузки.

Ответ 2

Использование JCodec, как продемонстрировал Станислав Витвицкий здесь.

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), лучшим способом кодирования видео является API Android Media Codec.

Из Android 4.3 API.

При кодировании видео, Android 4.1 (SDK 16) требуется, чтобы вы предоставляли медиа с массивом ByteBuffer, но Android 4.3 (SDK 18) теперь позволяет вы должны использовать Поверхность в качестве входа в кодировщик. Например, это позволяет вам кодировать входные данные из существующего видеофайла или использовать фреймы созданный с помощью OpenGL ES.

Media Muxer добавлен в Android 4.3 (SDK 18), поэтому для удобного способа записи файла mp4 с Media Muxer вы должны иметь SDK >= 18.

Используя Media Codec API, вы получите аппаратное ускоренное кодирование, и вы легко кодируете до 60 FPS.

Вы можете начать с 1) Как кодировать растровые изображения в видео с помощью MediaCodec? или используйте 2) Google Grafika или 3) Bigflake.

Начиная с Grafika RecordFBOActivity.java. Замените событие хореографа на свой собственный, содержащий растровое изображение, чтобы закодировать, удалить рисунок на экране, загрузить растровое изображение в виде Open GL Texture и нарисовать его на поверхности ввода медиакодека.

Ответ 4

jCodec добавил поддержку Android.

Вы должны добавить это в свой gradle...

compile 'org.jcodec:jcodec:0.2.3'
compile '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);
}

Ответ 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, вам не нужно использовать NDK в отличие от ffmpeg.

См. Https://github.com/dbof10/Bitmp4 для примера кода и загрузки.

Ответ 6

Abhishek V был прав, больше информации о jcodec SequenceEncoder: см. Android сделать анимированное видео из списка изображений

Недавно я построил видеосистему реального времени, использующую устройства для малины pi и Android, встретил ту же проблему, что и ваша. Вместо сохранения списка файлов изображений я использовал некоторые потоковые протоколы реального времени, такие как RTP/RTCP, чтобы передать поток данных пользователю. Если ваше требование - что-то вроде этого, возможно, вы могли бы изменить свои стратегии.

Другое предложение состоит в том, что вы можете изучить некоторые библиотеки C/С++, используя NDK/JNI, чтобы прервать ограничение Java.

Надеюсь, что предложения будут вам полезны:)

Ответ 8

у вас есть растровые изображения, которые вы можете перевернуть в видео с помощью 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());
}