Dana
2025-12-03 30678653058f62beda7ac3f3ac94bc830b6db85e
app/src/main/java/com/anyun/h264/H264EncodeService.java
@@ -1,7 +1,10 @@
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;
@@ -19,6 +22,8 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
 * H264编码服务
@@ -31,6 +36,11 @@
    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;
@@ -91,6 +101,18 @@
        // 停止并释放编码器和文件传输器
        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;
        }
    }
    
    /**
@@ -103,6 +125,7 @@
        int height;
        int framerate;
        String simPhone;
        Integer cameraId; // 摄像头ID(1或2,用于多进程方案)
        
        // 从JSON解析配置
        static EncodeConfig fromJson(String jsonConfig) throws JSONException {
@@ -115,6 +138,7 @@
                config.ip = null;
                config.port = 0;
                config.simPhone = null;
                config.cameraId = 1; // 默认使用第一个摄像头
                return config;
            }
            
@@ -125,6 +149,13 @@
            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;
        }
@@ -171,15 +202,34 @@
     *               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 {
@@ -191,6 +241,10 @@
                    }
                    
                case 1: // 停止h264编码并停止写入文件
                    // 检查是否指定了cameraId=2
                    if (cameraId != null && cameraId == 2) {
                        return controlEncodeInProcess2(action, jsonConfig);
                    }
                    return stopEncoder();
                    
                case 2: // 开启网络推送h264(不写入文件)
@@ -203,6 +257,10 @@
                    }
                    
                case 3: // 停止h264编码并停止网络推送
                    // 检查是否指定了cameraId=2
                    if (cameraId != null && cameraId == 2) {
                        return controlEncodeInProcess2(action, jsonConfig);
                    }
                    return stopEncoder();
                    
                case 4: // 开始传输H264文件
@@ -216,6 +274,10 @@
                    
                case 5: // 停止H264文件传输
                    Timber.i("客户端请求停止视频文件上传");
                    // 检查是否指定了cameraId=2
                    if (cameraId != null && cameraId == 2) {
                        return controlEncodeInProcess2(action, jsonConfig);
                    }
                    return stopFileTransmitter();
                    
                default:
@@ -226,6 +288,85 @@
            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];
    }
    
    /**
@@ -245,9 +386,9 @@
            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 秒
@@ -264,8 +405,14 @@
            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);
@@ -310,12 +457,9 @@
            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;
@@ -339,8 +483,14 @@
            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);