| app/src/main/java/com/anyun/h264/H264EncodeService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/java/com/anyun/h264/H264EncodeService2.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| app/src/main/java/com/anyun/h264/H264Encoder.java | ●●●●● 补丁 | 查看 | 原始文档 | 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网络传输