1.在文件传输前引入 resolvedFilePath,若原路径不存在,则从文件名解析时间戳,生成日期目录 yyyyMMdd,到 TF 卡路径 /h264/<日期>/ 下查找同名文件,找到后使用其绝对路径传输。
失败场景均记录日志并返回 1;成功找到会记录 TF 卡命中。
传输与日志都改用 resolvedFilePath。
3个文件已修改
399 ■■■■ 已修改文件
app/src/main/java/com/anyun/h264/H264EncodeService.java 194 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService2.java 203 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264Encoder.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService.java
@@ -20,6 +20,7 @@
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -37,6 +38,7 @@
    private H264FileTransmitter h264FileTransmitter; // H264文件传输器
    private String outputFileDirectory; // H264文件输出目录
    private WatermarkInfo currentWatermarkInfo; // 当前水印信息
    private boolean currentUseTFCard = true; // 当前是否使用TF卡配置
    private static final int H264_FILE_RETENTION_DAYS = 1; // 可根据需求调整为3或5天
    
    // 多进程支持:第二个摄像头的服务连接
@@ -500,7 +502,7 @@
                    Timber.e("Network transmit requires valid ip and port in config");
                    return 1; // 失败
                }
            }
        }
            
            // 创建编码器
            h264Encoder = new H264Encoder();
@@ -511,9 +513,12 @@
            // 设置编码参数(使用配置中的参数)
            int width = config != null && config.width > 0 ? config.width : DEFAULT_WIDTH;
            int height = config != null && config.height > 0 ? config.height : DEFAULT_HEIGHT;
        int framerate = config != null && config.framerate > 0 ? config.framerate : DEFAULT_FRAME_RATE;
            int framerate = config != null && config.framerate > 0 ? config.framerate : DEFAULT_FRAME_RATE;
            h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE);
            // 保存当前useTFCard配置
//            currentUseTFCard = config.useTFCard;
            // 获取输出文件目录(根据useTFCard配置)
            String outputDir = getOutputFileDirectory(config.useTFCard);
            
@@ -612,11 +617,48 @@
        }
        
        try {
            // 检查文件是否存在
            File file = new File(config.filePath);
            // 解析待传输文件路径,若不存在则尝试到TF卡目录按日期查找
            String resolvedFilePath = config.filePath;
            File file = new File(resolvedFilePath);
            if (!file.exists() || !file.isFile()) {
                Timber.e("File does not exist: %s", config.filePath);
                return 1; // 失败
                Timber.w("File does not exist, try TF card lookup: %s", resolvedFilePath);
                String fileName = file.getName();
                String timestampStr = null;
                if (fileName.startsWith("h264_") && fileName.endsWith(".h264")) {
                    timestampStr = fileName.substring(5, fileName.length() - 5);
                }
                if (timestampStr == null || timestampStr.trim().isEmpty()) {
                    Timber.e("Cannot parse timestamp from file name: %s", fileName);
                    return 1; // 失败
                }
                try {
                    long timestamp = Long.parseLong(timestampStr);
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
                    String dateDir = dateFormat.format(new Date(timestamp));
                    String storagePath = FileUtil.getStoragePath(this, true);
                    if (storagePath == null || storagePath.trim().isEmpty()) {
                        Timber.e("TF card storage path not available when searching file");
                        return 1; // 失败
                    }
                    File tfRoot = new File(storagePath, "h264");
                    File candidate = new File(new File(tfRoot, dateDir), fileName);
                    if (candidate.exists() && candidate.isFile()) {
                        resolvedFilePath = candidate.getAbsolutePath();
                        file = candidate;
                        Timber.i("Found file on TF card: %s", resolvedFilePath);
                    } else {
                        Timber.e("File not found on TF card path: %s", candidate.getAbsolutePath());
                        return 1; // 失败
                    }
                } catch (NumberFormatException e) {
                    Timber.e(e, "Failed to parse timestamp from file name: %s", fileName);
                    return 1; // 失败
                }
            }
            
            // 创建文件传输器
@@ -665,10 +707,10 @@
            }
            
            // 开始传输文件
            h264FileTransmitter.transmitFile(config.filePath);
            h264FileTransmitter.transmitFile(resolvedFilePath);
            
            Timber.d("File transmit started successfully, file: %s, server: %s:%d, protocol: %s, framerate: %d", 
                    config.filePath, config.ip, config.port,
                    resolvedFilePath, config.ip, config.port,
                    config.protocolType == JT1076ProtocolHelper.PROTOCOL_TYPE_UDP ? "UDP" : "TCP", framerate);
            return 0; // 成功
            
@@ -716,30 +758,90 @@
     * @return 资源列表
     */
    private List<ResourceInfo> getResourceList(String startTime, String endTime) {
        Timber.d("getResourceList called, startTime: %s, endTime: %s", startTime, endTime);
        Timber.d("getResourceList called, startTime: %s, endTime: %s, useTFCard: %b", startTime, endTime, currentUseTFCard);
        
        List<ResourceInfo> resourceList = new ArrayList<>();
        
        try {
            // 扫描输出目录中的H264文件
            File dir = new File(outputFileDirectory);
            if (!dir.exists() || !dir.isDirectory()) {
                Timber.w("Output directory does not exist: %s", outputFileDirectory);
                return resourceList;
            }
            File[] files = dir.listFiles((dir1, name) -> name.toLowerCase().endsWith(".h264"));
            if (files == null || files.length == 0) {
                Timber.d("No H264 files found in directory");
                return resourceList;
            }
            // 解析时间范围
            Date startDate = parseTime(startTime);
            Date endDate = parseTime(endTime);
            
            if (startDate == null || endDate == null) {
                Timber.e("Invalid time format, startTime: %s, endTime: %s", startTime, endTime);
                return resourceList;
            }
            if (currentUseTFCard) {
                // 使用TF卡:扫描TF卡上的h264文件夹,根据日期范围过滤
                String storagePath = FileUtil.getStoragePath(this, true);
                if (storagePath == null || storagePath.trim().isEmpty()) {
                    Timber.w("TF card storage path not available, fallback to app directory");
                    // 回退到应用目录
                    return getResourceListFromDirectory(outputFileDirectory, startDate, endDate);
                }
                File externalStorage = new File(storagePath);
                if (!externalStorage.exists()) {
                    Timber.w("TF card storage directory does not exist: %s, fallback to app directory", storagePath);
                    // 回退到应用目录
                    return getResourceListFromDirectory(outputFileDirectory, startDate, endDate);
                }
                // TF卡上的h264文件夹路径:/sdcard/h264/
                File h264Dir = new File(externalStorage, "h264");
                if (!h264Dir.exists() || !h264Dir.isDirectory()) {
                    Timber.w("TF card h264 directory does not exist: %s", h264Dir.getAbsolutePath());
                    return resourceList;
                }
                // 获取日期范围内的所有日期文件夹
                List<String> dateDirs = getDateDirectoriesInRange(startDate, endDate);
                Timber.d("Found %d date directories in range", dateDirs.size());
                // 扫描每个日期文件夹下的h264文件
                for (String dateDir : dateDirs) {
                    File dateDirFile = new File(h264Dir, dateDir);
                    if (dateDirFile.exists() && dateDirFile.isDirectory()) {
                        List<ResourceInfo> dateResources = getResourceListFromDirectory(
                                dateDirFile.getAbsolutePath(), startDate, endDate);
                        resourceList.addAll(dateResources);
                    }
                }
                Timber.d("Found %d resources in TF card time range", resourceList.size());
                return resourceList;
            } else {
                // 不使用TF卡:扫描应用目录
                return getResourceListFromDirectory(outputFileDirectory, startDate, endDate);
            }
        } catch (Exception e) {
            Timber.e(e, "Error getting resource list");
            return resourceList;
        }
    }
    /**
     * 从指定目录扫描H264文件并创建资源列表
     * @param directoryPath 目录路径
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @return 资源列表
     */
    private List<ResourceInfo> getResourceListFromDirectory(String directoryPath, Date startDate, Date endDate) {
        List<ResourceInfo> resourceList = new ArrayList<>();
        try {
            File dir = new File(directoryPath);
            if (!dir.exists() || !dir.isDirectory()) {
                Timber.w("Directory does not exist: %s", directoryPath);
                return resourceList;
            }
            File[] files = dir.listFiles((dir1, name) -> name.toLowerCase().endsWith(".h264"));
            if (files == null || files.length == 0) {
                Timber.d("No H264 files found in directory: %s", directoryPath);
                return resourceList;
            }
            
@@ -751,16 +853,60 @@
                }
            }
            
            Timber.d("Found %d resources in time range", resourceList.size());
            return resourceList;
            
        } catch (Exception e) {
            Timber.e(e, "Error getting resource list");
            Timber.e(e, "Error getting resource list from directory: %s", directoryPath);
            return resourceList;
        }
    }
    
    /**
     * 获取日期范围内的所有日期文件夹名称列表(格式:yyyyMMdd)
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @return 日期文件夹名称列表
     */
    private List<String> getDateDirectoriesInRange(Date startDate, Date endDate) {
        List<String> dateDirs = new ArrayList<>();
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(startDate);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            Date currentDate = calendar.getTime();
            Date endDateOnly = new Date(endDate.getTime());
            Calendar endCalendar = Calendar.getInstance();
            endCalendar.setTime(endDateOnly);
            endCalendar.set(Calendar.HOUR_OF_DAY, 23);
            endCalendar.set(Calendar.MINUTE, 59);
            endCalendar.set(Calendar.SECOND, 59);
            endCalendar.set(Calendar.MILLISECOND, 999);
            endDateOnly = endCalendar.getTime();
            // 遍历从开始日期到结束日期的所有日期
            while (!currentDate.after(endDateOnly)) {
                String dateDir = dateFormat.format(currentDate);
                dateDirs.add(dateDir);
                // 增加一天
                calendar.add(Calendar.DAY_OF_MONTH, 1);
                currentDate = calendar.getTime();
            }
        } catch (Exception e) {
            Timber.e(e, "Error getting date directories in range");
        }
        return dateDirs;
    }
    /**
     * 设置水印信息
     * @param watermarkInfoJson 水印信息JSON字符串,包含:车牌(plateNumber)、学员(student)、教练(coach)、
     *                          经度(longitude)、纬度(latitude)、驾校(drivingSchool)、车速(speed)
app/src/main/java/com/anyun/h264/H264EncodeService2.java
@@ -17,6 +17,7 @@
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -32,6 +33,7 @@
    private H264FileTransmitter h264FileTransmitter; // H264文件传输器
    private String outputFileDirectory; // H264文件输出目录
    private WatermarkInfo currentWatermarkInfo; // 当前水印信息
    private boolean currentUseTFCard = false; // 当前是否使用TF卡配置
    
    // 默认编码参数
    private static final int DEFAULT_WIDTH = 640;
@@ -289,6 +291,9 @@
            // 设置 Context(用于清理 TF 卡文件)
            h264Encoder.setContext(this);
            
            // 保存当前useTFCard配置
            currentUseTFCard = config != null && config.useTFCard;
            // 设置编码参数(使用配置中的参数)
            // 设置编码参数(使用配置中的参数)
            int width = config != null && config.width > 0 ? config.width : DEFAULT_WIDTH;
@@ -357,6 +362,9 @@
            
            // 设置 Context(用于清理 TF 卡文件)
            h264Encoder.setContext(this);
            // 保存当前useTFCard配置
            currentUseTFCard = config != null && config.useTFCard;
            
            // 设置编码参数(使用配置中的参数)
            int width = config != null && config.width > 0 ? config.width : DEFAULT_WIDTH;
@@ -456,11 +464,49 @@
        }
        
        try {
            // 检查文件是否存在
            File file = new File(config.filePath);
            // 解析待传输文件路径,若不存在则尝试到TF卡目录按日期查找
            String resolvedFilePath = config.filePath;
            File file = new File(resolvedFilePath);
            if (!file.exists() || !file.isFile()) {
                Timber.e("File does not exist: %s (camera2)", config.filePath);
                return 1; // 失败
                Timber.w("File does not exist, try TF card lookup (camera2): %s", resolvedFilePath);
                String fileName = file.getName();
                String timestampStr = null;
                // camera2的文件名格式:h264_camera2_1234567890123.h264
                if (fileName.startsWith("h264_camera2_") && fileName.endsWith(".h264")) {
                    timestampStr = fileName.substring(14, fileName.length() - 5); // 去掉 "h264_camera2_" 和 ".h264"
                }
                if (timestampStr == null || timestampStr.trim().isEmpty()) {
                    Timber.e("Cannot parse timestamp from file name (camera2): %s", fileName);
                    return 1; // 失败
                }
                try {
                    long timestamp = Long.parseLong(timestampStr);
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
                    String dateDir = dateFormat.format(new Date(timestamp));
                    String storagePath = FileUtil.getStoragePath(this, true);
                    if (storagePath == null || storagePath.trim().isEmpty()) {
                        Timber.e("TF card storage path not available when searching file (camera2)");
                        return 1; // 失败
                    }
                    File tfRoot = new File(storagePath, "h264");
                    File candidate = new File(new File(tfRoot, dateDir), fileName);
                    if (candidate.exists() && candidate.isFile()) {
                        resolvedFilePath = candidate.getAbsolutePath();
                        file = candidate;
                        Timber.i("Found file on TF card (camera2): %s", resolvedFilePath);
                    } else {
                        Timber.e("File not found on TF card path (camera2): %s", candidate.getAbsolutePath());
                        return 1; // 失败
                    }
                } catch (NumberFormatException e) {
                    Timber.e(e, "Failed to parse timestamp from file name (camera2): %s", fileName);
                    return 1; // 失败
                }
            }
            
            // 创建文件传输器
@@ -509,10 +555,10 @@
            }
            
            // 开始传输文件
            h264FileTransmitter.transmitFile(config.filePath);
            h264FileTransmitter.transmitFile(resolvedFilePath);
            
            Timber.d("File transmit started successfully (camera2), file: %s, server: %s:%d, protocol: %s, framerate: %d", 
                    config.filePath, config.ip, config.port,
                    resolvedFilePath, config.ip, config.port,
                    config.protocolType == JT1076ProtocolHelper.PROTOCOL_TYPE_UDP ? "UDP" : "TCP", framerate);
            return 0; // 成功
            
@@ -557,31 +603,98 @@
     * 获取资源列表(根据JT/T 1076-2016表23定义)
     */
    private List<ResourceInfo> getResourceList(String startTime, String endTime) {
        Timber.d("getResourceList called (camera2), startTime: %s, endTime: %s", startTime, endTime);
        Timber.d("getResourceList called (camera2), startTime: %s, endTime: %s, useTFCard: %b", startTime, endTime, currentUseTFCard);
        
        List<ResourceInfo> resourceList = new ArrayList<>();
        
        try {
            // 扫描输出目录中的H264文件(只查找camera2的文件)
            File dir = new File(outputFileDirectory);
            if (!dir.exists() || !dir.isDirectory()) {
                Timber.w("Output directory does not exist: %s", outputFileDirectory);
                return resourceList;
            }
            File[] files = dir.listFiles((dir1, name) ->
                name.toLowerCase().endsWith(".h264") && name.contains("camera2"));
            if (files == null || files.length == 0) {
                Timber.d("No H264 files found for camera2 in directory");
                return resourceList;
            }
            // 解析时间范围
            Date startDate = parseTime(startTime);
            Date endDate = parseTime(endTime);
            
            if (startDate == null || endDate == null) {
                Timber.e("Invalid time format, startTime: %s, endTime: %s", startTime, endTime);
                return resourceList;
            }
            if (currentUseTFCard) {
                // 使用TF卡:扫描TF卡上的h264文件夹,根据日期范围过滤
                String storagePath = FileUtil.getStoragePath(this, true);
                if (storagePath == null || storagePath.trim().isEmpty()) {
                    Timber.w("TF card storage path not available, fallback to app directory");
                    // 回退到应用目录
                    return getResourceListFromDirectory(outputFileDirectory, startDate, endDate, true);
                }
                File externalStorage = new File(storagePath);
                if (!externalStorage.exists()) {
                    Timber.w("TF card storage directory does not exist: %s, fallback to app directory", storagePath);
                    // 回退到应用目录
                    return getResourceListFromDirectory(outputFileDirectory, startDate, endDate, true);
                }
                // TF卡上的h264文件夹路径:/sdcard/h264/
                File h264Dir = new File(externalStorage, "h264");
                if (!h264Dir.exists() || !h264Dir.isDirectory()) {
                    Timber.w("TF card h264 directory does not exist: %s", h264Dir.getAbsolutePath());
                    return resourceList;
                }
                // 获取日期范围内的所有日期文件夹
                List<String> dateDirs = getDateDirectoriesInRange(startDate, endDate);
                Timber.d("Found %d date directories in range", dateDirs.size());
                // 扫描每个日期文件夹下的h264文件(只查找camera2的文件)
                for (String dateDir : dateDirs) {
                    File dateDirFile = new File(h264Dir, dateDir);
                    if (dateDirFile.exists() && dateDirFile.isDirectory()) {
                        List<ResourceInfo> dateResources = getResourceListFromDirectory(
                                dateDirFile.getAbsolutePath(), startDate, endDate, true);
                        resourceList.addAll(dateResources);
                    }
                }
                Timber.d("Found %d resources for camera2 in TF card time range", resourceList.size());
                return resourceList;
            } else {
                // 不使用TF卡:扫描应用目录(只查找camera2的文件)
                return getResourceListFromDirectory(outputFileDirectory, startDate, endDate, true);
            }
        } catch (Exception e) {
            Timber.e(e, "Error getting resource list (camera2)");
            return resourceList;
        }
    }
    /**
     * 从指定目录扫描H264文件并创建资源列表
     * @param directoryPath 目录路径
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @param camera2Only 是否只查找camera2的文件
     * @return 资源列表
     */
    private List<ResourceInfo> getResourceListFromDirectory(String directoryPath, Date startDate, Date endDate, boolean camera2Only) {
        List<ResourceInfo> resourceList = new ArrayList<>();
        try {
            File dir = new File(directoryPath);
            if (!dir.exists() || !dir.isDirectory()) {
                Timber.w("Directory does not exist: %s", directoryPath);
                return resourceList;
            }
            File[] files = dir.listFiles((dir1, name) -> {
                boolean isH264 = name.toLowerCase().endsWith(".h264");
                if (camera2Only) {
                    return isH264 && name.contains("camera2");
                } else {
                    return isH264;
                }
            });
            if (files == null || files.length == 0) {
                Timber.d("No H264 files found in directory: %s", directoryPath);
                return resourceList;
            }
            
@@ -593,16 +706,60 @@
                }
            }
            
            Timber.d("Found %d resources for camera2 in time range", resourceList.size());
            return resourceList;
            
        } catch (Exception e) {
            Timber.e(e, "Error getting resource list (camera2)");
            Timber.e(e, "Error getting resource list from directory: %s", directoryPath);
            return resourceList;
        }
    }
    
    /**
     * 获取日期范围内的所有日期文件夹名称列表(格式:yyyyMMdd)
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @return 日期文件夹名称列表
     */
    private List<String> getDateDirectoriesInRange(Date startDate, Date endDate) {
        List<String> dateDirs = new ArrayList<>();
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(startDate);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            Date currentDate = calendar.getTime();
            Date endDateOnly = new Date(endDate.getTime());
            Calendar endCalendar = Calendar.getInstance();
            endCalendar.setTime(endDateOnly);
            endCalendar.set(Calendar.HOUR_OF_DAY, 23);
            endCalendar.set(Calendar.MINUTE, 59);
            endCalendar.set(Calendar.SECOND, 59);
            endCalendar.set(Calendar.MILLISECOND, 999);
            endDateOnly = endCalendar.getTime();
            // 遍历从开始日期到结束日期的所有日期
            while (!currentDate.after(endDateOnly)) {
                String dateDir = dateFormat.format(currentDate);
                dateDirs.add(dateDir);
                // 增加一天
                calendar.add(Calendar.DAY_OF_MONTH, 1);
                currentDate = calendar.getTime();
            }
        } catch (Exception e) {
            Timber.e(e, "Error getting date directories in range");
        }
        return dateDirs;
    }
    /**
     * 设置水印信息
     */
    private void setWatermarkInfo(String watermarkInfoJson) {
app/src/main/java/com/anyun/h264/H264Encoder.java
@@ -87,7 +87,7 @@
    
    // Context 和清理配置
    private Context context; // Context 对象,用于清理 TF 卡文件
    private long maxH264TotalSizeGB = 5; // 最大 H264 文件总大小(GB),默认 5GB
    private long maxH264TotalSizeGB = 100; // 最大 H264 文件总大小(GB),默认 100GB
    // 网络传输控制
    private boolean enableNetworkTransmission = true; // 是否启用TCP/UDP网络传输