package com.safeluck.floatwindow.manager;
|
|
import android.content.Context;
|
import android.os.Handler;
|
import android.os.Looper;
|
import android.view.SurfaceHolder;
|
import android.view.SurfaceView;
|
import android.view.WindowManager;
|
|
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.Executors;
|
|
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.safeluck.floatwindow.MediaArgu;
|
import com.safeluck.floatwindow.ResponseVO;
|
import com.safeluck.floatwindow.util.AudioRecordManager;
|
|
/**
|
* 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();
|
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();
|
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());
|
}
|
}
|
|
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() {
|
// 移除隐藏的 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("音频推流线程池已关闭");
|
}
|
}
|
}
|