| | |
| | | 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 java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.concurrent.CountDownLatch; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | /** |
| | | * H264编码服务 |
| | |
| | | private H264FileTransmitter h264FileTransmitter; // H264文件传输器 |
| | | private String outputFileDirectory; // H264文件输出目录 |
| | | private WatermarkInfo currentWatermarkInfo; // 当前水印信息 |
| | | |
| | | // 多进程支持:第二个摄像头的服务连接 |
| | | private IH264EncodeService camera2Service; |
| | | private ServiceConnection camera2Connection; |
| | | private boolean isCamera2Bound = false; |
| | | |
| | | // 默认编码参数 |
| | | private static final int DEFAULT_WIDTH = 640; |
| | |
| | | // 停止并释放编码器和文件传输器 |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | int height; |
| | | int framerate; |
| | | String simPhone; |
| | | Integer cameraId; // 摄像头ID(1或2,用于多进程方案) |
| | | |
| | | // 从JSON解析配置 |
| | | static EncodeConfig fromJson(String jsonConfig) throws JSONException { |
| | |
| | | config.ip = null; |
| | | config.port = 0; |
| | | config.simPhone = null; |
| | | config.cameraId = 1; // 默认使用第一个摄像头 |
| | | return config; |
| | | } |
| | | |
| | |
| | | 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; |
| | | } |
| | |
| | | * 4-开始传输H264文件(从文件读取并网络推送), |
| | | * 5-停止H264文件传输 |
| | | * @param jsonConfig JSON格式的配置参数 |
| | | * action 0/2: 包含:ip、port、width、height、framerate、simPhone |
| | | * 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 |
| | | * 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 { |
| | |
| | | } |
| | | |
| | | case 1: // 停止h264编码并停止写入文件 |
| | | // 检查是否指定了cameraId=2 |
| | | if (cameraId != null && cameraId == 2) { |
| | | return controlEncodeInProcess2(action, jsonConfig); |
| | | } |
| | | return stopEncoder(); |
| | | |
| | | case 2: // 开启网络推送h264(不写入文件) |
| | |
| | | } |
| | | |
| | | case 3: // 停止h264编码并停止网络推送 |
| | | // 检查是否指定了cameraId=2 |
| | | if (cameraId != null && cameraId == 2) { |
| | | return controlEncodeInProcess2(action, jsonConfig); |
| | | } |
| | | return stopEncoder(); |
| | | |
| | | case 4: // 开始传输H264文件 |
| | |
| | | |
| | | case 5: // 停止H264文件传输 |
| | | Timber.i("客户端请求停止视频文件上传"); |
| | | // 检查是否指定了cameraId=2 |
| | | if (cameraId != null && cameraId == 2) { |
| | | return controlEncodeInProcess2(action, jsonConfig); |
| | | } |
| | | return stopFileTransmitter(); |
| | | |
| | | default: |
| | |
| | | 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]; |
| | | } |
| | | |
| | | /** |
| | |
| | | h264Encoder = new H264Encoder(); |
| | | |
| | | // 设置编码参数(使用配置中的参数) |
| | | int width = config != null ? config.width : DEFAULT_WIDTH; |
| | | int height = config != null ? config.height : DEFAULT_HEIGHT; |
| | | int framerate = config != null ? config.framerate : DEFAULT_FRAME_RATE; |
| | | 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); |
| | | |
| | | long timeFile = System.currentTimeMillis()/1000*1000;//Date是秒,所以为了跟下发的Date starttime一致,此处除以1000 秒 |
| | |
| | | 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(DEFAULT_CAMERA_ID_RANGE, null, resolution, false)) { |
| | | if (h264Encoder.initialize(cameraIdRange, null, resolution, false)) { |
| | | // 应用已保存的水印信息(如果有) |
| | | if (currentWatermarkInfo != null) { |
| | | h264Encoder.setWatermarkInfo(currentWatermarkInfo); |
| | |
| | | h264Encoder = new H264Encoder(); |
| | | |
| | | // 设置编码参数(使用配置中的参数) |
| | | |
| | | |
| | | // 设置编码参数(使用配置中的参数) |
| | | int width = DEFAULT_WIDTH; |
| | | int height = DEFAULT_HEIGHT; |
| | | int framerate = DEFAULT_FRAME_RATE; |
| | | 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); |
| | | |
| | | long timeFile = System.currentTimeMillis()/1000*1000; |
| | |
| | | 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(DEFAULT_CAMERA_ID_RANGE, null, resolution, false)) { |
| | | if (h264Encoder.initialize(cameraIdRange, null, resolution, false)) { |
| | | // 应用已保存的水印信息(如果有) |
| | | if (currentWatermarkInfo != null) { |
| | | h264Encoder.setWatermarkInfo(currentWatermarkInfo); |