| | |
| | | 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; |
| | |
| | | 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天 |
| | | |
| | | // 多进程支持:第二个摄像头的服务连接 |
| | |
| | | Timber.e("Network transmit requires valid ip and port in config"); |
| | | return 1; // 失败 |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 创建编码器 |
| | | h264Encoder = new H264Encoder(); |
| | |
| | | // 设置编码参数(使用配置中的参数) |
| | | 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); |
| | | |
| | |
| | | } |
| | | |
| | | 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; // 失败 |
| | | } |
| | | } |
| | | |
| | | // 创建文件传输器 |
| | |
| | | } |
| | | |
| | | // 开始传输文件 |
| | | 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; // 成功 |
| | | |
| | |
| | | * @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; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | 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) |