app/src/main/java/com/anyun/h264/util/FileUtil.java
@@ -179,6 +179,137 @@
    }
    /**
     * 检查内部 Flash(非 TF 卡)剩余空间,如果小于 800MB,则按时间顺序删除 h264_*.h264 文件
     * 目录:context.getExternalFilesDir(null).getAbsolutePath()
     * 文件名格式示例:h264_1735023032000.h264、h264_camera2_1735023032000.h264
     *
     * 删除规则:
     * - 按文件名中的时间戳从小到大(越早越先删)依次删除
     * - 每删除一次后重新计算剩余空间,直到 ≥ 800MB 或文件删完
     *
     * 返回值:
     * - 0:最终剩余空间 ≥ 800MB 或无需删除
     * - -1:删除完所有符合规则的文件后,剩余空间仍然 < 800MB
     */
    public static int ensureInternalFlashSpaceForH264(Context context) {
        if (context == null) {
            Timber.w("ensureInternalFlashSpaceForH264: context is null");
            return 0;
        }
        File externalDir = context.getExternalFilesDir(null);
        if (externalDir == null) {
            Timber.w("ensureInternalFlashSpaceForH264: external files dir is null");
            return 0;
        }
        String basePath = externalDir.getAbsolutePath();
        long minFreeBytes = 800L * 1024L * 1024L; // 800MB
        long freeBytes = getFreeSpaceBytes(basePath);
        Timber.d("ensureInternalFlashSpaceForH264: freeBytes=%d, minRequired=%d", freeBytes, minFreeBytes);
        if (freeBytes >= minFreeBytes) {
            // 空间充足,无需处理
            return 0;
        }
        // 收集符合命名规则的 h264 文件
        File[] files = externalDir.listFiles();
        if (files == null || files.length == 0) {
            Timber.w("ensureInternalFlashSpaceForH264: no files in dir -> %s", basePath);
            // 已经没有可删的文件,如果仍小于 800MB,则直接返回 -1
            return freeBytes >= minFreeBytes ? 0 : -1;
        }
        class H264FileInfo {
            File file;
            long timestamp;
            H264FileInfo(File file, long timestamp) {
                this.file = file;
                this.timestamp = timestamp;
            }
        }
        List<H264FileInfo> h264Files = new ArrayList<>();
        for (File file : files) {
            if (!file.isFile()) {
                continue;
            }
            String name = file.getName();
            // 只处理 .h264 结尾,且以 h264_ 开头的文件
            if (!name.toLowerCase(Locale.CHINA).endsWith(".h264")) {
                continue;
            }
            if (!name.startsWith("h264_") && !name.startsWith("h264_camera2_")) {
                continue;
            }
            // 提取时间戳部分
            String timePart = null;
            if (name.startsWith("h264_camera2_")) {
                // 前缀长度 13:"h264_camera2_"
                timePart = name.substring("h264_camera2_".length(), name.length() - ".h264".length());
            } else if (name.startsWith("h264_")) {
                // 前缀长度 5:"h264_"
                timePart = name.substring("h264_".length(), name.length() - ".h264".length());
            }
            if (timePart == null || timePart.isEmpty()) {
                continue;
            }
            try {
                long ts = Long.parseLong(timePart);
                h264Files.add(new H264FileInfo(file, ts));
            } catch (NumberFormatException e) {
                // 文件名不符合时间戳格式,跳过
                Timber.w("ensureInternalFlashSpaceForH264: invalid timestamp in file name -> %s", name);
            }
        }
        if (h264Files.isEmpty()) {
            Timber.w("ensureInternalFlashSpaceForH264: no matched h264 files in -> %s", basePath);
            return freeBytes >= minFreeBytes ? 0 : -1;
        }
        // 按时间戳升序排列(越早的越先删)
        Collections.sort(h264Files, new Comparator<H264FileInfo>() {
            @Override
            public int compare(H264FileInfo o1, H264FileInfo o2) {
                return Long.compare(o1.timestamp, o2.timestamp);
            }
        });
        int deletedCount = 0;
        for (H264FileInfo info : h264Files) {
            if (freeBytes >= minFreeBytes) {
                break;
            }
            File f = info.file;
            long size = f.length();
            Timber.d("ensureInternalFlashSpaceForH264: deleting file -> %s, size=%d, ts=%d",
                    f.getAbsolutePath(), size, info.timestamp);
            if (f.delete()) {
                deletedCount++;
                // 删除后重新获取剩余空间,更准确
                freeBytes = getFreeSpaceBytes(basePath);
                Timber.d("ensureInternalFlashSpaceForH264: after delete, freeBytes=%d", freeBytes);
            } else {
                Timber.e("ensureInternalFlashSpaceForH264: failed to delete file -> %s", f.getAbsolutePath());
            }
        }
        Timber.i("ensureInternalFlashSpaceForH264: deleted %d files, final freeBytes=%d", deletedCount, freeBytes);
        return freeBytes >= minFreeBytes ? 0 : -1;
    }
    /**
     * 计算目录下所有 .h264 文件的总大小(字节)
     */
    private static long calculateH264FilesSize(File dir) {
@@ -211,6 +342,21 @@
    }
    /**
     * 获取指定路径的剩余空间(字节)
     */
    private static long getFreeSpaceBytes(String path) {
        try {
            StatFs statFs = new StatFs(path);
            long blockSize = statFs.getBlockSizeLong();
            long availableBlocks = statFs.getAvailableBlocksLong();
            return availableBlocks * blockSize;
        } catch (Exception e) {
            Timber.e(e, "Error getting free space (bytes) for path: %s", path);
            return 0;
        }
    }
    /**
     * 递归删除目录及其所有内容
     */
    private static boolean deleteDirectory(File dir) {