Dana
2025-12-03 2fc938aa3f1518e7695afb589fc8e3782e66f068
1.1分钟循环写入h264
3个文件已修改
222 ■■■■ 已修改文件
app/src/main/java/com/anyun/h264/H264EncodeService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService2.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264Encoder.java 166 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/anyun/h264/H264EncodeService.java
@@ -391,14 +391,10 @@
            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 秒
            SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss");
            String str = bcdFormat.format(timeFile);
            Timber.i("文件名:%s", str);
            // 设置输出文件
            String fileName = "h264_" + timeFile+ ".h264";
            File outputFile = new File(outputFileDirectory, fileName);
            h264Encoder.setOutputFile(outputFile.getAbsolutePath());
            // 设置输出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件)
            // 使用一个临时文件名来设置目录,H264Encoder会在初始化时创建第一个文件
            File tempFile = new File(outputFileDirectory, "temp.h264");
            h264Encoder.setOutputFile(tempFile.getAbsolutePath());
            h264Encoder.setEnableFileOutput(true); // 启用文件输出
            
            // 禁用网络传输
@@ -419,8 +415,8 @@
                    Timber.d("Applied saved watermark info to encoder");
                }
                h264Encoder.start();
                Timber.d("File encode started successfully, output file: %s, resolution: %dx%d, framerate: %d",
                        outputFile.getAbsolutePath(), width, height, framerate);
                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");
@@ -462,14 +458,10 @@
            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;
            SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss");
            String str = bcdFormat.format(timeFile);
            Timber.i("startNetworkEncode 文件名:%s", str);
            // 设置输出文件
            String fileName = "h264_" + timeFile+ ".h264";
            File outputFile = new File(outputFileDirectory, fileName);
            h264Encoder.setOutputFile(outputFile.getAbsolutePath());
            // 设置输出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件)
            // 使用一个临时文件名来设置目录,H264Encoder会在初始化时创建第一个文件
            File tempFile = new File(outputFileDirectory, "temp.h264");
            h264Encoder.setOutputFile(tempFile.getAbsolutePath());
            h264Encoder.setEnableFileOutput(true); // 启用文件输出
            
app/src/main/java/com/anyun/h264/H264EncodeService2.java
@@ -238,14 +238,10 @@
            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;
            SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss");
            String str = bcdFormat.format(timeFile);
            Timber.i("文件名 (camera2):%s", str);
            // 设置输出文件(添加camera2标识)
            String fileName = "h264_camera2_" + timeFile+ ".h264";
            File outputFile = new File(outputFileDirectory, fileName);
            h264Encoder.setOutputFile(outputFile.getAbsolutePath());
            // 设置输出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件)
            // 使用一个临时文件名来设置目录,H264Encoder会在初始化时创建第一个文件
            File tempFile = new File(outputFileDirectory, "temp.h264");
            h264Encoder.setOutputFile(tempFile.getAbsolutePath());
            h264Encoder.setEnableFileOutput(true); // 启用文件输出
            
            // 禁用网络传输
@@ -260,8 +256,8 @@
                    Timber.d("Applied saved watermark info to encoder (camera2)");
                }
                h264Encoder.start();
                Timber.d("File encode started successfully (camera2), output file: %s, resolution: %dx%d, framerate: %d",
                        outputFile.getAbsolutePath(), width, height, framerate);
                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)");
@@ -303,14 +299,10 @@
            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;
            SimpleDateFormat bcdFormat = new SimpleDateFormat("yyMMddHHmmss");
            String str = bcdFormat.format(timeFile);
            Timber.i("startNetworkEncode (camera2) 文件名:%s", str);
            // 设置输出文件(添加camera2标识)
            String fileName = "h264_camera2_" + timeFile+ ".h264";
            File outputFile = new File(outputFileDirectory, fileName);
            h264Encoder.setOutputFile(outputFile.getAbsolutePath());
            // 设置输出文件目录(H264Encoder会自动管理文件创建,每分钟一个文件)
            // 使用一个临时文件名来设置目录,H264Encoder会在初始化时创建第一个文件
            File tempFile = new File(outputFileDirectory, "temp.h264");
            h264Encoder.setOutputFile(tempFile.getAbsolutePath());
            h264Encoder.setEnableFileOutput(true); // 启用文件输出
            
app/src/main/java/com/anyun/h264/H264Encoder.java
@@ -76,8 +76,12 @@
    // 文件输出
    private FileOutputStream fileOutputStream;
    private String outputFilePath;
    private String outputFileDirectory; // 输出文件目录
    private boolean enableFileOutput = false; // 是否启用文件输出
    private boolean spsPpsWritten = false; // 标记SPS/PPS是否已写入
    private int cameraId = 1; // 摄像头ID,默认为1(第一个摄像头)
    private long currentFileStartTime = 0; // 当前文件的开始时间(毫秒)
    private static final long FILE_DURATION_MS = 60 * 1000; // 文件时长:1分钟(毫秒)
    // 网络传输控制
    private boolean enableNetworkTransmission = true; // 是否启用TCP/UDP网络传输
@@ -135,6 +139,22 @@
     */
    public void setOutputFile(String filePath) {
        this.outputFilePath = filePath;
        // 提取目录路径
        if (filePath != null && !filePath.isEmpty()) {
            File file = new File(filePath);
            File parentDir = file.getParentFile();
            if (parentDir != null) {
                this.outputFileDirectory = parentDir.getAbsolutePath();
            }
        }
    }
    /**
     * 设置摄像头ID(用于生成文件名)
     * @param cameraId 摄像头ID,1表示第一个摄像头,2表示第二个摄像头
     */
    public void setCameraId(int cameraId) {
        this.cameraId = cameraId;
    }
    /**
@@ -241,6 +261,11 @@
     */
    public boolean initialize(int[] cameraIdRange, String cameraName, int[] resolution, boolean ayCamera) {
        try {
            // 从cameraIdRange中提取cameraId(使用第一个值)
            if (cameraIdRange != null && cameraIdRange.length > 0) {
                this.cameraId = cameraIdRange[0];
            }
            // 1. setenv
            usbCamera.setenv();
@@ -277,7 +302,7 @@
            // 更新实际分辨率
            width = actualResolution[0];
            height = actualResolution[1];
            Timber.d("Camera initialized with resolution: " + width + "x" + height);
            Timber.d("Camera initialized with resolution: " + width + "x" + height + ", cameraId: " + cameraId);
            // 3. 初始化H264编码器
            initEncoder();
@@ -293,7 +318,7 @@
            }
            // 5. 初始化文件输出(仅创建文件,SPS/PPS在第一次输出时写入)
            if (enableFileOutput && outputFilePath != null && !outputFilePath.isEmpty()) {
            if (enableFileOutput) {
                if (!initFileOutput()) {
                    Timber.w("File output initialization failed, continuing without file output");
                }
@@ -333,21 +358,35 @@
     */
    private boolean initFileOutput() {
        try {
            // 如果outputFileDirectory为空,尝试从outputFilePath提取
            if (outputFileDirectory == null || outputFileDirectory.isEmpty()) {
                if (outputFilePath != null && !outputFilePath.isEmpty()) {
            File file = new File(outputFilePath);
            File parentDir = file.getParentFile();
            if (parentDir != null && !parentDir.exists()) {
                boolean created = parentDir.mkdirs();
                if (!created && !parentDir.exists()) {
                    Timber.e("Failed to create parent directory: " + parentDir.getAbsolutePath());
                    if (parentDir != null) {
                        outputFileDirectory = parentDir.getAbsolutePath();
                    }
                }
            }
            // 如果仍然没有目录,使用默认路径
            if (outputFileDirectory == null || outputFileDirectory.isEmpty()) {
                Timber.e("Output file directory is not set");
                return false;
            }
            // 创建目录(如果不存在)
            File dir = new File(outputFileDirectory);
            if (!dir.exists()) {
                boolean created = dir.mkdirs();
                if (!created && !dir.exists()) {
                    Timber.e("Failed to create output directory: " + outputFileDirectory);
                    return false;
                }
            }
            fileOutputStream = new FileOutputStream(file);
            spsPpsWritten = false;
            Timber.d("File output initialized: " + outputFilePath);
            return true;
            // 创建第一个文件
            return createNewFile();
        } catch (Exception e) {
            Timber.e(e,"Initialize file output failed");
            if (fileOutputStream != null) {
@@ -363,7 +402,65 @@
    }
    /**
     * 写入SPS/PPS到文件(从CSD或关键帧数据中提取)
     * 创建新文件(每分钟调用一次)
     * @return 是否成功
     */
    private boolean createNewFile() {
        try {
            // 关闭旧文件
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.flush();
                    fileOutputStream.close();
                    Timber.d("Closed previous file: " + outputFilePath);
                } catch (IOException e) {
                    Timber.e(e, "Error closing previous file");
                }
                fileOutputStream = null;
            }
            // 生成新文件名
            long timeFile = System.currentTimeMillis() / 1000 * 1000;
            currentFileStartTime = timeFile;
            String fileName;
            if (cameraId == 2) {
                fileName = "h264_camera2_" + timeFile + ".h264";
            } else {
                fileName = "h264_" + timeFile + ".h264";
            }
            File newFile = new File(outputFileDirectory, fileName);
            outputFilePath = newFile.getAbsolutePath();
            // 创建新文件
            fileOutputStream = new FileOutputStream(newFile);
            spsPpsWritten = false; // 重置SPS/PPS标记,新文件需要重新写入
            // 如果已经有缓存的SPS/PPS,立即写入新文件
            if (spsBuffer != null && ppsBuffer != null) {
                writeSpsPpsToFile();
                Timber.d("SPS/PPS written to new file immediately");
            }
            Timber.d("Created new file: " + outputFilePath);
            return true;
        } catch (Exception e) {
            Timber.e(e, "Failed to create new file");
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException ie) {
                    Timber.e(ie, "Close file output stream failed");
                }
                fileOutputStream = null;
            }
            return false;
        }
    }
    /**
     * 写入SPS/PPS到文件(优先使用缓存的SPS/PPS,否则从CSD获取)
     */
    private void writeSpsPpsToFile() {
        if (!enableFileOutput || fileOutputStream == null || spsPpsWritten) {
@@ -371,17 +468,29 @@
        }
        try {
            // 尝试从编码器输出格式中获取CSD
            MediaFormat format = encoder.getOutputFormat();
            ByteBuffer spsBuffer = format.getByteBuffer("csd-0"); // SPS
            ByteBuffer ppsBuffer = format.getByteBuffer("csd-1"); // PPS
            // 优先使用缓存的SPS/PPS(这些是Annex-B格式,已经包含起始码)
            if (spsBuffer != null && ppsBuffer != null) {
                // 直接写入缓存的SPS/PPS(已经是Annex-B格式)
                fileOutputStream.write(spsBuffer);
                fileOutputStream.write(ppsBuffer);
                fileOutputStream.flush();
                spsPpsWritten = true;
                Timber.d("SPS/PPS written to file from cache, SPS size: " + spsBuffer.length + ", PPS size: " + ppsBuffer.length);
                return;
            }
            // 如果缓存中没有,尝试从编码器输出格式中获取CSD
            MediaFormat format = encoder.getOutputFormat();
            ByteBuffer csdSpsBuffer = format.getByteBuffer("csd-0"); // SPS
            ByteBuffer csdPpsBuffer = format.getByteBuffer("csd-1"); // PPS
            if (csdSpsBuffer != null && csdPpsBuffer != null) {
                // CSD格式通常是AVCC格式,需要转换为Annex-B
                byte[] sps = new byte[spsBuffer.remaining()];
                byte[] pps = new byte[ppsBuffer.remaining()];
                spsBuffer.get(sps);
                ppsBuffer.get(pps);
                byte[] sps = new byte[csdSpsBuffer.remaining()];
                byte[] pps = new byte[csdPpsBuffer.remaining()];
                csdSpsBuffer.get(sps);
                csdPpsBuffer.get(pps);
                // 写入SPS和PPS到文件(Annex-B格式)
                byte[] nalStartCode = {0x00, 0x00, 0x00, 0x01};
@@ -415,9 +524,9 @@
                fileOutputStream.flush();
                spsPpsWritten = true;
                Timber.d("SPS/PPS written to file, SPS size: " + spsLength + ", PPS size: " + ppsLength);
                Timber.d("SPS/PPS written to file from CSD, SPS size: " + spsLength + ", PPS size: " + ppsLength);
            } else {
                Timber.w("SPS/PPS not found in CSD, will extract from first key frame");
                Timber.w("SPS/PPS not found in cache or CSD, will extract from first key frame");
            }
        } catch (Exception e) {
            Timber.e(e,"Write SPS/PPS to file error");
@@ -680,6 +789,17 @@
        }
        try {
            // 检查是否需要创建新文件(每分钟)
            long currentTime = System.currentTimeMillis();
            if (currentFileStartTime > 0 && (currentTime - currentFileStartTime) >= FILE_DURATION_MS) {
                Timber.d("File duration reached 1 minute, creating new file");
                if (!createNewFile()) {
                    Timber.e("Failed to create new file, stopping file output");
                    enableFileOutput = false;
                    return;
                }
            }
            // 如果是第一个关键帧,确保SPS/PPS已写入
            if (isKeyFrame && !spsPpsWritten) {
                writeSpsPpsToFile();