1.notifyCallback synchronized
| | |
| | | |
| | | /** |
| | | * 通知回调:统一使用 ResponseVO |
| | | * 使用 synchronized 确保 RemoteCallbackList 操作的线程安全 |
| | | * @param response ResponseVO 对象,包含 type、errCode、message |
| | | */ |
| | | private void notifyCallback(ResponseVO response) { |
| | | private synchronized void notifyCallback(ResponseVO response) { |
| | | if (response == null) { |
| | | return; |
| | | } |
| | |
| | | currentManagerType = ManagerType.NONE; |
| | | } |
| | | |
| | | private void notifyCallback(ResponseVO response) { |
| | | /** |
| | | * 通知回调:统一使用 ResponseVO |
| | | * 使用 synchronized 确保 RemoteCallbackList 操作的线程安全 |
| | | */ |
| | | private synchronized void notifyCallback(ResponseVO response) { |
| | | if (response == null) return; |
| | | int count = mCallbacks.beginBroadcast(); |
| | | for (int i = 0; i < count; i++) { |
| | |
| | | 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 { |
| | |
| | | } |
| | | |
| | | cameraExists = true; |
| | | Timber.d("USB摄像头打开成功"); |
| | | Timber.d("%s USB摄像头打开成功", getCameraTag()); |
| | | |
| | | notifyCallback(1, 0, "推流线程已启动,等待推流状态就绪"); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to start push"); |
| | | Timber.e(e, "%s Failed to start push", getCameraTag()); |
| | | notifyCallback(1, -3, "启动推流失败: " + e.getMessage()); |
| | | } |
| | | } |
| | |
| | | * 停止推流 |
| | | */ |
| | | public void stopPush() { |
| | | Timber.d("stopPush called"); |
| | | Timber.d("%s stopPush called", getCameraTag()); |
| | | stopPushThread(); |
| | | // stopAudioTransfer(); |
| | | stopWaterMaskSchedule(); |
| | |
| | | @Override |
| | | public void surfaceCreated(SurfaceHolder holder) { |
| | | try { |
| | | Timber.d("previewSurfaceView surfaceCreated, startPreviewAysnc"); |
| | | Timber.d("%s previewSurfaceView surfaceCreated, startPreviewAysnc", getCameraTag()); |
| | | if (alivcPusher != null) { |
| | | alivcPusher.startPreviewAysnc(previewSurfaceView); |
| | | |
| | |
| | | startPushThread(); |
| | | } |
| | | } catch (Exception e) { |
| | | Timber.e(e, "startPreviewAysnc in surfaceCreated failed"); |
| | | Timber.e(e, "%s startPreviewAysnc in surfaceCreated failed", getCameraTag()); |
| | | notifyCallback(1, -3, "预览启动失败: " + e.getMessage()); |
| | | } |
| | | } |
| | |
| | | // 设置监听器 |
| | | setupListeners(); |
| | | |
| | | Timber.d("AlivcPusher initialized successfully"); |
| | | Timber.d("%s AlivcPusher initialized successfully", getCameraTag()); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to initialize AlivcPusher"); |
| | | Timber.e(e, "%s Failed to initialize AlivcPusher", getCameraTag()); |
| | | notifyCallback(1, -3, "初始化推流SDK失败: " + e.getMessage()); |
| | | } |
| | | } |
| | |
| | | alivcPusher.setLivePushInfoListener(new AlivcLivePushInfoListener() { |
| | | @Override |
| | | public void onPreviewStarted(AlivcLivePusher alivcLivePusher) { |
| | | Timber.d("onPreviewStarted"); |
| | | Timber.d("%s onPreviewStarted", getCameraTag()); |
| | | mainHandler.postDelayed(()->{ |
| | | // 预览就绪后再启动推流,避免 INIT 状态直接 startPush 报错 |
| | | if (alivcPusher != null && pushUrl != null && !pushUrl.isEmpty()) { |
| | | try { |
| | | AlivcLivePushStats s = alivcPusher.getCurrentStatus(); |
| | | Timber.i("onPreviewStarted, current status=%s", s != null ? s.name() : "null"); |
| | | Timber.d("开始推流: %s", pushUrl); |
| | | Timber.i("%s onPreviewStarted, current status=%s", getCameraTag(), s != null ? s.name() : "null"); |
| | | Timber.d("%s 开始推流: %s", getCameraTag(), pushUrl); |
| | | alivcPusher.startPushAysnc(pushUrl); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "startPushAysnc failed"); |
| | | Timber.e(e, "%s startPushAysnc failed", getCameraTag()); |
| | | notifyCallback(1, -3, "启动推流失败: " + e.getMessage()); |
| | | } |
| | | } |
| | |
| | | |
| | | @Override |
| | | public void onPushStarted(AlivcLivePusher alivcLivePusher) { |
| | | Timber.d("onPushStarted"); |
| | | Timber.d("%s onPushStarted", getCameraTag()); |
| | | pushStarted = true; |
| | | // startAudioTransfer(); |
| | | notifyCallback(1, 0, "推流已开始,分辨率: " + resolutionArr[0] + "x" + resolutionArr[1]); |
| | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | isRunning = true; |
| | | pushThread = new PushThread(); |
| | | pushThread.start(); |
| | | Timber.d("Push thread started"); |
| | | Timber.d("%s Push thread started", getCameraTag()); |
| | | } |
| | | } |
| | | |
| | |
| | | @Override |
| | | public void run() { |
| | | super.run(); |
| | | Timber.d("PushThread started"); |
| | | Timber.d("%s PushThread started", getCameraTag()); |
| | | |
| | | try { |
| | | int width = resolutionArr[0]; |
| | |
| | | int bufferSize = width * height * 3 / 2; |
| | | byte[] buffer = new byte[bufferSize]; |
| | | |
| | | Timber.d("开始推送视频数据,分辨率: %dx%d", width, height); |
| | | Timber.d("%s 开始推送视频数据,分辨率: %dx%d", getCameraTag(), width, height); |
| | | |
| | | // 循环处理摄像头数据 |
| | | while (isRunning && cameraExists) { |
| | | // 处理摄像头数据 |
| | | int processResult = usbCamera.processCamera(); |
| | | if (processResult == -1) { |
| | | Timber.w("processCamera返回-1,摄像头可能断开"); |
| | | Timber.w("%s processCamera返回-1,摄像头可能断开", getCameraTag()); |
| | | cameraExists = false; |
| | | notifyCallback(1, -1, "USB摄像头断开"); |
| | | break; |
| | |
| | | 0 // rotation |
| | | ); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Error pushing frame"); |
| | | Timber.e(e, "%s Error pushing frame", getCameraTag()); |
| | | } |
| | | } else if (!pushStarted) { |
| | | // 等待 onPushStarted 后再喂帧,避免 SDK invalid state |
| | |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Error in push thread"); |
| | | Timber.e(e, "%s Error in push thread", getCameraTag()); |
| | | cameraExists = false; |
| | | notifyCallback(1, -1, "推流线程异常: " + e.getMessage()); |
| | | } finally { |
| | | Timber.d("PushThread ended"); |
| | | Timber.d("%s PushThread 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) { |
| | |
| | | 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) { |