package com.safeluck.floatwindow.manager; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.WindowManager; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import com.alivc.live.pusher.AlivcAudioAACProfileEnum; import timber.log.Timber; import com.alivc.live.pusher.AlivcEncodeModeEnum; import com.alivc.live.pusher.AlivcFpsEnum; import com.alivc.live.pusher.AlivcImageFormat; import com.alivc.live.pusher.AlivcLivePushCameraTypeEnum; import com.alivc.live.pusher.AlivcLivePushConfig; import com.alivc.live.pusher.AlivcLivePushError; import com.alivc.live.pusher.AlivcLivePushErrorListener; import com.alivc.live.pusher.AlivcLivePushInfoListener; import com.alivc.live.pusher.AlivcLivePushNetworkListener; import com.alivc.live.pusher.AlivcLivePushStats; import com.alivc.live.pusher.AlivcLivePusher; import com.alivc.live.pusher.AlivcPreviewOrientationEnum; import com.alivc.live.pusher.AlivcQualityModeEnum; import com.alivc.live.pusher.AlivcResolutionEnum; import com.anyun.libusbcamera.UsbCamera; import com.anyun.libusbcamera.WatermarkParam; import com.safeluck.floatwindow.MediaArgu; import com.safeluck.floatwindow.ResponseVO; import com.safeluck.floatwindow.util.AudioRecordManager; import com.safeluck.floatwindow.util.GlobalData; /** * USB摄像头推流管理器 */ public class UsbCameraPushManager { private static final String TAG = "UsbCameraPushManager"; private Context context; private MediaArgu mediaArgu; private PushCallback callback; // 阿里推流相关 private AlivcLivePusher alivcPusher; private AlivcLivePushConfig alivcLivePushConfig; private SurfaceView previewSurfaceView; // USB摄像头相关 private UsbCamera usbCamera; private PushThread pushThread; private boolean isRunning = false; private boolean cameraExists = false; private volatile boolean pushStarted = false; // 推流URL private String pushUrl; // 分辨率数组 [width, height] private int[] resolutionArr = new int[]{640, 480}; // 是否开启摄像头加密 private boolean ay_encrypt = false; // 预览 SurfaceView 和隐藏的 Window private WindowManager windowManager; // 音频推流线程池(单线程) private ExecutorService audioPushExecutor; /** * 推流回调接口 */ public interface PushCallback { void onResult(ResponseVO response); } public UsbCameraPushManager(Context context) { this.context = context; } /** * 设置回调 */ public void setCallback(PushCallback callback) { this.callback = callback; } /** * 开始推流 */ public void startPush(MediaArgu media) { if (media == null) { notifyCallback(1, -1, "MediaArgu is null"); return; } this.mediaArgu = media; this.pushUrl = media.getUrl(); if (pushUrl == null || pushUrl.isEmpty()) { notifyCallback(1, -2, "Push URL is empty"); return; } // 设置分辨率 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 { // 初始化推流SDK initAlivcPusher(); setWaterMask(); pushStarted = false; // 检查并打开USB摄像头 if (!openUsbCamera()) { cameraExists = false; notifyCallback(1, -1, "USB摄像头打开失败"); return; } cameraExists = true; Timber.d("USB摄像头打开成功"); notifyCallback(1, 0, "推流线程已启动,等待推流状态就绪"); } catch (Exception e) { Timber.e(e, "Failed to start push"); notifyCallback(1, -3, "启动推流失败: " + e.getMessage()); } } /** * 停止推流 */ public void stopPush() { Timber.d("stopPush called"); stopPushThread(); // stopAudioTransfer(); stopWaterMaskSchedule(); releaseAlivcPusher(); if (usbCamera != null) { usbCamera.stopCamera(); } pushStarted = false; notifyCallback(1, 4, "推流已停止"); } /** * 初始化阿里推流 */ private void initAlivcPusher() { try { alivcLivePushConfig = new AlivcLivePushConfig(); // 根据分辨率设置 setResolutionFromArray(resolutionArr); // 建议用户使用20fps alivcLivePushConfig.setFps(AlivcFpsEnum.FPS_20); // 打开码率自适应 alivcLivePushConfig.setEnableBitrateControl(true); // 设置横屏方向 alivcLivePushConfig.setPreviewOrientation(AlivcPreviewOrientationEnum.ORIENTATION_LANDSCAPE_HOME_LEFT); // 设置音频编码模式 alivcLivePushConfig.setAudioProfile(AlivcAudioAACProfileEnum.AAC_LC); // 设置摄像头类型 alivcLivePushConfig.setCameraType(AlivcLivePushCameraTypeEnum.CAMERA_TYPE_BACK); // 设置视频编码模式为硬编码 alivcLivePushConfig.setVideoEncodeMode(AlivcEncodeModeEnum.Encode_MODE_HARD); // 关闭美颜 alivcLivePushConfig.setBeautyOn(false); // 清晰度优先模式 alivcLivePushConfig.setQualityMode(AlivcQualityModeEnum.QM_RESOLUTION_FIRST); // 设置自定义流模式 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()); // 在 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); // 启动摄像头数据推送线程 startPushThread(); } } 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"); } } // 设置监听器 setupListeners(); Timber.d("AlivcPusher initialized successfully"); } catch (Exception e) { Timber.e(e, "Failed to initialize AlivcPusher"); notifyCallback(1, -3, "初始化推流SDK失败: " + e.getMessage()); } } WatermarkParam watermarkParam; ArrayList watermarkParamList = new ArrayList<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); int baseY = 20; int fontSize= 24; private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private void setWaterMask() { // 防止重复 schedule(startPush 可能被多次调用) if (watermarkFuture != null && !watermarkFuture.isCancelled()) { return; } if (scheduledExecutorService == null || scheduledExecutorService.isShutdown()) { scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); } watermarkFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { if (pushStarted){ if (!TextUtils.isEmpty(GlobalData.getInstance().getWaterMaskInfo())){ Log.i(TAG,"tieshuiin"); if (resolutionArr[0]==320&&resolutionArr[1]==240){ fontSize = 24; baseY = 2; }else if (resolutionArr[0]==640&&resolutionArr[1]==480){ fontSize = 32; baseY = 4; }else if (resolutionArr[0]==1280&&resolutionArr[1]==720){ fontSize = 48; baseY = 6; }else{ baseY = 2; fontSize = 24; } String school = GlobalData.getInstance().parseWaterMaskInfo("school", "无", GlobalData.ShareType.STRING); watermarkParam = new WatermarkParam(10,baseY,school); watermarkParamList.clear(); watermarkParamList.add(watermarkParam); String teacher = GlobalData.getInstance().parseWaterMaskInfo("teacher", "无", GlobalData.ShareType.STRING); String stu = GlobalData.getInstance().parseWaterMaskInfo("student", "无", GlobalData.ShareType.STRING); baseY = fontSize*11/10+baseY; watermarkParam = new WatermarkParam(10,baseY,"教练:"+teacher+" 学员:"+stu); watermarkParamList.add(watermarkParam); double speed = GlobalData.getInstance().parseWaterMaskInfo("speed", 0.0, GlobalData.ShareType.DOUBLE); 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); double latitude = GlobalData.getInstance().parseWaterMaskInfo("latitude", 29.51228918, GlobalData.ShareType.DOUBLE); double longitude = GlobalData.getInstance().parseWaterMaskInfo("longitude", 106.45556208, GlobalData.ShareType.DOUBLE); // new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) baseY = fontSize*11/10+baseY; watermarkParam = new WatermarkParam(10,resolutionArr[1]-fontSize, String.format("%.6f %.6f", latitude, longitude)+" "+sdf.format(new Date())); watermarkParamList.add(watermarkParam); if (resolutionArr[0]==320&&resolutionArr[1]==240){ usbCamera.enableWatermark(true,"/system/ms_unicode_24.bin"); usbCamera.setWatermark(3,fontSize,1,watermarkParamList); }else if (resolutionArr[0]==640&&resolutionArr[1]==480){ usbCamera.enableWatermark(true,"/system/ms_unicode_32.bin"); usbCamera.setWatermark(3,fontSize,1,watermarkParamList); }else if (resolutionArr[0]==1280&&resolutionArr[1]==720){ usbCamera.enableWatermark(true,"/system/ms_unicode_48.bin"); usbCamera.setWatermark(3,fontSize,1,watermarkParamList); }else{ usbCamera.enableWatermark(true,"/system/ms_unicode_24.bin"); usbCamera.setWatermark(3,fontSize,1,watermarkParamList); } } } },1,1, TimeUnit.SECONDS); } private ScheduledFuture watermarkFuture; private void stopWaterMaskSchedule() { try { if (watermarkFuture != null) { watermarkFuture.cancel(true); watermarkFuture = null; } } catch (Throwable t) { Timber.w(t, "cancel watermarkFuture failed"); } try { if (scheduledExecutorService != null && !scheduledExecutorService.isShutdown()) { scheduledExecutorService.shutdownNow(); } } catch (Throwable t) { Timber.w(t, "shutdown watermark scheduledExecutorService failed"); } finally { scheduledExecutorService = null; } } private Handler mainHandler = new Handler(Looper.getMainLooper()); /** * 设置监听器 */ private void setupListeners() { // 推流信息监听器 alivcPusher.setLivePushInfoListener(new AlivcLivePushInfoListener() { @Override public void onPreviewStarted(AlivcLivePusher alivcLivePusher) { Timber.d("onPreviewStarted"); 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); alivcPusher.startPushAysnc(pushUrl); } catch (Exception e) { Timber.e(e, "startPushAysnc failed"); notifyCallback(1, -3, "启动推流失败: " + e.getMessage()); } } },1000); } @Override public void onPreviewStoped(AlivcLivePusher alivcLivePusher) { Timber.d("onPreviewStoped"); } @Override public void onPushStarted(AlivcLivePusher alivcLivePusher) { Timber.d("onPushStarted"); pushStarted = true; // startAudioTransfer(); notifyCallback(1, 0, "推流已开始,分辨率: " + resolutionArr[0] + "x" + resolutionArr[1]); } @Override public void onPushPauesed(AlivcLivePusher alivcLivePusher) { Timber.d("onPushPauesed"); } @Override public void onPushResumed(AlivcLivePusher alivcLivePusher) { Timber.d("onPushResumed"); } @Override public void onPushStoped(AlivcLivePusher alivcLivePusher) { Timber.d("onPushStoped"); pushStarted = false; notifyCallback(1, 4, "推流已停止"); } @Override public void onPushRestarted(AlivcLivePusher alivcLivePusher) { Timber.d("onPushRestarted"); } @Override public void onFirstFramePreviewed(AlivcLivePusher alivcLivePusher) { Timber.d("onFirstFramePreviewed"); } @Override public void onDropFrame(AlivcLivePusher alivcLivePusher, int i, int i1) { // 丢帧回调 } @Override public void onAdjustBitRate(AlivcLivePusher alivcLivePusher, int i, int i1) { // 码率调整回调 } @Override public void onAdjustFps(AlivcLivePusher alivcLivePusher, int i, int i1) { // 帧率调整回调 } }); // 错误监听器 alivcPusher.setLivePushErrorListener(new AlivcLivePushErrorListener() { @Override public void onSystemError(AlivcLivePusher alivcLivePusher, AlivcLivePushError alivcLivePushError) { Timber.e("onSystemError: %s", alivcLivePushError.toString()); notifyCallback(1, -3, "系统错误: " + alivcLivePushError.toString()); if (alivcLivePusher != null) { alivcLivePusher.stopPush(); } } @Override public void onSDKError(AlivcLivePusher alivcLivePusher, AlivcLivePushError alivcLivePushError) { Timber.e("onSDKError: %s", alivcLivePushError.toString()); notifyCallback(1, -3, "SDK错误: " + alivcLivePushError.toString()); if (alivcLivePusher != null) { alivcLivePusher.restartPushAync(); } } }); // 网络监听器 alivcPusher.setLivePushNetworkListener(new AlivcLivePushNetworkListener() { @Override public void onNetworkPoor(AlivcLivePusher alivcLivePusher) { Timber.w("onNetworkPoor"); notifyCallback(1, 3, "网络较差"); } @Override public void onNetworkRecovery(AlivcLivePusher alivcLivePusher) { Timber.d("onNetworkRecovery"); notifyCallback(1, 0, "网络恢复"); } @Override public void onReconnectStart(AlivcLivePusher alivcLivePusher) { Timber.d("onReconnectStart"); } @Override public void onReconnectFail(AlivcLivePusher alivcLivePusher) { Timber.e("onReconnectFail"); notifyCallback(1, 2, "重连失败"); } @Override public void onReconnectSucceed(AlivcLivePusher alivcLivePusher) { Timber.d("onReconnectSucceed"); notifyCallback(1, 0, "重连成功"); } @Override public void onSendDataTimeout(AlivcLivePusher alivcLivePusher) { Timber.w("onSendDataTimeout"); } @Override public void onConnectFail(AlivcLivePusher alivcLivePusher) { Timber.e("onConnectFail"); notifyCallback(1, -2, "连接失败"); } @Override public String onPushURLAuthenticationOverdue(AlivcLivePusher alivcLivePusher) { Timber.w("onPushURLAuthenticationOverdue"); return null; } @Override public void onSendMessage(AlivcLivePusher alivcLivePusher) { // 发送消息回调 } }); } /** * 根据分辨率数组设置分辨率 */ private void setResolutionFromArray(int[] arr) { for (int a : arr) { switch (a) { case 180: alivcLivePushConfig.setResolution(AlivcResolutionEnum.RESOLUTION_180P); break; case 240: alivcLivePushConfig.setResolution(AlivcResolutionEnum.RESOLUTION_240P); break; case 360: alivcLivePushConfig.setResolution(AlivcResolutionEnum.RESOLUTION_360P); break; case 480: alivcLivePushConfig.setResolution(AlivcResolutionEnum.RESOLUTION_480P); break; case 540: alivcLivePushConfig.setResolution(AlivcResolutionEnum.RESOLUTION_540P); break; case 720: alivcLivePushConfig.setResolution(AlivcResolutionEnum.RESOLUTION_720P); break; default: alivcLivePushConfig.setResolution(AlivcResolutionEnum.RESOLUTION_240P); break; } } } /** * 打开USB摄像头 */ private boolean openUsbCamera() { try { if (usbCamera == null) { usbCamera = new UsbCamera(); } // 打开摄像头之前先调用setenv usbCamera.setenv(); // 使用prepareCamera方法,camera_id范围[0,2] int[] cameraIds = {0, 2}; 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; } } /** * 启动推流线程 */ private void startPushThread() { if (pushThread == null || !isRunning) { isRunning = true; pushThread = new PushThread(); pushThread.start(); Timber.d("Push thread started"); } } /** * 停止推流线程 */ private void stopPushThread() { isRunning = false; if (pushThread != null) { try { pushThread.join(1000); } catch (InterruptedException e) { Timber.e(e, "Error stopping push thread"); } pushThread = null; } Timber.d("Push thread stopped"); } /** * 释放阿里推流资源 */ private void releaseAlivcPusher() { // 兜底:防止外部没有走 stopPush stopWaterMaskSchedule(); // 移除隐藏的 SurfaceView if (previewSurfaceView != null && windowManager != null) { try { windowManager.removeView(previewSurfaceView); Timber.d("previewSurfaceView removed from window"); } catch (Exception e) { Timber.e(e, "Error removing previewSurfaceView from window"); } previewSurfaceView = null; } if (alivcPusher != null) { try { AlivcLivePushStats stats = alivcPusher.getCurrentStatus(); Timber.d("当前推流状态: %s", stats != null ? stats.name() : "null"); if (stats != null && (stats == AlivcLivePushStats.PUSHED || stats == AlivcLivePushStats.PREVIEWED)) { alivcPusher.stopPush(); } alivcPusher.destroy(); } catch (Exception e) { Timber.e(e, "Error releasing AlivcPusher"); } alivcPusher = null; } alivcLivePushConfig = null; pushStarted = false; } /** * 推流线程 */ private class PushThread extends Thread { @Override public void run() { super.run(); Timber.d("PushThread started"); try { int width = resolutionArr[0]; int height = resolutionArr[1]; final long startTimeNs = System.nanoTime(); // 计算YUV420缓冲区大小 int bufferSize = width * height * 3 / 2; byte[] buffer = new byte[bufferSize]; Timber.d("开始推送视频数据,分辨率: %dx%d", width, height); // 循环处理摄像头数据 while (isRunning && cameraExists) { // 处理摄像头数据 int processResult = usbCamera.processCamera(); if (processResult == -1) { Timber.w("processCamera返回-1,摄像头可能断开"); cameraExists = false; notifyCallback(1, -1, "USB摄像头断开"); break; } // 获取YUV数据 (参数1表示推流) usbCamera.rgba(1, buffer); // 推流数据到阿里云 if (alivcPusher != null && cameraExists && pushStarted) { try { long ptsUs = (System.nanoTime() - startTimeNs) / 1000; alivcPusher.inputStreamVideoData( buffer, width, height, buffer.length, ptsUs, // 单调递增的时间戳(微秒) 0 // rotation ); } catch (Exception e) { Timber.e(e, "Error pushing frame"); } } else if (!pushStarted) { // 等待 onPushStarted 后再喂帧,避免 SDK invalid state Thread.sleep(20); } // 控制帧率,约20fps Thread.sleep(50); } } catch (Exception e) { Timber.e(e, "Error in push thread"); cameraExists = false; notifyCallback(1, -1, "推流线程异常: " + e.getMessage()); } finally { Timber.d("PushThread ended"); } } } /** * 通知回调 */ 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); } } private void startAudioTransfer() { Timber.i("开始通过mic录制声音,上传"); // 创建单线程线程池用于音频推流 if (audioPushExecutor == null || audioPushExecutor.isShutdown()) { audioPushExecutor = Executors.newSingleThreadExecutor(r -> { Thread thread = new Thread(r, "AudioPushThread"); thread.setDaemon(true); return thread; }); } AudioRecordManager.getInstance().startRecording((data, size) -> { if (alivcPusher != null && audioPushExecutor != null && !audioPushExecutor.isShutdown()) { // 在单线程线程池中执行音频推流 audioPushExecutor.execute(() -> { try { alivcPusher.inputStreamAudioData(data, data.length, System.nanoTime() / 1000); } catch (Exception e) { Timber.e(e, "Error pushing audio data"); } }); } }); } private void stopAudioTransfer() { Timber.i("停止通过mic录制声音,上传"); AudioRecordManager.getInstance().stopRecording(); // 停止并关闭音频推流线程池 if (audioPushExecutor != null && !audioPushExecutor.isShutdown()) { audioPushExecutor.shutdown(); try { // 等待最多1秒让任务完成 if (!audioPushExecutor.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS)) { audioPushExecutor.shutdownNow(); } } catch (InterruptedException e) { audioPushExecutor.shutdownNow(); Thread.currentThread().interrupt(); } audioPushExecutor = null; Timber.d("音频推流线程池已关闭"); } } }