You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android帧提取应用崩溃求助:超80帧触发数组越界异常

解决视频帧提取超过80帧崩溃的问题

首先,咱们来拆解你遇到的崩溃根源:IndexOutOfBoundsException: Index: 0, Size: 0 是因为当提取帧数量增多后,内存占用急剧飙升,导致 FFmpegMediaMetadataRetriever.getFrameAtTime() 无法成功获取到帧(返回了null),而你的extractRGB()方法在处理null Bitmap时返回了空列表,随后调用rgb.get(0)就触发了索引越界异常。

结合你的代码,下面给出针对性的修复方案:

1. 复用FFmpegMediaMetadataRetriever,减少资源消耗

你现在每次循环都创建新的FFmpegMediaMetadataRetriever实例,这会频繁申请和释放资源,不仅效率低,还会加速内存耗尽。应该把它移到循环外面,复用同一个实例。

2. 强制处理帧获取失败的情况

必须判断getFrameAtTime()返回的Bitmap是否为null,避免后续操作触发空指针或索引越界。

3. 优化内存占用(核心!)

提取800帧时,把所有Bitmap和RGB数据都存在内存里必然会内存溢出。建议边提取边写入文件,不要在内存中缓存所有数据;如果确实需要临时存储,也应该分批处理,或者用软引用等方式避免内存堆积。

4. 修正tabColor的数据结构逻辑

你现在每次循环都把rouge、vert、bleu添加到tabColor里,这会导致tabColor里的列表重复累积,数据结构完全混乱,同时也会额外占用大量内存。应该只在最后统一添加一次这三个列表即可。

5. 确保UI操作在主线程执行

线程里直接调用progressDialog.dismiss()是不安全的,需要用runOnUiThread切换到主线程操作。


修改后的代码示例

public void extractionFrame(final File file) throws IOException, JCodecException {
    if (file == null || !file.exists()) {
        return; // 先做基础参数校验
    }

    final String path = file.getAbsolutePath();
    final int nbrFrame = 800; // 改为你需要的800帧
    final long frameInterval = 34000; // 帧间隔时间,单位微秒

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        final ProgressDialog progressDialog = new ProgressDialog(getContext());
        progressDialog.setMessage("提取帧中...");
        progressDialog.setCancelable(false);
        progressDialog.setTitle("请稍候");
        progressDialog.show();

        Thread thread = new Thread() {
            @Override
            public void run() {
                FFmpegMediaMetadataRetriever mmr = new FFmpegMediaMetadataRetriever();
                try {
                    mmr.setDataSource(path);
                    final ArrayList<Integer> rouge = new ArrayList<>();
                    final ArrayList<Integer> vert = new ArrayList<>();
                    final ArrayList<Integer> bleu = new ArrayList<>();
                    // 提前初始化tabColor,避免重复添加
                    final ArrayList<ArrayList<Integer>> tabColor = new ArrayList<>();
                    tabColor.add(rouge);
                    tabColor.add(vert);
                    tabColor.add(bleu);

                    long currentTimeUs = 340000; // 起始时间
                    for (int i = 0; i < nbrFrame; i++) {
                        Bitmap b = mmr.getFrameAtTime(currentTimeUs, FFmpegMediaMetadataRetriever.OPTION_CLOSEST);
                        if (b == null) {
                            Log.w("FrameExtraction", "无法获取第" + i + "帧,跳过");
                            currentTimeUs += frameInterval;
                            continue;
                        }

                        ArrayList<Integer> rgb = extractRGB(b);
                        // 先判断rgb列表是否有效
                        if (rgb.size() >= 3) {
                            rouge.add(rgb.get(0));
                            vert.add(rgb.get(1));
                            bleu.add(rgb.get(2));
                        } else {
                            Log.w("FrameExtraction", "第" + i + "帧RGB提取失败");
                        }

                        // 及时回收Bitmap,释放内存
                        b.recycle();
                        currentTimeUs += frameInterval;
                        Log.d("FrameExtraction", "已提取第" + i + "帧");
                    }

                    // 保存数据,建议改成分批保存进一步降低内存压力
                    saveList(tabColor, getContext());

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    mmr.release(); // 最后统一释放资源
                }

                // 回到主线程关闭进度对话框
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        progressDialog.dismiss();
                    }
                });
            }
        };
        thread.start();
    }
}

// 确保extractRGB能正确处理null Bitmap,返回有效列表
private ArrayList<Integer> extractRGB(Bitmap bitmap) {
    ArrayList<Integer> rgb = new ArrayList<>();
    if (bitmap == null) {
        return rgb;
    }
    // 这里替换成你实际的RGB提取逻辑,确保返回size>=3的列表
    int pixel = bitmap.getPixel(0, 0);
    rgb.add(Color.red(pixel));
    rgb.add(Color.green(pixel));
    rgb.add(Color.blue(pixel));
    return rgb;
}

额外优化建议

  • 如果saveList是将数据写入文件,建议改成每提取50-100帧就写入一次,而不是等全部提取完再写入,进一步降低内存压力。
  • 可以考虑使用AsyncTask或者Kotlin的Coroutines来处理异步任务,比手动创建线程更安全、易维护。
  • 测试时可以用Android Studio的Profiler查看内存使用情况,定位内存泄漏或占用过高的点。

内容的提问来源于stack exchange,提问作者Bocar Timite

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:31:17