package com.anyun.h264; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import timber.log.Timber; import com.anyun.h264.model.ResourceInfo; import com.anyun.h264.model.WatermarkInfo; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * H264编码服务 * 提供AIDL接口供客户端调用,用于控制H264编码和查询资源列表 */ public class H264EncodeService extends Service { private static final String TAG = "H264EncodeService"; private H264Encoder h264Encoder; private H264FileTransmitter h264FileTransmitter; // H264文件传输器 private String outputFileDirectory; // H264文件输出目录 private WatermarkInfo currentWatermarkInfo; // 当前水印信息 private static final int H264_FILE_RETENTION_DAYS = 5; // 可根据需求调整为3或5天 // 多进程支持:第二个摄像头的服务连接 private IH264EncodeService camera2Service; private ServiceConnection camera2Connection; private boolean isCamera2Bound = false; // 默认编码参数 private static final int DEFAULT_WIDTH = 640; private static final int DEFAULT_HEIGHT = 480; private static final int DEFAULT_FRAME_RATE = 25; private static final int DEFAULT_BITRATE = 2000000; // 2Mbps // 默认摄像头参数 private static final int[] DEFAULT_CAMERA_ID_RANGE = {1, 2}; private static final int[] DEFAULT_RESOLUTION = {640, 480}; // AIDL接口实现 private final IH264EncodeService.Stub binder = new IH264EncodeService.Stub() { @Override public int controlEncode(int action, String jsonConfig) throws RemoteException { return H264EncodeService.this.controlEncode(action, jsonConfig); } @Override public List getResourceList(String startTime, String endTime) throws RemoteException { return H264EncodeService.this.getResourceList(startTime, endTime); } @Override public void setWatermarkInfo(String watermarkInfo) throws RemoteException { H264EncodeService.this.setWatermarkInfo(watermarkInfo); } }; @Override public void onCreate() { super.onCreate(); Timber.d("H264EncodeService created"); // 初始化输出文件目录(使用应用外部存储目录) outputFileDirectory = getExternalFilesDir(null).getAbsolutePath(); Timber.d("Output file directory: %s", outputFileDirectory); // 清理过期的H264文件 cleanupExpiredH264Files(H264_FILE_RETENTION_DAYS); } @Override public IBinder onBind(Intent intent) { Timber.d("Service bound"); return binder; } @Override public boolean onUnbind(Intent intent) { Timber.d("Service unbound"); // 不自动停止编码器,让它在服务中保持运行 return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); Timber.d("Service destroyed"); // 停止并释放编码器和文件传输器 stopEncoder(); stopFileTransmitter(); // 解绑第二个进程的服务 if (isCamera2Bound && camera2Connection != null) { try { unbindService(camera2Connection); } catch (Exception e) { Timber.e(e, "Error unbinding camera2 service"); } isCamera2Bound = false; camera2Service = null; camera2Connection = null; } } /** * 编码配置类 */ private static class EncodeConfig { String ip; int port; int width; int height; int framerate; String simPhone; Integer cameraId; // 摄像头ID(1或2,用于多进程方案) // 从JSON解析配置 static EncodeConfig fromJson(String jsonConfig) throws JSONException { EncodeConfig config = new EncodeConfig(); if (jsonConfig == null || jsonConfig.trim().isEmpty()) { // 使用默认值 config.width = DEFAULT_WIDTH; config.height = DEFAULT_HEIGHT; config.framerate = DEFAULT_FRAME_RATE; config.ip = null; config.port = 0; config.simPhone = null; config.cameraId = 1; // 默认使用第一个摄像头 return config; } JSONObject json = new JSONObject(jsonConfig); config.width = json.optInt("width", DEFAULT_WIDTH); config.height = json.optInt("height", DEFAULT_HEIGHT); config.framerate = json.optInt("framerate", DEFAULT_FRAME_RATE); config.ip = json.optString("ip", null); config.port = json.optInt("port", 0); config.simPhone = json.optString("simPhone", null); // 解析cameraId(如果未指定,默认为1) if (json.has("cameraId")) { config.cameraId = json.optInt("cameraId", 1); } else { config.cameraId = 1; // 默认使用第一个摄像头 } return config; } } /** * 文件传输配置类 */ private static class FileTransmitConfig { String ip; int port; int framerate; String simPhone; String filePath; // H264文件路径 int protocolType; // 协议类型:1-UDP,2-TCP // 从JSON解析配置 static FileTransmitConfig fromJson(String jsonConfig) throws JSONException { FileTransmitConfig config = new FileTransmitConfig(); if (jsonConfig == null || jsonConfig.trim().isEmpty()) { throw new JSONException("File transmit config cannot be empty"); } JSONObject json = new JSONObject(jsonConfig); config.ip = json.optString("ip", null); config.port = json.optInt("port", 0); config.framerate = json.optInt("framerate", DEFAULT_FRAME_RATE); config.simPhone = json.optString("simPhone", "013120122580"); config.filePath = json.optString("filePath", null); // 协议类型:默认TCP(2),1-UDP,2-TCP config.protocolType = json.optInt("protocolType", JT1076ProtocolHelper.PROTOCOL_TYPE_TCP); return config; } } /** * 控制H264编码和文件传输 * @param action 操作类型: * 0-开启h264文件写入, * 1-停止h264编码并停止写入文件, * 2-开启网络推送h264(不写入文件), * 3-停止h264编码并停止网络推送, * 4-开始传输H264文件(从文件读取并网络推送), * 5-停止H264文件传输 * @param jsonConfig JSON格式的配置参数 * action 0/2: 包含:ip、port、width、height、framerate、simPhone、cameraId(可选,1或2,默认1) * action 4: 包含:ip、port、framerate、simPhone、filePath、protocolType(可选,1-UDP,2-TCP,默认TCP) * action 1/3/5: 此参数可为空或null,或包含cameraId来指定要停止的摄像头 * @return 0-成功,1-失败 */ private synchronized int controlEncode(int action, String jsonConfig) { Timber.d("controlEncode called with action: %d, jsonConfig: %s", action, jsonConfig); try { // 解析cameraId(如果配置中有) Integer cameraId = null; if (jsonConfig != null && !jsonConfig.trim().isEmpty()) { try { JSONObject json = new JSONObject(jsonConfig); if (json.has("cameraId")) { cameraId = json.optInt("cameraId", 1); } } catch (JSONException e) { // 忽略解析错误,继续使用当前进程 } } // 如果指定了cameraId=2,路由到第二个进程 if (cameraId != null && cameraId == 2) { return controlEncodeInProcess2(action, jsonConfig); } // 否则在当前进程(cameraId=1)处理 switch (action) { case 0: // 开启h264文件写入 try { EncodeConfig config0 = EncodeConfig.fromJson(jsonConfig); return startFileEncode(config0); } catch (JSONException e) { Timber.e(e, "Failed to parse JSON config: %s", jsonConfig); return 1; } case 1: // 停止h264编码并停止写入文件 // 检查是否指定了cameraId=2 if (cameraId != null && cameraId == 2) { return controlEncodeInProcess2(action, jsonConfig); } return stopEncoder(); case 2: // 开启网络推送h264(不写入文件) try { EncodeConfig config2 = EncodeConfig.fromJson(jsonConfig); return startNetworkEncode(config2); } catch (JSONException e) { Timber.e(e, "Failed to parse JSON config: %s", jsonConfig); return 1; } case 3: // 停止h264编码并停止网络推送 // 检查是否指定了cameraId=2 if (cameraId != null && cameraId == 2) { return controlEncodeInProcess2(action, jsonConfig); } return stopEncoder(); case 4: // 开始传输H264文件 try { FileTransmitConfig config4 = FileTransmitConfig.fromJson(jsonConfig); return startFileTransmit(config4); } catch (JSONException e) { Timber.e(e, "Failed to parse JSON config: %s", jsonConfig); return 1; } case 5: // 停止H264文件传输 Timber.i("客户端请求停止视频文件上传"); // 检查是否指定了cameraId=2 if (cameraId != null && cameraId == 2) { return controlEncodeInProcess2(action, jsonConfig); } return stopFileTransmitter(); default: Timber.e("Unknown action: %d", action); return 1; // 失败 } } catch (Exception e) { Timber.e(e, "Error in controlEncode"); return 1; // 失败 } } /** * 在第二个进程(camera2)中执行编码控制 */ private int controlEncodeInProcess2(int action, String jsonConfig) { Timber.d("Routing to process 2 (camera2) for action: %d", action); try { // 确保第二个进程的服务已绑定 if (!ensureCamera2ServiceBound()) { Timber.e("Failed to bind camera2 service"); return 1; } // 调用第二个进程的服务 if (camera2Service != null) { return camera2Service.controlEncode(action, jsonConfig); } else { Timber.e("Camera2 service is null"); return 1; } } catch (RemoteException e) { Timber.e(e, "Error calling camera2 service"); return 1; } } /** * 确保第二个进程的服务已绑定 */ private synchronized boolean ensureCamera2ServiceBound() { if (isCamera2Bound && camera2Service != null) { return true; } Timber.d("Binding to camera2 service..."); final CountDownLatch latch = new CountDownLatch(1); final boolean[] success = {false}; camera2Connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Timber.d("Camera2 service connected"); camera2Service = IH264EncodeService.Stub.asInterface(service); isCamera2Bound = true; success[0] = true; latch.countDown(); } @Override public void onServiceDisconnected(ComponentName name) { Timber.w("Camera2 service disconnected"); camera2Service = null; isCamera2Bound = false; } }; Intent intent = new Intent(this, H264EncodeService2.class); boolean bound = bindService(intent, camera2Connection, Context.BIND_AUTO_CREATE); if (!bound) { Timber.e("Failed to bind camera2 service"); return false; } // 等待服务连接(最多5秒) try { if (!latch.await(5, TimeUnit.SECONDS)) { Timber.e("Timeout waiting for camera2 service connection"); return false; } } catch (InterruptedException e) { Timber.e(e, "Interrupted while waiting for camera2 service"); return false; } return success[0]; } /** * 启动文件编码模式(只写入文件,不进行网络推送) */ private int startFileEncode(EncodeConfig config) { Timber.d("Starting file encode mode"); // 如果编码器已经在运行,先停止 if (h264Encoder != null) { Timber.w("Encoder is already running, stopping it first"); stopEncoder(); } try { // 创建编码器 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; h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE); // 设置输出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件) // 使用一个临时文件名来设置目录,H264Encoder会在初始化时创建第一个文件 File tempFile = new File(outputFileDirectory, "temp.h264"); h264Encoder.setOutputFile(tempFile.getAbsolutePath()); h264Encoder.setEnableFileOutput(true); // 启用文件输出 // 禁用网络传输 h264Encoder.setEnableNetworkTransmission(false); // 初始化并启动(使用配置中的分辨率) // 根据cameraId选择摄像头范围 int[] cameraIdRange = DEFAULT_CAMERA_ID_RANGE; if (config != null && config.cameraId != null) { // 如果指定了cameraId,使用对应的摄像头 cameraIdRange = new int[]{config.cameraId, config.cameraId}; } int[] resolution = {width, height}; if (h264Encoder.initialize(cameraIdRange, null, resolution, false)) { // 应用已保存的水印信息(如果有) if (currentWatermarkInfo != null) { h264Encoder.setWatermarkInfo(currentWatermarkInfo); Timber.d("Applied saved watermark info to encoder"); } h264Encoder.start(); Timber.d("File encode started successfully, output directory: %s, resolution: %dx%d, framerate: %d", outputFileDirectory, width, height, framerate); return 0; // 成功 } else { Timber.e("Failed to initialize encoder"); h264Encoder = null; return 1; // 失败 } } catch (Exception e) { Timber.e(e, "Failed to start file encode"); h264Encoder = null; return 1; // 失败 } } /** * 启动网络推送模式(只进行网络推送,不写入文件) */ private int startNetworkEncode(EncodeConfig config) { Timber.d("Starting network encode mode"); // 如果编码器已经在运行,先停止 if (h264Encoder != null) { Timber.w("Encoder is already running, stopping it first"); stopEncoder(); } // 检查必需的配置参数 if (config == null || config.ip == null || config.ip.trim().isEmpty() || config.port <= 0) { Timber.e("Network encode requires valid ip and port in config"); return 1; // 失败 } try { // 创建编码器 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; h264Encoder.setEncoderParams(width, height, framerate, DEFAULT_BITRATE); // 设置输出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件) // 使用一个临时文件名来设置目录,H264Encoder会在初始化时创建第一个文件 File tempFile = new File(outputFileDirectory, "temp.h264"); h264Encoder.setOutputFile(tempFile.getAbsolutePath()); h264Encoder.setEnableFileOutput(true); // 启用文件输出 // 启用网络传输并设置服务器地址 h264Encoder.setEnableNetworkTransmission(true); h264Encoder.setServerAddress(config.ip, config.port); // 设置协议参数(使用配置中的simPhone,如果未提供则使用默认值) String simPhone = config.simPhone != null && !config.simPhone.trim().isEmpty() ? config.simPhone : "013120122580"; h264Encoder.setProtocolParams(simPhone, (byte)1); // 初始化并启动(使用配置中的分辨率) // 根据cameraId选择摄像头范围 int[] cameraIdRange = DEFAULT_CAMERA_ID_RANGE; if (config != null && config.cameraId != null) { // 如果指定了cameraId,使用对应的摄像头 cameraIdRange = new int[]{config.cameraId, config.cameraId}; } int[] resolution = {width, height}; if (h264Encoder.initialize(cameraIdRange, null, resolution, false)) { // 应用已保存的水印信息(如果有) if (currentWatermarkInfo != null) { h264Encoder.setWatermarkInfo(currentWatermarkInfo); Timber.d("Applied saved watermark info to encoder"); } h264Encoder.start(); Timber.d("Network encode started successfully, server: %s:%d, resolution: %dx%d, framerate: %d", config.ip, config.port, width, height, framerate); return 0; // 成功 } else { Timber.e("Failed to initialize encoder"); h264Encoder = null; return 1; // 失败 } } catch (Exception e) { Timber.e(e, "Failed to start network encode"); h264Encoder = null; return 1; // 失败 } } /** * 停止编码器 */ private int stopEncoder() { Timber.d("Stopping encoder"); if (h264Encoder != null) { try { h264Encoder.stop(); h264Encoder.release(); h264Encoder = null; Timber.d("Encoder stopped successfully"); return 0; // 成功 } catch (Exception e) { Timber.e(e, "Error stopping encoder"); h264Encoder = null; return 1; // 失败 } } else { Timber.w("Encoder is not running"); return 0; // 成功(没有运行的编码器,视为成功) } } /** * 启动文件传输模式(从H264文件读取并网络推送) */ private int startFileTransmit(FileTransmitConfig config) { Timber.d("Starting file transmit mode"); // 如果文件传输器已经在运行,先停止 if (h264FileTransmitter != null) { Timber.w("File transmitter is already running, stopping it first"); stopFileTransmitter(); } // 检查必需的配置参数 if (config == null || config.ip == null || config.ip.trim().isEmpty() || config.port <= 0) { Timber.e("File transmit requires valid ip and port in config"); return 1; // 失败 } if (config.filePath == null || config.filePath.trim().isEmpty()) { Timber.e("File transmit requires valid filePath in config"); return 1; // 失败 } try { // 检查文件是否存在 File file = new File(config.filePath); if (!file.exists() || !file.isFile()) { Timber.e("File does not exist: %s", config.filePath); return 1; // 失败 } // 创建文件传输器 h264FileTransmitter = new H264FileTransmitter(); // 设置服务器地址 h264FileTransmitter.setServerAddress(config.ip, config.port); // 设置协议类型 h264FileTransmitter.setProtocolType(1); //1-tcp // 设置协议参数(SIM卡号和逻辑通道号) String simPhone = config.simPhone != null && !config.simPhone.trim().isEmpty() ? config.simPhone : "013120122580"; h264FileTransmitter.setProtocolParams(simPhone, (byte)1); // 设置帧率(用于计算时间戳间隔) int framerate = config.framerate > 0 ? config.framerate : DEFAULT_FRAME_RATE; h264FileTransmitter.setFrameRate(framerate); // 设置进度回调(可选,用于日志输出) h264FileTransmitter.setOnTransmitProgressCallback(new H264FileTransmitter.OnTransmitProgressCallback() { @Override public void onProgress(int currentFrame, int totalFrames) { Timber.d("File transmit progress: frame %d%s", currentFrame, totalFrames > 0 ? " of " + totalFrames : ""); } @Override public void onComplete() { Timber.d("File transmit completed"); stopFileTransmitter(); } @Override public void onError(String error) { Timber.e("File transmit error: %s", error); } }); // 初始化Socket连接 if (!h264FileTransmitter.initialize()) { Timber.e("Failed to initialize file transmitter socket"); h264FileTransmitter = null; return 1; // 失败 } // 开始传输文件 h264FileTransmitter.transmitFile(config.filePath); Timber.d("File transmit started successfully, file: %s, server: %s:%d, protocol: %s, framerate: %d", config.filePath, config.ip, config.port, config.protocolType == JT1076ProtocolHelper.PROTOCOL_TYPE_UDP ? "UDP" : "TCP", framerate); return 0; // 成功 } catch (Exception e) { Timber.e(e, "Failed to start file transmit"); if (h264FileTransmitter != null) { try { h264FileTransmitter.stop(); } catch (Exception ex) { Timber.e(ex, "Error stopping file transmitter after failure"); } h264FileTransmitter = null; } return 1; // 失败 } } /** * 停止文件传输器 */ private int stopFileTransmitter() { Timber.d("Stopping file transmitter"); if (h264FileTransmitter != null) { try { h264FileTransmitter.stop(); h264FileTransmitter = null; Timber.d("File transmitter stopped successfully"); return 0; // 成功 } catch (Exception e) { Timber.e(e, "Error stopping file transmitter"); h264FileTransmitter = null; return 1; // 失败 } } else { Timber.w("File transmitter is not running"); return 0; // 成功(没有运行的文件传输器,视为成功) } } /** * 获取资源列表(根据JT/T 1076-2016表23定义) * @param startTime 开始时间(格式:YYMMDDHHmmss) * @param endTime 结束时间(格式:YYMMDDHHmmss) * @return 资源列表 */ private List getResourceList(String startTime, String endTime) { Timber.d("getResourceList called, startTime: %s, endTime: %s", startTime, endTime); List 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; } // 遍历文件,查找在时间范围内的文件 for (File file : files) { ResourceInfo resourceInfo = createResourceInfoFromFile(file, startDate, endDate); if (resourceInfo != null) { resourceList.add(resourceInfo); } } Timber.d("Found %d resources in time range", resourceList.size()); return resourceList; } catch (Exception e) { Timber.e(e, "Error getting resource list"); return resourceList; } } /** * 设置水印信息 * @param watermarkInfoJson 水印信息JSON字符串,包含:车牌(plateNumber)、学员(student)、教练(coach)、 * 经度(longitude)、纬度(latitude)、驾校(drivingSchool)、车速(speed) * 示例:{"plateNumber":"京A12345","student":"张三","coach":"李四", * "longitude":116.397128,"latitude":39.916527,"drivingSchool":"XX驾校","speed":60.5} */ private void setWatermarkInfo(String watermarkInfoJson) { Timber.d("setWatermarkInfo called, watermarkInfoJson: %s", watermarkInfoJson); try { if (watermarkInfoJson == null || watermarkInfoJson.trim().isEmpty()) { Timber.w("Watermark info JSON is null or empty, clearing watermark"); currentWatermarkInfo = null; // 如果编码器正在运行,清除水印 if (h264Encoder != null) { h264Encoder.setWatermarkInfo(null); } return; } // 解析JSON JSONObject json = new JSONObject(watermarkInfoJson); WatermarkInfo watermarkInfo = new WatermarkInfo(); // 解析各个字段(使用 optString/optDouble 避免字段不存在时抛出异常) watermarkInfo.setPlateNumber(json.optString("plateNumber", null)); watermarkInfo.setStudent(json.optString("student", null)); watermarkInfo.setCoach(json.optString("coach", null)); // 经度和纬度可能是数字或字符串 if (json.has("longitude")) { Object lonObj = json.get("longitude"); if (lonObj instanceof Number) { watermarkInfo.setLongitude(((Number) lonObj).doubleValue()); } else if (lonObj instanceof String) { try { watermarkInfo.setLongitude(Double.parseDouble((String) lonObj)); } catch (NumberFormatException e) { Timber.w("Invalid longitude format: %s", lonObj); } } } if (json.has("latitude")) { Object latObj = json.get("latitude"); if (latObj instanceof Number) { watermarkInfo.setLatitude(((Number) latObj).doubleValue()); } else if (latObj instanceof String) { try { watermarkInfo.setLatitude(Double.parseDouble((String) latObj)); } catch (NumberFormatException e) { Timber.w("Invalid latitude format: %s", latObj); } } } watermarkInfo.setDrivingSchool(json.optString("drivingSchool", null)); // 车速可能是数字或字符串 if (json.has("speed")) { Object speedObj = json.get("speed"); if (speedObj instanceof Number) { watermarkInfo.setSpeed(((Number) speedObj).doubleValue()); } else if (speedObj instanceof String) { try { watermarkInfo.setSpeed(Double.parseDouble((String) speedObj)); } catch (NumberFormatException e) { Timber.w("Invalid speed format: %s", speedObj); } } } // 保存水印信息 currentWatermarkInfo = watermarkInfo; Timber.i("Watermark info parsed successfully: %s", watermarkInfo); // 如果编码器正在运行,立即应用水印 if (h264Encoder != null) { h264Encoder.setWatermarkInfo(watermarkInfo); Timber.d("Watermark applied to encoder"); } else { Timber.d("Encoder not running, watermark will be applied when encoder starts"); } } catch (JSONException e) { Timber.e(e, "Failed to parse watermark info JSON: %s", watermarkInfoJson); currentWatermarkInfo = null; } catch (Exception e) { Timber.e(e, "Unexpected error setting watermark info"); currentWatermarkInfo = null; } } /** * 删除超过保留期的H264文件 */ private void cleanupExpiredH264Files(int retentionDays) { if (outputFileDirectory == null) { Timber.w("cleanupExpiredH264Files: outputFileDirectory is null"); return; } File dir = new File(outputFileDirectory); if (!dir.exists() || !dir.isDirectory()) { Timber.w("cleanupExpiredH264Files: directory invalid -> %s", outputFileDirectory); return; } long retentionMillis = TimeUnit.DAYS.toMillis(Math.max(1, retentionDays)); long cutoffTime = System.currentTimeMillis() - retentionMillis; File[] files = dir.listFiles((d, name) -> name.toLowerCase(Locale.CHINA).endsWith(".h264")); if (files == null || files.length == 0) { return; } for (File file : files) { if (file.lastModified() < cutoffTime) { boolean deleted = file.delete(); if (deleted) { Timber.i("Deleted expired H264 file: %s", file.getAbsolutePath()); } else { Timber.w("Failed to delete expired H264 file: %s", file.getAbsolutePath()); } } } } /** * 从文件创建资源信息(如果文件在时间范围内) */ private ResourceInfo createResourceInfoFromFile(File file, Date startDate, Date endDate) { try { // 从文件名中提取时间戳(格式:h264_1234567890123.h264) String fileName = file.getName(); Date startTimeFromFileName = null; if (fileName.startsWith("h264_") && fileName.endsWith(".h264")) { try { // 提取文件名中的时间戳(h264_ 和 .h264 之间的部分) String timestampStr = fileName.substring(5, fileName.length() - 5); // 去掉 "h264_" 和 ".h264" long timestamp = Long.parseLong(timestampStr); startTimeFromFileName = new Date(timestamp); } catch (NumberFormatException e) { Timber.w("Failed to parse timestamp from filename: %s", fileName); } } // 如果无法从文件名解析时间戳,则使用文件修改时间作为开始时间 if (startTimeFromFileName == null) { startTimeFromFileName = new Date(file.lastModified()); } // 结束时间使用文件修改时间 Date endTimeFromFile = new Date(file.lastModified()); // 检查文件时间是否在指定范围内 // 开始时间应该 >= startDate,结束时间应该 <= endDate // 如果文件的开始时间在范围内,或者结束时间在范围内,或者文件时间范围包含查询范围,则包含该文件 if (startTimeFromFileName.after(endDate) || endTimeFromFile.before(startDate)) { return null; // 不在时间范围内 } // 创建资源信息对象 ResourceInfo resourceInfo = new ResourceInfo(); // 逻辑通道号(默认值,实际应从配置获取) resourceInfo.setLogicalChannelNumber((byte) 1); // 开始时间:从文件名中的时间戳 SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss", Locale.CHINA); resourceInfo.setStartTime(bcdFormat.format(startTimeFromFileName)); // 结束时间:使用文件修改时间 resourceInfo.setEndTime(bcdFormat.format(endTimeFromFile)); // 报警标志(默认值,实际应从文件元数据获取) resourceInfo.setAlarmFlag(0L); // 音视频资源类型:2-视频 resourceInfo.setResourceType((byte) 2); // 码流类型:1-主码流 resourceInfo.setStreamType((byte) 1); // 存储器类型:1-主存储器 resourceInfo.setStorageType((byte) 1); // 文件大小 resourceInfo.setFileSize(file.length()); return resourceInfo; } catch (Exception e) { Timber.e(e, "Error creating resource info from file: %s", file.getName()); return null; } } /** * 解析BCD时间字符串 * @param timeStr 时间字符串(格式:YYMMDDHHmmss) * @return Date对象,如果解析失败返回null */ private Date parseTime(String timeStr) { if (timeStr == null || timeStr.length() != 12) { return null; } try { SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss", Locale.CHINA); return format.parse(timeStr); } catch (ParseException e) { Timber.e(e, "Failed to parse time: %s", timeStr); return null; } } }