1个文件已添加
7个文件已修改
677 ■■■■ 已修改文件
app/src/main/aidl/com/safeluck/floatwindow/IMediaAidlInterface.aidl 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/FloatingService.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/MainActivity.kt 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/P2UsbCameraVideoService.java 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/manager/AndroidCameraRecordManager.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraPushManager.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraRecordManager.java 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/util/VideoFileUtils.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/aidl/com/safeluck/floatwindow/IMediaAidlInterface.aidl
@@ -15,7 +15,8 @@
//            void transferInfo(String info);
            void startMedia(in MediaArgu media);
            void stopMedia();
            void stopMedia(in MediaArgu media);
            void sendInfo(String s,int processId);//水印信息 json,processid=1 为发给FloatingService的的信息;procesid=2为发给P2UsbCameraVideoService
            void registerCallback(in IMyCallback cb);
            void unregisterCallback(in IMyCallback cb);
}
app/src/main/java/com/safeluck/floatwindow/FloatingService.java
@@ -12,6 +12,7 @@
import com.safeluck.floatwindow.manager.AndroidCameraRecordManager;
import com.safeluck.floatwindow.manager.UsbCameraPushManager;
import com.safeluck.floatwindow.manager.UsbCameraRecordManager;
import com.safeluck.floatwindow.util.GlobalData;
import timber.log.Timber;
@@ -86,9 +87,7 @@
        NONE,
        USB_PUSH,
        USB_RECORD,
        ANDROID_RECORD,
        P2_USB_PUSH,
        P2_USB_RECORD
        ANDROID_RECORD
    }
    
    // AIDL Binder
@@ -107,11 +106,24 @@
        }
        
        @Override
        public void stopMedia() throws RemoteException {
        public void stopMedia(MediaArgu media) throws RemoteException {
            Timber.d("stopMedia called via AIDL");
            FloatingService.this.stopMedia();
            FloatingService.this.stopMedia(media);
        }
        @Override
        public void sendInfo(String s, int processId) throws RemoteException {
            if (processId == 2) {
                ensureP2Bound();
                if (p2Service != null) {
                    p2Service.sendInfo(s,processId);
                }
            }else{
                GlobalData.getInstance().setWaterMaskInfo(s);
            }
        }
        @Override
        public void registerCallback(IMyCallback cb) throws RemoteException {
            if (cb != null) {
@@ -190,14 +202,12 @@
            return;
        }
        // usbCameraId == 2:走 P2 跨进程服务,支持两路 USB 同时工作
        // usbCameraId == 2:走 P2 跨进程服务,支持两路 USB 同时工作(本 Service 不再跟踪其状态)
        if (media.isUsedOutCamera() && media.getUsbCameraId() == 2) {
            stopCurrentManager();
            ensureP2Bound();
            if (p2Service != null) {
                try {
                    p2Service.startMedia(media);
                    currentManagerType = media.isPush() ? ManagerType.P2_USB_PUSH : ManagerType.P2_USB_RECORD;
                } catch (RemoteException e) {
                    Timber.e(e, "startMedia forward to P2 failed");
                    notifyCallback(1, -3, "启动P2服务失败: " + e.getMessage());
@@ -205,7 +215,6 @@
            } else {
                // 等待连接完成后执行
                pendingP2StartMedia = media;
                currentManagerType = media.isPush() ? ManagerType.P2_USB_PUSH : ManagerType.P2_USB_RECORD;
            }
            return;
        }
@@ -242,7 +251,7 @@
    }
    
    /**
     * 停止当前管理器
     * 停止当前本地管理器(仅管理本进程中的 USB(P1)/Android 录像或推流)
     */
    private void stopCurrentManager() {
        switch (currentManagerType) {
@@ -261,18 +270,6 @@
                    androidCameraRecordManager.stopRecord();
                }
                break;
            case P2_USB_PUSH:
            case P2_USB_RECORD:
                if (p2Service != null) {
                    try {
                        p2Service.stopMedia();
                    } catch (RemoteException e) {
                        Timber.e(e, "stopMedia forward to P2 failed");
                    }
                } else {
                    pendingP2StartMedia = null;
                }
                break;
            case NONE:
                break;
        }
@@ -280,11 +277,26 @@
    }
    
    /**
     * 停止媒体
     * 停止媒体(通过 AIDL 调用,带 MediaArgu,用于区分 P1/P2)
     */
    private void stopMedia() {
        Timber.d("stopMedia called");
        stopCurrentManager();
    private void stopMedia(MediaArgu media) {
        Timber.d("stopMedia called, media=%s", media);
        if (media != null && media.isUsedOutCamera() && media.getUsbCameraId() == 2) {
            // P2 USB 摄像头由 P2UsbCameraVideoService 管理
            ensureP2Bound();
            if (p2Service != null) {
                try {
                    p2Service.stopMedia(media);
                } catch (RemoteException e) {
                    Timber.e(e, "stopMedia forward to P2 failed");
                }
            } else {
                pendingP2StartMedia = null;
            }
        } else {
            // 其他情况(包括 P1 USB 与 Android 相机)由本 Service 自己管理
            stopCurrentManager();
        }
    }
    
    @Override
@@ -296,7 +308,7 @@
    @Override
    public void onDestroy() {
        super.onDestroy();
        stopMedia();
        stopCurrentManager();
        mCallbacks.kill();
        if (p2Bound) {
            try {
@@ -349,4 +361,29 @@
        response.setMessage(message);
        notifyCallback(response);
    }
  /*  private BroadcastReceiver mCloseBroadCastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals("com.safeluck.floatwindow_video2.studyinfo")) {
                String str=  intent.getStringExtra("info");
                Log.i(TAG, "广播"+str);
                GlobalData.getInstance().setWaterMaskInfo(str);
            }
        }
    };
    private void registBroadCastReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.safeluck.floatwindow_video2.studyinfo");
        registerReceiver(mCloseBroadCastReceiver,filter);
    }
    private void unRegisterTestBroadReceiver() {
        if (mCloseBroadCastReceiver != null) {
            //查询到相应的BroadcastReceiver
            unregisterReceiver(mCloseBroadCastReceiver);
        }
    }*/
}
app/src/main/java/com/safeluck/floatwindow/MainActivity.kt
@@ -80,7 +80,11 @@
                        onStartUsbRecord = { startUsbRecord() },
                        onStopUsbRecord = { stopUsbRecord() },
                        onStartUsbPush = { startUsbPush() },
                        onStopUsbPush = { stopUsbPush() }
                        onStopUsbPush = { stopUsbPush() },
                        onStartP2Push = { startP2Push() },
                        onStopP2Push = { stopP2Push() },
                        onStartP2Record = { startP2Record() },
                        onStopP2Record = { stopP2Record() }
                    )
                }
            }
@@ -119,7 +123,12 @@
        }
        
        try {
            mediaAidlInterface?.stopMedia()
            val mediaArgu = MediaArgu().apply {
                isPush = false
                isUsedOutCamera = false // Android 内置摄像头
                usbCameraId = 0 // Android 相机,usbCameraId 为 0
            }
            mediaAidlInterface?.stopMedia(mediaArgu)
            Timber.d("Stopped Android camera record")
        } catch (e: RemoteException) {
            Timber.e(e, "Error stopping Android record")
@@ -136,6 +145,7 @@
            val mediaArgu = MediaArgu().apply {
                isPush = false
                isUsedOutCamera = true // USB 摄像头
                usbCameraId = 1 // P1 USB 摄像头
                codeRate = 0
                frameRate = 0
                m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率
@@ -145,7 +155,7 @@
            
            mediaAidlInterface?.registerCallback(callback)
            mediaAidlInterface?.startMedia(mediaArgu)
            Timber.d("Started USB camera record")
            Timber.d("Started USB camera record (P1)")
        } catch (e: RemoteException) {
            Timber.e(e, "Error starting USB record")
        }
@@ -158,8 +168,13 @@
        }
        
        try {
            mediaAidlInterface?.stopMedia()
            Timber.d("Stopped USB camera record")
            val mediaArgu = MediaArgu().apply {
                isPush = false
                isUsedOutCamera = true // USB 摄像头
                usbCameraId = 1 // P1 USB 摄像头
            }
            mediaAidlInterface?.stopMedia(mediaArgu)
            Timber.d("Stopped USB camera record (P1)")
        } catch (e: RemoteException) {
            Timber.e(e, "Error stopping USB record")
        }
@@ -173,6 +188,7 @@
        
        try {
            val mediaArgu = MediaArgu().apply {
                usbCameraId = 1 // P1 USB 摄像头
                isPush = true
                isUsedOutCamera = true // USB 摄像头
                codeRate = 0
@@ -185,7 +201,7 @@
            
            mediaAidlInterface?.registerCallback(callback)
            mediaAidlInterface?.startMedia(mediaArgu)
            Timber.d("Started USB camera push")
            Timber.d("Started USB camera push (P1)")
        } catch (e: RemoteException) {
            Timber.e(e, "Error starting USB push")
        }
@@ -198,10 +214,106 @@
        }
        
        try {
            mediaAidlInterface?.stopMedia()
            Timber.d("Stopped USB camera push")
            val mediaArgu = MediaArgu().apply {
                usbCameraId = 1 // P1 USB 摄像头
                isPush = true
                isUsedOutCamera = true // USB 摄像头
            }
            mediaAidlInterface?.stopMedia(mediaArgu)
            Timber.d("Stopped USB camera push (P1)")
        } catch (e: RemoteException) {
            Timber.e(e, "Error stopping USB push")
        }
    }
    private fun startP2Push() {
        if (mediaAidlInterface == null) {
            Timber.w("Service not bound, cannot start P2 push")
            return
        }
        try {
            val mediaArgu = MediaArgu().apply {
                usbCameraId = 2 // P2 USB 摄像头
                isPush = true
                isUsedOutCamera = true // USB 摄像头
                codeRate = 0
                frameRate = 0
                m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率
                url = "rtmp://192.168.16.143/live/livestream" // TODO: 需要设置实际的推流地址
                userName = ""
                pwd = ""
            }
            mediaAidlInterface?.registerCallback(callback)
            mediaAidlInterface?.startMedia(mediaArgu)
            Timber.d("Started P2 USB camera push")
        } catch (e: RemoteException) {
            Timber.e(e, "Error starting P2 push")
        }
    }
    private fun stopP2Push() {
        if (mediaAidlInterface == null) {
            Timber.w("Service not bound, cannot stop P2 push")
            return
        }
        try {
            val mediaArgu = MediaArgu().apply {
                usbCameraId = 2 // P2 USB 摄像头
                isPush = true
                isUsedOutCamera = true // USB 摄像头
            }
            mediaAidlInterface?.stopMedia(mediaArgu)
            Timber.d("Stopped P2 USB camera push")
        } catch (e: RemoteException) {
            Timber.e(e, "Error stopping P2 push")
        }
    }
    private fun startP2Record() {
        if (mediaAidlInterface == null) {
            Timber.w("Service not bound, cannot start P2 record")
            return
        }
        try {
            val mediaArgu = MediaArgu().apply {
                isPush = false
                isUsedOutCamera = true // USB 摄像头
                usbCameraId = 2 // P2 USB 摄像头
                codeRate = 0
                frameRate = 0
                m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率
                recordTime = 0
                tfCardFlag = 0 // 内部存储
            }
            mediaAidlInterface?.registerCallback(callback)
            mediaAidlInterface?.startMedia(mediaArgu)
            Timber.d("Started P2 USB camera record")
        } catch (e: RemoteException) {
            Timber.e(e, "Error starting P2 record")
        }
    }
    private fun stopP2Record() {
        if (mediaAidlInterface == null) {
            Timber.w("Service not bound, cannot stop P2 record")
            return
        }
        try {
            val mediaArgu = MediaArgu().apply {
                isPush = false
                isUsedOutCamera = true // USB 摄像头
                usbCameraId = 2 // P2 USB 摄像头
            }
            mediaAidlInterface?.stopMedia(mediaArgu)
            Timber.d("Stopped P2 USB camera record")
        } catch (e: RemoteException) {
            Timber.e(e, "Error stopping P2 record")
        }
    }
    
@@ -240,7 +352,11 @@
    onStartUsbRecord: () -> Unit,
    onStopUsbRecord: () -> Unit,
    onStartUsbPush: () -> Unit,
    onStopUsbPush: () -> Unit
    onStopUsbPush: () -> Unit,
    onStartP2Push: () -> Unit,
    onStopP2Push: () -> Unit,
    onStartP2Record: () -> Unit,
    onStopP2Record: () -> Unit
) {
    Column(
        modifier = Modifier.fillMaxSize()
@@ -339,22 +455,62 @@
            
            Divider(modifier = Modifier.padding(vertical = 8.dp))
            
            // 按钮 7: 开始 USB 推流
            // 按钮 7: 开始 USB 推流 (P1)
            Button(
                onClick = onStartUsbPush,
                modifier = Modifier.fillMaxWidth(),
                enabled = isServiceBound
            ) {
                Text("7. 开始 USB 推流")
                Text("7. 开始 USB 推流 (P1)")
            }
            
            // 按钮 8: 结束 USB 推流
            // 按钮 8: 结束 USB 推流 (P1)
            Button(
                onClick = onStopUsbPush,
                modifier = Modifier.fillMaxWidth(),
                enabled = isServiceBound
            ) {
                Text("8. 结束 USB 推流")
                Text("8. 结束 USB 推流 (P1)")
            }
            Divider(modifier = Modifier.padding(vertical = 8.dp))
            // 按钮 9: 开始 P2 USB 推流
            Button(
                onClick = onStartP2Push,
                modifier = Modifier.fillMaxWidth(),
                enabled = isServiceBound
            ) {
                Text("9. 开始 P2 USB 推流")
            }
            // 按钮 10: 结束 P2 USB 推流
            Button(
                onClick = onStopP2Push,
                modifier = Modifier.fillMaxWidth(),
                enabled = isServiceBound
            ) {
                Text("10. 结束 P2 USB 推流")
            }
            Divider(modifier = Modifier.padding(vertical = 8.dp))
            // 按钮 11: 开始 P2 USB 录像
            Button(
                onClick = onStartP2Record,
                modifier = Modifier.fillMaxWidth(),
                enabled = isServiceBound
            ) {
                Text("11. 开始 P2 USB 录像")
            }
            // 按钮 12: 结束 P2 USB 录像
            Button(
                onClick = onStopP2Record,
                modifier = Modifier.fillMaxWidth(),
                enabled = isServiceBound
            ) {
                Text("12. 结束 P2 USB 录像")
            }
            
            Spacer(modifier = Modifier.height(12.dp))
app/src/main/java/com/safeluck/floatwindow/P2UsbCameraVideoService.java
New file
@@ -0,0 +1,197 @@
package com.safeluck.floatwindow;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import com.safeluck.floatwindow.manager.UsbCameraPushManager;
import com.safeluck.floatwindow.manager.UsbCameraRecordManager;
import com.safeluck.floatwindow.util.GlobalData;
import timber.log.Timber;
/**
 * 运行在独立进程(:p2)的 USB 摄像头视频服务,用于同时支持两路 USB 摄像头工作。
 * 约定:当 MediaArgu.usbCameraId == 2 时,调用方应使用该服务进行推流/录像。
 */
public class P2UsbCameraVideoService extends Service {
    private static final String TAG = "P2UsbCameraVideoService";
    private Context context;
    private UsbCameraPushManager usbCameraPushManager;
    private UsbCameraRecordManager usbCameraRecordManager;
    private ManagerType currentManagerType = ManagerType.NONE;
    // 主线程 Handler,用于确保 AlivcLivePusher 初始化在主线程执行
    private final Handler mainHandler = new Handler(Looper.getMainLooper());
    private enum ManagerType {
        NONE,
        USB_PUSH,
        USB_RECORD
    }
    private final RemoteCallbackList<IMyCallback> mCallbacks = new RemoteCallbackList<>();
    private final IMediaAidlInterface.Stub mBinder = new IMediaAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) {
            Timber.d("[%s] basicTypes called", TAG);
        }
        @Override
        public void startMedia(MediaArgu media) throws RemoteException {
            Timber.d("[%s] startMedia called via AIDL", TAG);
            P2UsbCameraVideoService.this.startMedia(media);
        }
        @Override
        public void stopMedia(MediaArgu media) throws RemoteException {
            Timber.d("[%s] stopMedia called via AIDL", TAG);
            P2UsbCameraVideoService.this.stopMedia(media);
        }
        @Override
        public void sendInfo(String s, int processId) throws RemoteException {
            GlobalData.getInstance().setWaterMaskInfo(s);
        }
        @Override
        public void registerCallback(IMyCallback cb) throws RemoteException {
            if (cb != null) {
                mCallbacks.register(cb);
                Timber.d("[%s] Callback registered", TAG);
            }
        }
        @Override
        public void unregisterCallback(IMyCallback cb) throws RemoteException {
            if (cb != null) {
                mCallbacks.unregister(cb);
                Timber.d("[%s] Callback unregistered", TAG);
            }
        }
    };
    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
        usbCameraPushManager = new UsbCameraPushManager(context);
        usbCameraPushManager.setCallback(this::notifyCallback);
        usbCameraRecordManager = new UsbCameraRecordManager(context);
        usbCameraRecordManager.setCallback(this::notifyCallback);
        Timber.d("[%s] onCreate", TAG);
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }
    @Override
    public IBinder onBind(Intent intent) {
        Timber.d("[%s] onBind", TAG);
        return mBinder;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        stopCurrentManager();
        mCallbacks.kill();
        Timber.d("[%s] onDestroy", TAG);
    }
    private void startMedia(MediaArgu media) {
        if (media == null) {
            notifyCallback(1, -1, "MediaArgu is null");
            return;
        }
        // 该服务只处理 USB 摄像头
        if (!media.isUsedOutCamera()) {
            notifyCallback(1, -4, "P2 service only supports USB camera");
            return;
        }
        // 确保在主线程执行,因为 AlivcLivePusher 初始化需要 Handler(必须在有 Looper 的线程)
        if (Looper.myLooper() == Looper.getMainLooper()) {
            startMediaInternal(media);
        } else {
            mainHandler.post(() -> startMediaInternal(media));
        }
    }
    private void startMediaInternal(MediaArgu media) {
        // 先停止当前
        stopCurrentManager();
        if (media.isPush()) {
            currentManagerType = ManagerType.USB_PUSH;
            usbCameraPushManager.startPush(media);
        } else {
            currentManagerType = ManagerType.USB_RECORD;
            usbCameraRecordManager.startRecord(media);
        }
    }
    private void stopMedia(MediaArgu media) {
        // 确保在主线程执行
        if (Looper.myLooper() == Looper.getMainLooper()) {
            stopCurrentManager();
        } else {
            mainHandler.post(this::stopCurrentManager);
        }
    }
    private void stopCurrentManager() {
        switch (currentManagerType) {
            case USB_PUSH:
                if (usbCameraPushManager != null) {
                    usbCameraPushManager.stopPush();
                }
                break;
            case USB_RECORD:
                if (usbCameraRecordManager != null) {
                    usbCameraRecordManager.stopRecord();
                }
                break;
            case NONE:
                break;
        }
        currentManagerType = ManagerType.NONE;
    }
    private void notifyCallback(ResponseVO response) {
        if (response == null) return;
        int count = mCallbacks.beginBroadcast();
        for (int i = 0; i < count; i++) {
            try {
                mCallbacks.getBroadcastItem(i).onResult(response);
            } catch (RemoteException e) {
                Timber.e(e, "[%s] Error notifying callback", TAG);
            }
        }
        mCallbacks.finishBroadcast();
    }
    private void notifyCallback(int type, int errCode, String message) {
        ResponseVO response = new ResponseVO();
        response.setType(type);
        response.setErrCode(errCode);
        response.setMessage(message);
        notifyCallback(response);
    }
}
app/src/main/java/com/safeluck/floatwindow/manager/AndroidCameraRecordManager.java
@@ -179,7 +179,7 @@
        
        try {
            // 创建新的视频文件
            currentVideoFile = VideoFileUtils.getVideoFile(context, mediaArgu.getTfCardFlag());
            currentVideoFile = VideoFileUtils.getVideoFile(context, mediaArgu.getTfCardFlag(),0);
            if (currentVideoFile == null) {
                Timber.e("Failed to create video file");
                return;
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraPushManager.java
@@ -283,7 +283,11 @@
    int fontSize= 24;
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    private void setWaterMask() {
        if (mediaArgu.getUsbCameraId()==2){
            GlobalData.getInstance().getCameraTag="_P2";
        }else{
            GlobalData.getInstance().getCameraTag="_P1";
        }
        // 防止重复 schedule(startPush 可能被多次调用)
        if (watermarkFuture != null && !watermarkFuture.isCancelled()) {
            return;
@@ -329,7 +333,10 @@
                    double speed = GlobalData.getInstance().parseWaterMaskInfo("speed", 0.0, GlobalData.ShareType.DOUBLE);
                    String czh = GlobalData.getInstance().parseWaterMaskInfo("car_license", "无", GlobalData.ShareType.STRING) + GlobalData.getInstance().getCameraTag;
                    String czh = GlobalData.getInstance().parseWaterMaskInfo("car_license", "无", GlobalData.ShareType.STRING) +
                            GlobalData.getInstance().getCameraTag;
                    baseY = fontSize*11/10+baseY;
                    watermarkParam = new WatermarkParam(10,resolutionArr[1]-baseY,czh +"    "+String.format("速度:%.1f",speed));
                    watermarkParamList.add(watermarkParam);
@@ -591,9 +598,9 @@
            int usbId = (mediaArgu != null) ? mediaArgu.getUsbCameraId() : 0;
            int[] cameraIds;
            if (usbId == 2) {
                cameraIds = new int[]{2};
                cameraIds = new int[]{2,3};
            } else if (usbId == 1) {
                cameraIds = new int[]{0};
                cameraIds = new int[]{1,2};
            } else {
                cameraIds = new int[]{0, 2};
            }
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraRecordManager.java
@@ -149,7 +149,11 @@
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    private ScheduledFuture<?> watermarkFuture;
    private void setWaterMask() {
        if (mediaArgu.getUsbCameraId()==2){
            GlobalData.getInstance().getCameraTag="_P2";
        }else{
            GlobalData.getInstance().getCameraTag="_P1";
        }
        // 防止重复 schedule(startRecord 可能被多次调用)
        if (watermarkFuture != null && !watermarkFuture.isCancelled()) {
            return;
@@ -293,11 +297,11 @@
            int usbId = (mediaArgu != null) ? mediaArgu.getUsbCameraId() : 0;
            int[] cameraIds;
            if (usbId == 2) {
                cameraIds = new int[]{2};
                cameraIds = new int[]{2,3};
            } else if (usbId == 1) {
                cameraIds = new int[]{0};
                cameraIds = new int[]{1,2};
            } else {
                cameraIds = new int[]{0, 2};
                cameraIds = new int[]{0, 1};
            }
            String cameraName = null; // 不指定特定名称
@@ -340,36 +344,48 @@
            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模式:跳过音频初始化,仅录制视频");
            }
            
            // 创建新的视频文件
            currentVideoFile = VideoFileUtils.getVideoFile(context, mediaArgu.getTfCardFlag());
            currentVideoFile = VideoFileUtils.getVideoFile(context, mediaArgu.getTfCardFlag(),mediaArgu.getUsbCameraId());
            if (currentVideoFile == null) {
                Timber.e("Failed to create video file");
                return false;
@@ -377,11 +393,14 @@
            
            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失败");
@@ -494,19 +513,23 @@
                    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);
                
@@ -543,12 +566,14 @@
                        // 重置开始时间
                        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;
@@ -674,19 +699,39 @@
    }
    
    /**
     * 检查并启动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);
            }
        }
    }
    
@@ -705,6 +750,12 @@
            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; // 总采样数
app/src/main/java/com/safeluck/floatwindow/util/VideoFileUtils.java
@@ -67,9 +67,16 @@
     * 生成视频文件名(时分秒.mp4)
     * @return 文件名,例如:143025.mp4
     */
    public static String generateVideoFileName() {
    public static String generateVideoFileName(int usbCamId) {
        SimpleDateFormat timeFormat = new SimpleDateFormat("HHmmss", Locale.getDefault());
        return timeFormat.format(new Date()) + ".mp4";
        if (usbCamId==2){
            return timeFormat.format(new Date()) + "_P2.mp4";
        }else if (usbCamId==1){
            return timeFormat.format(new Date()) + "_P1.mp4";
        }else{
            return timeFormat.format(new Date()) + ".mp4";
        }
    }
    
    /**
@@ -78,24 +85,25 @@
     * @param tfCardFlag 0-内部存储,1-外部存储
     * @return 完整的文件路径
     */
    public static File getVideoFile(Context context, int tfCardFlag) {
    public static File getVideoFile(Context context, int tfCardFlag,int usbCameraId) {
        File dateDir = getVideoDirectory(context, tfCardFlag);
        if (dateDir == null) {
            return null;
        }
        
        String fileName = generateVideoFileName();
        String fileName = generateVideoFileName(usbCameraId);
        return new File(dateDir, fileName);
    }
    
    /**
     * 获取视频文件路径字符串
     * 获取视频文件路径字符串 只能获取到内部相机文件的路径
     * @param context 上下文
     * @param tfCardFlag 0-内部存储,1-外部存储
     * @param cameraid 0-android系统相机 1-P1 2-P2
     * @return 文件路径字符串
     */
    public static String getVideoFilePath(Context context, int tfCardFlag) {
        File file = getVideoFile(context, tfCardFlag);
    public static String getVideoFilePath(Context context, int tfCardFlag,int cameraid) {
        File file = getVideoFile(context, tfCardFlag,cameraid);
        return file != null ? file.getAbsolutePath() : null;
    }
}