Dana
2025-12-04 e4b7cbea399e3dcc40011eaa2d2fe25696d7e894
1.可以先见h264 写入文件。再发推流都可以;边录边推
3个文件已修改
207 ■■■■■ 已修改文件
app/src/main/java/com/anyun/h264/H264EncodeService.java 183 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264Encoder.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService.java
@@ -36,7 +36,7 @@
    private H264FileTransmitter h264FileTransmitter; // H264文件传输器
    private String outputFileDirectory; // H264文件输出目录
    private WatermarkInfo currentWatermarkInfo; // 当前水印信息
    private static final int H264_FILE_RETENTION_DAYS = 5; // 可根据需求调整为3或5天
    private static final int H264_FILE_RETENTION_DAYS = 1; // 可根据需求调整为3或5天
    
    // 多进程支持:第二个摄像头的服务连接
    private IH264EncodeService camera2Service;
@@ -141,6 +141,8 @@
                config.width = DEFAULT_WIDTH;
                config.height = DEFAULT_HEIGHT;
                config.framerate = DEFAULT_FRAME_RATE;
                config.enableFileOutput = false;
                config.enableNetworkTransmit = false;
                config.ip = null;
                config.port = 0;
                config.simPhone = null;
@@ -152,6 +154,8 @@
            config.width = json.optInt("width", DEFAULT_WIDTH);
            config.height = json.optInt("height", DEFAULT_HEIGHT);
            config.framerate = json.optInt("framerate", DEFAULT_FRAME_RATE);
            config.enableFileOutput = json.optBoolean("enableFileOutput", false);
            config.enableNetworkTransmit = json.optBoolean("enableNetworkTransmit", false);
            config.ip = json.optString("ip", null);
            config.port = json.optInt("port", 0);
            config.simPhone = json.optString("simPhone", null);
@@ -240,7 +244,9 @@
                case 0: // 开启h264文件写入
                    try {
                        EncodeConfig config0 = EncodeConfig.fromJson(jsonConfig);
                        return startFileEncode(config0);
                        config0.enableFileOutput = true;
                        config0.enableNetworkTransmit = false;
                        return startEncode(config0);
                    } catch (JSONException e) {
                        Timber.e(e, "Failed to parse JSON config: %s", jsonConfig);
                        return 1;
@@ -253,21 +259,35 @@
                    }
                    return stopEncoder();
                    
                case 2: // 开启网络推送h264(不写入文件)
                case 2: // 开启网络推送h264(可同时写入文件)
                    try {
                        EncodeConfig config2 = EncodeConfig.fromJson(jsonConfig);
                        return startNetworkEncode(config2);
                        // 检查必需的配置参数
                        if (config2 == null || config2.ip == null || config2.ip.trim().isEmpty() || config2.port <= 0) {
                            Timber.e("Network encode requires valid ip and port in config");
                            return 1; // 失败
                        }
                        config2.enableNetworkTransmit = true;
                        return startEncode(config2);
                    } catch (JSONException e) {
                        Timber.e(e, "Failed to parse JSON config: %s", jsonConfig);
                        return 1;
                    }
                    
                case 3: // 停止h264编码并停止网络推送
                case 3: // 停止网络推送(保持文件写入)
                    // 检查是否指定了cameraId=2
                    if (cameraId != null && cameraId == 2) {
                        return controlEncodeInProcess2(action, jsonConfig);
                    }
                    return stopEncoder();
                    // 只关闭网络传输,保持文件写入
                    if (h264Encoder != null) {
                        h264Encoder.setEnableNetworkTransmission(false);
                        Timber.d("Network transmission stopped, file output continues");
                        return 0;
                    } else {
                        Timber.w("Encoder is not running");
                        return 0; // 成功(没有运行的编码器,视为成功)
                    }
                    
                case 4: // 开始传输H264文件
                    try {
@@ -376,35 +396,82 @@
    }
    
    /**
     * 启动文件编码模式(只写入文件,不进行网络推送)
     * 启动编码(统一方法,支持文件写入和网络传输的组合)
     * @param config 编码配置
     * @return 0-成功,1-失败
     */
    private int startFileEncode(EncodeConfig config) {
        Timber.d("Starting file encode mode");
    private int startEncode(EncodeConfig config) {
        if (config == null) {
            Timber.e("Encode config cannot be null");
            return 1;
        }
        Timber.d("Starting encode mode, fileOutput: %b, networkTransmit: %b",
                config.enableFileOutput, config.enableNetworkTransmit);
        
        // 如果编码器已经在运行,先停止
        // 如果编码器已经在运行,只更新配置
        if (h264Encoder != null) {
            Timber.w("Encoder is already running, stopping it first");
            stopEncoder();
            Timber.d("Encoder is already running, updating configuration");
            try {
                h264Encoder.setEnableFileOutput(config.enableFileOutput);
                // 如果开启网络传输,需要设置服务器地址和协议参数
                if (config.enableNetworkTransmit) {
                    if (config == null || config.ip == null || config.ip.trim().isEmpty() || config.port <= 0) {
                        Timber.e("Network transmit requires valid ip and port in config");
                        return 1; // 失败
                    }
                    h264Encoder.setServerAddress(config.ip, config.port);
                    // 设置协议参数(使用配置中的simPhone,如果未提供则使用默认值)
                    String simPhone = config.simPhone != null && !config.simPhone.trim().isEmpty()
                            ? config.simPhone : "013120122580";
                    h264Encoder.setProtocolParams(simPhone, (byte)1);
                }
                h264Encoder.setEnableNetworkTransmission(config.enableNetworkTransmit);
                Timber.d("Encoder configuration updated successfully");
                return 0; // 成功
            } catch (Exception e) {
                Timber.e(e, "Error updating encoder configuration");
                return 1; // 失败
            }
        }
        
        // 编码器未运行,需要初始化并启动
        try {
            // 如果开启网络传输,检查必需的配置参数
        if (config.enableNetworkTransmit) {
                if (config == null || config.ip == null || config.ip.trim().isEmpty() || config.port <= 0) {
                    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);
            // 设置输出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件)
            // 使用一个临时文件名来设置目录,H264Encoder会在初始化时创建第一个文件
            File tempFile = new File(outputFileDirectory, "temp.h264");
            h264Encoder.setOutputFile(tempFile.getAbsolutePath());
            h264Encoder.setEnableFileOutput(true); // 启用文件输出
        h264Encoder.setOutputFile(tempFile.getAbsolutePath());
        h264Encoder.setEnableFileOutput(config.enableFileOutput);
            
            // 禁用网络传输
            h264Encoder.setEnableNetworkTransmission(false);
            // 设置网络传输
        h264Encoder.setEnableNetworkTransmission(config.enableNetworkTransmit);
        if (config.enableNetworkTransmit) {
                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选择摄像头范围
@@ -418,79 +485,11 @@
                    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(config.enableFileOutput); // 启用文件输出
            // 启用网络传输并设置服务器地址
            h264Encoder.setEnableNetworkTransmission(config.enableNetworkTransmit);
            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;
            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");
                Timber.d("Encode started successfully, fileOutput: %b, networkTransmit: %b, resolution: %dx%d, framerate: %d",
                        config.enableFileOutput, config.enableNetworkTransmit, width, height, framerate);
                if (config.enableNetworkTransmit) {
                    Timber.d("Network server: %s:%d", config.ip, config.port);
                }
                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");
@@ -498,7 +497,7 @@
                return 1; // 失败
            }
        } catch (Exception e) {
            Timber.e(e, "Failed to start network encode");
            Timber.e(e, "Failed to start encode");
            h264Encoder = null;
            return 1; // 失败
        }
@@ -581,8 +580,8 @@
            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 : "");
//                    Timber.d("File transmit progress: frame %d%s", currentFrame,
//                            totalFrames > 0 ? " of " + totalFrames : "");
                }
                
                @Override
app/src/main/java/com/anyun/h264/H264Encoder.java
@@ -173,6 +173,18 @@
    public void setEnableNetworkTransmission(boolean enable) {
        this.enableNetworkTransmission = enable;
        Timber.d("Network transmission " + (enable ? "enabled" : "disabled"));
        // 如果在编码过程中动态开启网络传输,需要确保底层Socket已经建立
        if (enable) {
            if (!protocolHelper.initializeSocket()) {
                Timber.e("Failed to initialize socket when enabling network transmission");
            }
        } else {
            // 动态关闭网络传输时,及时释放底层Socket资源
            if (protocolHelper != null) {
                protocolHelper.closeSocket();
            }
        }
    }
    /**
app/src/main/java/com/anyun/h264/JT1076ProtocolHelper.java
@@ -55,6 +55,8 @@
    
    // TCP参数
    private JT1076TcpClient tcpClient;
    // 控制未连接日志的输出频率,避免刷屏
    private boolean tcpNotConnectedLogged = false;
    
    // RTP协议参数
    private String simCardNumber = "123456789012"; // 12位SIM卡号
@@ -162,6 +164,8 @@
                    @Override
                    public void onConnected() {
                        Timber.d("TCP connection established");
                        // 连接成功后,允许下次断开时再次打印未连接告警
                        tcpNotConnectedLogged = false;
                    }
                    
                    @Override
@@ -255,8 +259,14 @@
    public void sendTcpPacket(byte[] packet) {
        if (tcpClient != null && tcpClient.isConnected()) {
            tcpClient.sendPacket(packet);
            // 发送成功,重置未连接日志标记
            tcpNotConnectedLogged = false;
        } else {
            Timber.w("TCP socket not connected");
            // 仅在第一次检测到未连接时打印warn,避免日志刷屏
            if (!tcpNotConnectedLogged) {
                Timber.w("TCP socket not connected");
                tcpNotConnectedLogged = true;
            }
        }
    }