| | |
| | | videoEncoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
| | | videoEncoder.start(); |
| | | |
| | | // 创建音频编码器 |
| | | MediaFormat audioFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNEL_COUNT); |
| | | audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); |
| | | audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE); |
| | | // P2 摄像头(usbCameraId == 2)不录制音频,因为麦克风只有一个,只在 P1 时使用 |
| | | boolean enableAudio = (mediaArgu == null || mediaArgu.getUsbCameraId() != 2); |
| | | |
| | | audioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC); |
| | | audioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
| | | audioEncoder.start(); |
| | | |
| | | // 初始化AudioRecord |
| | | audioBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); |
| | | if (audioBufferSize <= 0) { |
| | | audioBufferSize = SAMPLE_RATE * 2; // 默认缓冲区大小 |
| | | } |
| | | |
| | | audioRecord = new AudioRecord( |
| | | MediaRecorder.AudioSource.MIC, |
| | | SAMPLE_RATE, |
| | | CHANNEL_CONFIG, |
| | | AUDIO_FORMAT, |
| | | audioBufferSize * 2 |
| | | ); |
| | | |
| | | if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { |
| | | Timber.e("AudioRecord初始化失败"); |
| | | return false; |
| | | if (enableAudio) { |
| | | // 创建音频编码器 |
| | | MediaFormat audioFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNEL_COUNT); |
| | | audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); |
| | | audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE); |
| | | |
| | | audioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC); |
| | | audioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
| | | audioEncoder.start(); |
| | | |
| | | // 初始化AudioRecord |
| | | audioBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); |
| | | if (audioBufferSize <= 0) { |
| | | audioBufferSize = SAMPLE_RATE * 2; // 默认缓冲区大小 |
| | | } |
| | | |
| | | audioRecord = new AudioRecord( |
| | | MediaRecorder.AudioSource.MIC, |
| | | SAMPLE_RATE, |
| | | CHANNEL_CONFIG, |
| | | AUDIO_FORMAT, |
| | | audioBufferSize * 2 |
| | | ); |
| | | |
| | | if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { |
| | | Timber.e("AudioRecord初始化失败"); |
| | | return false; |
| | | } |
| | | Timber.d("音频编码器和AudioRecord初始化成功(P1模式)"); |
| | | } else { |
| | | // P2 模式:不初始化音频相关资源 |
| | | audioEncoder = null; |
| | | audioRecord = null; |
| | | audioTrackIndex = -1; |
| | | Timber.d("P2模式:跳过音频初始化,仅录制视频"); |
| | | } |
| | | |
| | | // 创建新的视频文件 |
| | |
| | | |
| | | mediaMuxer = new MediaMuxer(currentVideoFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); |
| | | videoTrackIndex = -1; |
| | | audioTrackIndex = -1; |
| | | if (enableAudio) { |
| | | audioTrackIndex = -1; // 只有在启用音频时才初始化为-1,否则保持-1(表示无音频轨道) |
| | | } |
| | | muxerStarted = false; |
| | | currentFileStartTime = System.currentTimeMillis(); |
| | | |
| | | Timber.d("编码器和Muxer初始化成功,文件: %s", currentVideoFile.getAbsolutePath()); |
| | | Timber.d("编码器和Muxer初始化成功,文件: %s, 音频: %s", |
| | | currentVideoFile.getAbsolutePath(), enableAudio ? "启用" : "禁用"); |
| | | return true; |
| | | } catch (Exception e) { |
| | | Timber.e(e, "初始化编码器和Muxer失败"); |
| | |
| | | return; |
| | | } |
| | | |
| | | // 启动音频录制 |
| | | if (audioRecord != null) { |
| | | // P2模式(usbCameraId == 2)不启动音频录制 |
| | | boolean isP2Mode = (mediaArgu != null && mediaArgu.getUsbCameraId() == 2); |
| | | if (!isP2Mode && audioRecord != null) { |
| | | // 启动音频录制 |
| | | audioRecord.startRecording(); |
| | | Timber.d("音频录制已启动"); |
| | | Timber.d("音频录制已启动(P1模式)"); |
| | | |
| | | // 注意:不要在这里主动检查音频编码器输出格式 |
| | | // 因为 MediaCodec 的 getOutputFormat() 在编码器启动后可能返回 null |
| | | // 应该等待 INFO_OUTPUT_FORMAT_CHANGED 事件 |
| | | |
| | | // 启动音频编码线程 |
| | | audioThread = new AudioThread(); |
| | | audioThread.start(); |
| | | } else { |
| | | Timber.d("P2模式:跳过音频录制和编码线程"); |
| | | } |
| | | |
| | | // 注意:不要在这里主动检查音频编码器输出格式 |
| | | // 因为 MediaCodec 的 getOutputFormat() 在编码器启动后可能返回 null |
| | | // 应该等待 INFO_OUTPUT_FORMAT_CHANGED 事件 |
| | | |
| | | // 启动音频编码线程 |
| | | audioThread = new AudioThread(); |
| | | audioThread.start(); |
| | | |
| | | Timber.d("开始录像,分辨率: %dx%d", width, height); |
| | | |
| | |
| | | // 重置开始时间 |
| | | recordingStartTimeNs = System.nanoTime(); |
| | | |
| | | // 重新启动音频录制 |
| | | if (audioRecord != null) { |
| | | // P2模式不启动音频录制 |
| | | isP2Mode = (mediaArgu != null && mediaArgu.getUsbCameraId() == 2); |
| | | if (!isP2Mode && audioRecord != null) { |
| | | // 重新启动音频录制 |
| | | audioRecord.startRecording(); |
| | | audioThread = new AudioThread(); |
| | | audioThread.start(); |
| | | } |
| | | audioThread = new AudioThread(); |
| | | audioThread.start(); |
| | | |
| | | lastFileChangeTime = currentTime; |
| | | frameCount = 0; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 检查并启动Muxer(当视频和音频轨道都准备好时) |
| | | * 检查并启动Muxer |
| | | * P1模式:当视频和音频轨道都准备好时启动 |
| | | * P2模式:当视频轨道准备好时即可启动(无音频轨道) |
| | | */ |
| | | private synchronized void checkAndStartMuxer() { |
| | | if (!muxerStarted && videoTrackIndex >= 0 && audioTrackIndex >= 0) { |
| | | try { |
| | | mediaMuxer.start(); |
| | | muxerStarted = true; |
| | | Timber.d("Muxer started, video track: %d, audio track: %d", videoTrackIndex, audioTrackIndex); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to start muxer"); |
| | | boolean isP2Mode = (mediaArgu != null && mediaArgu.getUsbCameraId() == 2); |
| | | |
| | | if (isP2Mode) { |
| | | // P2模式:只要有视频轨道就启动 |
| | | if (!muxerStarted && videoTrackIndex >= 0) { |
| | | try { |
| | | mediaMuxer.start(); |
| | | muxerStarted = true; |
| | | Timber.d("Muxer started (P2模式,仅视频), video track: %d", videoTrackIndex); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to start muxer"); |
| | | } |
| | | } else { |
| | | Timber.d("Muxer not started yet (P2模式), video track: %d", videoTrackIndex); |
| | | } |
| | | } else { |
| | | Timber.d("Muxer not started yet, video track: %d, audio track: %d", videoTrackIndex, audioTrackIndex); |
| | | // P1模式:需要视频和音频轨道都准备好 |
| | | if (!muxerStarted && videoTrackIndex >= 0 && audioTrackIndex >= 0) { |
| | | try { |
| | | mediaMuxer.start(); |
| | | muxerStarted = true; |
| | | Timber.d("Muxer started (P1模式,视频+音频), video track: %d, audio track: %d", videoTrackIndex, audioTrackIndex); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to start muxer"); |
| | | } |
| | | } else { |
| | | Timber.d("Muxer not started yet (P1模式), video track: %d, audio track: %d", videoTrackIndex, audioTrackIndex); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | super.run(); |
| | | Timber.d("AudioThread started"); |
| | | |
| | | // 如果音频资源未初始化(P2模式),直接退出 |
| | | if (audioRecord == null || audioEncoder == null) { |
| | | Timber.d("AudioThread: 音频资源未初始化,退出(可能是P2模式)"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | byte[] audioBuffer = new byte[audioBufferSize]; |
| | | long totalSamplesRead = 0; // 总采样数 |