如何把rgba图片像素数组传递给ffmpeg?

我有一张1920x1080像素的图片,内存中保存的该图片的像素数组,rgba格式,四个字节为一个像素,也就是一个1920x1080x4的字节数组.

我想把这个数组传递给ffmpeg,并希望ffmpeg正确解析数据格式和图片宽高,我应该如何描述数据格式或者像素格式。

我使用的pipe在java端做的数据的输入和输出,下边是我把像素数组转mjpeg后,使用ffmpeg生成avi格式视频的命令,可以正确生成视频.

        ProcessBuilder extractBuilder =
                new ProcessBuilder(
                        ffmpeg
                        , "-f", "image2pipe"
                        , "-y", "-framerate", "5.0"
                        , "-codec", "mjpeg"
//                        , "-codec", "bmp"
                        , "-i", "pipe:0"
                        , "-y"
                        , "-loglevel"
                        , "quiet"
                        , "-f", "avi"
                        , "pipe:1"
                );

.
.
省略一些中间过程
.
      OutputStream ffmpegInput = process.getOutputStream();
    BufferedImage img = "像素数组转成的img";
     ImageIO.write(image, "jpg", ffmpegInput);

使用jpg虽然可以,但是ImageIO.write一次耗时30毫秒左右,所有我想直接把原始的像素数组传递给ffmpeg,但是不知道如何让ffmpegg知道数据格式以及一张图片的宽和高.

阅读 2.8k
2 个回答

今天用chatgpt,突然想到以前问过的问题,结果真的给我了更好的方案。
把原来的"-f", "image2pipe"改成

"-f", "rawvideo"
"-video_size", " 1920x1080"
"-pixel_format", "rgb24"

然后输入就是一个个1920x1080x3的字节数组

public class ImageToVideo2 {
    static String ffmpeg = "D:\\tools\\ffmpeg-n5.0-latest-win64-gpl-5.0\\bin\\ffmpeg.exe";
    
    public static void main(String[] args) {
        ProcessBuilder extractBuilder =
                new ProcessBuilder(
                        ffmpeg
                        , "-f", "rawvideo"
                        , "-video_size", " 1920x1080"
                        , "-pixel_format", "rgb24"
//                        , "-framerate", "30.0"
                        , "-vsync", "vfr"
                        , "-i", "pipe:0"
                        , "-loglevel"
                        , "quiet"
                        , "-c:v", "libx264"
//                        ,"-pix_fmt", "yuv420p"
//                        ,"-preset" ,"ultrafast"
//                        ,"-pix_fmt","bgr24"
                        , "-y"
                        , "output.mkv"
                );

        System.out.println(extractBuilder.command().toString());
        extractBuilder.redirectErrorStream(true);
        extractBuilder.redirectInput(ProcessBuilder.Redirect.PIPE);

        try {
            Process process = extractBuilder.start();
            OutputStream ffmpegInput = process.getOutputStream();
            RobotPeer robotPeer = getRobotPeer();
            BlockingDeque<byte[]> blockingDeque = new LinkedBlockingDeque<byte[]>();
            AtomicBoolean hashImg = new AtomicBoolean(true);
            new Thread(() -> {
                while (hashImg.get()) {
                    try {
                        byte[] bmpPixels = blockingDeque.take();
                        ffmpegInput.write(bmpPixels);
                    } catch (InterruptedException | IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                try {
                    ffmpegInput.close();
                } catch (Exception e) {

                }

            }).start();

            for (int i = 0; i < 30; i++) {
                byte[] bmpPixels = screenToBmp(robotPeer);
                blockingDeque.add(bmpPixels);
            }
            hashImg.set(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static RobotPeer getRobotPeer() {
        try {
            Robot robot = new Robot();

            Field peerField = robot.getClass().getDeclaredField("peer");
            peerField.setAccessible(true);
            RobotPeer peer = (RobotPeer) peerField.get(robot);
            return peer;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static byte[] screenToBmp(RobotPeer peer) {
        //这个头中包含了视频是 1920 * 1080 * 3的格式了
        Rectangle screenRect = new Rectangle(0, 0, 1920, 1080);
        //截图
        int[] pixels = peer.getRGBPixels(screenRect);
        //解析像素,返回的是bgr格式
        int length = screenRect.width * screenRect.height;
        byte[] imgBytes = new byte[length * 3];
        int byteIndex = 0;
        for (int i = 0, pixel = 0; i < length; i++) {
            pixel = pixels[i];
            imgBytes[byteIndex + 2] = (byte) (pixel);
            pixel = pixel >> 8;
            imgBytes[byteIndex + 1] = (byte) (pixel);
            imgBytes[byteIndex] = (byte) (pixel >> 8);
            byteIndex += 3;
        }
        return imgBytes;
    }
}

你要想通过管道给ffmpeg喂数据,那么必须转成已知的数据流,那么ffmpeg支持的解码器可以用命令ffmpeg -decoders

你必须将数据流转成上述命令列出来的数据类型的任意一种。

另外你用jpg显然不是一个好的选择,你的原始数据是rgba类型,jpg直接就扔掉了Alpha通道,并且对其他数据做有损压缩处理,当然会慢。如果你想保留原始数据类型,那么你首先应该考虑的是bmp或png数据类型,如果对速度有要求,那么bmp是你唯一的选择,它是无损不压缩的数据类型,理论上会比转换成jpg快。

另外-codec mjpeg这个参数可以删了,理论上适用于AVI最佳的格式是ffmpeg默认使用的XVID,让他用默认的这个编码即可

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题