一个用于记录和存储 H.264 视频帧的工具类

发布于:2025-06-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

CarPlayPacketDumper 是一个用于记录和存储 CarPlay 数据包(特别是 H.264 视频帧)的工具类。它将接收到的数据帧按顺序写入文件,并提供开始、停止和写入操作。

public class CarPlayPacketDumper {
    private static final String TAG = "CarPlayPacketDumper";
    private final String filePath;
    private FileOutputStream fos; // 文件输出流
    private FileChannel channel; // 文件通道,提供更高效的文件操作
    public boolean isRecording = false; // 记录状态标志
    private final AtomicLong writtenBytes = new AtomicLong(0); // 线程安全的计数器

    public CarPlayPacketDumper(String filePath) {
        this.filePath = filePath;
    }

    public synchronized void start() throws IOException {
        Log.d(TAG, "启动H.264顺序录制...");
        stop(); // 确保先关闭现有资源

        // 追加模式(false表示覆盖原有文件)
        fos = new FileOutputStream(filePath, false);
        channel = fos.getChannel(); // 获取文件通道
        isRecording = true;
        writtenBytes.set(0);

        Log.d(TAG, "已清空并准备写入: " + filePath);
    }

    public synchronized void writeFrame(ByteBuffer frame) throws IOException {
        if (!isRecording) {
            Log.e(TAG, "当前未在录制状态!");
            throw new IllegalStateException("Not recording");
        }

        final int frameSize = frame.remaining(); // 获取剩余字节数
        // 调试信息:采样前16字节
        byte[] sample = new byte[Math.min(16, frameSize)];
        frame.get(sample); // 读取数据到字节数组
        Log.d(TAG, String.format(
                "写入帧 [大小:%,d字节] [头16字节:%s]",
                frameSize,
                bytesToHex(sample)
        ));
        frame.rewind(); // 重置position

        // 关键点:直接顺序写入
        channel.write(frame);
        writtenBytes.addAndGet(frameSize);
    }

    public synchronized void stop() {
        if (isRecording) {
            isRecording = false;
            Log.d(TAG, "停止录制,总写入大小: " + formatFileSize(writtenBytes.get()));

            try {
                if (channel != null) {
                    channel.force(true); // 强制刷盘
                    channel.close();
                }
                if (fos != null) fos.close();
            } catch (IOException e) {
                Log.e(TAG, "关闭资源失败", e);
            } finally {
                channel = null;
                fos = null;
            }
        }
    }

    // 辅助方法:字节转十六进制
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString();
    }

    // 辅助方法:格式化文件大小
    private static String formatFileSize(long bytes) {
        if (bytes < 1024) return bytes + "B";
        return String.format("%.2fMB", bytes / (1024.0 * 1024.0));
    }
}
//使用示例

//1.初始化
private final CarPlayPacketDumper dumper = new CarPlayPacketDumper("/storage/emulated/0/Movies/video.h264");

//2.开始录制 
dumper.start();

//3.写入帧数据
// 在CarPlay视频流处理中的典型用法
public void onVideoFrameReceived(ByteBuffer frame) {
    if (dumper != null && dumper.isRecording) {
        try {
            dumper.writeFrame(frame);
        } catch (IOException e) {
            Log.e(TAG, "写入帧失败", e);
            dumper.stop();
        }
    }
}

//4.停止录制
dumper.stop();
//5.异常处理:必须处理IOException,调用stop()确保资源释放


网站公告

今日签到

点亮在社区的每一天
去签到