| | |
| | | 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]); |
| | | Timber.d("%s 设置分辨率: %dx%d", getCameraTag(), resolutionArr[0], resolutionArr[1]); |
| | | } |
| | | |
| | | try { |
| | |
| | | } |
| | | setWaterMask(); |
| | | cameraExists = true; |
| | | Timber.d("USB摄像头打开成功"); |
| | | Timber.d("%s USB摄像头打开成功", getCameraTag()); |
| | | |
| | | // 启动录像线程 |
| | | startRecordThread(); |
| | | |
| | | notifyCallback(0, 0, "录像已启动"); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to start record"); |
| | | Timber.e(e, "%s Failed to start record", getCameraTag()); |
| | | notifyCallback(0, -3, "启动录像失败: " + e.getMessage()); |
| | | } |
| | | } |
| | |
| | | * 停止录像 |
| | | */ |
| | | public void stopRecord() { |
| | | Timber.d("stopRecord called"); |
| | | Timber.d("%s stopRecord called", getCameraTag()); |
| | | |
| | | stopWaterMaskSchedule(); |
| | | |
| | |
| | | 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]); |
| | | Timber.d("%s USB录像摄像头第%d次打开结果: %d, 分辨率: %dx%d", getCameraTag(), i + 1, ret, resolutionArr[0], resolutionArr[1]); |
| | | if (ret == 0) { |
| | | break; |
| | | } |
| | |
| | | // 成功标准:prepareCamera 返回 0 |
| | | return ret == 0; |
| | | } catch (Exception e) { |
| | | Timber.e(e, "打开USB摄像头异常"); |
| | | Timber.e(e, "%s 打开USB摄像头异常", getCameraTag()); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | ); |
| | | |
| | | if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { |
| | | Timber.e("AudioRecord初始化失败"); |
| | | Timber.e("%s AudioRecord初始化失败", getCameraTag()); |
| | | return false; |
| | | } |
| | | Timber.d("音频编码器和AudioRecord初始化成功(P1模式)"); |
| | | Timber.d("%s 音频编码器和AudioRecord初始化成功(P1模式)", getCameraTag()); |
| | | } else { |
| | | // P2 模式:不初始化音频相关资源 |
| | | audioEncoder = null; |
| | | audioRecord = null; |
| | | audioTrackIndex = -1; |
| | | Timber.d("P2模式:跳过音频初始化,仅录制视频"); |
| | | Timber.d("%s P2模式:跳过音频初始化,仅录制视频", getCameraTag()); |
| | | } |
| | | |
| | | // 创建新的视频文件 |
| | | currentVideoFile = VideoFileUtils.getVideoFile(context, mediaArgu.getTfCardFlag(),mediaArgu.getUsbCameraId()); |
| | | if (currentVideoFile == null) { |
| | | Timber.e("Failed to create video file"); |
| | | Timber.e("%s Failed to create video file", getCameraTag()); |
| | | return false; |
| | | } |
| | | |
| | |
| | | muxerStarted = false; |
| | | currentFileStartTime = System.currentTimeMillis(); |
| | | |
| | | Timber.d("编码器和Muxer初始化成功,文件: %s, 音频: %s", |
| | | Timber.d("%s 编码器和Muxer初始化成功,文件: %s, 音频: %s", getCameraTag(), |
| | | currentVideoFile.getAbsolutePath(), enableAudio ? "启用" : "禁用"); |
| | | return true; |
| | | } catch (Exception e) { |
| | | Timber.e(e, "初始化编码器和Muxer失败"); |
| | | Timber.e(e, "%s 初始化编码器和Muxer失败", getCameraTag()); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | isRunning = true; |
| | | recordThread = new RecordThread(); |
| | | recordThread.start(); |
| | | Timber.d("Record thread started"); |
| | | Timber.d("%s Record thread started", getCameraTag()); |
| | | } |
| | | } |
| | | |
| | |
| | | @Override |
| | | public void run() { |
| | | super.run(); |
| | | Timber.d("RecordThread started"); |
| | | Timber.d("%s RecordThread started", getCameraTag()); |
| | | |
| | | try { |
| | | int width = resolutionArr[0]; |
| | |
| | | if (!isP2Mode && audioRecord != null) { |
| | | // 启动音频录制 |
| | | audioRecord.startRecording(); |
| | | Timber.d("音频录制已启动(P1模式)"); |
| | | Timber.d("%s 音频录制已启动(P1模式)", getCameraTag()); |
| | | |
| | | // 注意:不要在这里主动检查音频编码器输出格式 |
| | | // 因为 MediaCodec 的 getOutputFormat() 在编码器启动后可能返回 null |
| | |
| | | audioThread = new AudioThread(); |
| | | audioThread.start(); |
| | | } else { |
| | | Timber.d("P2模式:跳过音频录制和编码线程"); |
| | | Timber.d("%s P2模式:跳过音频录制和编码线程", getCameraTag()); |
| | | } |
| | | |
| | | Timber.d("开始录像,分辨率: %dx%d", width, height); |
| | | Timber.d("%s 开始录像,分辨率: %dx%d", getCameraTag(), width, height); |
| | | |
| | | // 记录开始时间(纳秒,用于精确时间戳) |
| | | recordingStartTimeNs = System.nanoTime(); |
| | |
| | | // 处理摄像头数据 |
| | | int processResult = usbCamera.processCamera(); |
| | | if (processResult == -1) { |
| | | Timber.w("processCamera返回-1,摄像头可能断开"); |
| | | Timber.w("%s processCamera返回-1,摄像头可能断开", getCameraTag()); |
| | | cameraExists = false; |
| | | notifyCallback(0, -1, "USB摄像头断开"); |
| | | break; |
| | |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Error in record thread"); |
| | | Timber.e(e, "%s Error in record thread,%s", getCameraTag(),e.getMessage()); |
| | | cameraExists = false; |
| | | notifyCallback(0, -1, "录像线程异常: " + e.getMessage()); |
| | | } finally { |
| | |
| | | try { |
| | | audioThread.join(1000); |
| | | } catch (InterruptedException e) { |
| | | Timber.e(e, "Error stopping audio thread"); |
| | | Timber.e(e, "%s Error stopping audio thread,%s", getCameraTag(),e.getMessage()); |
| | | } |
| | | audioThread = null; |
| | | } |
| | |
| | | completedVideoFile = null; |
| | | } |
| | | |
| | | Timber.d("RecordThread ended"); |
| | | Timber.d("%s RecordThread ended", getCameraTag()); |
| | | } |
| | | } |
| | | |
| | |
| | | @Override |
| | | public void run() { |
| | | super.run(); |
| | | Timber.d("AudioThread started"); |
| | | Timber.d("%s AudioThread started", getCameraTag()); |
| | | |
| | | // 如果音频资源未初始化(P2模式),直接退出 |
| | | if (audioRecord == null || audioEncoder == null) { |
| | | Timber.d("AudioThread: 音频资源未初始化,退出(可能是P2模式)"); |
| | | Timber.d("%s AudioThread: 音频资源未初始化,退出(可能是P2模式)", getCameraTag()); |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Error in audio thread"); |
| | | Timber.e(e, "%s Error in audio thread", getCameraTag()); |
| | | } finally { |
| | | Timber.d("AudioThread ended"); |
| | | Timber.d("%s AudioThread ended", getCameraTag()); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 获取摄像头ID标签,用于日志 |
| | | * @return "[P1]" 或 "[P2]" |
| | | */ |
| | | private String getCameraTag() { |
| | | if (mediaArgu != null) { |
| | | int usbCameraId = mediaArgu.getUsbCameraId(); |
| | | return usbCameraId == 2 ? "[P2]" : "[P1]"; |
| | | } |
| | | return "[P1]"; // 默认 P1 |
| | | } |
| | | |
| | | /** |
| | | * 通知回调 |
| | | */ |
| | | private void notifyCallback(int type, int errCode, String message) { |