使用 AndroidFrameConverter 将 javacv 帧转换为位图

Convert a javacv Frame Into Bitmap using AndroidFrameConverter

我正在尝试使用 javacv AndroidFrameConverter 将大小为 1280x720 的帧转换为位图,但转换需要很长时间。这是转换的示例代码。

FrameGrabber grabber = new FFmpegFrameGrabber(videoUrl);
frame = grabber.grab();
AndroidFrameConverter converter = new AndroidFrameConverter();
Bitmap originalBitmap = converter.convert(frame);

还有比这更快的解决方案吗?

FrameGrabber.grab() 返回的

Frame 是 javacv 特定的,Bitmap 是 Android 特定的,我怀疑两者之间是否有更快的转换方法,因为它们没有彼此了解并且都针对自己的使用进行了优化,他们无法优化两种数据格式之间的转换过程。

您正在移动 1280 * 720 * 4 字节,大约每帧 3.6 MB,这对于移动设备来说很多,尤其是考虑到内存分配。

我怀疑您是否会在不将 javacv 换成其他东西的情况下找到另一个转换工具 - 无法想象专门用于将 javacv 的 Frame 转换为 Android Bitmap 的工具来替换内置方法。

此外,AndroidFrameConverter.convert() 已经很聪明了 - 它只在第一次创建 Bitmap 或者如果规范发生变化(如大小等),然后只复制字节。

幸运的是,它有能力重用创建的 Bitmap 实例,FFmpegFrameGrabber 也有这种能力,所以在第一个 运行 他们将创建每个合适的对象,但是从第二帧应该重用它们拥有的对象——这样至少不会发生不必要的内存分配,这会降低移动设备的性能。这意味着平均转换许多帧应该比只转换一帧快得多——只是不要为每一帧创建新的抓取器和转换器实例。

使用 GPU 进行并行化可能是可能的 - 手动创建一个 Bitmap,分区 Frame 的缓冲区并并行设置 Bitmap 的像素。我自己从来没有做过这样的事情,所以真的不能发表更多评论。请记住,并行算法有其自身的开销,并不总是比顺序算法更快。而且我不确定 GPU 编程的可移植性如何。使用 CPU 进行并行化不值得尝试 - 当前设备有多少个内核? 4?开销应该大于收益。

您可以使用此代码并根据需要更改过滤器。

public class FilterAsync extends AsyncTask {

    private FFmpegFrameGrabber VIDEO_GRABBER;
    private FFmpegFrameRecorder videoRecorder;
    private File file = new File(Environment.getExternalStorageDirectory() + "/Download/Abc.mp4");
    private Context mContext;
    private FFmpegFrameFilter filter;
    private boolean isTrue = false;
    private ArrayList<String> videoPaths;
    private File myDirectory;


    public FilterAsync(Context context) {
        mContext = context;
        VIDEO_GRABBER = new FFmpegFrameGrabber(file);
        myDirectory = new File(Environment.getExternalStorageDirectory() + "/Folder/");
        if (!myDirectory.exists()) {
            myDirectory.mkdirs();
        }
        videoPaths = new ArrayList<>();
    }


    @Override
    protected void onPreExecute() {
        super.onPreExecute();

    }

    @Override
    protected Object doInBackground(Object[] params) {
        Frame tempVideoFrame;
        try {
            VIDEO_GRABBER.start();
            initVideoRecorder(myDirectory + "/video" + System.currentTimeMillis() + ".mp4");
            filter.start();
            while (VIDEO_GRABBER.grab() != null) {
                tempVideoFrame = VIDEO_GRABBER.grabImage();
                if (tempVideoFrame != null) {
                    filter.push(tempVideoFrame);
                    tempVideoFrame = filter.pull();
                    videoRecorder.record(tempVideoFrame);
                }
            }
            videoRecorder.stop();
            filter.stop();
            videoRecorder.release();
            VIDEO_GRABBER.stop();
            VIDEO_GRABBER.release();
        } catch (FrameGrabber.Exception e) {
            e.printStackTrace();
        } catch (FrameRecorder.Exception e) {
            e.printStackTrace();
        } catch (FrameFilter.Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    @Override
    protected void onPostExecute(Object o) {
        super.onPostExecute(o);
    }


    private void initVideoRecorder(String path) {
        try {
            filter = new FFmpegFrameFilter("transpose=clock ,crop=w=640:h=480:x=0:y=0", VIDEO_GRABBER.getImageWidth(), VIDEO_GRABBER.getImageHeight());

            videoRecorder = FFmpegFrameRecorder.createDefault(path, VIDEO_GRABBER.getImageWidth(), VIDEO_GRABBER.getImageHeight());
           videoRecorder.setAudioChannels(VIDEO_GRABBER.getAudioChannels());
            videoRecorder.start();
        } catch (FrameRecorder.Exception e) {
            e.printStackTrace();
        }
    }
}