package com.anyun.h264; import android.app.Service; import android.content.Intent; 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; /** * H264编码服务2(第二个摄像头,运行在独立进程) * 提供AIDL接口供客户端调用,用于控制第二个USB摄像头的H264编码 */ public class H264EncodeService2 extends Service { private static final String TAG = "H264EncodeService2"; private H264Encoder h264Encoder; private H264FileTransmitter h264FileTransmitter; // H264文件传输器 private String outputFileDirectory; // H264文件输出目录 private WatermarkInfo currentWatermarkInfo; // 当前水印信息 // 默认编码参数 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 // 第二个摄像头固定使用cameraId=2 private static final int[] CAMERA2_ID_RANGE = {2, 3}; // AIDL接口实现 private final IH264EncodeService.Stub binder = new IH264EncodeService.Stub() { @Override public int controlEncode(int action, String jsonConfig) throws RemoteException { return H264EncodeService2.this.controlEncode(action, jsonConfig); } @Override public List getResourceList(String startTime, String endTime) throws RemoteException { return H264EncodeService2.this.getResourceList(startTime, endTime); } @Override public void setWatermarkInfo(String watermarkInfo) throws RemoteException { H264EncodeService2.this.setWatermarkInfo(watermarkInfo); } }; @Override public void onCreate() { super.onCreate(); Timber.d("H264EncodeService2 created (process 2 for camera 2)"); // 初始化输出文件目录(使用应用外部存储目录) outputFileDirectory = getExternalFilesDir(null).getAbsolutePath(); Timber.d("Output file directory: %s", outputFileDirectory); } @Override public IBinder onBind(Intent intent) { Timber.d("Service2 bound"); return binder; } @Override public boolean onUnbind(Intent intent) { Timber.d("Service2 unbound"); // 不自动停止编码器,让它在服务中保持运行 return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); Timber.d("Service2 destroyed"); // 停止并释放编码器和文件传输器 stopEncoder(); stopFileTransmitter(); } /** * 编码配置类 */ private static class EncodeConfig { String ip; int port; int width; int height; int framerate; String simPhone; // 从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; 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); 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编码和文件传输(第二个摄像头) */ private synchronized int controlEncode(int action, String jsonConfig) { Timber.d("controlEncode (camera2) called with action: %d, jsonConfig: %s", action, jsonConfig); try { 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编码并停止写入文件 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编码并停止网络推送 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("客户端请求停止视频文件上传 (camera2)"); return stopFileTransmitter(); default: Timber.e("Unknown action: %d", action); return 1; // 失败 } } catch (Exception e) { Timber.e(e, "Error in controlEncode (camera2)"); return 1; // 失败 } } /** * 启动文件编码模式(只写入文件,不进行网络推送) */ private int startFileEncode(EncodeConfig config) { Timber.d("Starting file encode mode (camera2)"); // 如果编码器已经在运行,先停止 if (h264Encoder != null) { Timber.w("Encoder is already running (camera2), 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=2) int[] resolution = {width, height}; if (h264Encoder.initialize(CAMERA2_ID_RANGE, null, resolution, false)) { // 应用已保存的水印信息(如果有) if (currentWatermarkInfo != null) { h264Encoder.setWatermarkInfo(currentWatermarkInfo); Timber.d("Applied saved watermark info to encoder (camera2)"); } h264Encoder.start(); Timber.d("File encode started successfully (camera2), output directory: %s, resolution: %dx%d, framerate: %d", outputFileDirectory, width, height, framerate); return 0; // 成功 } else { Timber.e("Failed to initialize encoder (camera2)"); h264Encoder = null; return 1; // 失败 } } catch (Exception e) { Timber.e(e, "Failed to start file encode (camera2)"); h264Encoder = null; return 1; // 失败 } } /** * 启动网络推送模式(只进行网络推送,不写入文件) */ private int startNetworkEncode(EncodeConfig config) { Timber.d("Starting network encode mode (camera2)"); // 如果编码器已经在运行,先停止 if (h264Encoder != null) { Timber.w("Encoder is already running (camera2), 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 (camera2)"); 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)2); // 第二个摄像头使用channelId=2 // 初始化并启动(使用第二个摄像头,cameraId=2) int[] resolution = {width, height}; if (h264Encoder.initialize(CAMERA2_ID_RANGE, null, resolution, false)) { // 应用已保存的水印信息(如果有) if (currentWatermarkInfo != null) { h264Encoder.setWatermarkInfo(currentWatermarkInfo); Timber.d("Applied saved watermark info to encoder (camera2)"); } h264Encoder.start(); Timber.d("Network encode started successfully (camera2), server: %s:%d, resolution: %dx%d, framerate: %d", config.ip, config.port, width, height, framerate); return 0; // 成功 } else { Timber.e("Failed to initialize encoder (camera2)"); h264Encoder = null; return 1; // 失败 } } catch (Exception e) { Timber.e(e, "Failed to start network encode (camera2)"); h264Encoder = null; return 1; // 失败 } } /** * 停止编码器 */ private int stopEncoder() { Timber.d("Stopping encoder (camera2)"); if (h264Encoder != null) { try { h264Encoder.stop(); h264Encoder.release(); h264Encoder = null; Timber.d("Encoder stopped successfully (camera2)"); return 0; // 成功 } catch (Exception e) { Timber.e(e, "Error stopping encoder (camera2)"); h264Encoder = null; return 1; // 失败 } } else { Timber.w("Encoder is not running (camera2)"); return 0; // 成功(没有运行的编码器,视为成功) } } /** * 启动文件传输模式(从H264文件读取并网络推送) */ private int startFileTransmit(FileTransmitConfig config) { Timber.d("Starting file transmit mode (camera2)"); // 如果文件传输器已经在运行,先停止 if (h264FileTransmitter != null) { Timber.w("File transmitter is already running (camera2), 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 (camera2)"); return 1; // 失败 } if (config.filePath == null || config.filePath.trim().isEmpty()) { Timber.e("File transmit requires valid filePath in config (camera2)"); return 1; // 失败 } try { // 检查文件是否存在 File file = new File(config.filePath); if (!file.exists() || !file.isFile()) { Timber.e("File does not exist: %s (camera2)", config.filePath); return 1; // 失败 } // 创建文件传输器 h264FileTransmitter = new H264FileTransmitter(); // 设置服务器地址 h264FileTransmitter.setServerAddress(config.ip, config.port); // 设置协议类型 h264FileTransmitter.setProtocolType(1); //1-tcp // 设置协议参数(SIM卡号和逻辑通道号,第二个摄像头使用channelId=2) String simPhone = config.simPhone != null && !config.simPhone.trim().isEmpty() ? config.simPhone : "013120122580"; h264FileTransmitter.setProtocolParams(simPhone, (byte)2); // 设置帧率(用于计算时间戳间隔) 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 (camera2): frame %d%s", currentFrame, totalFrames > 0 ? " of " + totalFrames : ""); } @Override public void onComplete() { Timber.d("File transmit completed (camera2)"); stopFileTransmitter(); } @Override public void onError(String error) { Timber.e("File transmit error (camera2): %s", error); } }); // 初始化Socket连接 if (!h264FileTransmitter.initialize()) { Timber.e("Failed to initialize file transmitter socket (camera2)"); h264FileTransmitter = null; return 1; // 失败 } // 开始传输文件 h264FileTransmitter.transmitFile(config.filePath); Timber.d("File transmit started successfully (camera2), 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 (camera2)"); if (h264FileTransmitter != null) { try { h264FileTransmitter.stop(); } catch (Exception ex) { Timber.e(ex, "Error stopping file transmitter after failure (camera2)"); } h264FileTransmitter = null; } return 1; // 失败 } } /** * 停止文件传输器 */ private int stopFileTransmitter() { Timber.d("Stopping file transmitter (camera2)"); if (h264FileTransmitter != null) { try { h264FileTransmitter.stop(); h264FileTransmitter = null; Timber.d("File transmitter stopped successfully (camera2)"); return 0; // 成功 } catch (Exception e) { Timber.e(e, "Error stopping file transmitter (camera2)"); h264FileTransmitter = null; return 1; // 失败 } } else { Timber.w("File transmitter is not running (camera2)"); return 0; // 成功(没有运行的文件传输器,视为成功) } } /** * 获取资源列表(根据JT/T 1076-2016表23定义) */ private List getResourceList(String startTime, String endTime) { Timber.d("getResourceList called (camera2), startTime: %s, endTime: %s", startTime, endTime); List 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; } // 遍历文件,查找在时间范围内的文件 for (File file : files) { ResourceInfo resourceInfo = createResourceInfoFromFile(file, startDate, endDate); if (resourceInfo != null) { resourceList.add(resourceInfo); } } 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)"); return resourceList; } } /** * 设置水印信息 */ private void setWatermarkInfo(String watermarkInfoJson) { Timber.d("setWatermarkInfo called (camera2), watermarkInfoJson: %s", watermarkInfoJson); try { if (watermarkInfoJson == null || watermarkInfoJson.trim().isEmpty()) { Timber.w("Watermark info JSON is null or empty, clearing watermark (camera2)"); 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 (camera2): %s", watermarkInfo); // 如果编码器正在运行,立即应用水印 if (h264Encoder != null) { h264Encoder.setWatermarkInfo(watermarkInfo); Timber.d("Watermark applied to encoder (camera2)"); } else { Timber.d("Encoder not running, watermark will be applied when encoder starts (camera2)"); } } catch (JSONException e) { Timber.e(e, "Failed to parse watermark info JSON (camera2): %s", watermarkInfoJson); currentWatermarkInfo = null; } catch (Exception e) { Timber.e(e, "Unexpected error setting watermark info (camera2)"); currentWatermarkInfo = null; } } /** * 从文件创建资源信息(如果文件在时间范围内) */ private ResourceInfo createResourceInfoFromFile(File file, Date startDate, Date endDate) { try { // 从文件名中提取时间戳(格式:h264_camera2_1234567890123.h264) String fileName = file.getName(); Date startTimeFromFileName = null; if (fileName.startsWith("h264_camera2_") && fileName.endsWith(".h264")) { try { // 提取文件名中的时间戳 String timestampStr = fileName.substring(14, fileName.length() - 5); // 去掉 "h264_camera2_" 和 ".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()); // 检查文件时间是否在指定范围内 if (startTimeFromFileName.after(endDate) || endTimeFromFile.before(startDate)) { return null; // 不在时间范围内 } // 创建资源信息对象 ResourceInfo resourceInfo = new ResourceInfo(); // 逻辑通道号(第二个摄像头使用channelId=2) resourceInfo.setLogicalChannelNumber((byte) 2); // 开始时间:从文件名中的时间戳 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时间字符串 */ 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; } } }