镜头降落

This commit is contained in:
cxf 2026-04-08 13:43:50 +08:00
parent 984dadb7bf
commit 1b0de5e340
29 changed files with 1333 additions and 446 deletions

View File

@ -243,12 +243,12 @@ dependencies {
//vlc拉流
implementation 'org.videolan.android:libvlc-all:3.6.0'
//implementation 'org.videolan.android:libvlc-all:3.6.0'
//TTS
implementation files('libs/AIKit.aar')
//implementation(name: 'AIKit', ext: 'aar')
//implementation files('libs/AIKit.aar')
}

BIN
app/libs/AIKit.aar Normal file

Binary file not shown.

View File

@ -129,7 +129,8 @@ class ConfigActivity : BaseActivity() {
configBinding.rbCameraCenter.isChecked = PreferenceUtils.getInstance().cameraLocationType ==1//中间
configBinding.rbCameraRight.isChecked = PreferenceUtils.getInstance().cameraLocationType ==2//右边
configBinding.rbCameraNull.isChecked = PreferenceUtils.getInstance().cameraLocationType ==3//下视
configBinding.rbCameraMix.isChecked = PreferenceUtils.getInstance().cameraLocationType ==4//融合
configBinding.rbCameraMix.isChecked = PreferenceUtils.getInstance().cameraLocationType ==4//融右
configBinding.rbCameraMixCenter.isChecked = PreferenceUtils.getInstance().cameraLocationType ==5//融中
configBinding.btnConfig.setOnClickListener { config() }
@ -238,7 +239,7 @@ class ConfigActivity : BaseActivity() {
}
}
if (!configBinding.rbCameraRight.isChecked && !configBinding.rbCameraCenter.isChecked && !configBinding.rbCameraNull.isChecked && !configBinding.rbCameraMix.isChecked) {
if (!configBinding.rbCameraRight.isChecked && !configBinding.rbCameraCenter.isChecked && !configBinding.rbCameraNull.isChecked && !configBinding.rbCameraMix.isChecked&& !configBinding.rbCameraMixCenter.isChecked) {
ToastUtil.showToast("未配置主相机位置")
return
}
@ -382,6 +383,8 @@ class ConfigActivity : BaseActivity() {
PreferenceUtils.getInstance().cameraLocationType = 3
} else if(configBinding.rbCameraMix.isChecked){
PreferenceUtils.getInstance().cameraLocationType = 4
}else if(configBinding.rbCameraMixCenter.isChecked){
PreferenceUtils.getInstance().cameraLocationType = 5
}
ToastUtil.showToast("配置已保存")

View File

@ -90,8 +90,8 @@ open class ConnectionActivity : BaseActivity() {
}
if (TextUtils.isEmpty(PreferenceUtils.getInstance().mqttServerUri)){
PreferenceUtils.getInstance().mqttServerUri =
"tcp://192.168.20.90:2883"
// "tcp://broker.emqx.io"
//"tcp://192.168.20.90:2883"
"tcp://broker.emqx.io"
}
if (TextUtils.isEmpty(PreferenceUtils.getInstance().mqttUserName)){

View File

@ -40,10 +40,13 @@ import com.aros.apron.manager.OSDManager
import com.aros.apron.manager.PayloadWidgetManager
import com.aros.apron.manager.PerceptionManager
import com.aros.apron.manager.RTKManager
import com.aros.apron.manager.SpeakerManager
import com.aros.apron.manager.StickManager
import com.aros.apron.manager.StreamManager
import com.aros.apron.manager.UdpStreamManager
import com.aros.apron.manager.WirelessLinkManager
import com.aros.apron.mix.Aprondown
import com.aros.apron.mix.Aprongim
import com.aros.apron.tools.AlternateArucoDetect
import com.aros.apron.tools.ApronArucoDetect
import com.aros.apron.tools.ApronArucoDetectPort
@ -529,6 +532,8 @@ open class MainActivity : BaseActivity() {
}
private fun initView() {
fpvParentView = findViewById(R.id.fpv_holder)
mDrawerLayout = findViewById(R.id.root_view)
@ -704,6 +709,7 @@ open class MainActivity : BaseActivity() {
enableStream()
initFpvStream()
startVtxHeartbeat()
SpeakerManager.getInstance().initMegaphoneInfo()
//PayloadWidgetManager.getInstance().initPayloadInfo();
@ -763,8 +769,7 @@ open class MainActivity : BaseActivity() {
LogUtil.log(TAG, "推流类型:" + PreferenceUtils.getInstance().customStreamType)
}
} else if (PreferenceUtils.getInstance().cameraLocationType == 4) {
} else if (PreferenceUtils.getInstance().cameraLocationType == 4||PreferenceUtils.getInstance().cameraLocationType == 5) {
if ((isFlightControllerConnect == null || isFlightControllerConnect != true) && (isCameraConnect == null || isCameraConnect != true)) {
handler.postDelayed({
initDJIManager()
@ -802,6 +807,8 @@ open class MainActivity : BaseActivity() {
initMixedStream()
startVtxHeartbeat()
SpeakerManager.getInstance().initMegaphoneInfo()
GeoidManager.getInstance().init(this);
//开启雷达监听器
@ -904,6 +911,8 @@ open class MainActivity : BaseActivity() {
initCameraStream()
startVtxHeartbeat()
//
SpeakerManager.getInstance().initMegaphoneInfo()
GeoidManager.getInstance().init(this);
@ -978,7 +987,7 @@ open class MainActivity : BaseActivity() {
@SuppressLint("SuspiciousIndentation")
private fun initMixedStream() {
// 初始化融合视觉降落识别器
val mixedLanding = MixedVisionLanding.getInstance()
//val mixedLanding = MixedVisionLanding.getInstance()
// 为 PORT_1云台相机添加帧监听器
cameraManager.addFrameListener(
@ -989,7 +998,34 @@ open class MainActivity : BaseActivity() {
updateVtxHeartbeat()
streamReceive = true
// 使用融合视觉识别器处理云台相机帧
mixedLanding.processGimbalFrame(height, width, frameData, dictionary)
//mixedLanding.processGimbalFrame(height, width, frameData, dictionary)
//使用云台
synchronized(Synchronizedstatus.LOCK_OBJ) {
if (!Synchronizedstatus.isIsruningframe() && Synchronizedstatus.isAprongim() && Synchronizedstatus.isSwitchtime()) {
try {
Synchronizedstatus.setIsruningframe(true)
if (startArucoType == 1) {
Aprongim.getInstance()?.detectArucoTags(
height,
width,
frameData,
dictionary
)
} else if (startArucoType == 2) {
AlternateArucoDetect.getInstance()?.detectArucoTags(
height,
width,
frameData,
dictionary
)
}
} finally {
Synchronizedstatus.setIsruningframe(false)
}
}
}
}
// 为 FPV下视相机添加帧监听器
@ -1001,7 +1037,33 @@ open class MainActivity : BaseActivity() {
updateVtxHeartbeat()
streamReceive = true
// 使用融合视觉识别器处理下视相机帧
mixedLanding.processDownwardFrame(height, width, frameData, dictionary)
//mixedLanding.processDownwardFrame(height, width, frameData, dictionary)
//使用下视觉
synchronized(Synchronizedstatus.LOCK_OBJ) {
if (!Synchronizedstatus.isIsruningframe() && !Synchronizedstatus.isAprongim() && Synchronizedstatus.isSwitchtime()) {
try {
Synchronizedstatus.setIsruningframe(true)
if (startArucoType == 1) {
Aprondown.getInstance()?.detectArucoTags(
height,
width,
frameData,
dictionary
)
} else if (startArucoType == 2) {
AlternateArucoDetect.getInstance()?.detectArucoTags(
height,
width,
frameData,
dictionary
)
}
} finally {
Synchronizedstatus.setIsruningframe(false)
}
}
}
}
}
@ -1086,13 +1148,6 @@ open class MainActivity : BaseActivity() {
}
// private val mLoaderCallback: BaseLoaderCallback = object : BaseLoaderCallback(this) {
// override fun onManagerConnected(status: Int) {
// if (status == SUCCESS) {
@ -1114,7 +1169,6 @@ open class MainActivity : BaseActivity() {
object : CommonCallbacks.CompletionCallbackWithParam<EmptyMsg?> {
override fun onSuccess(emptyMsg: EmptyMsg?) {
LogUtil.log(TAG, "取消降落,识别机库二维码")
if (PreferenceUtils.getInstance().cameraLocationType == 3) {
Handler().postDelayed(Runnable {
if (!ApronArucoDetect.getInstance().isTriggerSuccess) {
@ -1123,6 +1177,14 @@ open class MainActivity : BaseActivity() {
AlternateLandingManager.getInstance().startTaskProcess(null)
}
}, 6000)
} else if (PreferenceUtils.getInstance().cameraLocationType == 4 ||PreferenceUtils.getInstance().cameraLocationType == 5) {
Handler().postDelayed(Runnable {
if (!Aprongim.getInstance().isTriggerSuccess) {
LogUtil.log(TAG, "图传异常:飞往备降点")
//测试图传丢失
AlternateLandingManager.getInstance().startTaskProcess(null)
}
}, 6000)
} else {
Handler().postDelayed(Runnable {
if (!ApronArucoDetectPort.getInstance().isTriggerSuccess) {
@ -1138,12 +1200,15 @@ open class MainActivity : BaseActivity() {
startArucoType = 1
ApronArucoDetect.getInstance().setDetectedBigMarkers()
ApronArucoDetectPort.getInstance().setDetectedBigMarkers()
Aprongim.getInstance().setDetectedBigMarkers()
Aprondown.getInstance().setDetectedBigMarkers()
DroneHelper.getInstance().setGimbalPitchDegree()
//每次触发识别二维码时,为避免获取控制权失败,使多次获取控制权
DroneHelper.getInstance().isVirtualStickEnable = false
DroneHelper.getInstance().setVerticalModeToVelocity()
}
override fun onFailure(error: IDJIError) {
if (startArucoType == 1) {
return
@ -1152,6 +1217,10 @@ open class MainActivity : BaseActivity() {
LogUtil.log(TAG, "取消降落,识别机库二维码失败:" + Gson().toJson(error))
ApronArucoDetect.getInstance().setDetectedBigMarkers()
ApronArucoDetectPort.getInstance().setDetectedBigMarkers()
Aprongim.getInstance().setDetectedBigMarkers()
Aprondown.getInstance().setDetectedBigMarkers()
DroneHelper.getInstance().setGimbalPitchDegree()
//每次触发识别二维码时,为避免获取控制权失败,使多次获取控制权
DroneHelper.getInstance().isVirtualStickEnable = false
@ -1167,20 +1236,28 @@ open class MainActivity : BaseActivity() {
LogUtil.log(TAG, "取消降落,识别备降点二维码")
if (PreferenceUtils.getInstance().cameraLocationType == 3) {
Handler().postDelayed(Runnable {
if (!AlternateArucoDetect.getInstance().isTriggerSuccess) {
LogUtil.log(TAG, "图传异常:备降点直接降落")
if (!ApronArucoDetect.getInstance().isTriggerSuccess) {
LogUtil.log(TAG, "图传异常:飞往备降点")
//测试图传丢失
FlightManager.getInstance().startAutoLanding(null)
AlternateLandingManager.getInstance().startTaskProcess(null)
}
}, 4000)
}, 6000)
} else if (PreferenceUtils.getInstance().cameraLocationType == 4|| PreferenceUtils.getInstance().cameraLocationType==5) {
Handler().postDelayed(Runnable {
if (!Aprongim.getInstance().isTriggerSuccess) {
LogUtil.log(TAG, "图传异常:飞往备降点")
//测试图传丢失
AlternateLandingManager.getInstance().startTaskProcess(null)
}
}, 6000)
} else {
Handler().postDelayed(Runnable {
if (!AlternateArucoDetect.getInstance().isTriggerSuccess) {
LogUtil.log(TAG, "图传异常:备降点直接降落")
if (!ApronArucoDetectPort.getInstance().isTriggerSuccess) {
LogUtil.log(TAG, "图传异常:飞往备降点")
//测试图传丢失
FlightManager.getInstance().startAutoLanding(null)
AlternateLandingManager.getInstance().startTaskProcess(null)
}
}, 4000)
}, 6000)
}
if (startArucoType == 2) {
return
@ -1208,12 +1285,13 @@ open class MainActivity : BaseActivity() {
}
})
FLAG_DOWN_LAND ->
{
FLAG_DOWN_LAND -> {
//重置降落变量
ApronArucoDetect.getInstance().setStartAruco(false);
ApronArucoDetectPort.getInstance().setStartAruco(false);
Aprongim.getInstance().setStartAruco(false);
Aprondown.getInstance().setStartAruco(false);
KeyManager.getInstance().performAction<EmptyMsg>(
KeyTools.createKey<EmptyMsg, EmptyMsg>(FlightControllerKey.KeyStartAutoLanding),

View File

@ -1,54 +1,129 @@
package com.aros.apron.app
import android.R
import android.app.Application
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import com.aros.apron.models.MSDKManagerVM
import com.aros.apron.models.globalViewModels
import com.aros.apron.tools.LogUtil
import com.aros.apron.tools.XTtsPcmGenerator
import com.aros.apron.tts.CopyAsset
import com.aros.apron.xclog.CrashHandler
import com.aros.apron.xclog.XcFileLog
import com.aros.apron.xclog.XcLogConfig
import com.dji.wpmzsdk.manager.WPMZManager
import com.iflytek.aikit.core.AiHelper
import com.iflytek.aikit.core.BaseLibrary
import com.iflytek.aikit.core.CoreListener
import com.iflytek.aikit.core.ErrType
import com.iflytek.aikit.core.LogLvl
import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.Logger
import com.orhanobut.logger.PrettyFormatStrategy
import dji.v5.common.utils.GeoidManager
import dji.v5.utils.common.ContextUtil
import org.opencv.android.OpenCVLoader
open class ApronApp : Application() {
companion object {
fun getApplication(): Context? {
return context
}
var context: Context? = null
var context: Context? = null
// 参数别改(你的密钥)
const val APP_ID = "753fd711"
const val API_KEY = "35099a7d31abf2259f5ee58cbf8b6791"
const val API_SECRET = "ZTdiMDIwYzEwNTA1NjllYjJlYjdmYWFi"
const val ABILITY_ID = "e2e44feff" // XTTS 能力 ID
const val WORK_DIR = "/storage/self/primary/DJIDemo/cache/tts/" // SDK工作目录
private var authResult = -1
fun getAuthResult(): Int {
return authResult
}
}
private val msdkManagerVM: MSDKManagerVM by globalViewModels()
override fun onCreate() {
super.onCreate()
context=this
context = this
initConfig()
msdkManagerVM.initMobileSDK(this)
// 复制资源文件到指定目录
CopyAsset.getInstance(context).copyAssetsToSdcard()
// 科大讯飞初始化
initXunfeiSDK()
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
com.cySdkyc.clx.Helper.install(this)
}
/**
* 初始化科大讯飞SDK
*/
private fun initXunfeiSDK() {
// 设置日志信息
AiHelper.getInst().setLogInfo(LogLvl.VERBOSE, 1, "$WORK_DIR/aeeLog.txt")
// 设定初始化参数
val params = BaseLibrary.Params.builder()
.appId(APP_ID) // 应用ID
.apiKey(API_KEY) // APIKEY
.apiSecret(API_SECRET) // APISECRET
.workDir(WORK_DIR) // SDK工作目录
.build()
// 初始化SDK在子线程中执行
Thread {
AiHelper.getInst().initEntry(applicationContext, params)
}.start()
// 注册SDK初始化状态监听
AiHelper.getInst().registerListener(coreListener)
}
// 授权结果回调
private val coreListener = object : CoreListener {
override fun onAuthStateChange(type: ErrType, code: Int) {
Log.i("AEELog", "core listener code:$code")
// 使用Handler在主线程中执行UI操作
Handler(Looper.getMainLooper()).post {
when (type) {
ErrType.AUTH -> {
authResult = code
if (code == 0) {
Toast.makeText(applicationContext, "SDK授权成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(applicationContext, "SDK授权失败授权码为:$authResult", Toast.LENGTH_SHORT).show()
}
}
ErrType.HTTP -> {
Toast.makeText(applicationContext, "SDK状态HTTP认证结果$code", Toast.LENGTH_SHORT).show()
}
else -> {
Toast.makeText(applicationContext, "SDK状态其他错误$code", Toast.LENGTH_SHORT).show()
}
}
}
}
}
/**
* Logger 初始化配置
*/
private fun initConfig() {
var formatStrategy = PrettyFormatStrategy.newBuilder()
val formatStrategy = PrettyFormatStrategy.newBuilder()
.showThreadInfo(false) // 隐藏线程信息 默认:显示
.methodCount(0) // 决定打印多少行每一行代表一个方法默认2
.methodOffset(7) // (Optional) Hides internal method calls up to offset. Default 5
@ -65,12 +140,23 @@ open class ApronApp : Application() {
CrashHandler.getInstance().init()
// 初始化 OpenCV
if (OpenCVLoader.initLocal()) {
LogUtil.log("qwq","opencv" +
"初始化完成")
LogUtil.log(
"qwq", "opencv" +
"初始化完成"
)
return;
}else{
LogUtil.log("qwq","opencv" +
"初始化失败")
} else {
LogUtil.log(
"qwq", "opencv" +
"初始化失败"
)
}
}
override fun onTerminate() {
super.onTerminate()
// 逆初始化SDK
AiHelper.getInst().unInit()
}
}

View File

@ -29,6 +29,8 @@ import com.aros.apron.manager.StickManager;
import com.aros.apron.manager.StreamManager;
import com.aros.apron.manager.SystemManager;
import com.aros.apron.manager.TakeOffToPointManager;
import com.aros.apron.mix.Aprondown;
import com.aros.apron.mix.Aprongim;
import com.aros.apron.tools.ApronArucoDetect;
import com.aros.apron.tools.ApronArucoDetectPort;
import com.aros.apron.tools.Generakmztools;
@ -52,7 +54,7 @@ import dji.v5.common.callback.CommonCallbacks;
import dji.v5.common.error.IDJIError;
import dji.v5.manager.KeyManager;
public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
private String TAG = "MqttCallBack";
Object lock = Synchronizedstatus.LOCK_OBJ;
@ -60,11 +62,12 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
@Override
public void connectionLost(Throwable cause) {
LogUtil.log(TAG, "MQtt connectionLost:"+cause.toString());
LogUtil.log(TAG, "MQtt connectionLost:" + cause.toString());
//短线也要清空这个
clearVirtualStickHeartbeat();
}
//断线重连
public void reConnect() throws Exception {
if (null != MqttManager.getInstance().mqttAndroidClient) {
@ -78,7 +81,9 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
private Runnable vsTimeoutCheck;
private static final long TIMEOUT_MS = 500; // 500ms 无数据触发
/** 每次收到控制指令都调 */
/**
* 每次收到控制指令都调
*/
private void resetVirtualStickHeartbeat() {
lastDrControlTime = System.currentTimeMillis();
@ -97,7 +102,9 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
vsHandler.postDelayed(vsTimeoutCheck, TIMEOUT_MS);
}
/** 清理虚拟摇杆心跳(断网/退出/销毁时调) */
/**
* 清理虚拟摇杆心跳断网/退出/销毁时调
*/
private void clearVirtualStickHeartbeat() {
if (vsTimeoutCheck != null) {
vsHandler.removeCallbacks(vsTimeoutCheck);
@ -107,11 +114,10 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
}
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) {
String jsonString = null;
Log.e(TAG, "入口打印:" +mqttMessage.toString());
Log.e(TAG, "入口打印:" + mqttMessage.toString());
try {
jsonString = new String(mqttMessage.getPayload(), "UTF-8");
@ -122,9 +128,9 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
MessageDown message = new Gson().fromJson(jsonString, MessageDown.class);
//LogUtil.log(TAG,message.getMethod());
if(topic.equals(AMSConfig.DOWN_UAV_PSDK_EVENT)){
if (topic.equals(AMSConfig.DOWN_UAV_PSDK_EVENT)) {
//负载设置控件
}else {
} else {
switch (message.getMethod()) {
case Constant.PILOT_ON:
// LogUtil.log(TAG, "收到:遥控器是否开机" + jsonString);
@ -159,19 +165,19 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
CameraManager.getInstance().setCameraVideoStreamSource(message);
break;
case Constant.FLIGHTTASK_EXECUTE:
synchronized(lock){
synchronized (lock) {
// 检查是否是第一次收到航线指令
if(Synchronizedstatus.isIsruning()){
if (Synchronizedstatus.isIsruning()) {
LogUtil.log(TAG, "自检正在运行");
return;
} else if(!Synchronizedstatus.getInitStatus()){
} else if (!Synchronizedstatus.getInitStatus()) {
Synchronizedstatus.setIsruning(true);
//自检
MissionV3Manager.getInstance().checkVtxWithDelay(()->{
MissionV3Manager.getInstance().checkVtxWithDelay(() -> {
Synchronizedstatus.setIsruning(false);
Synchronizedstatus.setInitStatus(true);
});
}else if(Synchronizedstatus.getInitStatus()){
} else if (Synchronizedstatus.getInitStatus()) {
LogUtil.log(TAG, "收到:航线" + jsonString);
//设置modecode
Movement.getInstance().setMode_code(1);
@ -197,7 +203,7 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
LogUtil.log(TAG, "收到:返航" + jsonString);
//自动返航 (如果调用方法失败了 这个设置就有一个问题但是为了方便看懂我就放这里了没放成功的回调里面)
Movement.getInstance().setMode_code(9);
if(!Movement.getInstance().isAlternate()&&!ApronArucoDetectPort.getInstance().isStartAruco()&&!ApronArucoDetect.getInstance().isStartAruco()){
if (!Movement.getInstance().isAlternate() && !ApronArucoDetectPort.getInstance().isStartAruco() && !ApronArucoDetect.getInstance().isStartAruco() && !Aprongim.getInstance().isStartAruco() && !Aprondown.getInstance().isStartAruco()) {
// if(Movement.getInstance().getElevation()<15){
//
//
@ -207,9 +213,9 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
// }
FlightManager.getInstance().startGoHome(message);
}else{
} else {
sendMsg2Server(message);
sendEvent2Server("正在视觉或飞往备降不允许返航",1);
sendEvent2Server("正在视觉或飞往备降不允许返航", 1);
}
break;
case Constant.INBOUND:
@ -237,18 +243,18 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
// MissionDataBean data = new Gson().fromJson(new Gson().toJson(message.getData()), MissionDataBean.class);
// Boolean generateKmz = Generakmztools.getInstance().generateKmz(data);
synchronized(lock){
if(Synchronizedstatus.isIsruning()){
synchronized (lock) {
if (Synchronizedstatus.isIsruning()) {
LogUtil.log(TAG, "自检正在运行");
return;
} else if(!Synchronizedstatus.getInitStatus()){
} else if (!Synchronizedstatus.getInitStatus()) {
Synchronizedstatus.setIsruning(true);
//自检
TakeOffToPointManager.getInstance().checkVtxWithDelay(()->{
TakeOffToPointManager.getInstance().checkVtxWithDelay(() -> {
Synchronizedstatus.setIsruning(false);
Synchronizedstatus.setInitStatus(true);
});
}else if(Synchronizedstatus.getInitStatus()){
} else if (Synchronizedstatus.getInitStatus()) {
LogUtil.log(TAG, "收到:一键起飞" + jsonString);
//设置modecode
Movement.getInstance().setMode_code(1);
@ -321,46 +327,46 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.CAMERA_SCREEN_DRAG:
LogUtil.log(TAG, "收到:负载控制—画面拖动控制" + jsonString);
if(ApronArucoDetect.getInstance().isStartAruco()||ApronArucoDetectPort.getInstance().isStartAruco()){
sendEvent2Server("自动降落不可以动负载",1);
if (ApronArucoDetect.getInstance().isStartAruco() || ApronArucoDetectPort.getInstance().isStartAruco() || Aprongim.getInstance().isStartAruco() || Aprondown.getInstance().isStartAruco()) {
sendEvent2Server("自动降落不可以动负载", 1);
return;
}else{
} else {
GimbalManager.getInstance().camera_screen_drag(message);
}
break;
case Constant.CAMERA_AIM:
LogUtil.log(TAG, "收到:负载控制—双击成为 AIM" + jsonString);
if(ApronArucoDetect.getInstance().isStartAruco()||ApronArucoDetectPort.getInstance().isStartAruco()){
sendEvent2Server("自动降落不可以动负载",1);
if (ApronArucoDetect.getInstance().isStartAruco() || ApronArucoDetectPort.getInstance().isStartAruco() || Aprongim.getInstance().isStartAruco() || Aprondown.getInstance().isStartAruco()) {
sendEvent2Server("自动降落不可以动负载", 1);
return;
}else{
} else {
CameraManager.getInstance().tapZoomAtTarget(message);
}
break;
case Constant.CAMERA_FOCAL_LENGTH_SET:
LogUtil.log(TAG, "收到:负载控制—变焦" + jsonString);
if(ApronArucoDetect.getInstance().isStartAruco()||ApronArucoDetectPort.getInstance().isStartAruco()){
sendEvent2Server("自动降落不可以动负载",1);
if (ApronArucoDetect.getInstance().isStartAruco() || ApronArucoDetectPort.getInstance().isStartAruco() || Aprongim.getInstance().isStartAruco() || Aprondown.getInstance().isStartAruco()) {
sendEvent2Server("自动降落不可以动负载", 1);
return;
}else{
} else {
CameraManager.getInstance().setCameraZoomRatios(message);
}
break;
case Constant.GIMBAL_RESET:
LogUtil.log(TAG, "收到:负载控制—重置云台" + jsonString);
if(ApronArucoDetect.getInstance().isStartAruco()||ApronArucoDetectPort.getInstance().isStartAruco()){
sendEvent2Server("自动降落不可以动负载",1);
if (ApronArucoDetect.getInstance().isStartAruco() || ApronArucoDetectPort.getInstance().isStartAruco() || Aprongim.getInstance().isStartAruco() || Aprondown.getInstance().isStartAruco()) {
sendEvent2Server("自动降落不可以动负载", 1);
return;
}else{
} else {
GimbalManager.getInstance().gimbalReset(message);
}
break;
case Constant.CAMERA_LOOK_AT:
LogUtil.log(TAG, "收到负载控制—Look At" + jsonString);
if(ApronArucoDetect.getInstance().isStartAruco()||ApronArucoDetectPort.getInstance().isStartAruco()){
sendEvent2Server("自动降落不可以动负载",1);
if (ApronArucoDetect.getInstance().isStartAruco() || ApronArucoDetectPort.getInstance().isStartAruco() || Aprongim.getInstance().isStartAruco() || Aprondown.getInstance().isStartAruco()) {
sendEvent2Server("自动降落不可以动负载", 1);
return;
}else{
} else {
GimbalManager.getInstance().gimbalLookAt(message);
}
break;
@ -410,51 +416,139 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
break;
case Constant.CAMERA_FRAME_ZOOM:
LogUtil.log(TAG, "收到:框选变焦" + jsonString);
if(ApronArucoDetect.getInstance().isStartAruco()||ApronArucoDetectPort.getInstance().isStartAruco()){
sendEvent2Server("自动降落不可以动负载",1);
if (ApronArucoDetect.getInstance().isStartAruco() || ApronArucoDetectPort.getInstance().isStartAruco() || Aprongim.getInstance().isStartAruco() || Aprondown.getInstance().isStartAruco()) {
sendEvent2Server("自动降落不可以动负载", 1);
return;
}else{
} else {
CameraManager.getInstance().camera_frame_zoom(message);
}
break;
case Constant.SPEAKER_AUDIO_PLAY_START:
LogUtil.log(TAG, "收到:喊话器-开始播放音频" + jsonString);
SpeakerProgressReporter.getInstance().startAudioReport(2);
SpeakerManager.getInstance().speakerAudioPlayStart(message);
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerAudioPlayStart(message);
Synchronizedstatus.setSpeakrunning(true);
}else{
sendMsg2Server(message);
return;
}
}
break;
case Constant.SPEAKER_TTS_PLAY_START:
LogUtil.log(TAG, "收到:喊话器-开始播放TTS文本" + jsonString);
SpeakerProgressReporter.getInstance().startTTSReport(2);
SpeakerManager.getInstance().speakerTTSPlayStart(message, 0);
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerTTSPlayStart(message, 0);
Synchronizedstatus.setSpeakTTSrunning(true);
}else{
sendMsg2Server(message);
return;
}
}
break;
case Constant.SPEAKER_REPLAY:
LogUtil.log(TAG, "收到:喊话器-重新播放" + jsonString);
SpeakerManager.getInstance().speakerReply(message);
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerReply(message);
Synchronizedstatus.setSpeaksetrunning(true);
}else{
sendMsg2Server(message);
return;
}
}
break;
case Constant.SPEAKER_PLAY_STOP:
LogUtil.log(TAG, "收到:喊话器-停止播放" + jsonString);
SpeakerManager.getInstance().speakerStop(message);
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerStop(message);
Synchronizedstatus.setSpeaksetrunning(true);
}else{
sendMsg2Server(message);
return;
}
}
break;
case Constant.SPEAKER_PLAY_MODE_SET:
LogUtil.log(TAG, "收到:喊话器-设置播放模式" + jsonString);
SpeakerManager.getInstance().speakerPlayModeSet(message);
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerPlayModeSet(message);
Synchronizedstatus.setSpeaksetrunning(true);
}else{
sendMsg2Server(message);
return;
}
}
break;
case Constant.SPEAKER_PLAY_VOLUME_SET:
LogUtil.log(TAG, "收到:喊话器-设置音量" + jsonString);
SpeakerManager.getInstance().speakerPlayVolumeSet(message);
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerPlayVolumeSet(message);
Synchronizedstatus.setSpeaksetrunning(true);
}else{
sendMsg2Server(message);
return;
}
}
break;
case Constant.DRC_SPEAKER_TTS_SET:
LogUtil.log(TAG, "收到:喊话器-TTS喊话设置" + jsonString);
SpeakerManager.getInstance().speakerTTSPlayStart(message, 1);
synchronized (lock) {
if (!Synchronizedstatus.isSpeakrunning()) {
SpeakerManager.getInstance().speakerTTSPlayStart(message, 1);
Synchronizedstatus.setSpeaksetrunning(true);
}else{
sendMsg2Server(message);
return;
}
}
break;
case Constant.UAV_LIVE_FPV:
//LogUtil.log(TAG, "收到切换推流fpv" + jsonString);
StreamManager.getInstance().switchptspfpv(ComponentIndexType.FPV,message);
StreamManager.getInstance().switchptspfpv(ComponentIndexType.FPV, message);
break;
case Constant.UAV_LIVE_CAMERA:
//LogUtil.log(TAG, "收到切换推流camera" + jsonString);
StreamManager.getInstance().switchptspport(ComponentIndexType.PORT_1,message);
StreamManager.getInstance().switchptspport(ComponentIndexType.PORT_1, message);
break;
case Constant.DRC_LIGHT_BRIGHTNESS_SET:
synchronized (lock) {
if (Synchronizedstatus.isLight_brightnessrunning()){
return;
}else {
}
}
// LogUtil.log(TAG, "收到A1亮度设置 " + jsonString);
// 处理亮度设置逻辑
break;
case Constant.DRC_LIGHT_MODE_SET:
// LogUtil.log(TAG, "收到A1模式设置 " + jsonString);
// 处理模式设置逻辑
break;
case Constant.DRC_LIGHT_FINE_TUNING_SET:
// LogUtil.log(TAG, "收到A1左右角度微调 " + jsonString);
// 处理左右角度微调逻辑
break;
case Constant.DRC_LIGHT_CALIBRATION:
// LogUtil.log(TAG, "收到A1云台校准 " + jsonString);
// 处理云台校准逻辑
break;
// //获取控制权
// case 60007:
@ -808,21 +902,23 @@ public class MqttCallBack extends BaseManager implements MqttCallbackExtended {
public void deliveryComplete(IMqttDeliveryToken token) {
}
String[] topics = new String[] {
String[] topics = new String[]{
AMSConfig.getInstance().DOWN_UAV_EVENT_REPLY,
AMSConfig.getInstance().DOWN_UAV_SERVICES,
};
int[] qos = new int[] {
int[] qos = new int[]{
1, 1
};
@Override
public void connectComplete(boolean reconnect, String serverURI) {
try {
// if (reconnect) {//重新订阅
LogUtil.log(TAG, "MQtt ConnectComplete:" + serverURI);
MqttManager.getInstance().mqttAndroidClient.subscribe(topics, qos);//订阅主题:注册
LogUtil.log(TAG, "MQtt ConnectComplete:" + serverURI);
MqttManager.getInstance().mqttAndroidClient.subscribe(topics, qos);//订阅主题:注册
// MqttManager.getInstance().mqttAndroidClient.subscribe(AMSConfig.DOWN_UAV_EVENT_REPLY, 1);//订阅主题:注册
// publish(topic,"注册",0);
// publish(topic,"注册",0);
// }
} catch (Exception e) {
LogUtil.log(TAG, "MQtt ConnectException:" + e.toString());

View File

@ -330,4 +330,21 @@ public class Constant {
* 推流切换camera
*/
public static final String UAV_LIVE_CAMERA="uav_live_camera";
public static final String DRC_LIGHT_BRIGHTNESS_SET="drc_light_brightness_set";
//A1模式设置
public static final String DRC_LIGHT_MODE_SET="drc_light_mode_set";
//A1左右角度微调
public static final String DRC_LIGHT_FINE_TUNING_SET="drc_light_fine_tuning_set";
//A1云台校准
public static final String DRC_LIGHT_CALIBRATION="drc_light_calibration";
}

View File

@ -117,11 +117,68 @@ public class MessageDown {
private int volume;
private int type;
private int language;
private Tts tts;
//探照灯
private int group;
// brightness: 亮度 1-100 (int)
private int brightness;
// mode: 模式 0-关闭 1-常亮 2-爆闪 3-快速爆闪 4-交替爆闪 (enum_int)
private int mode;
// position: 灯位置 0-左灯 1-右灯 (enum_int)
private int position;
// value: 左右角度微调值 -3° +3° (int)
private int value;
// saved: 是否保存 0- 1- (bool)
private boolean saved;
public int getGroup() {
return group;
}
public void setGroup(int group) {
this.group = group;
}
public int getBrightness() {
return brightness;
}
public void setBrightness(int brightness) {
this.brightness = brightness;
}
public int getMode() {
return mode;
}
public void setMode(int mode) {
this.mode = mode;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public boolean isSaved() {
return saved;
}
public void setSaved(boolean saved) {
this.saved = saved;
}
public Tts getTts() {
return tts;

View File

@ -17,10 +17,76 @@ public class Synchronizedstatus {
private static volatile boolean isruningframe=false;
private static volatile boolean aprongim=true;
private static volatile boolean switchtime=true;
//探照灯线程
private static volatile boolean light_brightnessrunning=false;
//喊话器线程
private static volatile boolean speakrunning=false;
private static volatile boolean speakTTSrunning=false;
private static volatile boolean speaksetrunning=false;
public static boolean isSpeaksetrunning() {
return speaksetrunning;
}
public static void setSpeaksetrunning(boolean speaksetrunning) {
Synchronizedstatus.speaksetrunning = speaksetrunning;
}
public static boolean isSpeakTTSrunning() {
return speakTTSrunning;
}
public static void setSpeakTTSrunning(boolean speakTTSrunning) {
Synchronizedstatus.speakTTSrunning = speakTTSrunning;
}
public static boolean isSpeakrunning() {
return speakrunning;
}
public static void setSpeakrunning(boolean speakrunning) {
Synchronizedstatus.speakrunning = speakrunning;
}
public static boolean isLight_brightnessrunning() {
return light_brightnessrunning;
}
public static void setLight_brightnessrunning(boolean light_brightnessrunning) {
Synchronizedstatus.light_brightnessrunning = light_brightnessrunning;
}
public static boolean isSwitchtime() {
return switchtime;
}
public static void setSwitchtime(boolean switchtime) {
Synchronizedstatus.switchtime = switchtime;
}
public static boolean isAprongim() {
return aprongim;
}
public static void setAprongim(boolean aprongim) {
Synchronizedstatus.aprongim = aprongim;
}
public static boolean isIsruningframe() {
return isruningframe;
}
public static void setIsruningframe(boolean isruningframe) {
Synchronizedstatus.isruningframe = isruningframe;
}

View File

@ -1,12 +1,123 @@
package com.aros.apron.entity;
public class XTTSParams {
public String vcn = "xiaoyan";
public int language =1 ;
public int pitch = 50;
public int speed = 50;
public int volume = 100;
// 默认参数值
public static final String DEFAULT_VCN = "xiaoyan";
public static final int DEFAULT_LANGUAGE = 1;
public static final int DEFAULT_PITCH = 50;
public static final int DEFAULT_SPEED = 50;
public static final int DEFAULT_VOLUME = 100;
// 参数范围
public static final int MIN_PITCH = 0;
public static final int MAX_PITCH = 100;
public static final int MIN_SPEED = 0;
public static final int MAX_SPEED = 100;
public static final int MIN_VOLUME = 0;
public static final int MAX_VOLUME = 100;
public String vcn = DEFAULT_VCN;
public int language = DEFAULT_LANGUAGE;
public int pitch = DEFAULT_PITCH;
public int speed = DEFAULT_SPEED;
public int volume = DEFAULT_VOLUME;
// 默认构造方法
public XTTSParams() {
}
// 全参数构造方法
public XTTSParams(String vcn, int language, int pitch, int speed, int volume) {
setVcn(vcn);
setLanguage(language);
setPitch(pitch);
setSpeed(speed);
setVolume(volume);
}
// Getter Setter 方法
public String getVcn() {
return vcn;
}
public void setVcn(String vcn) {
if (vcn != null && !vcn.isEmpty()) {
this.vcn = vcn;
}
}
public int getLanguage() {
return language;
}
public void setLanguage(int language) {
// 确保语言代码为正数
if (language > 0) {
this.language = language;
}
}
public int getPitch() {
return pitch;
}
public void setPitch(int pitch) {
// 确保语调在有效范围内
this.pitch = Math.max(MIN_PITCH, Math.min(MAX_PITCH, pitch));
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
// 确保语速在有效范围内
this.speed = Math.max(MIN_SPEED, Math.min(MAX_SPEED, speed));
}
public int getVolume() {
return volume;
}
public void setVolume(int volume) {
// 确保音量在有效范围内
this.volume = Math.max(MIN_VOLUME, Math.min(MAX_VOLUME, volume));
}
// 预设配置方法
public static XTTSParams createDefault() {
return new XTTSParams();
}
public static XTTSParams createFemaleVoice() {
return new XTTSParams("xiaoyan", 1, 50, 50, 100);
}
public static XTTSParams createMaleVoice() {
return new XTTSParams("xiaofeng", 1, 50, 50, 100);
}
public static XTTSParams createFastSpeed() {
XTTSParams params = new XTTSParams();
params.setSpeed(70);
return params;
}
public static XTTSParams createSlowSpeed() {
XTTSParams params = new XTTSParams();
params.setSpeed(30);
return params;
}
@Override
public String toString() {
return "XTTSParams{" +
"vcn='" + vcn + '\'' +
", language=" + language +
", pitch=" + pitch +
", speed=" + speed +
", volume=" + volume +
'}';
}
}

View File

@ -0,0 +1,49 @@
package com.aros.apron.manager;
public class A1Manager {
private String TAG = this.getClass().getSimpleName();
public A1Manager() {
}
private static class A1Managerholder {
private static final A1Manager INSTANCE = new A1Manager();
}
public static A1Manager getInstance() {
return A1Manager.A1Managerholder.INSTANCE;
}
//探照灯亮度设置
public void setdrc_light_brightness_set(){
}
//探照灯模式设置
public void setdrc_light_mode_set(){
}
//探照灯左右角度微调
public void setdrc_light_fine_tuning_set(){
}
// 探照灯云台校准
public void setdrc_light_calibrationt(){
}
}

View File

@ -86,6 +86,7 @@ public class CameraManager extends BaseManager {
panoHandler.removeCallbacks(panoRunnable);
sendCameraPhotoTakeProgress2Server(); // 最后报一次
Movement.getInstance().setMode_code(3);
}
@ -139,11 +140,12 @@ public void initCameraInfo() {
// Movement.getInstance().setPhoto_status("fail");
} else if (t1 == VisionPhotoPanoramaMissionState.PROCESSING) {
Movement.getInstance().setPhoto_current_step(3005);
} else if (t1 == VisionPhotoPanoramaMissionState.FORBIDDEN) {
Movement.getInstance().setPhoto_result(1);
Movement.getInstance().setPhoto_current_step(3000);
Movement.getInstance().setPhoto_status("fail");
}
// else if (t1 == VisionPhotoPanoramaMissionState.FORBIDDEN) {
// Movement.getInstance().setPhoto_result(1);
// Movement.getInstance().setPhoto_current_step(3000);
// Movement.getInstance().setPhoto_status("fail");
// }
}
}
});
@ -663,6 +665,10 @@ public void setCameraMode(MessageDown message) {
} else if (cameraMode == 3) {
cameraModevalue = 12;
}
// if(cameraMode == 3){
// //退出回放模式
// MediaManager.getInstance().disablePlayback();
// }
KeyManager.getInstance().setValue(KeyTools.createKey(CameraKey.KeyCameraMode, ComponentIndexType.PORT_1),
CameraMode.find(cameraModevalue),

View File

@ -16,6 +16,8 @@ import com.aros.apron.constant.AMSConfig;
import com.aros.apron.entity.ApronExecutionStatus;
import com.aros.apron.entity.MessageDown;
import com.aros.apron.entity.Movement;
import com.aros.apron.mix.Aprondown;
import com.aros.apron.mix.Aprongim;
import com.aros.apron.tools.AlternateArucoDetect;
import com.aros.apron.tools.ApronArucoDetect;
import com.aros.apron.tools.ApronArucoDetectPort;
@ -1164,6 +1166,7 @@ public class FlightManager extends BaseManager {
// 释放控制权失败时的处理
LogUtil.log(TAG, "释放控制权失败,无法执行自动降落: " + error.toString());
// 可以添加重试逻辑或其他错误处理
EventBus.getDefault().post(FLAG_DOWN_LAND);
}
});
}
@ -1178,7 +1181,7 @@ public class FlightManager extends BaseManager {
if (PreferenceUtils.getInstance().getNeedTriggerAlterArucoLand()) {
return !isTriggerLanding && (isFlying || isMotorsOn) && AlternateArucoDetect.getInstance().isCanLanding();
} else {
return !isTriggerLanding && (isFlying || isMotorsOn) && (ApronArucoDetect.getInstance().isCanLanding() || ApronArucoDetectPort.getInstance().isCanLanding());
return !isTriggerLanding && (isFlying || isMotorsOn) && (ApronArucoDetect.getInstance().isCanLanding() || ApronArucoDetectPort.getInstance().isCanLanding()|| Aprongim.getInstance().isCanLanding()|| Aprondown.getInstance().isCanLanding());
}
}
@ -1199,6 +1202,9 @@ public class FlightManager extends BaseManager {
sendCloseCabinDoorMsg = false;
ApronArucoDetect.getInstance().setCanLanding(false);
ApronArucoDetectPort.getInstance().setCanLanding(false);
Aprondown.getInstance().setCanLanding(false);
Aprongim.getInstance().setCanLanding(false);
// 发布事件通知其他组件停止Aruco检测
EventBus.getDefault().post(FLAG_STOP_ARUCO);

View File

@ -11,6 +11,7 @@ import androidx.annotation.Nullable;
import com.aros.apron.base.BaseManager;
import com.aros.apron.entity.MessageDown;
import com.aros.apron.entity.Movement;
import com.aros.apron.mix.Aprongim;
import com.aros.apron.tools.ApronArucoDetect;
import com.aros.apron.tools.ApronArucoDetectPort;
import com.aros.apron.tools.LogUtil;
@ -59,6 +60,12 @@ public class GimbalManager extends BaseManager {
public void initGimbalInfo() {
ApronArucoDetect.getInstance().setDoublePayload(PreferenceUtils.getInstance().getCameraLocationType() == 2);
ApronArucoDetectPort.getInstance().setDoublePayload(PreferenceUtils.getInstance().getCameraLocationType() == 2);
Aprongim.getInstance().setDoublePayload(PreferenceUtils.getInstance().getCameraLocationType() == 4);
LogUtil.log(TAG, "主摄像头位置:" + PreferenceUtils.getInstance().getCameraLocationType());
Boolean isConnect = KeyManager.getInstance().getValue(KeyTools.createKey(FlightControllerKey.KeyConnection));

View File

@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
import com.aros.apron.base.BaseManager;
import com.aros.apron.entity.MessageDown;
import com.aros.apron.entity.Movement;
import com.aros.apron.entity.Synchronizedstatus;
import com.aros.apron.entity.XTTSParams;
import com.aros.apron.tools.LogUtil;
import com.aros.apron.tools.SpeakerProgressReporter;
@ -45,6 +46,7 @@ public class SpeakerManager extends BaseManager {
return SpeakerHolder.INSTANCE;
}
private int megaphoneStatus;
private XTtsPcmGenerator ttsGenerator;
public void initMegaphoneInfo() {
MegaphoneManager.getInstance().addMegaphoneInfoListener(new MegaphoneInfoListener() {
@Override
@ -65,10 +67,16 @@ public class SpeakerManager extends BaseManager {
LogUtil.log(TAG,"喊话器位置设置失败:"+getIDJIErrorMsg(error));
}
});
}
public void speakerAudioPlayStart(MessageDown message) {
sendMsg2Server(message);
//暂停播放
stop();
if (TextUtils.isEmpty(message.getData().getFile().getUrl())) {
sendFailMsg2Server(message, "喊话文件下载地址为空");
LogUtil.log(TAG,"喊话文件下载地址为空");
return;
}
Request request = new Request.Builder().url(message.getData().getFile().getUrl()).build();
@ -76,6 +84,7 @@ public class SpeakerManager extends BaseManager {
@Override
public void onFailure(Call call, IOException e) {
sendEvent2Server(".pcm文件下载失败:" + e.toString(), 2);
LogUtil.log(TAG,"pcm文件下载失败");
}
@Override
@ -86,7 +95,7 @@ public class SpeakerManager extends BaseManager {
int len = 0;
FileOutputStream fos = null;
// 储存下载文件的目录
File dir = new File(Environment.getExternalStorageDirectory().getPath());
File dir = new File("/storage/self/primary/DJIDemo/cache/tts/output/");
if (!dir.exists()) {
dir.mkdirs();
}
@ -98,8 +107,14 @@ public class SpeakerManager extends BaseManager {
fos.write(buf, 0, len);
}
fos.flush();
// 检查 PCM 文件大小
LogUtil.log(TAG, "PCM 文件大小: " + file.length() + " bytes");
sendEvent2Server("喊话.pcm文件下载成功", 1);
String opusFilePath = PCMTools.INSTANCE.convertToOpusFileSync(file.getAbsolutePath());
// 检查 opus 文件路径和大小
File opusFile = new File(opusFilePath);
LogUtil.log(TAG, "Opus 文件路径: " + opusFilePath);
LogUtil.log(TAG, "Opus 文件大小: " + (opusFile.exists() ? opusFile.length() : 0) + " bytes");
Movement.getInstance().setStep_key("download");
Movement.getInstance().setTTS_status("in_progress");
@ -109,6 +124,8 @@ public class SpeakerManager extends BaseManager {
null);
MegaphoneManager.getInstance().startPushingFileToMegaphone(fileInfo,
new CommonCallbacks.CompletionCallbackWithProgress<Integer>() {
private boolean isCallbackExecuted = false; // 确保回调只执行一次
@Override
public void onProgressUpdate(Integer integer) {
@ -119,6 +136,12 @@ public class SpeakerManager extends BaseManager {
@Override
public void onSuccess() {
// 确保回调只执行一次
if (isCallbackExecuted) {
return;
}
isCallbackExecuted = true;
LogUtil.log(TAG, "喊话器内容上传成功");
new Handler().postDelayed(new Runnable() {
@Override
@ -127,13 +150,16 @@ public class SpeakerManager extends BaseManager {
@Override
public void onSuccess() {
Movement.getInstance().setTTS_status("ok");
sendEvent2Server("喊话器播放音频成功", 1);
LogUtil.log(TAG,"喊话器播放音频成功");
Synchronizedstatus.setSpeakrunning(false);
}
@Override
public void onFailure(@NonNull IDJIError error) {
sendEvent2Server("喊话器播放音频失败:" + getIDJIErrorMsg(error), 2);
Synchronizedstatus.setSpeakrunning(false);
LogUtil.log(TAG,"喊话器播放音频失败"+ getIDJIErrorMsg(error));
}
});
}
@ -142,17 +168,22 @@ public class SpeakerManager extends BaseManager {
@Override
public void onFailure(@NonNull IDJIError error) {
// 确保回调只执行一次
if (isCallbackExecuted) {
return;
}
isCallbackExecuted = true;
sendFailMsg2Server(message, "喊话器内容上传失败:" + getIDJIErrorMsg(error));
}
});
} catch (Exception e) {
sendEvent2Server("喊话.pcm文件下载失败:" + e.toString(), 2);
}
}
}
});
}
@ -160,14 +191,17 @@ public class SpeakerManager extends BaseManager {
MegaphoneManager.getInstance().startPlay(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
Synchronizedstatus.setSpeaksetrunning(false);
sendMsg2Server(message);
}
@Override
public void onFailure(@NonNull IDJIError error) {
Synchronizedstatus.setSpeaksetrunning(false);
sendFailMsg2Server(message, "喊话器播放音频失败:" + getIDJIErrorMsg(error));
}
});
}
public void speakerStop(MessageDown message) {
@ -176,6 +210,7 @@ public class SpeakerManager extends BaseManager {
public void onSuccess() {
SpeakerProgressReporter.getInstance().stopAudioReport();
SpeakerProgressReporter.getInstance().stopTTSReport();
Synchronizedstatus.setSpeaksetrunning(false);
sendMsg2Server(message);
}
@ -183,23 +218,41 @@ public class SpeakerManager extends BaseManager {
public void onFailure(@NonNull IDJIError error) {
SpeakerProgressReporter.getInstance().stopAudioReport();
SpeakerProgressReporter.getInstance().stopTTSReport();
sendMsg2Server(message);
sendFailMsg2Server(message, "喊话器停止播放失败:" + getIDJIErrorMsg(error));
Synchronizedstatus.setSpeaksetrunning(false);
}
});
}
public void stop(){
MegaphoneManager.getInstance().stopPlay(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
SpeakerProgressReporter.getInstance().stopAudioReport();
SpeakerProgressReporter.getInstance().stopTTSReport();
}
@Override
public void onFailure(@NonNull IDJIError error) {
SpeakerProgressReporter.getInstance().stopAudioReport();
SpeakerProgressReporter.getInstance().stopTTSReport();
}
});
}
public void speakerPlayModeSet(MessageDown message) {
MegaphoneManager.getInstance().setPlayMode(message.getData().getPlay_mode() == 0 ?
PlayMode.SINGLE : PlayMode.LOOP, new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
Movement.getInstance().setStep_key("change_work_mode");
sendMsg2Server(message);
Movement.getInstance().setStep_key("change_work_mode");
Synchronizedstatus.setSpeaksetrunning(false);
}
@Override
public void onFailure(@NonNull IDJIError error) {
Synchronizedstatus.setSpeaksetrunning(false);
sendFailMsg2Server(message, "喊话器播放模式设置失败:" + getIDJIErrorMsg(error));
}
});
@ -217,60 +270,105 @@ public class SpeakerManager extends BaseManager {
sendFailMsg2Server(message, "喊话器音量设置失败:" + getIDJIErrorMsg(error));
}
});
Synchronizedstatus.setSpeaksetrunning(false);
}
private String text;
public void speakerTTSPlayStart(MessageDown message, int type) {
//暂停播放
stop();
LogUtil.log(TAG, "开始执行 speakerTTSPlayStart 方法type: " + type);
// 检查是否正在运行
if (Synchronizedstatus.isSpeakTTSrunning()) {
LogUtil.log(TAG, "喊话器TTS播放正在运行跳过本次请求");
sendFailMsg2Server(message, "喊话器TTS播放正在运行");
return;
}
// 设置为正在运行
Synchronizedstatus.setSpeakTTSrunning(true);
LogUtil.log(TAG, "设置 speakTTSrunning 为 true");
message.getData().getTts().getMd5();
// 使用 com.aros.apron.entity.XTTSParams 类型
XTTSParams params = new XTTSParams();
if (type == 0) {
text = message.getData().getTts().getText();
// 设置默认参数值
params.setVcn("xiaofeng");
params.setLanguage(1);
params.setSpeed(50);
params.setVolume(100);
LogUtil.log(TAG, "type == 0text: " + text + ", vcn: " + params.getVcn() + ", language: " + params.getLanguage() + ", speed: " + params.getSpeed() + ", volume: " + params.getVolume());
} else {
if (megaphoneStatus == 2) {
LogUtil.log(TAG, "type != 0megaphoneStatus == 2执行 stopPlay");
MegaphoneManager.getInstance().stopPlay(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
LogUtil.log(TAG, "终止喊话");
LogUtil.log(TAG, "终止喊话成功");
}
@Override
public void onFailure(@NonNull IDJIError error) {
LogUtil.log(TAG, "终止喊话失败:" + getIDJIErrorMsg(error));
}
});
}
params.vcn = message.getData().getType() == 0 ? "xiaofeng" : "xiaoyan";
params.language = message.getData().getLanguage() == 0 ? 1 : 2;
params.speed = message.getData().getSpeed();
params.volume = message.getData().getVolume();
params.setVcn(message.getData().getType() == 0 ? "xiaofeng" : "xiaoyan");
params.setLanguage(message.getData().getLanguage() == 0 ? 1 : 2);
params.setSpeed(message.getData().getSpeed());
params.setVolume(message.getData().getVolume());
LogUtil.log(TAG, "type != 0vcn: " + params.getVcn() + ", language: " + params.getLanguage() + ", speed: " + params.getSpeed() + ", volume: " + params.getVolume());
}
if (TextUtils.isEmpty(text)) {
LogUtil.log(TAG, "喊话内容为空,跳过本次请求");
sendFailMsg2Server(message, "喊话失败:喊话内容为空");
Synchronizedstatus.setSpeakTTSrunning(false);
LogUtil.log(TAG, "设置 speakTTSrunning 为 false");
return;
}
XTtsPcmGenerator tts = new XTtsPcmGenerator();
AiListener listener = tts.buildListener();
tts.init(listener);
LogUtil.log(TAG, "创建 XTtsPcmGenerator 实例");
ttsGenerator = new XTtsPcmGenerator();
LogUtil.log(TAG, "准备 PCM 文件");
File pcmFile = new File(Utils.getSDCardPath() + "/pcm", "tts_output_local.pcm");
// 使用固定的文件夹路径
String fixedDirPath = "/storage/self/primary/DJIDemo/cache/tts/output/";
File outputDir = new File(fixedDirPath);
if (!outputDir.exists()) {
LogUtil.log(TAG, "创建输出目录: " + fixedDirPath);
outputDir.mkdirs();
}
// 使用带时间戳的文件名避免覆盖
String fileName = "output_" + System.currentTimeMillis() + ".pcm";
File pcmFile = new File(outputDir, fileName);
LogUtil.log(TAG, "创建 PCM 文件: " + pcmFile.getAbsolutePath());
int synthToPcm = tts.synthToPcm(
text,
params,
pcmFile
);
if (synthToPcm == 0) {
// 合成文本到固定路径
LogUtil.log(TAG, "开始调用 synthToPcm 方法text: " + text);
File pcmFileResult = ttsGenerator.synthToPcm(text, params, pcmFile);
if (pcmFileResult != null && pcmFileResult.length() > 0) {
LogUtil.log(TAG, "合成完成PCM 文件路径: " + pcmFileResult.getAbsolutePath());
sendMsg2Server(message);
String opusFilePath = PCMTools.INSTANCE.convertToOpusFileSync(pcmFile.getAbsolutePath());
// 检查 PCM 文件大小
LogUtil.log(TAG, "TTS PCM 文件大小: " + pcmFileResult.length() + " bytes");
// 转换为 OPUS 格式
LogUtil.log(TAG, "开始转换为 OPUS 格式");
String opusFilePath = PCMTools.INSTANCE.convertToOpusFileSync(pcmFileResult.getAbsolutePath());
// 检查 opus 文件路径和大小
File opusFile = new File(opusFilePath);
LogUtil.log(TAG, "TTS Opus 文件路径: " + opusFilePath);
LogUtil.log(TAG, "TTS Opus 文件大小: " + (opusFile.exists() ? opusFile.length() : 0) + " bytes");
FileInfo fileInfo = new FileInfo(UploadType.VOICE_FILE,
new File(opusFilePath),
null);
LogUtil.log(TAG, "开始上传文件到喊话器");
MegaphoneManager.getInstance().startPushingFileToMegaphone(fileInfo,
new CommonCallbacks.CompletionCallbackWithProgress<Integer>() {
private boolean isCallbackExecuted = false; // 确保回调只执行一次
@Override
public void onProgressUpdate(Integer integer) {
Movement.getInstance().setTTS_status("in_progress");
@ -281,21 +379,32 @@ public class SpeakerManager extends BaseManager {
@Override
public void onSuccess() {
// 确保回调只执行一次
if (isCallbackExecuted) {
return;
}
isCallbackExecuted = true;
LogUtil.log(TAG, "喊话器内容上传成功");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
LogUtil.log(TAG, "开始播放音频");
MegaphoneManager.getInstance().startPlay(new CommonCallbacks.CompletionCallback() {
@Override
public void onSuccess() {
Movement.getInstance().setTTS_status("ok");
Movement.getInstance().setStep_key("play");
sendEvent2Server("喊话器播放TTS音频成功", 1);
LogUtil.log(TAG,"喊话器播放TTS音频成功");
Synchronizedstatus.setSpeakTTSrunning(false);
}
@Override
public void onFailure(@NonNull IDJIError error) {
sendEvent2Server("喊话器播放TTS音频失败:" + getIDJIErrorMsg(error), 2);
LogUtil.log(TAG,"喊话器播放TTS音频失败qwq" + getIDJIErrorMsg(error));
Synchronizedstatus.setSpeakTTSrunning(false);
}
});
}
@ -304,11 +413,22 @@ public class SpeakerManager extends BaseManager {
@Override
public void onFailure(@NonNull IDJIError error) {
// 确保回调只执行一次
if (isCallbackExecuted) {
return;
}
isCallbackExecuted = true;
sendFailMsg2Server(message, "喊话器内容上传失败:" + getIDJIErrorMsg(error));
LogUtil.log(TAG, "喊话器内容上传失败:" + getIDJIErrorMsg(error));
}
});
} else {
sendFailMsg2Server(message, "tts合成失败");
LogUtil.log(TAG, "合成失败PCM 文件为空");
sendFailMsg2Server(message, "合成失败PCM 文件为空");
}
LogUtil.log(TAG, "设置 speakTTSrunning 为 false");
LogUtil.log(TAG, "speakerTTSPlayStart 方法执行完成");
}
}

View File

@ -79,10 +79,13 @@ public class TakeOffToPointManager extends BaseManager {
Movement.getInstance().setIstakeoffex(true);
PreferenceUtils.getInstance().setFlightId(message.getData().getFlight_id());
PreferenceUtils.getInstance().setAlternatePointLon(message.getData().getAlternate_land_point().getLongitude() + "");
PreferenceUtils.getInstance().setAlternatePointLat(message.getData().getAlternate_land_point().getLatitude() + "");
PreferenceUtils.getInstance().setAlternatePointSecurityHeight(
message.getData().getAlternate_land_point().getSafe_land_height() + "");
Movement.getInstance().setTask_current_step(5);
LogUtil.log(TAG, "生成");

View File

@ -1,133 +1,133 @@
package com.aros.apron.manager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import com.aros.apron.tools.LogUtil;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
import java.util.ArrayList;
import java.util.List;
/**
* RTSP流检测工具严格匹配LibVLC 3.6.0源码
* 核心单例初始化拉流检测成功/失败回调无任何冗余
*/
public class VlcRtspManager {
private static volatile VlcRtspManager instance;
private static final long CHECK_TIMEOUT = 5000L; // 5秒超时
private LibVLC libVLC;
private MediaPlayer mediaPlayer;
private Handler handler = new Handler(Looper.getMainLooper());
private OnRtspCheckListener checkListener;
private boolean isChecking = false;
// 仅保留成功/失败回调
public interface OnRtspCheckListener {
void onSuccess(); // 拉流成功
void onFailed(); // 拉流失败
}
// 单例
private VlcRtspManager() {}
public static VlcRtspManager getInstance() {
if (instance == null) {
synchronized (VlcRtspManager.class) {
if (instance == null) {
instance = new VlcRtspManager();
}
}
}
return instance;
}
// 初始化3.6.0 源码级配置
public void init(Context context) {
if (libVLC != null) return;
List<String> options = new ArrayList<>();
options.add("--rtsp-tcp");
options.add("--network-caching=500");
options.add("--vout=none");
options.add("--aout=none");
options.add("--quiet");
libVLC = new LibVLC(context.getApplicationContext(), options);
mediaPlayer = new MediaPlayer(libVLC);
// ====================== 核心修正3.6.0 事件判定源码级 ======================
// 参考3.6.0源码MediaPlayer.Event的type是int型对应EventType枚举的ordinal()
mediaPlayer.setEventListener(new MediaPlayer.EventListener() {
@Override
public void onEvent(MediaPlayer.Event event) {
// 3.6.0源码中
// EventType.Playing.ordinal() = 0
// EventType.Error.ordinal() = 1
// 直接用整型判定避开枚举引用错误
if (event.type == 0) { // Playing事件
if (checkListener != null) checkListener.onSuccess();
stopCheck();
} else if (event.type == 1) { // Error事件
if (checkListener != null) checkListener.onFailed();
stopCheck();
}
}
});
}
// 检测RTSP流拉流测试
public void checkRtspStream(String rtspUrl, OnRtspCheckListener listener) {
stopCheck();
this.checkListener = listener;
this.isChecking = true;
// 超时检测
handler.postDelayed(() -> {
if (isChecking) {
if (checkListener != null) checkListener.onFailed();
stopCheck();
}
}, CHECK_TIMEOUT);
// 拉流3.6.0 源码级写法
try {
Media media = new Media(libVLC, rtspUrl);
mediaPlayer.setMedia(media);
media.release();
mediaPlayer.play();
} catch (Exception e) {
LogUtil.log("VlcRtspManager", "拉流异常:" + e.getMessage());
if (checkListener != null) checkListener.onFailed();
stopCheck();
}
}
// 停止检测私有
private void stopCheck() {
isChecking = false;
handler.removeCallbacksAndMessages(null);
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
}
// 释放资源可选
public void release() {
stopCheck();
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
if (libVLC != null) {
libVLC.release();
libVLC = null;
}
instance = null;
}
}
//package com.aros.apron.manager;
//
//import android.content.Context;
//import android.os.Handler;
//import android.os.Looper;
//
//import com.aros.apron.tools.LogUtil;
//
//import org.videolan.libvlc.LibVLC;
//import org.videolan.libvlc.Media;
//import org.videolan.libvlc.MediaPlayer;
//
//import java.util.ArrayList;
//import java.util.List;
//
///**
// * RTSP流检测工具严格匹配LibVLC 3.6.0源码
// * 核心单例初始化拉流检测成功/失败回调无任何冗余
// */
//public class VlcRtspManager {
// private static volatile VlcRtspManager instance;
// private static final long CHECK_TIMEOUT = 5000L; // 5秒超时
//
// private LibVLC libVLC;
// private MediaPlayer mediaPlayer;
// private Handler handler = new Handler(Looper.getMainLooper());
// private OnRtspCheckListener checkListener;
// private boolean isChecking = false;
//
// // 仅保留成功/失败回调
// public interface OnRtspCheckListener {
// void onSuccess(); // 拉流成功
// void onFailed(); // 拉流失败
// }
//
// // 单例
// private VlcRtspManager() {}
// public static VlcRtspManager getInstance() {
// if (instance == null) {
// synchronized (VlcRtspManager.class) {
// if (instance == null) {
// instance = new VlcRtspManager();
// }
// }
// }
// return instance;
// }
//
// // 初始化3.6.0 源码级配置
// public void init(Context context) {
// if (libVLC != null) return;
//
// List<String> options = new ArrayList<>();
// options.add("--rtsp-tcp");
// options.add("--network-caching=500");
// options.add("--vout=none");
// options.add("--aout=none");
// options.add("--quiet");
//
// libVLC = new LibVLC(context.getApplicationContext(), options);
// mediaPlayer = new MediaPlayer(libVLC);
//
// // ====================== 核心修正3.6.0 事件判定源码级 ======================
// // 参考3.6.0源码MediaPlayer.Event的type是int型对应EventType枚举的ordinal()
// mediaPlayer.setEventListener(new MediaPlayer.EventListener() {
// @Override
// public void onEvent(MediaPlayer.Event event) {
// // 3.6.0源码中
// // EventType.Playing.ordinal() = 0
// // EventType.Error.ordinal() = 1
// // 直接用整型判定避开枚举引用错误
// if (event.type == 0) { // Playing事件
// if (checkListener != null) checkListener.onSuccess();
// stopCheck();
// } else if (event.type == 1) { // Error事件
// if (checkListener != null) checkListener.onFailed();
// stopCheck();
// }
// }
// });
// }
//
// // 检测RTSP流拉流测试
// public void checkRtspStream(String rtspUrl, OnRtspCheckListener listener) {
// stopCheck();
//
// this.checkListener = listener;
// this.isChecking = true;
//
// // 超时检测
// handler.postDelayed(() -> {
// if (isChecking) {
// if (checkListener != null) checkListener.onFailed();
// stopCheck();
// }
// }, CHECK_TIMEOUT);
//
// // 拉流3.6.0 源码级写法
// try {
// Media media = new Media(libVLC, rtspUrl);
// mediaPlayer.setMedia(media);
// media.release();
// mediaPlayer.play();
// } catch (Exception e) {
// LogUtil.log("VlcRtspManager", "拉流异常:" + e.getMessage());
// if (checkListener != null) checkListener.onFailed();
// stopCheck();
// }
// }
//
// // 停止检测私有
// private void stopCheck() {
// isChecking = false;
// handler.removeCallbacksAndMessages(null);
// if (mediaPlayer != null && mediaPlayer.isPlaying()) {
// mediaPlayer.stop();
// }
// }
//
// // 释放资源可选
// public void release() {
// stopCheck();
// if (mediaPlayer != null) {
// mediaPlayer.release();
// mediaPlayer = null;
// }
// if (libVLC != null) {
// libVLC.release();
// libVLC = null;
// }
// instance = null;
// }
//}

View File

@ -61,6 +61,7 @@ public class MixedVisionLanding {
private static final int MAX_SWITCH_COUNT = 2;
private boolean isLanding = false;
private boolean isDoublePayload = false;
private int startArucoType = 0; // 1执行机库二维码识别 2执行备降点二维码识别
// ========== 云台相机参数 ==========
private static double GIMBAL_LENS_OFFSET_X = 0;
@ -105,6 +106,7 @@ public class MixedVisionLanding {
private boolean downwardDropTimesTag = false;
private long downwardStartTime = 0;
private long downwardEndTime = 0;
private boolean downwardIsYawAligned = false;
// ========== 执行器 ==========
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
@ -134,19 +136,24 @@ public class MixedVisionLanding {
}
public void startLanding() {
LogUtil.log(TAG_LOG, "开始融合视觉降落");
isLanding = true;
currentMode = LandingMode.GIMBAL_CAMERA;
switchCount = 0;
resetGimbalState();
resetDownwardState();
synchronized (this) {
LogUtil.log(TAG_LOG, "开始融合视觉降落");
isLanding = true;
currentMode = LandingMode.GIMBAL_CAMERA;
switchCount = 0;
resetGimbalState();
resetDownwardState();
}
}
public void stopLanding() {
LogUtil.log(TAG_LOG, "停止融合视觉降落");
isLanding = false;
resetGimbalState();
resetDownwardState();
synchronized (this) {
LogUtil.log(TAG_LOG, "停止融合视觉降落");
isLanding = false;
startArucoType = 0;
resetGimbalState();
resetDownwardState();
}
}
public LandingMode getCurrentMode() {
@ -161,84 +168,100 @@ public class MixedVisionLanding {
return isLanding;
}
public int getStartArucoType() {
return startArucoType;
}
public void setStartArucoType(int type) {
startArucoType = type;
}
// ========== 云台相机帧处理 ==========
public void processGimbalFrame(int height, int width, byte[] data, Dictionary dictionary) {
if (!isLanding || currentMode != LandingMode.GIMBAL_CAMERA) {
return;
}
gimbalIsTriggerSuccess = true;
Movement.getInstance().setVirtualStickEnableReason(2);
if (gimbalIsStartAruco || gimbalStartFastStick) {
return;
}
int currentFrame = gimbalFrameCounter++;
if (currentFrame % 3 != 0) {
return;
}
if (currentFrame % 30 != 0) {
DroneHelper.getInstance().setGimbalPitchdown();
}
gimbalIsStartAruco = true;
if (lastGimbalFuture != null && !lastGimbalFuture.isDone()) {
lastGimbalFuture.cancel(true);
}
int ultHeight = Movement.getInstance().getUltrasonicHeight();
updateGimbalLensOffset(ultHeight);
final int finalUltHeight = ultHeight;
lastGimbalFuture = executor.schedule(() -> {
try {
processGimbalFrameInternal(height, width, data, dictionary, finalUltHeight);
} catch (Exception e) {
LogUtil.log(TAG_LOG, "云台相机处理异常: " + e);
gimbalIsStartAruco = false;
handleLandingFailure();
synchronized (this) {
if (!isLanding || currentMode != LandingMode.GIMBAL_CAMERA || startArucoType == 0) {
return;
}
}, 0, TimeUnit.MILLISECONDS);
gimbalIsTriggerSuccess = true;
Movement.getInstance().setVirtualStickEnableReason(2);
if (gimbalIsStartAruco || gimbalStartFastStick) {
return;
}
int currentFrame = gimbalFrameCounter++;
if (currentFrame % 3 != 0) {
return;
}
if (currentFrame % 30 != 0) {
DroneHelper.getInstance().setGimbalPitchdown();
}
gimbalIsStartAruco = true;
if (lastGimbalFuture != null && !lastGimbalFuture.isDone()) {
lastGimbalFuture.cancel(true);
}
int ultHeight = Movement.getInstance().getUltrasonicHeight();
updateGimbalLensOffset(ultHeight);
final int finalUltHeight = ultHeight;
lastGimbalFuture = executor.schedule(() -> {
try {
processGimbalFrameInternal(height, width, data, dictionary, finalUltHeight);
} catch (Exception e) {
LogUtil.log(TAG_LOG, "云台相机处理异常: " + e);
synchronized (MixedVisionLanding.this) {
gimbalIsStartAruco = false;
}
handleLandingFailure();
}
}, 0, TimeUnit.MILLISECONDS);
}
}
// ========== 下视相机帧处理 ==========
public void processDownwardFrame(int height, int width, byte[] data, Dictionary dictionary) {
if (!isLanding || currentMode != LandingMode.DOWNWARD_CAMERA) {
return;
}
downwardIsTriggerSuccess = true;
Movement.getInstance().setVirtualStickEnableReason(2);
if (downwardIsStartAruco || downwardStartFastStick) {
return;
}
int currentFrame = downwardFrameCounter++;
if (currentFrame % 2 != 0) {
return;
}
downwardIsStartAruco = true;
if (lastDownwardFuture != null && !lastDownwardFuture.isDone()) {
lastDownwardFuture.cancel(true);
}
int ultHeight = Movement.getInstance().getUltrasonicHeight();
updateDownwardLensOffset(ultHeight);
final int finalUltHeight = ultHeight;
lastDownwardFuture = executor.schedule(() -> {
try {
processDownwardFrameInternal(height, width, data, dictionary, finalUltHeight);
} catch (Exception e) {
LogUtil.log(TAG_LOG, "下视相机处理异常: " + e);
downwardIsStartAruco = false;
handleLandingFailure();
synchronized (this) {
if (!isLanding || currentMode != LandingMode.DOWNWARD_CAMERA || startArucoType == 0) {
return;
}
}, 0, TimeUnit.MILLISECONDS);
downwardIsTriggerSuccess = true;
Movement.getInstance().setVirtualStickEnableReason(2);
if (downwardIsStartAruco || downwardStartFastStick) {
return;
}
int currentFrame = downwardFrameCounter++;
if (currentFrame % 2 != 0) {
return;
}
downwardIsStartAruco = true;
if (lastDownwardFuture != null && !lastDownwardFuture.isDone()) {
lastDownwardFuture.cancel(true);
}
int ultHeight = Movement.getInstance().getUltrasonicHeight();
updateDownwardLensOffset(ultHeight);
final int finalUltHeight = ultHeight;
lastDownwardFuture = executor.schedule(() -> {
try {
processDownwardFrameInternal(height, width, data, dictionary, finalUltHeight);
} catch (Exception e) {
LogUtil.log(TAG_LOG, "下视相机处理异常: " + e);
synchronized (MixedVisionLanding.this) {
downwardIsStartAruco = false;
}
handleLandingFailure();
}
}, 0, TimeUnit.MILLISECONDS);
}
}
// ========== 云台相机内部处理 ==========
@ -397,8 +420,24 @@ public class MixedVisionLanding {
}
}
// 正常修正
moveOnArucoDetected(new ArucoMarker(1, corner6, 0.24f), rgbMat.width(), rgbMat.height());
// 旋转处理
if (ultHeight > 30 && !downwardIsYawAligned) {
double yawError = calculateYawErrorFromCorners(points);
double absYaw = Math.abs(yawError);
if (absYaw < 10.0) {
downwardIsYawAligned = true;
LogUtil.log(TAG_LOG, "【下视旋转到位】偏航已对准");
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f);
} else {
float yawRate = calculateYawRate(yawError, ultHeight);
LogUtil.log(TAG_LOG, String.format("【下视执行】纯旋转 yawRate=%.1f avgYaw=%.1f", yawRate, yawError));
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, yawRate, 0f);
}
} else {
// 正常修正
moveOnArucoDetected(new ArucoMarker(1, corner6, 0.24f), rgbMat.width(), rgbMat.height());
}
}
} else {
handleDownwardLostMarker(ultHeight);
@ -817,30 +856,32 @@ public class MixedVisionLanding {
// ========== 降落失败处理切换相机 ==========
private void handleLandingFailure() {
if (!isLanding) {
return;
}
synchronized (this) {
if (!isLanding) {
return;
}
switchCount++;
LogUtil.log(TAG_LOG, "切换相机,当前切换次数: " + switchCount);
switchCount++;
LogUtil.log(TAG_LOG, "切换相机,当前切换次数: " + switchCount);
// 切换到另一个相机
if (currentMode == LandingMode.GIMBAL_CAMERA) {
currentMode = LandingMode.DOWNWARD_CAMERA;
LogUtil.log(TAG_LOG, "切换到下视相机");
resetDownwardState();
} else {
currentMode = LandingMode.GIMBAL_CAMERA;
LogUtil.log(TAG_LOG, "切换到云台相机");
resetGimbalState();
}
// 切换到另一个相机
if (currentMode == LandingMode.GIMBAL_CAMERA) {
currentMode = LandingMode.DOWNWARD_CAMERA;
LogUtil.log(TAG_LOG, "切换到下视相机");
resetDownwardState();
} else {
currentMode = LandingMode.GIMBAL_CAMERA;
LogUtil.log(TAG_LOG, "切换到云台相机");
resetGimbalState();
}
// 如果达到最大切换次数触发备降
if (switchCount >= MAX_SWITCH_COUNT) {
LogUtil.log(TAG_LOG, "达到最大切换次数,触发备降");
stopLanding();
AlternateLandingManager.getInstance().startTaskProcess(null);
Movement.getInstance().setAlternate(true);
// 如果达到最大切换次数触发备降
if (switchCount >= MAX_SWITCH_COUNT) {
LogUtil.log(TAG_LOG, "达到最大切换次数,触发备降");
stopLanding();
AlternateLandingManager.getInstance().startTaskProcess(null);
Movement.getInstance().setAlternate(true);
}
}
}
@ -876,6 +917,7 @@ public class MixedVisionLanding {
downwardDropTimesTag = false;
downwardStartTime = 0;
downwardEndTime = 0;
downwardIsYawAligned = false;
}
// ========== 更新云台相机镜头偏移 ==========

View File

@ -14,6 +14,10 @@ import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
/**
* TTS 文本转 PCM 工具类
* 使用同步方式实现避免依赖回调机制
*/
public class XTtsPcmGenerator {
private static final String TAG = "XTTS";
@ -21,31 +25,40 @@ public class XTtsPcmGenerator {
private AiHandle aiHandle;
private FileOutputStream pcmOut;
public void init(AiListener listener) {
AiHelper.getInst().registerListener(ABILITY_ID, listener);
}
private boolean isSynthesisComplete = false;
/**
* 合成文本为 PCM 文件
* 合成文本为 PCM 文件同步方法
*/
public int synthToPcm(
String text,
XTTSParams params,
File pcmFile
) {
public File synthToPcm(String text, XTTSParams params, File pcmFile) {
LogUtil.log(TAG, "开始执行 synthToPcm 方法text: " + text);
// 参数验证
if (text == null || text.length() == 0 || params == null || pcmFile == null) {
LogUtil.log(TAG, "参数无效");
return null;
}
// 重置合成完成标志
isSynthesisComplete = false;
// 准备文件输出流
File parent = pcmFile.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
try {
pcmOut = new FileOutputStream(pcmFile, false);
} catch (Exception e) {
e.printStackTrace();
return -1;
LogUtil.log(TAG, "创建输出目录: " + parent.getAbsolutePath());
}
try {
pcmOut = new FileOutputStream(pcmFile, false);
LogUtil.log(TAG, "文件输出流创建成功: " + pcmFile.getAbsolutePath());
} catch (Exception e) {
LogUtil.log(TAG, "创建文件输出流失败: " + e.getMessage());
e.printStackTrace();
return null;
}
// 构建参数
AiInput.Builder paramBuilder = AiInput.builder();
paramBuilder.param("vcn", params.vcn);
paramBuilder.param("language", params.language);
@ -54,44 +67,79 @@ public class XTtsPcmGenerator {
paramBuilder.param("speed", params.speed);
paramBuilder.param("volume", params.volume);
aiHandle = AiHelper.getInst().start(
ABILITY_ID,
paramBuilder.build(),
null
);
// 构建监听器
AiListener listener = buildListener();
LogUtil.log(TAG, "监听器创建成功");
if (aiHandle.getCode() != 0) {
LogUtil.log(TAG, "start failed: " + aiHandle.getCode());
return aiHandle.getCode();
// 注册监听器
try {
AiHelper.getInst().registerListener(ABILITY_ID, listener);
LogUtil.log(TAG, "监听器注册成功");
} catch (Exception e) {
LogUtil.log(TAG, "监听器注册失败: " + e.getMessage());
e.printStackTrace();
close();
return null;
}
AiText aiText = AiText.get("text")
.data(text)
.valid();
// 启动 TTS 引擎
aiHandle = AiHelper.getInst().start(ABILITY_ID, paramBuilder.build(), null);
if (aiHandle.getCode() != 0) {
LogUtil.log(TAG, "启动 TTS 引擎失败: " + aiHandle.getCode());
close();
return null;
}
LogUtil.log(TAG, "TTS 引擎启动成功");
AiRequest request = AiRequest.builder()
.payload(aiText)
.build();
// 构建请求
AiText aiText = AiText.get("text").data(text).valid();
AiRequest request = AiRequest.builder().payload(aiText).build();
// 发送请求
int ret = AiHelper.getInst().write(request, aiHandle);
if (ret != 0) {
LogUtil.log(TAG, "write failed: " + ret);
return ret;
LogUtil.log(TAG, "发送 TTS 请求失败: " + ret);
close();
return null;
}
LogUtil.log(TAG, "TTS 请求发送成功,开始合成");
// 等待合成完成最多等待 30
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < 30000) {
if (isSynthesisComplete) {
LogUtil.log(TAG, "合成完成标志已设置");
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return 0;
// 关闭资源
close();
// 检查 PCM 文件大小
if (pcmFile.exists() && pcmFile.length() > 0) {
LogUtil.log(TAG, "合成成功PCM 文件大小: " + pcmFile.length() + " bytes");
return pcmFile;
} else {
LogUtil.log(TAG, "合成失败PCM 文件为空");
return null;
}
}
/**
* 内部使用的监听器
* 构建监听器
*/
public AiListener buildListener() {
private AiListener buildListener() {
return new AiListener() {
@Override
public void onResult(int handleID, List<AiResponse> list, Object usrContext) {
if (list == null){
LogUtil.log(TAG,"list == null");
if (list == null) {
LogUtil.log(TAG, "onResult: list == null");
return;
}
@ -99,6 +147,7 @@ public class XTtsPcmGenerator {
if ("audio".equals(resp.getKey())) {
byte[] pcm = resp.getValue();
if (pcm != null && pcm.length > 0) {
LogUtil.log(TAG, "接收到 PCM 数据,长度: " + pcm.length + " bytes");
writePcm(pcm);
}
}
@ -107,48 +156,59 @@ public class XTtsPcmGenerator {
@Override
public void onEvent(int handleID, int event, List<AiResponse> eventData, Object usrContext) {
LogUtil.log(TAG, "收到事件: " + event);
if (event == AeeEvent.AEE_EVENT_END.getValue()) {
close();
LogUtil.log(TAG, "合成完成事件");
isSynthesisComplete = true;
}
}
@Override
public void onError(int handleID, int err, String msg, Object usrContext) {
LogUtil.log(TAG, "XTTS error: " + err + ", " + msg);
close();
LogUtil.log(TAG, "合成错误: " + err + ", " + msg);
isSynthesisComplete = true;
}
};
}
/**
* 写入 PCM 数据
*/
private synchronized void writePcm(byte[] data) {
try {
if (pcmOut != null) {
if (pcmOut != null) {
try {
pcmOut.write(data);
} catch (Exception e) {
LogUtil.log(TAG, "写入 PCM 数据失败: " + e.getMessage());
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭资源
*/
private void close() {
LogUtil.log(TAG, "关闭资源");
try {
if (pcmOut != null) {
pcmOut.flush();
pcmOut.close();
pcmOut = null;
LogUtil.log(TAG, "文件输出流已关闭");
}
} catch (Exception ignored) {}
} catch (Exception e) {
LogUtil.log(TAG, "关闭文件输出流失败: " + e.getMessage());
}
if (aiHandle != null) {
AiHelper.getInst().end(aiHandle);
aiHandle = null;
try {
if (aiHandle != null) {
AiHelper.getInst().end(aiHandle);
aiHandle = null;
LogUtil.log(TAG, "AI 引擎已关闭");
}
} catch (Exception e) {
LogUtil.log(TAG, "关闭 AI 引擎失败: " + e.getMessage());
}
}
/**
* 释放引擎App 退出时调用
*/
// public void release() {
// AiHelper.getInst().engineUnInit(ABILITY_ID);
// }
}

View File

@ -0,0 +1,66 @@
package com.aros.apron.tts;
import android.content.Context;
import android.content.res.AssetManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class CopyAsset {
private static volatile CopyAsset instance;
private Context context;
// 私有构造方法
private CopyAsset(Context context) {
// 使用Application Context避免内存泄漏
this.context = context.getApplicationContext();
}
// 获取单例实例
public static CopyAsset getInstance(Context context) {
if (instance == null) {
synchronized (CopyAsset.class) {
if (instance == null) {
instance = new CopyAsset(context);
}
}
}
return instance;
}
public void copyAssetsToSdcard() {
// 修改目标路径
String sdcardPath = "/storage/self/primary/DJIDemo/cache/tts/";
File sdcardDir = new File(sdcardPath);
if (!sdcardDir.exists()) {
// 创建目录包括所有父目录
sdcardDir.mkdirs();
}
try {
AssetManager assetManager = context.getAssets();
String[] files = assetManager.list("aikit_resources");
if (files != null) { // 防止assets目录不存在导致的空指针
for (String file : files) {
File destFile = new File(sdcardPath + file);
if (!destFile.exists()) { // 仅在文件不存在时复制
InputStream in = assetManager.open("aikit_resources/" + file);
FileOutputStream out = new FileOutputStream(destFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
out.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -738,26 +738,33 @@
android:id="@+id/rb_camera_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="右" />
android:text="右" />
<RadioButton
android:id="@+id/rb_camera_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="中间" />
android:layout_marginLeft="0dp"
android:text="中" />
<RadioButton
android:id="@+id/rb_camera_null"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="下" />
android:layout_marginLeft="0dp"
android:text="下" />
<RadioButton
android:id="@+id/rb_camera_mix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="融合" />
android:layout_marginLeft="0dp"
android:text="融右" />
<RadioButton
android:id="@+id/rb_camera_mix_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="0dp"
android:text="融中" />
</RadioGroup>
</LinearLayout>

View File

@ -738,27 +738,33 @@
android:id="@+id/rb_camera_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="右" />
android:text="右" />
<RadioButton
android:id="@+id/rb_camera_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="中" />
android:layout_marginLeft="0dp"
android:text="中" />
<RadioButton
android:id="@+id/rb_camera_null"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="下" />
android:layout_marginLeft="0dp"
android:text="下" />
<RadioButton
android:id="@+id/rb_camera_mix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="融合" />
android:layout_marginLeft="0dp"
android:text="融右" />
<RadioButton
android:id="@+id/rb_camera_mix_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="0dp"
android:text="融中" />
</RadioGroup>
</LinearLayout>

View File

@ -30,6 +30,7 @@ allprojects {
repositories {
google()
jcenter()
//flatDir { dirs 'libs' }
jcenter(){ url 'https://jcenter.bintray.com/'}
maven { url 'https://jitpack.io' }
maven {