Dana
2025-12-23 284f3dbd343b8f40e921b4b887384ebc14ba3dcb
1.h264文件写入的时候检查tfcard剩余空间  ,并执行删除
4个文件已修改
252 ■■■■■ 已修改文件
app/src/main/java/com/anyun/h264/H264EncodeService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService2.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264Encoder.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/util/FileUtil.java 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService.java
@@ -505,6 +505,9 @@
            // 创建编码器
            h264Encoder = new H264Encoder();
            
            // 设置 Context(用于清理 TF 卡文件)
            h264Encoder.setContext(this);
            // 设置编码参数(使用配置中的参数)
            int width = config != null && config.width > 0 ? config.width : DEFAULT_WIDTH;
            int height = config != null && config.height > 0 ? config.height : DEFAULT_HEIGHT;
app/src/main/java/com/anyun/h264/H264EncodeService2.java
@@ -286,6 +286,9 @@
            // 创建编码器
            h264Encoder = new H264Encoder();
            
            // 设置 Context(用于清理 TF 卡文件)
            h264Encoder.setContext(this);
            // 设置编码参数(使用配置中的参数)
            // 设置编码参数(使用配置中的参数)
            int width = config != null && config.width > 0 ? config.width : DEFAULT_WIDTH;
@@ -352,6 +355,9 @@
            // 创建编码器
            h264Encoder = new H264Encoder();
            
            // 设置 Context(用于清理 TF 卡文件)
            h264Encoder.setContext(this);
            // 设置编码参数(使用配置中的参数)
            int width = config != null && config.width > 0 ? config.width : DEFAULT_WIDTH;
            int height = config != null && config.height > 0 ? config.height : DEFAULT_HEIGHT;
app/src/main/java/com/anyun/h264/H264Encoder.java
@@ -1,11 +1,13 @@
package com.anyun.h264;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import com.anyun.libusbcamera.UsbCamera;
import com.anyun.libusbcamera.WatermarkParam;
import com.anyun.h264.model.WatermarkInfo;
import com.anyun.h264.util.FileUtil;
import java.io.File;
import java.io.FileOutputStream;
@@ -82,6 +84,10 @@
    private int cameraId = 1; // 摄像头ID,默认为1(第一个摄像头)
    private long currentFileStartTime = 0; // 当前文件的开始时间(毫秒)
    private static final long FILE_DURATION_MS = 60 * 1000; // 文件时长:1分钟(毫秒)
    // Context 和清理配置
    private Context context; // Context 对象,用于清理 TF 卡文件
    private long maxH264TotalSizeGB = 5; // 最大 H264 文件总大小(GB),默认 5GB
    // 网络传输控制
    private boolean enableNetworkTransmission = true; // 是否启用TCP/UDP网络传输
@@ -155,6 +161,22 @@
     */
    public void setCameraId(int cameraId) {
        this.cameraId = cameraId;
    }
    /**
     * 设置 Context(用于清理 TF 卡文件)
     * @param context Context 对象
     */
    public void setContext(Context context) {
        this.context = context;
    }
    /**
     * 设置最大 H264 文件总大小(GB)
     * @param maxTotalSizeGB 最大总大小(GB),默认 5GB
     */
    public void setMaxH264TotalSizeGB(long maxTotalSizeGB) {
        this.maxH264TotalSizeGB = maxTotalSizeGB;
    }
    /**
@@ -432,6 +454,25 @@
                fileOutputStream = null;
            }
            
            // 检查并清理 TF 卡上的 h264 文件(如果需要)
            if (context != null && outputFileDirectory != null && !outputFileDirectory.isEmpty()) {
                try {
                    // 判断当前目录是否在 TF 卡的 h264 目录下
                    // 目录结构:/sdcard/h264/yyyyMMdd/,h264 根目录应该是父目录的父目录
                    File currentDir = new File(outputFileDirectory);
                    File parentDir = currentDir.getParentFile();
                    if (parentDir != null && "h264".equals(parentDir.getName())) {
                        // 当前目录在 h264 目录下,获取 h264 根目录
                        String h264RootDir = parentDir.getAbsolutePath();
                        Timber.d("Checking and cleaning up h264 files in: %s", h264RootDir);
                        FileUtil.cleanupH264Files(context, h264RootDir, maxH264TotalSizeGB, 1);
                    }
                } catch (Exception e) {
                    Timber.e(e, "Error during h264 files cleanup check");
                    // 清理失败不影响文件创建,继续执行
                }
            }
            // 生成新文件名
            long timeFile = System.currentTimeMillis() / 1000 * 1000;
            currentFileStartTime = timeFile;
app/src/main/java/com/anyun/h264/util/FileUtil.java
@@ -2,10 +2,22 @@
import android.content.Context;
import android.os.storage.StorageManager;
import android.os.StatFs;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import timber.log.Timber;
public class FileUtil {
    //获取插入的TFCard目录路径
@@ -44,4 +56,194 @@
        }
    }
    /**
     * 清理 TF 卡上的 h264 文件
     * 当 h264 文件总大小超过指定阈值或 TF 卡剩余空间小于指定值时,删除日期最早的文件夹
     *
     * @param context Context 对象,用于获取 TF 卡路径和剩余空间
     * @param h264RootDir h264 根目录路径,例如 "/sdcard/h264"
     * @param maxTotalSizeGB 最大总大小(GB),默认 5GB
     * @param minFreeSpaceGB 最小剩余空间(GB),默认 1GB
     */
    public static void cleanupH264Files(Context context, String h264RootDir, long maxTotalSizeGB, long minFreeSpaceGB) {
        if (context == null || h264RootDir == null || h264RootDir.trim().isEmpty()) {
            Timber.w("Context or h264 root directory is null, skip cleanup");
            return;
        }
        File h264Root = new File(h264RootDir);
        if (!h264Root.exists() || !h264Root.isDirectory()) {
            Timber.d("H264 root directory does not exist: %s, skip cleanup", h264RootDir);
            return;
        }
        try {
            // 获取 TF 卡路径
            String tfCardPath = getStoragePath(context, true);
            if (tfCardPath == null || tfCardPath.trim().isEmpty()) {
                Timber.w("TF card path not available, skip cleanup");
                return;
            }
            // 获取 TF 卡剩余空间(GB)
            long freeSpaceGB = getFreeSpaceGB(tfCardPath);
            Timber.d("TF card free space: %d GB", freeSpaceGB);
            // 扫描所有日期文件夹
            File[] dateDirs = h264Root.listFiles(File::isDirectory);
            if (dateDirs == null || dateDirs.length == 0) {
                Timber.d("No date directories found in h264 root: %s", h264RootDir);
                return;
            }
            // 按日期排序(最早的在前)
            List<DateDirInfo> dateDirList = new ArrayList<>();
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
            for (File dateDir : dateDirs) {
                String dirName = dateDir.getName();
                // 只处理符合日期格式的文件夹(yyyyMMdd)
                if (dirName.length() == 8 && dirName.matches("\\d{8}")) {
                    try {
                        Date date = dateFormat.parse(dirName);
                        long totalSize = calculateH264FilesSize(dateDir);
                        dateDirList.add(new DateDirInfo(dateDir, date, totalSize));
                    } catch (ParseException e) {
                        Timber.w("Invalid date directory name: %s", dirName);
                    }
                }
            }
            // 按日期排序(最早的在前)
            Collections.sort(dateDirList, new Comparator<DateDirInfo>() {
                @Override
                public int compare(DateDirInfo o1, DateDirInfo o2) {
                    return o1.date.compareTo(o2.date);
                }
            });
            // 计算所有 h264 文件的总大小(GB)
            long totalSizeGB = 0;
            for (DateDirInfo info : dateDirList) {
                totalSizeGB += info.totalSize / (1024L * 1024L * 1024L);
            }
            Timber.d("Total h264 files size: %d GB, Max allowed: %d GB", totalSizeGB, maxTotalSizeGB);
            Timber.d("TF card free space: %d GB, Min required: %d GB", freeSpaceGB, minFreeSpaceGB);
            // 检查是否需要清理
            boolean needCleanup = (totalSizeGB > maxTotalSizeGB) || (freeSpaceGB < minFreeSpaceGB);
            if (!needCleanup) {
                Timber.d("No cleanup needed");
                return;
            }
            // 删除最早的日期文件夹,直到满足条件
            int deletedCount = 0;
            while (!dateDirList.isEmpty() && ((totalSizeGB > maxTotalSizeGB) || (freeSpaceGB < minFreeSpaceGB))) {
                DateDirInfo oldestDir = dateDirList.remove(0);
                Timber.d("Deleting oldest date directory: %s (size: %d GB, date: %s)",
                        oldestDir.dir.getName(), oldestDir.totalSize / (1024L * 1024L * 1024L),
                        dateFormat.format(oldestDir.date));
                // 删除文件夹及其所有内容
                if (deleteDirectory(oldestDir.dir)) {
                    deletedCount++;
                    totalSizeGB -= oldestDir.totalSize / (1024L * 1024L * 1024L);
                    // 重新获取剩余空间
                    freeSpaceGB = getFreeSpaceGB(tfCardPath);
                    Timber.d("After deletion - Total size: %d GB, Free space: %d GB",
                            totalSizeGB, freeSpaceGB);
                } else {
                    Timber.e("Failed to delete directory: %s", oldestDir.dir.getAbsolutePath());
                    break; // 删除失败,停止清理
                }
            }
            Timber.i("Cleanup completed. Deleted %d date directories. Final total size: %d GB, Free space: %d GB",
                    deletedCount, totalSizeGB, freeSpaceGB);
        } catch (Exception e) {
            Timber.e(e, "Error during h264 files cleanup");
        }
    }
    /**
     * 清理 TF 卡上的 h264 文件(使用默认参数:最大5GB,最小剩余空间1GB)
     */
    public static void cleanupH264Files(Context context, String h264RootDir) {
        cleanupH264Files(context, h264RootDir, 5, 1);
    }
    /**
     * 计算目录下所有 .h264 文件的总大小(字节)
     */
    private static long calculateH264FilesSize(File dir) {
        long totalSize = 0;
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isFile() && file.getName().toLowerCase().endsWith(".h264")) {
                    totalSize += file.length();
                }
            }
        }
        return totalSize;
    }
    /**
     * 获取指定路径的剩余空间(GB)
     */
    private static long getFreeSpaceGB(String path) {
        try {
            StatFs statFs = new StatFs(path);
            long blockSize = statFs.getBlockSizeLong();
            long availableBlocks = statFs.getAvailableBlocksLong();
            long freeBytes = availableBlocks * blockSize;
            return freeBytes / (1024L * 1024L * 1024L); // 转换为 GB
        } catch (Exception e) {
            Timber.e(e, "Error getting free space for path: %s", path);
            return 0;
        }
    }
    /**
     * 递归删除目录及其所有内容
     */
    private static boolean deleteDirectory(File dir) {
        if (dir == null || !dir.exists()) {
            return true;
        }
        if (dir.isDirectory()) {
            File[] children = dir.listFiles();
            if (children != null) {
                for (File child : children) {
                    if (!deleteDirectory(child)) {
                        return false;
                    }
                }
            }
        }
        return dir.delete();
    }
    /**
     * 日期文件夹信息
     */
    private static class DateDirInfo {
        File dir;
        Date date;
        long totalSize; // 字节
        DateDirInfo(File dir, Date date, long totalSize) {
            this.dir = dir;
            this.date = date;
            this.totalSize = totalSize;
        }
    }
}