| | |
| | | // 文件输出 |
| | | 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网络传输 |
| | |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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(); |
| | | |
| | |
| | | // 更新实际分辨率 |
| | | 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(); |
| | |
| | | } |
| | | |
| | | // 5. 初始化文件输出(仅创建文件,SPS/PPS在第一次输出时写入) |
| | | if (enableFileOutput && outputFilePath != null && !outputFilePath.isEmpty()) { |
| | | if (enableFileOutput) { |
| | | if (!initFileOutput()) { |
| | | Timber.w("File output initialization failed, continuing without file output"); |
| | | } |
| | |
| | | */ |
| | | 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) { |
| | |
| | | } |
| | | |
| | | /** |
| | | * 写入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) { |
| | |
| | | } |
| | | |
| | | 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}; |
| | |
| | | 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"); |
| | |
| | | } |
| | | |
| | | 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(); |