package com.safeluck.floatwindow.manager; import android.content.Context; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.MediaMuxer; import android.media.MediaRecorder; import com.anyun.libusbcamera.UsbCamera; import com.safeluck.floatwindow.MediaArgu; import com.safeluck.floatwindow.ResponseVO; import com.safeluck.floatwindow.util.VideoFileUtils; import timber.log.Timber; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; /** * USB摄像头录像管理器 * 每分钟保存一个MP4文件 */ public class UsbCameraRecordManager { private static final String TAG = "UsbCameraRecordManager"; private static final int FRAME_RATE = 20; private static final int I_FRAME_INTERVAL = 1; // 1秒一个I帧 private static final long RECORD_INTERVAL_MS = 60 * 1000; // 60秒 // 音频参数 private static final int SAMPLE_RATE = 44100; // 采样率 private static final int CHANNEL_COUNT = 1; // 单声道 private static final int AUDIO_BIT_RATE = 64000; // 音频码率 private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; private Context context; private MediaArgu mediaArgu; private RecordCallback callback; // USB摄像头相关 private UsbCamera usbCamera; private RecordThread recordThread; private boolean isRunning = false; private boolean cameraExists = false; // 分辨率数组 [width, height] private int[] resolutionArr = new int[]{640, 480}; // 是否开启摄像头加密 private boolean ay_encrypt = false; // MediaCodec和MediaMuxer private MediaCodec videoEncoder; private MediaCodec audioEncoder; private AudioRecord audioRecord; private MediaMuxer mediaMuxer; private int videoTrackIndex = -1; private int audioTrackIndex = -1; private boolean muxerStarted = false; // 音频相关 private AudioThread audioThread; private int audioBufferSize; // 当前录像文件 private File currentVideoFile; private long currentFileStartTime; /** * 录像回调接口 */ public interface RecordCallback { void onResult(ResponseVO response); } public UsbCameraRecordManager(Context context) { this.context = context; } /** * 设置回调 */ public void setCallback(RecordCallback callback) { this.callback = callback; } /** * 开始录像 */ public void startRecord(MediaArgu media) { if (media == null) { notifyCallback(0, -1, "MediaArgu is null"); return; } this.mediaArgu = media; // 设置分辨率 if (media.getM_screen() != null) { resolutionArr[0] = media.getM_screen().getWidth(); resolutionArr[1] = media.getM_screen().getHeight(); Timber.d("设置分辨率: %dx%d", resolutionArr[0], resolutionArr[1]); } try { // 检查并打开USB摄像头 if (!openUsbCamera()) { cameraExists = false; notifyCallback(0, -1, "USB摄像头打开失败"); return; } cameraExists = true; Timber.d("USB摄像头打开成功"); // 启动录像线程 startRecordThread(); notifyCallback(0, 0, "录像已启动"); } catch (Exception e) { Timber.e(e, "Failed to start record"); notifyCallback(0, -3, "启动录像失败: " + e.getMessage()); } } /** * 停止录像 */ public void stopRecord() { Timber.d("stopRecord called"); // 停止音频线程 if (audioThread != null) { audioThread.stopRecording(); try { audioThread.join(1000); } catch (InterruptedException e) { Timber.e(e, "Error stopping audio thread"); } audioThread = null; } stopRecordThread(); releaseResources(); if (usbCamera != null) { usbCamera.stopCamera(); } notifyCallback(0, 4, "录像已停止"); } /** * 打开USB摄像头 */ private boolean openUsbCamera() { try { if (usbCamera == null) { usbCamera = new UsbCamera(); } // 打开摄像头之前先调用setenv usbCamera.setenv(); // 使用prepareCamera方法,camera_id范围[0,9] int[] cameraIds = {0, 9}; String cameraName = null; // 不指定特定名称 // 如果返回非0,代表打开失败,则先stopCamera再重试,最多3次 int ret = -1; for (int i = 0; i < 3; i++) { ret = usbCamera.prepareCamera(cameraIds, cameraName, resolutionArr, ay_encrypt); Timber.d("USB录像摄像头第%d次打开结果: %d, 分辨率: %dx%d", i + 1, ret, resolutionArr[0], resolutionArr[1]); if (ret == 0) { break; } // 打开失败则先关闭再重试 usbCamera.stopCamera(); } // 成功标准:prepareCamera 返回 0 return ret == 0; } catch (Exception e) { Timber.e(e, "打开USB摄像头异常"); return false; } } /** * 初始化编码器和Muxer */ private boolean initEncoderAndMuxer() { try { int width = resolutionArr[0]; int height = resolutionArr[1]; // 创建视频编码器 MediaFormat videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height); videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 3); // 码率 videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL); videoEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); 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); 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; } // 创建新的视频文件 currentVideoFile = VideoFileUtils.getVideoFile(context, mediaArgu.getTfCardFlag()); if (currentVideoFile == null) { Timber.e("Failed to create video file"); return false; } mediaMuxer = new MediaMuxer(currentVideoFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); videoTrackIndex = -1; audioTrackIndex = -1; muxerStarted = false; currentFileStartTime = System.currentTimeMillis(); Timber.d("编码器和Muxer初始化成功,文件: %s", currentVideoFile.getAbsolutePath()); return true; } catch (Exception e) { Timber.e(e, "初始化编码器和Muxer失败"); return false; } } /** * 释放资源 */ private void releaseResources() { if (audioRecord != null) { try { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } audioRecord.release(); } catch (Exception e) { Timber.e(e, "Error releasing audio record"); } audioRecord = null; } if (audioEncoder != null) { try { audioEncoder.stop(); audioEncoder.release(); } catch (Exception e) { Timber.e(e, "Error releasing audio encoder"); } audioEncoder = null; } if (videoEncoder != null) { try { videoEncoder.stop(); videoEncoder.release(); } catch (Exception e) { Timber.e(e, "Error releasing video encoder"); } videoEncoder = null; } if (mediaMuxer != null) { try { if (muxerStarted) { mediaMuxer.stop(); } mediaMuxer.release(); } catch (Exception e) { Timber.e(e, "Error releasing media muxer"); } mediaMuxer = null; } muxerStarted = false; videoTrackIndex = -1; audioTrackIndex = -1; } /** * 启动录像线程 */ private void startRecordThread() { if (recordThread == null || !isRunning) { isRunning = true; recordThread = new RecordThread(); recordThread.start(); Timber.d("Record thread started"); } } /** * 停止录像线程 */ private void stopRecordThread() { isRunning = false; if (recordThread != null) { try { recordThread.join(2000); } catch (InterruptedException e) { Timber.e(e, "Error stopping record thread"); } recordThread = null; } Timber.d("Record thread stopped"); } /** * 录像线程 */ private class RecordThread extends Thread { @Override public void run() { super.run(); Timber.d("RecordThread started"); try { int width = resolutionArr[0]; int height = resolutionArr[1]; // 计算YUV420缓冲区大小 int bufferSize = width * height * 3 / 2; byte[] buffer = new byte[bufferSize]; // 初始化编码器和Muxer if (!initEncoderAndMuxer()) { notifyCallback(0, -1, "初始化编码器失败"); return; } // 启动音频录制 if (audioRecord != null) { audioRecord.startRecording(); 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("音频编码器输出格式尚未准备好,将在处理数据时添加"); } // 启动音频编码线程 audioThread = new AudioThread(); audioThread.start(); Timber.d("开始录像,分辨率: %dx%d", width, height); long frameCount = 0; long lastFileChangeTime = System.currentTimeMillis(); long audioTrackWaitStartTime = System.currentTimeMillis(); final long AUDIO_TRACK_TIMEOUT_MS = 3000; // 3秒超时 // 循环处理摄像头数据 while (isRunning && cameraExists) { // 检查是否需要创建新文件(每分钟) long currentTime = System.currentTimeMillis(); if (currentTime - lastFileChangeTime >= RECORD_INTERVAL_MS) { // 停止音频线程 if (audioThread != null) { audioThread.stopRecording(); try { audioThread.join(1000); } catch (InterruptedException e) { Timber.e(e, "Error stopping audio thread"); } audioThread = null; } // 释放当前资源 releaseResources(); // 初始化新的编码器和Muxer if (!initEncoderAndMuxer()) { Timber.e("Failed to create new video file"); break; } // 重新启动音频录制 if (audioRecord != null) { audioRecord.startRecording(); } audioThread = new AudioThread(); audioThread.start(); lastFileChangeTime = currentTime; frameCount = 0; // 通知文件创建 if (currentVideoFile != null) { notifyCallback(2, 0, currentVideoFile.getName()); } } // 处理摄像头数据 int processResult = usbCamera.processCamera(); if (processResult == -1) { Timber.w("processCamera返回-1,摄像头可能断开"); cameraExists = false; notifyCallback(0, -1, "USB摄像头断开"); break; } // 获取YUV数据 (参数0表示录像) usbCamera.rgba(0, buffer); // 编码并写入文件 if (videoEncoder != null && mediaMuxer != null) { encodeFrame(buffer, frameCount, 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 Thread.sleep(50); } } catch (Exception e) { Timber.e(e, "Error in record thread"); cameraExists = false; notifyCallback(0, -1, "录像线程异常: " + e.getMessage()); } finally { // 停止音频线程 if (audioThread != null) { audioThread.stopRecording(); try { audioThread.join(1000); } catch (InterruptedException e) { Timber.e(e, "Error stopping audio thread"); } audioThread = null; } releaseResources(); Timber.d("RecordThread ended"); } } /** * 编码一帧数据 */ private void encodeFrame(byte[] yuvData, long frameIndex, int width, int height) { try { // 获取输入缓冲区 int inputBufferIndex = videoEncoder.dequeueInputBuffer(10000); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = videoEncoder.getInputBuffer(inputBufferIndex); if (inputBuffer != null) { inputBuffer.clear(); inputBuffer.put(yuvData); long presentationTimeUs = (frameIndex * 1000000) / FRAME_RATE; videoEncoder.queueInputBuffer(inputBufferIndex, 0, yuvData.length, presentationTimeUs, 0); } } // 获取输出缓冲区 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = videoEncoder.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) { if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // 输出格式改变,添加视频轨道 MediaFormat newFormat = videoEncoder.getOutputFormat(); synchronized (UsbCameraRecordManager.this) { videoTrackIndex = mediaMuxer.addTrack(newFormat); Timber.d("视频轨道已添加: %d", videoTrackIndex); checkAndStartMuxer(); } outputBufferIndex = videoEncoder.dequeueOutputBuffer(bufferInfo, 0); continue; } else if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = videoEncoder.getOutputBuffer(outputBufferIndex); if (outputBuffer != null && muxerStarted && videoTrackIndex >= 0) { outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo); } videoEncoder.releaseOutputBuffer(outputBufferIndex, false); } outputBufferIndex = videoEncoder.dequeueOutputBuffer(bufferInfo, 0); } } catch (Exception e) { Timber.e(e, "Error encoding frame"); } } } /** * 检查并启动Muxer(当视频和音频轨道都准备好时) */ 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"); } } else { Timber.d("Muxer not started yet, video track: %d, audio track: %d", videoTrackIndex, audioTrackIndex); } } /** * 音频录制和编码线程 */ private class AudioThread extends Thread { private volatile boolean isRecording = true; public void stopRecording() { isRecording = false; } @Override public void run() { super.run(); Timber.d("AudioThread started"); try { byte[] audioBuffer = new byte[audioBufferSize]; long audioFrameCount = 0; while (isRecording && audioRecord != null && audioEncoder != null) { // 读取音频数据 int readSize = audioRecord.read(audioBuffer, 0, audioBuffer.length); if (readSize < 0) { Timber.w("AudioRecord read error: %d", readSize); break; } // 编码音频数据 encodeAudio(audioBuffer, readSize, audioFrameCount); audioFrameCount += readSize / 2; // 16位采样,每个采样2字节 } } catch (Exception e) { Timber.e(e, "Error in audio thread"); } finally { Timber.d("AudioThread ended"); } } /** * 编码音频数据 */ private void encodeAudio(byte[] audioData, int size, long frameCount) { try { // 获取输入缓冲区 int inputBufferIndex = audioEncoder.dequeueInputBuffer(10000); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = audioEncoder.getInputBuffer(inputBufferIndex); if (inputBuffer != null) { inputBuffer.clear(); inputBuffer.put(audioData, 0, size); long presentationTimeUs = (frameCount * 1000000) / SAMPLE_RATE; audioEncoder.queueInputBuffer(inputBufferIndex, 0, size, presentationTimeUs, 0); } } // 获取输出缓冲区 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = audioEncoder.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) { if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // 输出格式改变,添加音频轨道 MediaFormat newFormat = audioEncoder.getOutputFormat(); synchronized (UsbCameraRecordManager.this) { audioTrackIndex = mediaMuxer.addTrack(newFormat); Timber.d("音频轨道已添加: %d", audioTrackIndex); checkAndStartMuxer(); } outputBufferIndex = audioEncoder.dequeueOutputBuffer(bufferInfo, 0); continue; } else if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = audioEncoder.getOutputBuffer(outputBufferIndex); if (outputBuffer != null && muxerStarted && audioTrackIndex >= 0) { outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); mediaMuxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo); } audioEncoder.releaseOutputBuffer(outputBufferIndex, false); } outputBufferIndex = audioEncoder.dequeueOutputBuffer(bufferInfo, 0); } } catch (Exception e) { Timber.e(e, "Error encoding audio"); } } } /** * 通知回调 */ private void notifyCallback(int type, int errCode, String message) { if (callback != null) { ResponseVO response = new ResponseVO(); response.setType(type); response.setErrCode(errCode); response.setMessage(message); callback.onResult(response); } } }