6个文件已修改
431 ■■■■■ 已修改文件
app/src/main/java/com/safeluck/floatwindow/FloatingService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/P2UsbCameraVideoService.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/ResponseVO.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/manager/AndroidCameraRecordManager.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraPushManager.java 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraRecordManager.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/FloatingService.java
@@ -198,7 +198,7 @@
    private void startMedia(MediaArgu media) {
        if (media == null) {
            Timber.w("startMedia: media is null");
            notifyCallback(1, -1, "MediaArgu is null");
            notifyCallback(1, -1, "MediaArgu is null",1);
            return;
        }
@@ -210,7 +210,7 @@
                    p2Service.startMedia(media);
                } catch (RemoteException e) {
                    Timber.e(e, "startMedia forward to P2 failed");
                    notifyCallback(1, -3, "启动P2服务失败: " + e.getMessage());
                    notifyCallback(1, -3, "启动P2服务失败: " + e.getMessage(),2);
                }
            } else {
                // 等待连接完成后执行
@@ -232,7 +232,7 @@
                usbCameraPushManager.startPush(media);
            } else {
                Timber.w("推流仅支持USB摄像头");
                notifyCallback(1, -4, "推流仅支持USB摄像头");
                notifyCallback(1, -4, "推流仅支持USB摄像头",1);
            }
        } else {
            // 录像
@@ -331,9 +331,10 @@
    
    /**
     * 通知回调:统一使用 ResponseVO
     * 使用 synchronized 确保 RemoteCallbackList 操作的线程安全
     * @param response ResponseVO 对象,包含 type、errCode、message
     */
    private void notifyCallback(ResponseVO response) {
    private synchronized void notifyCallback(ResponseVO response) {
        if (response == null) {
            return;
        }
@@ -353,10 +354,12 @@
     * @param type 类型:0-录像 1-推流 2-文件名类型等
     * @param errCode 错误码:0-成功,其他为错误码
     * @param message 消息内容
     * @param cameraId 0-android camera 1-P1 usb 2-P2 usb
     */
    private void notifyCallback(int type, int errCode, String message) {
    private void notifyCallback(int type, int errCode, String message,int cameraId) {
        ResponseVO response = new ResponseVO();
        response.setType(type);
        response.setCameraId(cameraId);
        response.setErrCode(errCode);
        response.setMessage(message);
        notifyCallback(response);
app/src/main/java/com/safeluck/floatwindow/P2UsbCameraVideoService.java
@@ -115,13 +115,13 @@
    private void startMedia(MediaArgu media) {
        if (media == null) {
            notifyCallback(1, -1, "MediaArgu is null");
            notifyCallback(1, -1, "MediaArgu is null",2);
            return;
        }
        // 该服务只处理 USB 摄像头
        if (!media.isUsedOutCamera()) {
            notifyCallback(1, -4, "P2 service only supports USB camera");
            notifyCallback(1, -4, "P2 service only supports USB camera",2);
            return;
        }
@@ -173,7 +173,11 @@
        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++) {
@@ -186,8 +190,16 @@
        mCallbacks.finishBroadcast();
    }
    private void notifyCallback(int type, int errCode, String message) {
    /***
     *
     * @param type
     * @param errCode
     * @param message
     * @param cameraId 0-内置摄像头(android camera) 1-P1 2-P2
     */
    private void notifyCallback(int type, int errCode, String message,int cameraId) {
        ResponseVO response = new ResponseVO();
        response.setCameraId(cameraId);
        response.setType(type);
        response.setErrCode(errCode);
        response.setMessage(message);
app/src/main/java/com/safeluck/floatwindow/ResponseVO.java
@@ -11,6 +11,8 @@
 */
public class ResponseVO implements Parcelable {
    private int type;//0-luxiang 1-推流 2-文件名类型(在message里面就是文件名)
    private int cameraId;// @param cameraId 0-内置摄像头(android camera) 1-P1 2-P2
    private String message;//提示信息
    private int errCode;//0-成功(正在推流或正在录像)   1,没有外置sd卡 2.长时间断网 3.短时间断网和网络切换 4.推流结束、或录像结束 -1usbCamera打不开,6-usbCamera实际使用的分辨率(包含在message) 7Dev(本机摄像)分辨率 8.录像时长  5-录像片段结束  err:-3 msg:SystemError
@@ -30,6 +32,14 @@
        this.type = type;
    }
    public int getCameraId() {
        return cameraId;
    }
    public void setCameraId(int cameraId) {
        this.cameraId = cameraId;
    }
    public String getMessage() {
        return message;
    }
@@ -46,6 +56,7 @@
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.type);
        dest.writeInt(this.cameraId);
        dest.writeString(this.message);
        dest.writeInt(this.errCode);
    }
@@ -55,6 +66,7 @@
    protected ResponseVO(Parcel in) {
        this.type = in.readInt();
        this.cameraId = in.readInt();
        this.message = in.readString();
        this.errCode = in.readInt();
    }
@@ -75,6 +87,7 @@
    public String toString() {
        return "ResponseVO{" +
                "type=" + type +
                "cameraId=" + cameraId +
                ", message='" + message + '\'' +
                ", errCode=" + errCode +
                '}';
app/src/main/java/com/safeluck/floatwindow/manager/AndroidCameraRecordManager.java
@@ -399,6 +399,8 @@
            response.setType(type);
            response.setErrCode(errCode);
            response.setMessage(message);
            // Android 内置摄像头,cameraId = 0
            response.setCameraId(0);
            callback.onResult(response);
        }
    }
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraPushManager.java
@@ -78,6 +78,9 @@
    // 音频推流线程池(单线程)
    private ExecutorService audioPushExecutor;
    // 保护 alivcPusher / previewSurfaceView 等生命周期,避免 start/stop 并发导致 NPE
    private final Object pusherLock = new Object();
    /**
     * 推流回调接口
     */
@@ -97,9 +100,24 @@
    }
    /**
     * 开始推流
     * 开始推流(对外接口)
     * 注意:AlivcLivePusher 必须在主线程初始化,否则会抛出
     * "Can't create handler inside thread that has not called Looper.prepare()"
     */
    public void startPush(MediaArgu media) {
        // 确保在主线程执行实际的启动逻辑
        if (Looper.myLooper() == Looper.getMainLooper()) {
            startPushInternal(media);
        } else {
            // 当前是 Binder 线程或其他后台线程,切到主线程
            mainHandler.post(() -> startPushInternal(media));
        }
    }
    /**
     * 真正的启动推流逻辑,必须在主线程调用
     */
    private void startPushInternal(MediaArgu media) {
        if (media == null) {
            notifyCallback(1, -1, "MediaArgu is null");
            return;
@@ -117,11 +135,11 @@
        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 {
            // 初始化推流SDK
            // 初始化推流SDK(此时已保证在主线程)
            initAlivcPusher();
            setWaterMask();
            pushStarted = false;
@@ -134,12 +152,11 @@
            }
            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());
        }
    }
@@ -148,131 +165,146 @@
     * 停止推流
     */
    public void stopPush() {
        Timber.d("stopPush called");
        stopPushThread();
//        stopAudioTransfer();
        stopWaterMaskSchedule();
        releaseAlivcPusher();
        if (usbCamera != null) {
            usbCamera.stopCamera();
        // stop 同样强制在主线程串行执行,避免与 init/setupListeners 并发
        if (Looper.myLooper() == Looper.getMainLooper()) {
            stopPushInternal();
        } else {
            mainHandler.post(this::stopPushInternal);
        }
        pushStarted = false;
        notifyCallback(1, 4, "推流已停止");
    }
    private void stopPushInternal() {
        synchronized (pusherLock) {
            Timber.d("%s stopPush called", getCameraTag());
            stopPushThread();
//            stopAudioTransfer();
            stopWaterMaskSchedule();
            releaseAlivcPusherLocked();
            if (usbCamera != null) {
                usbCamera.stopCamera();
            }
            pushStarted = false;
            notifyCallback(1, 4, "推流已停止");
        }
    }
    /**
     * 初始化阿里推流
     */
    private void initAlivcPusher() {
        try {
            alivcLivePushConfig = new AlivcLivePushConfig();
        synchronized (pusherLock) {
            try {
                alivcLivePushConfig = new AlivcLivePushConfig();
            // 根据分辨率设置
            setResolutionFromArray(resolutionArr);
                // 根据分辨率设置
                setResolutionFromArray(resolutionArr);
            // 建议用户使用20fps
            alivcLivePushConfig.setFps(AlivcFpsEnum.FPS_20);
                // 建议用户使用20fps
                alivcLivePushConfig.setFps(AlivcFpsEnum.FPS_20);
            // 打开码率自适应
            alivcLivePushConfig.setEnableBitrateControl(true);
                // 打开码率自适应
                alivcLivePushConfig.setEnableBitrateControl(true);
            // 设置横屏方向
            alivcLivePushConfig.setPreviewOrientation(AlivcPreviewOrientationEnum.ORIENTATION_LANDSCAPE_HOME_LEFT);
                // 设置横屏方向
                alivcLivePushConfig.setPreviewOrientation(AlivcPreviewOrientationEnum.ORIENTATION_LANDSCAPE_HOME_LEFT);
            // 设置音频编码模式
            alivcLivePushConfig.setAudioProfile(AlivcAudioAACProfileEnum.AAC_LC);
                // 设置音频编码模式
                alivcLivePushConfig.setAudioProfile(AlivcAudioAACProfileEnum.AAC_LC);
            // 设置摄像头类型
            alivcLivePushConfig.setCameraType(AlivcLivePushCameraTypeEnum.CAMERA_TYPE_BACK);
                // 设置摄像头类型
                alivcLivePushConfig.setCameraType(AlivcLivePushCameraTypeEnum.CAMERA_TYPE_BACK);
            // 设置视频编码模式为硬编码
            alivcLivePushConfig.setVideoEncodeMode(AlivcEncodeModeEnum.Encode_MODE_HARD);
                // 设置视频编码模式为硬编码
                alivcLivePushConfig.setVideoEncodeMode(AlivcEncodeModeEnum.Encode_MODE_HARD);
            // 关闭美颜
            alivcLivePushConfig.setBeautyOn(false);
                // 关闭美颜
                alivcLivePushConfig.setBeautyOn(false);
            // 清晰度优先模式
            alivcLivePushConfig.setQualityMode(AlivcQualityModeEnum.QM_RESOLUTION_FIRST);
                // 清晰度优先模式
                alivcLivePushConfig.setQualityMode(AlivcQualityModeEnum.QM_RESOLUTION_FIRST);
            // 设置自定义流模式
            alivcLivePushConfig.setExternMainStream(true);
            alivcLivePushConfig.setAlivcExternMainImageFormat(AlivcImageFormat.IMAGE_FORMAT_YUV420P);
                // 设置自定义流模式
                alivcLivePushConfig.setExternMainStream(true);
                alivcLivePushConfig.setAlivcExternMainImageFormat(AlivcImageFormat.IMAGE_FORMAT_YUV420P);
            // 初始化推流器
            alivcPusher = new AlivcLivePusher();
            alivcPusher.init(context.getApplicationContext(), alivcLivePushConfig);
            // 外部自定义流模式下,同样需要先开启预览,让状态从 INIT 进入 PREVIEWED
            // 创建一个隐藏的 Window 来承载 SurfaceView,确保 Surface 能够被创建
            windowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
            previewSurfaceView = new SurfaceView(context.getApplicationContext());
                // 初始化推流器:先用局部变量,避免中途被 stop 置空导致 setupListeners NPE
                AlivcLivePusher localPusher = new AlivcLivePusher();
                localPusher.init(context.getApplicationContext(), alivcLivePushConfig);
            // 在 SurfaceView 的 surfaceCreated 回调中再启动预览,确保 Surface 已经创建
            previewSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                    try {
                        Timber.d("previewSurfaceView surfaceCreated, startPreviewAysnc");
                        if (alivcPusher != null) {
                            alivcPusher.startPreviewAysnc(previewSurfaceView);
                // 外部自定义流模式下,同样需要先开启预览,让状态从 INIT 进入 PREVIEWED
                // 创建一个隐藏的 Window 来承载 SurfaceView,确保 Surface 能够被创建
                windowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
                previewSurfaceView = new SurfaceView(context.getApplicationContext());
                // 在 SurfaceView 的 surfaceCreated 回调中再启动预览,确保 Surface 已经创建
                previewSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
                    @Override
                    public void surfaceCreated(SurfaceHolder holder) {
                        try {
                            Timber.d("%s previewSurfaceView surfaceCreated, startPreviewAysnc", getCameraTag());
                            synchronized (pusherLock) {
                                if (alivcPusher != null && previewSurfaceView != null) {
                                    alivcPusher.startPreviewAysnc(previewSurfaceView);
                            // 启动摄像头数据推送线程
                            startPushThread();
                                    // 启动摄像头数据推送线程
                                    startPushThread();
                                }
                            }
                        } catch (Exception e) {
                            Timber.e(e, "%s startPreviewAysnc in surfaceCreated failed", getCameraTag());
                            notifyCallback(1, -3, "预览启动失败: " + e.getMessage());
                        }
                    } catch (Exception e) {
                        Timber.e(e, "startPreviewAysnc in surfaceCreated failed");
                        notifyCallback(1, -3, "预览启动失败: " + e.getMessage());
                    }
                    @Override
                    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                        Timber.d("previewSurfaceView surfaceChanged: %dx%d", width, height);
                    }
                    @Override
                    public void surfaceDestroyed(SurfaceHolder holder) {
                        Timber.d("previewSurfaceView surfaceDestroyed");
                    }
                });
                // 将 SurfaceView 添加到隐藏的 Window 中,这样 Surface 才会被创建
                WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                        1, 1, // 1x1 像素,几乎不可见
                        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                        android.graphics.PixelFormat.TRANSLUCENT
                );
                params.x = -1000; // 移到屏幕外
                params.y = -1000;
                params.alpha = 0.0f; // 完全透明
                try {
                    windowManager.addView(previewSurfaceView, params);
                    Timber.d("previewSurfaceView added to window");
                } catch (Exception e) {
                    Timber.e(e, "Failed to add previewSurfaceView to window");
                    // 如果添加失败,尝试使用 TYPE_APPLICATION 类型
                    params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
                    try {
                        windowManager.addView(previewSurfaceView, params);
                        Timber.d("previewSurfaceView added to window with TYPE_APPLICATION");
                    } catch (Exception e2) {
                        Timber.e(e2, "Failed to add previewSurfaceView with TYPE_APPLICATION");
                    }
                }
                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                    Timber.d("previewSurfaceView surfaceChanged: %dx%d", width, height);
                }
                // 设置监听器(对局部 pusher 先绑定),最后再发布到字段
                setupListeners(localPusher);
                alivcPusher = localPusher;
                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                    Timber.d("previewSurfaceView surfaceDestroyed");
                }
            });
            // 将 SurfaceView 添加到隐藏的 Window 中,这样 Surface 才会被创建
            WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                    1, 1, // 1x1 像素,几乎不可见
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                            | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                    android.graphics.PixelFormat.TRANSLUCENT
            );
            params.x = -1000; // 移到屏幕外
            params.y = -1000;
            params.alpha = 0.0f; // 完全透明
            try {
                windowManager.addView(previewSurfaceView, params);
                Timber.d("previewSurfaceView added to window");
                Timber.d("%s AlivcPusher initialized successfully", getCameraTag());
            } catch (Exception e) {
                Timber.e(e, "Failed to add previewSurfaceView to window");
                // 如果添加失败,尝试使用 TYPE_APPLICATION 类型
                params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
                try {
                    windowManager.addView(previewSurfaceView, params);
                    Timber.d("previewSurfaceView added to window with TYPE_APPLICATION");
                } catch (Exception e2) {
                    Timber.e(e2, "Failed to add previewSurfaceView with TYPE_APPLICATION");
                }
                Timber.e(e, "%s Failed to initialize AlivcPusher", getCameraTag());
                notifyCallback(1, -3, "初始化推流SDK失败: " + e.getMessage());
            }
            // 设置监听器
            setupListeners();
            Timber.d("AlivcPusher initialized successfully");
        } catch (Exception e) {
            Timber.e(e, "Failed to initialize AlivcPusher");
            notifyCallback(1, -3, "初始化推流SDK失败: " + e.getMessage());
        }
    }
@@ -395,22 +427,26 @@
    /**
     * 设置监听器
     */
    private void setupListeners() {
    private void setupListeners(AlivcLivePusher pusher) {
        if (pusher == null) {
            Timber.w("%s setupListeners skipped: pusher is null", getCameraTag());
            return;
        }
        // 推流信息监听器
        alivcPusher.setLivePushInfoListener(new AlivcLivePushInfoListener() {
        pusher.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());
                        }
                    }
@@ -425,7 +461,7 @@
            @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]);
@@ -475,7 +511,7 @@
        });
        // 错误监听器
        alivcPusher.setLivePushErrorListener(new AlivcLivePushErrorListener() {
        pusher.setLivePushErrorListener(new AlivcLivePushErrorListener() {
            @Override
            public void onSystemError(AlivcLivePusher alivcLivePusher, AlivcLivePushError alivcLivePushError) {
                Timber.e("onSystemError: %s", alivcLivePushError.toString());
@@ -496,7 +532,7 @@
        });
        // 网络监听器
        alivcPusher.setLivePushNetworkListener(new AlivcLivePushNetworkListener() {
        pusher.setLivePushNetworkListener(new AlivcLivePushNetworkListener() {
            @Override
            public void onNetworkPoor(AlivcLivePusher alivcLivePusher) {
                Timber.w("onNetworkPoor");
@@ -610,7 +646,7 @@
            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;
                }
@@ -621,7 +657,7 @@
            // 成功标准:prepareCamera 返回 0
            return ret == 0;
        } catch (Exception e) {
            Timber.e(e, "打开USB摄像头异常");
            Timber.e(e, "%s 打开USB摄像头异常", getCameraTag());
            return false;
        }
    }
@@ -634,7 +670,7 @@
            isRunning = true;
            pushThread = new PushThread();
            pushThread.start();
            Timber.d("Push thread started");
            Timber.d("%s Push thread started", getCameraTag());
        }
    }
@@ -658,6 +694,12 @@
     * 释放阿里推流资源
     */
    private void releaseAlivcPusher() {
        synchronized (pusherLock) {
            releaseAlivcPusherLocked();
        }
    }
    private void releaseAlivcPusherLocked() {
        // 兜底:防止外部没有走 stopPush
        stopWaterMaskSchedule();
        // 移除隐藏的 SurfaceView
@@ -677,7 +719,7 @@
                Timber.d("当前推流状态: %s", stats != null ? stats.name() : "null");
                if (stats != null && (stats == AlivcLivePushStats.PUSHED ||
                    stats == AlivcLivePushStats.PREVIEWED)) {
                        stats == AlivcLivePushStats.PREVIEWED)) {
                    alivcPusher.stopPush();
                }
                alivcPusher.destroy();
@@ -697,7 +739,7 @@
        @Override
        public void run() {
            super.run();
            Timber.d("PushThread started");
            Timber.d("%s PushThread started", getCameraTag());
            try {
                int width = resolutionArr[0];
@@ -708,14 +750,14 @@
                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;
@@ -737,7 +779,7 @@
                                    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
@@ -749,13 +791,25 @@
                }
                
            } 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
    }
    
    /**
@@ -767,6 +821,13 @@
            response.setType(type);
            response.setErrCode(errCode);
            response.setMessage(message);
            // 设置 cameraId:根据 usbCameraId 区分 P1(1) 和 P2(2)
            if (mediaArgu != null) {
                int usbCameraId = mediaArgu.getUsbCameraId();
                response.setCameraId(usbCameraId == 2 ? 2 : 1); // 2 -> P2, 其他 -> P1
            } else {
                response.setCameraId(1); // 默认 P1
            }
            callback.onResult(response);
        }
    }
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraRecordManager.java
@@ -122,7 +122,7 @@
        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 {
@@ -134,14 +134,14 @@
            }
            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());
        }
    }
@@ -262,7 +262,7 @@
     * 停止录像
     */
    public void stopRecord() {
        Timber.d("stopRecord called");
        Timber.d("%s stopRecord called", getCameraTag());
        stopWaterMaskSchedule();
        
@@ -321,7 +321,7 @@
            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;
                }
@@ -332,7 +332,7 @@
            // 成功标准:prepareCamera 返回 0
            return ret == 0;
        } catch (Exception e) {
            Timber.e(e, "打开USB摄像头异常");
            Timber.e(e, "%s 打开USB摄像头异常", getCameraTag());
            return false;
        }
    }
@@ -384,22 +384,22 @@
                );
                
                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;
            }
            
@@ -411,11 +411,11 @@
            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;
        }
    }
@@ -487,7 +487,7 @@
            isRunning = true;
            recordThread = new RecordThread();
            recordThread.start();
            Timber.d("Record thread started");
            Timber.d("%s Record thread started", getCameraTag());
        }
    }
    
@@ -514,7 +514,7 @@
        @Override
        public void run() {
            super.run();
            Timber.d("RecordThread started");
            Timber.d("%s RecordThread started", getCameraTag());
            
            try {
                int width = resolutionArr[0];
@@ -535,7 +535,7 @@
                if (!isP2Mode && audioRecord != null) {
                    // 启动音频录制
                    audioRecord.startRecording();
                    Timber.d("音频录制已启动(P1模式)");
                    Timber.d("%s 音频录制已启动(P1模式)", getCameraTag());
                    
                    // 注意:不要在这里主动检查音频编码器输出格式
                    // 因为 MediaCodec 的 getOutputFormat() 在编码器启动后可能返回 null
@@ -545,10 +545,10 @@
                    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();
@@ -613,7 +613,7 @@
                    // 处理摄像头数据
                    int processResult = usbCamera.processCamera();
                    if (processResult == -1) {
                        Timber.w("processCamera返回-1,摄像头可能断开");
                        Timber.w("%s processCamera返回-1,摄像头可能断开", getCameraTag());
                        cameraExists = false;
                        notifyCallback(0, -1, "USB摄像头断开");
                        break;
@@ -635,7 +635,7 @@
                }
                
            } 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 {
@@ -645,7 +645,7 @@
                    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;
                }
@@ -658,7 +658,7 @@
                    completedVideoFile = null;
                }
                Timber.d("RecordThread ended");
                Timber.d("%s RecordThread ended", getCameraTag());
            }
        }
        
@@ -776,8 +776,13 @@
            if (originalFile.renameTo(newFile)) {
                Timber.d("文件重命名成功: %s -> %s", originalFile.getName(), newFileName);
                // 更新回调中的文件名
                if (durationSeconds==666){
                    Timber.i("录制错误,此文件[%s]需要都踢",newFile.getAbsolutePath());
                    newFile.delete();
                }else{
                    notifyCallback(2,0,newFileName);
                }
                notifyCallback(2,0,newFileName);
            } else {
                Timber.e("文件重命名失败: %s -> %s", originalFile.getName(), newFileName);
            }
@@ -813,7 +818,7 @@
            }
        }
        // 如果获取失败,返回默认值60秒
        return 60;
        return 666;
    }
    /**
@@ -901,11 +906,11 @@
        @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;
            }
            
@@ -927,9 +932,9 @@
                }
                
            } 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());
            }
        }
        
@@ -1003,6 +1008,18 @@
    }
    
    /**
     * 获取摄像头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) {
@@ -1011,6 +1028,13 @@
            response.setType(type);
            response.setErrCode(errCode);
            response.setMessage(message);
            // 设置 cameraId:根据 usbCameraId 区分 P1(1) 和 P2(2)
            if (mediaArgu != null) {
                int usbCameraId = mediaArgu.getUsbCameraId();
                response.setCameraId(usbCameraId == 2 ? 2 : 1); // 2 -> P2, 其他 -> P1
            } else {
                response.setCameraId(1); // 默认 P1
            }
            callback.onResult(response);
        }
    }