1.usb rgba 2 ,正常颜色
2:使用帧索引计算时间戳,假设固定帧率。实际采集帧率可能不同,导致时间戳不准确
解决方案:使用实际经过的时间计算时间戳,确保时间戳与实际时间一致
3个文件已修改
124 ■■■■ 已修改文件
app/build/generated/aidl_source_output_dir/debug/out/com/safeluck/floatwindow/IMediaAidlInterface.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build/generated/aidl_source_output_dir/debug/out/com/safeluck/floatwindow/IMyCallback.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraRecordManager.java 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build/generated/aidl_source_output_dir/debug/out/com/safeluck/floatwindow/IMediaAidlInterface.java
@@ -1,6 +1,6 @@
/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Using: D:\Program\ Files\Android\Sdk\build-tools\35.0.0\aidl.exe -pD:\Program\ Files\Android\Sdk\platforms\android-35\framework.aidl -oD:\JetBrainsProjects\AndroidProject\anyunVideo\app\build\generated\aidl_source_output_dir\debug\out -ID:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\main\aidl -ID:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\debug\aidl -ID:\data\.gradle\caches\8.10.2\transforms\53a750d70626c759bd7a6dbaf50185ee\transformed\core-1.12.0\aidl -ID:\data\.gradle\caches\8.10.2\transforms\dc945394860d4e1c7d02ff0c8d3e2e6f\transformed\versionedparcelable-1.1.1\aidl -dC:\Users\Dana\AppData\Local\Temp\aidl1635290626695601630.d D:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\main\aidl\com\safeluck\floatwindow\IMediaAidlInterface.aidl
 * Using: D:\Program\ Files\Android\Sdk\build-tools\35.0.0\aidl.exe -pD:\Program\ Files\Android\Sdk\platforms\android-35\framework.aidl -oD:\JetBrainsProjects\AndroidProject\anyunVideo\app\build\generated\aidl_source_output_dir\debug\out -ID:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\main\aidl -ID:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\debug\aidl -ID:\data\.gradle\caches\8.10.2\transforms\53a750d70626c759bd7a6dbaf50185ee\transformed\core-1.12.0\aidl -ID:\data\.gradle\caches\8.10.2\transforms\dc945394860d4e1c7d02ff0c8d3e2e6f\transformed\versionedparcelable-1.1.1\aidl -dC:\Users\Dana\AppData\Local\Temp\aidl10441886692587460682.d D:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\main\aidl\com\safeluck\floatwindow\IMediaAidlInterface.aidl
 */
package com.safeluck.floatwindow;
// Declare any non-default types here with import statements
app/build/generated/aidl_source_output_dir/debug/out/com/safeluck/floatwindow/IMyCallback.java
@@ -1,6 +1,6 @@
/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Using: D:\Program\ Files\Android\Sdk\build-tools\35.0.0\aidl.exe -pD:\Program\ Files\Android\Sdk\platforms\android-35\framework.aidl -oD:\JetBrainsProjects\AndroidProject\anyunVideo\app\build\generated\aidl_source_output_dir\debug\out -ID:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\main\aidl -ID:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\debug\aidl -ID:\data\.gradle\caches\8.10.2\transforms\53a750d70626c759bd7a6dbaf50185ee\transformed\core-1.12.0\aidl -ID:\data\.gradle\caches\8.10.2\transforms\dc945394860d4e1c7d02ff0c8d3e2e6f\transformed\versionedparcelable-1.1.1\aidl -dC:\Users\Dana\AppData\Local\Temp\aidl7435277295093533370.d D:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\main\aidl\com\safeluck\floatwindow\IMyCallback.aidl
 * Using: D:\Program\ Files\Android\Sdk\build-tools\35.0.0\aidl.exe -pD:\Program\ Files\Android\Sdk\platforms\android-35\framework.aidl -oD:\JetBrainsProjects\AndroidProject\anyunVideo\app\build\generated\aidl_source_output_dir\debug\out -ID:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\main\aidl -ID:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\debug\aidl -ID:\data\.gradle\caches\8.10.2\transforms\53a750d70626c759bd7a6dbaf50185ee\transformed\core-1.12.0\aidl -ID:\data\.gradle\caches\8.10.2\transforms\dc945394860d4e1c7d02ff0c8d3e2e6f\transformed\versionedparcelable-1.1.1\aidl -dC:\Users\Dana\AppData\Local\Temp\aidl6260459078460591487.d D:\JetBrainsProjects\AndroidProject\anyunVideo\app\src\main\aidl\com\safeluck\floatwindow\IMyCallback.aidl
 */
package com.safeluck.floatwindow;
/**
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraRecordManager.java
@@ -70,6 +70,9 @@
    private File currentVideoFile;
    private long currentFileStartTime;
    
    // 录制开始时间(纳秒),用于时间戳同步
    private volatile long recordingStartTimeNs = 0;
    /**
     * 录像回调接口
     */
@@ -165,7 +168,7 @@
            usbCamera.setenv();
            // 使用prepareCamera方法,camera_id范围[0,9]
            int[] cameraIds = {0, 9};
            int[] cameraIds = {0, 2};
            String cameraName = null; // 不指定特定名称
            // 如果返回非0,代表打开失败,则先stopCamera再重试,最多3次
@@ -366,19 +369,9 @@
                    Timber.d("音频录制已启动");
                }
                
                // 主动检查音频编码器输出格式(可能在处理数据前就有格式)
                try {
                    MediaFormat audioFormat = audioEncoder.getOutputFormat();
                    if (audioFormat != null) {
                        synchronized (UsbCameraRecordManager.this) {
                            audioTrackIndex = mediaMuxer.addTrack(audioFormat);
                            Timber.d("音频轨道已添加(主动检查): %d", audioTrackIndex);
                            checkAndStartMuxer();
                        }
                    }
                } catch (Exception e) {
                    Timber.d("音频编码器输出格式尚未准备好,将在处理数据时添加");
                }
                // 注意:不要在这里主动检查音频编码器输出格式
                // 因为 MediaCodec 的 getOutputFormat() 在编码器启动后可能返回 null
                // 应该等待 INFO_OUTPUT_FORMAT_CHANGED 事件
                
                // 启动音频编码线程
                audioThread = new AudioThread();
@@ -386,10 +379,10 @@
                
                Timber.d("开始录像,分辨率: %dx%d", width, height);
                
                // 记录开始时间(纳秒,用于精确时间戳)
                recordingStartTimeNs = System.nanoTime();
                long frameCount = 0;
                long lastFileChangeTime = System.currentTimeMillis();
                long audioTrackWaitStartTime = System.currentTimeMillis();
                final long AUDIO_TRACK_TIMEOUT_MS = 3000; // 3秒超时
                
                // 循环处理摄像头数据
                while (isRunning && cameraExists) {
@@ -415,6 +408,9 @@
                            Timber.e("Failed to create new video file");
                            break;
                        }
                        // 重置开始时间
                        recordingStartTimeNs = System.nanoTime();
                        
                        // 重新启动音频录制
                        if (audioRecord != null) {
@@ -442,33 +438,14 @@
                    }
                    
                    // 获取YUV数据 (参数0表示录像)
                    usbCamera.rgba(0, buffer);
                    usbCamera.rgba(2, buffer);
                    
                    // 编码并写入文件
                    if (videoEncoder != null && mediaMuxer != null) {
                        encodeFrame(buffer, frameCount, width, height);
                        // 计算实际经过的时间(微秒)
                        long elapsedTimeUs = (System.nanoTime() - recordingStartTimeNs) / 1000;
                        encodeFrame(buffer, elapsedTimeUs, width, height);
                        frameCount++;
                        // 如果音频轨道超时未准备好,尝试仅用视频轨道启动
                        if (!muxerStarted && videoTrackIndex >= 0 && audioTrackIndex < 0) {
                            // 重用循环中已定义的 currentTime 变量
                            if (currentTime - audioTrackWaitStartTime > AUDIO_TRACK_TIMEOUT_MS) {
                                Timber.w("音频轨道超时未准备好,仅使用视频轨道启动Muxer");
                                synchronized (UsbCameraRecordManager.this) {
                                    // 创建一个假的音频轨道索引,或者直接启动(但MediaMuxer要求至少一个轨道)
                                    // 实际上MediaMuxer需要至少一个轨道,所以如果视频轨道准备好了就可以启动
                                    if (videoTrackIndex >= 0) {
                                        try {
                                            mediaMuxer.start();
                                            muxerStarted = true;
                                            Timber.d("Muxer started with video track only: %d", videoTrackIndex);
                                        } catch (Exception e) {
                                            Timber.e(e, "Failed to start muxer with video only");
                                        }
                                    }
                                }
                            }
                        }
                    }
                    
                    // 控制帧率,约20fps
@@ -498,8 +475,12 @@
        
        /**
         * 编码一帧数据
         * @param yuvData YUV数据
         * @param presentationTimeUs 时间戳(微秒),基于实际开始时间
         * @param width 宽度
         * @param height 高度
         */
        private void encodeFrame(byte[] yuvData, long frameIndex, int width, int height) {
        private void encodeFrame(byte[] yuvData, long presentationTimeUs, int width, int height) {
            try {
                // 获取输入缓冲区
                int inputBufferIndex = videoEncoder.dequeueInputBuffer(10000);
@@ -509,7 +490,7 @@
                        inputBuffer.clear();
                        inputBuffer.put(yuvData);
                        
                        long presentationTimeUs = (frameIndex * 1000000) / FRAME_RATE;
                        // 使用实际经过的时间作为时间戳
                        videoEncoder.queueInputBuffer(inputBufferIndex, 0, yuvData.length, 
                                presentationTimeUs, 0);
                    }
@@ -524,18 +505,30 @@
                        // 输出格式改变,添加视频轨道
                        MediaFormat newFormat = videoEncoder.getOutputFormat();
                        synchronized (UsbCameraRecordManager.this) {
                            videoTrackIndex = mediaMuxer.addTrack(newFormat);
                            Timber.d("视频轨道已添加: %d", videoTrackIndex);
                            checkAndStartMuxer();
                            // 检查 muxer 是否已启动,如果已启动则无法添加轨道
                            if (muxerStarted) {
                                Timber.e("Muxer already started, cannot add video track");
                                break;
                            }
                            if (videoTrackIndex < 0) {
                                videoTrackIndex = mediaMuxer.addTrack(newFormat);
                                Timber.d("视频轨道已添加: %d", videoTrackIndex);
                                checkAndStartMuxer();
                            }
                        }
                        outputBufferIndex = videoEncoder.dequeueOutputBuffer(bufferInfo, 0);
                        continue;
                    } else if (outputBufferIndex >= 0) {
                        ByteBuffer outputBuffer = videoEncoder.getOutputBuffer(outputBufferIndex);
                        // 只有在 muxer 已启动且轨道索引有效时才写入数据
                        if (outputBuffer != null && muxerStarted && videoTrackIndex >= 0) {
                            outputBuffer.position(bufferInfo.offset);
                            outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                            mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo);
                            try {
                                mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo);
                            } catch (Exception e) {
                                Timber.e(e, "Error writing video sample data");
                            }
                        }
                        videoEncoder.releaseOutputBuffer(outputBufferIndex, false);
                    }
@@ -583,7 +576,7 @@
            
            try {
                byte[] audioBuffer = new byte[audioBufferSize];
                long audioFrameCount = 0;
                long totalSamplesRead = 0; // 总采样数
                
                while (isRecording && audioRecord != null && audioEncoder != null) {
                    // 读取音频数据
@@ -594,8 +587,8 @@
                    }
                    
                    // 编码音频数据
                    encodeAudio(audioBuffer, readSize, audioFrameCount);
                    audioFrameCount += readSize / 2; // 16位采样,每个采样2字节
                    encodeAudio(audioBuffer, readSize, totalSamplesRead);
                    totalSamplesRead += readSize / 2; // 16位采样,每个采样2字节
                }
                
            } catch (Exception e) {
@@ -607,10 +600,13 @@
        
        /**
         * 编码音频数据
         * @param audioData 音频数据
         * @param size 数据大小(字节)
         * @param totalSamples 总采样数(从开始到现在的累计采样数)
         */
        private void encodeAudio(byte[] audioData, int size, long frameCount) {
        private void encodeAudio(byte[] audioData, int size, long totalSamples) {
            try {
                // 获取输入缓冲区
                // 获取输入缓冲区(即使 muxer 未启动也要编码,以便尽快获得输出格式)
                int inputBufferIndex = audioEncoder.dequeueInputBuffer(10000);
                if (inputBufferIndex >= 0) {
                    ByteBuffer inputBuffer = audioEncoder.getInputBuffer(inputBufferIndex);
@@ -618,7 +614,9 @@
                        inputBuffer.clear();
                        inputBuffer.put(audioData, 0, size);
                        
                        long presentationTimeUs = (frameCount * 1000000) / SAMPLE_RATE;
                        // 使用采样数计算时间戳(微秒)
                        // totalSamples 是采样数,SAMPLE_RATE 是每秒采样数
                        long presentationTimeUs = (totalSamples * 1000000) / SAMPLE_RATE;
                        audioEncoder.queueInputBuffer(inputBufferIndex, 0, size, 
                                presentationTimeUs, 0);
                    }
@@ -633,18 +631,30 @@
                        // 输出格式改变,添加音频轨道
                        MediaFormat newFormat = audioEncoder.getOutputFormat();
                        synchronized (UsbCameraRecordManager.this) {
                            audioTrackIndex = mediaMuxer.addTrack(newFormat);
                            Timber.d("音频轨道已添加: %d", audioTrackIndex);
                            checkAndStartMuxer();
                            // 检查 muxer 是否已启动,如果已启动则无法添加轨道
                            if (muxerStarted) {
                                Timber.e("Muxer already started, cannot add audio track");
                                break;
                            }
                            if (audioTrackIndex < 0) {
                                audioTrackIndex = mediaMuxer.addTrack(newFormat);
                                Timber.d("音频轨道已添加: %d", audioTrackIndex);
                                checkAndStartMuxer();
                            }
                        }
                        outputBufferIndex = audioEncoder.dequeueOutputBuffer(bufferInfo, 0);
                        continue;
                    } else if (outputBufferIndex >= 0) {
                        ByteBuffer outputBuffer = audioEncoder.getOutputBuffer(outputBufferIndex);
                        // 只有在 muxer 已启动且轨道索引有效时才写入数据
                        if (outputBuffer != null && muxerStarted && audioTrackIndex >= 0) {
                            outputBuffer.position(bufferInfo.offset);
                            outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                            mediaMuxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo);
                            try {
                                mediaMuxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo);
                            } catch (Exception e) {
                                Timber.e(e, "Error writing audio sample data");
                            }
                        }
                        audioEncoder.releaseOutputBuffer(outputBufferIndex, false);
                    }