980 lines
36 KiB
Java
980 lines
36 KiB
Java
package com.ismart.ib86.app;
|
||
|
||
import android.Manifest;
|
||
import android.content.pm.PackageManager;
|
||
import android.media.AudioAttributes;
|
||
import android.media.AudioFormat;
|
||
import android.media.AudioManager;
|
||
import android.media.AudioRecord;
|
||
import android.media.AudioTrack;
|
||
import android.media.MediaRecorder;
|
||
import android.os.Bundle;
|
||
import android.os.Handler;
|
||
import android.os.Looper;
|
||
import android.view.KeyEvent;
|
||
import android.view.Window;
|
||
import android.view.WindowInsets;
|
||
import android.view.WindowInsetsController;
|
||
import android.os.Build;
|
||
import android.view.View;
|
||
import android.util.Log;
|
||
import android.widget.Button;
|
||
import android.widget.ImageView;
|
||
import android.widget.SeekBar;
|
||
import android.widget.TextView;
|
||
import android.widget.Toast;
|
||
import androidx.annotation.NonNull;
|
||
import androidx.appcompat.app.AppCompatActivity;
|
||
import androidx.core.app.ActivityCompat;
|
||
import androidx.core.content.ContextCompat;
|
||
|
||
import com.ismart.ib86.feature.serial.SerialPort.ASR5515Protocol;
|
||
import com.ismart.ib86.feature.gpio.GpioManager;
|
||
import com.ismart.ib86.feature.serial.SerialPort.ASR5515DeviceManager;
|
||
import com.ismart.ib86.common.utils.LogManager;
|
||
import com.ismart.ib86.feature.motor.MotorController;
|
||
import com.lhht.xiaozhi.models.websokcet.send.WebSocketSendMsgFactory;
|
||
import com.lhht.xiaozhi.settings.SettingsManager;
|
||
import com.lhht.xiaozhi.utils.DeviceUtils;
|
||
import com.lhht.xiaozhi.websocket.WebSocketManager;
|
||
|
||
import org.json.JSONObject;
|
||
|
||
import java.util.concurrent.ExecutorService;
|
||
import java.util.concurrent.Executors;
|
||
|
||
import vip.inode.demo.opusaudiodemo.utils.OpusUtils;
|
||
|
||
public class MainActivity extends AppCompatActivity implements WebSocketManager.WebSocketListener {
|
||
private static final String TAG = "MainActivity";
|
||
private static final String DEVICE_PATH = "/dev/ttyS3";
|
||
|
||
private static final int HEAD_TOUCH_KEY = 285;
|
||
private static final int BAUD_RATE = 921600;
|
||
|
||
// 权限请求码
|
||
private static final int PERMISSION_REQUEST_CODE = 1001;
|
||
// 需要请求的权限
|
||
private static final String[] REQUIRED_PERMISSIONS = {
|
||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||
Manifest.permission.RECORD_AUDIO
|
||
};
|
||
|
||
private static final int BT_POWER_EN = 75;
|
||
private ASR5515DeviceManager deviceManager;
|
||
private MotorController motorController;
|
||
// 使用弱引用避免内存泄漏
|
||
|
||
private GpioManager gpioManager;
|
||
//xiaozhi start
|
||
private static final int SAMPLE_RATE = 16000;
|
||
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
|
||
private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
|
||
private static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
|
||
private static final int OPUS_FRAME_SIZE = 960;
|
||
private static final int PLAY_BUFFER_SIZE = 65536;
|
||
private static final int OPUS_FRAME_DURATION = 60; // 60ms per frame
|
||
private static final long UI_UPDATE_TIMEOUT = 1500; // 1.5秒无声音就更新UI
|
||
private static final long MIC_ENABLE_DELAY = 1000; // UI更新1秒后开启麦克风
|
||
private static final long CHECK_INTERVAL = 50; // 检测频率提高到50ms
|
||
private AudioRecord audioRecord;
|
||
private AudioTrack audioTrack;
|
||
private ExecutorService executorService;
|
||
private ExecutorService audioExecutor;
|
||
private Handler mainHandler;
|
||
private WebSocketManager webSocketManager;
|
||
private OpusUtils opusUtils;
|
||
private long encoderHandle;
|
||
private long decoderHandle;
|
||
private short[] decodedBuffer;
|
||
private short[] recordBuffer;
|
||
private byte[] encodedBuffer;
|
||
|
||
private boolean hasStartedCall = false;
|
||
private boolean isRecording = false;
|
||
private volatile boolean isDestroyed = false;
|
||
private boolean isMuted = false;
|
||
private boolean isPlaying = false;
|
||
private String sessionId = ""; // 添加session_id字段
|
||
private long lastAudioDataTime = 0; // 新增:记录最后一次收到音频数据的时间
|
||
private boolean isCheckingPlaybackStatus = false;
|
||
//xiaozhi end
|
||
// HeadTouchManager removed
|
||
// UI控件
|
||
private ImageView ivFace;
|
||
private Button btnRotateForward;
|
||
private Button btnRotateReverse;
|
||
private SeekBar seekBarDutyCycle;
|
||
private TextView tvDutyCycleValue;
|
||
private int currentDutyCyclePercent = 30; // 默认占空比30%
|
||
private static final int PWM_PERIOD = 1000000; // 1ms周期
|
||
|
||
// 动画管理器
|
||
private RobotFaceAnimationManager animationManager;
|
||
private boolean isCalibrating = false;
|
||
private boolean isForwardCalibration = false;
|
||
private boolean isReverseCalibration = false;
|
||
|
||
|
||
private Runnable wearDetectionRunnable = new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
// 发送佩戴检测请求
|
||
deviceManager.sendWearDetectionRequest();
|
||
|
||
// 2秒后再次执行
|
||
handler.postDelayed(this, 2000);
|
||
}
|
||
};
|
||
@Override
|
||
protected void onCreate(Bundle savedInstanceState) {
|
||
super.onCreate(savedInstanceState);
|
||
setContentView(R.layout.activity_robot_face);
|
||
|
||
// 初始化UI控件
|
||
ivFace = (ImageView) this.findViewById(R.id.iv_face);
|
||
// btnRotateForward = (Button) this.findViewById(R.id.btn_rotate_forward);
|
||
// btnRotateReverse = (Button) this.findViewById(R.id.btn_rotate_reverse);
|
||
// seekBarDutyCycle = (SeekBar) this.findViewById(R.id.seekbar_duty_cycle);
|
||
// tvDutyCycleValue = (TextView) this.findViewById(R.id.tv_duty_cycle_value);
|
||
|
||
// // 设置占空比调整监听器
|
||
// seekBarDutyCycle.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||
// @Override
|
||
// public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||
// currentDutyCyclePercent = progress;
|
||
// tvDutyCycleValue.setText("占空比: " + progress + "%");
|
||
// }
|
||
//
|
||
// @Override
|
||
// public void onStartTrackingTouch(SeekBar seekBar) {}
|
||
//
|
||
// @Override
|
||
// public void onStopTrackingTouch(SeekBar seekBar) {}
|
||
// });
|
||
//
|
||
// // 设置正转按钮点击监听器
|
||
// btnRotateForward.setOnClickListener(v -> {
|
||
// if (motorController != null) {
|
||
// int dutyCycle = (int) (PWM_PERIOD * currentDutyCyclePercent / 100.0);
|
||
// motorController.rotateVerticalDown(PWM_PERIOD, 0); // 确保垂直向下PWM关闭
|
||
// motorController.rotateVerticalUp(PWM_PERIOD, dutyCycle);
|
||
// }
|
||
// });
|
||
//
|
||
// // 设置反转按钮点击监听器
|
||
// btnRotateReverse.setOnClickListener(v -> {
|
||
// if (motorController != null) {
|
||
// int dutyCycle = (int) (PWM_PERIOD * currentDutyCyclePercent / 100.0);
|
||
// motorController.rotateVerticalUp(PWM_PERIOD, 0); // 确保垂直向上PWM关闭
|
||
// motorController.rotateVerticalDown(PWM_PERIOD, dutyCycle);
|
||
// }
|
||
// });
|
||
|
||
// 初始化动画管理器
|
||
animationManager = new RobotFaceAnimationManager(this, ivFace);
|
||
|
||
// 预加载所有动画资源
|
||
new Thread(() -> {
|
||
// 预加载所有动画
|
||
for (int i = 0; i < 5; i++) {
|
||
animationManager.loadFaceAnimation(i);
|
||
}
|
||
|
||
// 创建可重复执行的Runnable
|
||
Runnable faceAnimationRunnable = new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
playRandomFaceAnimation();
|
||
handler.postDelayed(this, 20000); // 20秒后再次执行
|
||
}
|
||
};
|
||
|
||
// 启动第一次动画切换
|
||
handler.post(faceAnimationRunnable);
|
||
}).start();
|
||
|
||
|
||
|
||
// 检查并请求必要的权限
|
||
checkAndRequestPermissions();
|
||
|
||
|
||
// 隐藏状态栏和导航栏
|
||
hideSystemBars();
|
||
// 初始化设备管理器
|
||
initDeviceManager();
|
||
|
||
// testMotorControl();
|
||
|
||
|
||
motorController.setVerticalSensorEnabled(true);
|
||
|
||
new Thread(() -> {
|
||
boolean direction = true;
|
||
try {
|
||
motorController.controlVerticalCalibration();
|
||
Thread.sleep(2000);
|
||
int max = motorController.getVerticalMaxAngle();
|
||
int min = motorController.getVerticalMinAngle();
|
||
int mid = max - min;
|
||
Log.d(TAG,"ver max:" + max + " ver min:"+min + " mid:"+ mid);
|
||
// while(true)
|
||
// {
|
||
// try {
|
||
// angleListener.rotateToAngle(direction, direction? 15 : 15 , direction ? 35 : 12);
|
||
// direction = !direction;
|
||
// Thread.sleep(200);
|
||
// }catch (InterruptedException e) {
|
||
// Log.e(TAG, "初始化延时失败", e);
|
||
// }
|
||
// }
|
||
} catch (InterruptedException e) {
|
||
Log.e(TAG, "初始化延时失败", e);
|
||
}
|
||
}).start();
|
||
|
||
//xiaozhi
|
||
initAudio();
|
||
// setupListeners();
|
||
initWebSocket();
|
||
|
||
}
|
||
|
||
private void initDeviceManager() {
|
||
//打开5515电源
|
||
gpioManager = GpioManager.getInstance();
|
||
gpioManager.exportGpio(BT_POWER_EN);
|
||
gpioManager.setDirection(BT_POWER_EN, GpioManager.DIRECTION_OUT);
|
||
gpioManager.setValue(BT_POWER_EN, GpioManager.VALUE_HIGH);
|
||
|
||
// 初始化电机控制器
|
||
motorController = MotorController.getInstance();
|
||
motorController.init();
|
||
LogManager.d(TAG, "电机控制器初始化完成");
|
||
|
||
deviceManager = new ASR5515DeviceManager(this, DEVICE_PATH, BAUD_RATE);
|
||
// 设置设备检查回调
|
||
deviceManager.setDeviceCheckListener(deviceInfo -> {
|
||
if (deviceInfo != null) {
|
||
LogManager.d(TAG, "设备检查成功: " + deviceInfo.toString());
|
||
} else {
|
||
LogManager.e(TAG, "设备检查失败");
|
||
}
|
||
});
|
||
|
||
// 设置设备控制回调
|
||
deviceManager.setDeviceControlListener(success -> {
|
||
if (success) {
|
||
LogManager.d(TAG, "设备重启成功");
|
||
} else {
|
||
LogManager.e(TAG, "设备重启失败");
|
||
}
|
||
});
|
||
|
||
// 设置日志控制回调
|
||
deviceManager.setLogControlListener(success -> {
|
||
if (success) {
|
||
LogManager.d(TAG, "日志控制设置成功");
|
||
} else {
|
||
LogManager.e(TAG, "日志控制设置失败");
|
||
}
|
||
});
|
||
|
||
// 设置蓝牙版本回调
|
||
deviceManager.setBtVersionListener(version -> {
|
||
if (version != null) {
|
||
LogManager.d(TAG, "蓝牙版本: " + version);
|
||
} else {
|
||
LogManager.e(TAG, "获取蓝牙版本失败");
|
||
}
|
||
});
|
||
|
||
// 设置蓝牙升级回调
|
||
deviceManager.setBtUpgradeListener(response ->
|
||
LogManager.d(TAG, "蓝牙升级: " + response.toString())
|
||
);
|
||
|
||
// 设置主机状态回调
|
||
deviceManager.setHostStatusListener(response -> LogManager.d(TAG, "主机状态: " + response.status));
|
||
deviceManager.setBootBinCheckListener(response -> LogManager.d(TAG, "Boot bin检查: " + response.toString()));
|
||
deviceManager.setCollectFreqSetListener(response -> LogManager.d(TAG, "采集频率设置: " + response.toString()));
|
||
|
||
// 设置心率血压血氧自动测量回调
|
||
deviceManager.setHrBpBoAutoMeasureListener(response -> LogManager.d(TAG, "HrBpBo自动测量: " + response.toString()));
|
||
|
||
// 设置心率血压血氧手动测量回调
|
||
deviceManager.setHrBpBoManualMeasureListener(response -> LogManager.d(TAG, "HrBpBo手动测量: " + response.toString()));
|
||
|
||
// 设置动态测量回调
|
||
deviceManager.setDynamicMeasureListener(response ->
|
||
LogManager.d(TAG, "动态测量: " + response.toString())
|
||
);
|
||
|
||
// 设置佩戴检测回调
|
||
deviceManager.setWearDetectionListener(response ->
|
||
LogManager.d(TAG, "佩戴检测: " + response.isWearing)
|
||
);
|
||
|
||
// 设置算法结果回调
|
||
deviceManager.setAlgoResultListener(response -> {
|
||
LogManager.d(TAG, "算法结果: " + response.toString());
|
||
|
||
// 在后台线程处理算法结果
|
||
new Thread(() -> {
|
||
// 根据类型处理不同的算法结果
|
||
if (response.type == ASR5515Protocol.AlgoResultResponse.TYPE_HEART_RATE) {
|
||
// 简化日志输出
|
||
LogManager.d(TAG, "HR: " + response.getHeartRate() +
|
||
" Conf: " + response.getConfidence() + "%");
|
||
} else if (response.type == ASR5515Protocol.AlgoResultResponse.TYPE_SPO2 &&
|
||
response.getSpo2() > 0) {
|
||
LogManager.d(TAG, "SpO2: " + response.getSpo2() + "%");
|
||
}
|
||
}).start();
|
||
});
|
||
|
||
//设置lead状态回调
|
||
deviceManager.setLeadStatusListener(response -> {
|
||
LogManager.d(TAG, "lead 状态: " + response.status);
|
||
if(response.status == 1)
|
||
{
|
||
deviceManager.switchEcgDataReport((byte) 0x01);
|
||
deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.ECG);
|
||
}
|
||
|
||
});
|
||
|
||
}
|
||
|
||
@Override
|
||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||
Log.d(TAG, "keyCode: "+ keyCode);
|
||
if (keyCode == HEAD_TOUCH_KEY) {
|
||
// 处理返回键按下事件
|
||
Log.d(TAG, "触摸了机器人的头");
|
||
return true; // 返回true表示事件已被处理
|
||
}
|
||
return super.onKeyDown(keyCode, event); // 让系统继续处理其他按键事件
|
||
}
|
||
private void startProtocolTests() {
|
||
LogManager.d(TAG, "开始协议测试...");
|
||
|
||
// handler.postDelayed(new Runnable() {
|
||
// @Override
|
||
// public void run() {
|
||
// deviceManager.checkDevice();
|
||
// }
|
||
// }, 1000);
|
||
//
|
||
// handler.postDelayed(new Runnable() {
|
||
// @Override
|
||
// public void run() {
|
||
// deviceManager.queryBtVersion();
|
||
// }
|
||
// }, 2000);
|
||
|
||
|
||
deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.ADT);
|
||
deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.LEAD);
|
||
// deviceManager.switchEcgDataReport((byte) 0x01);
|
||
// deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR);
|
||
// deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2);
|
||
// deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.ECG);
|
||
|
||
handler.postDelayed(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
// deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.LEAD);
|
||
}
|
||
}, 2000);
|
||
// deviceManager.sendWearDetectionRequest();
|
||
// handler.postDelayed(wearDetectionRunnable, 1000);
|
||
}
|
||
@Override
|
||
protected void onStop(){
|
||
super.onStop();
|
||
Log.d(TAG, "onStop");
|
||
}
|
||
@Override
|
||
protected void onDestroy() {
|
||
super.onDestroy();
|
||
Log.d(TAG, "onDestroy");
|
||
// 移至后台线程执行耗时操作,避免ANR
|
||
new Thread(() -> {
|
||
try {
|
||
|
||
// 关闭GPIO电源
|
||
if (gpioManager != null) {
|
||
try {
|
||
gpioManager.setValue(BT_POWER_EN, GpioManager.VALUE_LOW);
|
||
} catch (Exception e) {
|
||
LogManager.e(TAG, "关闭GPIO电源失败: " + e.getMessage());
|
||
}
|
||
gpioManager = null;
|
||
}
|
||
|
||
// 释放电机控制器资源
|
||
if (motorController != null) {
|
||
try {
|
||
motorController.release();
|
||
LogManager.d(TAG, "电机控制器资源已释放");
|
||
} catch (Exception e) {
|
||
LogManager.e(TAG, "释放电机控制器资源失败: " + e.getMessage());
|
||
}
|
||
}
|
||
// 停止所有测量
|
||
if (deviceManager != null) {
|
||
deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR);
|
||
deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2);
|
||
deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.LEAD);
|
||
deviceManager.syncHostStatus(false);
|
||
deviceManager.close();
|
||
deviceManager = null;
|
||
}
|
||
|
||
|
||
} catch (Exception e) {
|
||
LogManager.e(TAG, "资源释放过程中发生错误: " + e.getMessage());
|
||
}
|
||
}).start();
|
||
|
||
isDestroyed = true;
|
||
hasStartedCall = false;
|
||
if (webSocketManager != null) {
|
||
try {
|
||
webSocketManager.sendMessage(WebSocketSendMsgFactory.getInstance().createEndMsg());
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "发送结束消息失败", e);
|
||
}
|
||
webSocketManager.disconnect();
|
||
}
|
||
endCall();
|
||
|
||
if (encoderHandle != 0) {
|
||
opusUtils.destroyEncoder(encoderHandle);
|
||
encoderHandle = 0;
|
||
}
|
||
if (decoderHandle != 0) {
|
||
opusUtils.destroyDecoder(decoderHandle);
|
||
decoderHandle = 0;
|
||
}
|
||
|
||
executorService.shutdown();
|
||
audioExecutor.shutdown();
|
||
|
||
// 恢复音频模式
|
||
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
|
||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||
audioManager.setSpeakerphoneOn(false);
|
||
|
||
isCheckingPlaybackStatus = false;
|
||
|
||
// 移除所有Handler消息和回调,避免内存泄漏
|
||
if (handler != null) {
|
||
handler.removeCallbacksAndMessages(null);
|
||
handler = null;
|
||
}
|
||
|
||
// 释放动画管理器资源
|
||
if (animationManager != null) {
|
||
animationManager.release();
|
||
animationManager = null;
|
||
}
|
||
}
|
||
|
||
private Handler handler = new Handler();
|
||
|
||
/**
|
||
* 随机播放一个表情动画
|
||
*/
|
||
private void playRandomFaceAnimation() {
|
||
// 随机选择一个表情
|
||
int selectedAnimation = (int)(Math.random() * 5);
|
||
|
||
LogManager.d(TAG, "随机选择表情动画: " + selectedAnimation);
|
||
|
||
// 加载选中的动画
|
||
if (animationManager != null) {
|
||
animationManager.loadFaceAnimation(selectedAnimation);
|
||
animationManager.setSpeed(0.4f);
|
||
} else {
|
||
LogManager.e(TAG, "动画管理器未初始化");
|
||
}
|
||
}
|
||
|
||
private void hideSystemBars() {
|
||
Window window = getWindow();
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||
WindowInsetsController insetsController = window.getInsetsController();
|
||
if (insetsController != null) {
|
||
// 隐藏状态栏和导航栏 (API 30+)
|
||
insetsController.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
||
insetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||
}
|
||
} else {
|
||
// 兼容旧版本 (API < 30)
|
||
View decorView = window.getDecorView();
|
||
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
|
||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||
decorView.setSystemUiVisibility(flags);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 测试电机控制功能
|
||
*/
|
||
private void testMotorControl() {
|
||
if (motorController == null) {
|
||
LogManager.e(TAG, "电机控制器未初始化");
|
||
return;
|
||
}
|
||
byte[] data = new byte[1];
|
||
motorController.sendCommand(MotorController.CMD_CYCLE_TEST, new byte[]{0x01});
|
||
|
||
}
|
||
|
||
@Override
|
||
public void onWindowFocusChanged(boolean hasFocus) {
|
||
super.onWindowFocusChanged(hasFocus);
|
||
if (hasFocus) {
|
||
hideSystemBars();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查并请求必要的权限
|
||
*/
|
||
private void checkAndRequestPermissions() {
|
||
// 检查是否已经有所需的权限
|
||
boolean allPermissionsGranted = true;
|
||
for (String permission : REQUIRED_PERMISSIONS) {
|
||
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
||
allPermissionsGranted = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 如果没有所有权限,则请求它们
|
||
if (!allPermissionsGranted) {
|
||
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, PERMISSION_REQUEST_CODE);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理权限请求结果
|
||
*/
|
||
@Override
|
||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||
|
||
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||
boolean allGranted = true;
|
||
for (int result : grantResults) {
|
||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||
allGranted = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (allGranted) {
|
||
// 所有权限都已授予,可以继续操作
|
||
LogManager.d(TAG, "所有必要权限已授予");
|
||
|
||
} else {
|
||
// 有些权限被拒绝,显示提示并可能影响功能
|
||
Toast.makeText(this, "需要所有请求的权限才能正常运行应用", Toast.LENGTH_LONG).show();
|
||
LogManager.e(TAG, "部分权限被拒绝,可能影响应用功能");
|
||
}
|
||
}
|
||
}
|
||
|
||
private void initAudio() {
|
||
executorService = Executors.newSingleThreadExecutor();
|
||
audioExecutor = Executors.newSingleThreadExecutor();
|
||
mainHandler = new Handler(Looper.getMainLooper());
|
||
|
||
// 初始化Opus编解码器
|
||
opusUtils = OpusUtils.getInstance();
|
||
// 使用OPUS_APPLICATION_VOIP模式,设置比特率为32000
|
||
encoderHandle = opusUtils.createEncoder(SAMPLE_RATE, 1, 32000);
|
||
decoderHandle = opusUtils.createDecoder(SAMPLE_RATE, 1);
|
||
decodedBuffer = new short[OPUS_FRAME_SIZE];
|
||
recordBuffer = new short[OPUS_FRAME_SIZE];
|
||
encodedBuffer = new byte[OPUS_FRAME_SIZE * 2];
|
||
|
||
// 设置音频模式
|
||
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
|
||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||
audioManager.setSpeakerphoneOn(true);
|
||
|
||
// 初始化音频播放器
|
||
initAudioTrack();
|
||
}
|
||
|
||
private void initAudioTrack() {
|
||
try {
|
||
if (audioTrack != null) {
|
||
audioTrack.release();
|
||
}
|
||
|
||
audioTrack = new AudioTrack.Builder()
|
||
.setAudioAttributes(new AudioAttributes.Builder()
|
||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||
.build())
|
||
.setAudioFormat(new AudioFormat.Builder()
|
||
.setEncoding(AUDIO_FORMAT)
|
||
.setSampleRate(SAMPLE_RATE)
|
||
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
||
.build())
|
||
.setBufferSizeInBytes(PLAY_BUFFER_SIZE)
|
||
.setTransferMode(AudioTrack.MODE_STREAM)
|
||
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
|
||
.build();
|
||
|
||
audioTrack.play();
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "创建AudioTrack失败", e);
|
||
}
|
||
}
|
||
|
||
private void initWebSocket() {
|
||
// 从MainActivity获取WebSocket配置
|
||
String deviceId = DeviceUtils.getMacFromAndroidId(this);
|
||
SettingsManager settingsManager = new SettingsManager(this);
|
||
String wsUrl = settingsManager.getWsUrl();
|
||
String token = settingsManager.getToken();
|
||
boolean enableToken = settingsManager.isTokenEnabled();
|
||
|
||
webSocketManager = new WebSocketManager("f0:4a:7b:5b:72:46");
|
||
webSocketManager.setListener(this);
|
||
|
||
// 连接WebSocket
|
||
try {
|
||
webSocketManager.connect(wsUrl, token, enableToken);
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "WebSocket连接失败", e);
|
||
Toast.makeText(this, "连接失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||
finish();
|
||
}
|
||
}
|
||
private void sendAudioData(short[] data, int size) {
|
||
if (webSocketManager != null && webSocketManager.isConnected() && !isMuted) {
|
||
try {
|
||
// 编码音频数据
|
||
int encodedSize = opusUtils.encode(encoderHandle, data, 0, encodedBuffer);
|
||
if (encodedSize > 0) {
|
||
byte[] encodedData = new byte[encodedSize];
|
||
System.arraycopy(encodedBuffer, 0, encodedData, 0, encodedSize);
|
||
|
||
// 检查是否全是静音数据
|
||
boolean isAllZero = true;
|
||
for (int i = 0; i < size && isAllZero; i++) {
|
||
if (data[i] != 0) {
|
||
isAllZero = false;
|
||
}
|
||
}
|
||
|
||
if (!isAllZero) {
|
||
webSocketManager.sendBinaryMessage(encodedData);
|
||
Log.d(TAG, "发送音频数据: " + encodedSize + " bytes");
|
||
|
||
// 更新波形图
|
||
// byte[] buffer = new byte[size * 2];
|
||
// for (int i = 0; i < size; i++) {
|
||
// buffer[i * 2] = (byte) (data[i] & 0xFF);
|
||
// buffer[i * 2 + 1] = (byte) ((data[i] >> 8) & 0xFF);
|
||
// }
|
||
// updateUserWaveform(buffer);
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "发送音频数据失败", e);
|
||
}
|
||
}
|
||
}
|
||
private void interruptAiResponse() {
|
||
if (webSocketManager != null && webSocketManager.isConnected()) {
|
||
try {
|
||
webSocketManager.sendMessage(WebSocketSendMsgFactory.getInstance().createAbortMsg());
|
||
Log.d(TAG,"已打断AI回答");
|
||
|
||
// 停止当前音频播放
|
||
stopCurrentAudio();
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "发送中断消息失败", e);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void stopCurrentAudio() {
|
||
audioExecutor.execute(() -> {
|
||
try {
|
||
if (audioTrack != null && isPlaying) {
|
||
audioTrack.pause();
|
||
audioTrack.flush();
|
||
isPlaying = false;
|
||
// 重新初始化AudioTrack
|
||
initAudioTrack();
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "停止音频播放失败", e);
|
||
}
|
||
});
|
||
}
|
||
|
||
private void startRecording() {
|
||
if (audioRecord == null) {
|
||
try {
|
||
audioRecord = new AudioRecord.Builder()
|
||
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
|
||
.setAudioFormat(new AudioFormat.Builder()
|
||
.setEncoding(AUDIO_FORMAT)
|
||
.setSampleRate(SAMPLE_RATE)
|
||
.setChannelMask(CHANNEL_CONFIG)
|
||
.build())
|
||
.setBufferSizeInBytes(BUFFER_SIZE)
|
||
.build();
|
||
|
||
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||
Log.e(TAG, "AudioRecord初始化失败");
|
||
return;
|
||
}
|
||
|
||
Log.d(TAG, "AudioRecord 初始化成功,缓冲区大小: " + BUFFER_SIZE);
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "创建AudioRecord失败", e);
|
||
return;
|
||
}
|
||
}
|
||
|
||
executorService.execute(() -> {
|
||
try {
|
||
audioRecord.startRecording();
|
||
Log.d(TAG, "开始录音");
|
||
|
||
while (!isDestroyed) {
|
||
if (!isRecording || isMuted) {
|
||
// 如果不在录音状态或静音,暂停一下
|
||
Thread.sleep(100);
|
||
continue;
|
||
}
|
||
|
||
int read = audioRecord.read(recordBuffer, 0, OPUS_FRAME_SIZE);
|
||
if (read > 0) {
|
||
sendAudioData(recordBuffer, read);
|
||
} else if (read < 0) {
|
||
Log.e(TAG, "读取音频数据失败: " + read);
|
||
break;
|
||
}
|
||
|
||
// 控制采样率
|
||
Thread.sleep(OPUS_FRAME_DURATION);
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "录音失败", e);
|
||
}
|
||
});
|
||
}
|
||
private void startCall() {
|
||
if (!webSocketManager.isConnected()) {
|
||
Log.d(TAG,"未连接");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 发送开始通话消息
|
||
webSocketManager.sendMessage(WebSocketSendMsgFactory.getInstance().createStartMsg(SAMPLE_RATE));
|
||
|
||
// 开始录音
|
||
isRecording = true;
|
||
startRecording();
|
||
Log.d(TAG,"正在通话中...");
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "开始通话失败", e);
|
||
}
|
||
}
|
||
private void endCall() {
|
||
isRecording = false;
|
||
if (audioRecord != null) {
|
||
audioRecord.stop();
|
||
audioRecord.release();
|
||
audioRecord = null;
|
||
}
|
||
if (audioTrack != null) {
|
||
audioTrack.stop();
|
||
audioTrack.release();
|
||
audioTrack = null;
|
||
}
|
||
// finish();
|
||
}
|
||
|
||
private void sendListenMessage() {
|
||
try {
|
||
webSocketManager.sendMessage(WebSocketSendMsgFactory.getInstance().createListenMsg(sessionId));
|
||
Log.d(TAG, "发送listen消息");
|
||
|
||
// 开始录音
|
||
isRecording = true;
|
||
startRecording();
|
||
Log.d(TAG,"正在通话中...");
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "发送listen消息失败", e);
|
||
}
|
||
}
|
||
|
||
private void checkPlaybackStatus() {
|
||
if (audioTrack != null && isPlaying) {
|
||
long currentTime = System.currentTimeMillis();
|
||
long timeSinceLastAudio = currentTime - lastAudioDataTime;
|
||
|
||
// 如果超过1.5秒没有收到新的音频数据,更新UI
|
||
if (timeSinceLastAudio > UI_UPDATE_TIMEOUT) {
|
||
isPlaying = false;
|
||
Log.d(TAG,"正在通话中...");
|
||
Log.d(TAG, "AI说话结束,更新UI状态");
|
||
|
||
// 延迟1秒后开启麦克风
|
||
mainHandler.postDelayed(() -> {
|
||
if (!isPlaying) { // 再次检查是否还是非播放状态
|
||
isRecording = true;
|
||
Log.d("VoiceCall", "延迟开启麦克风");
|
||
}
|
||
}, MIC_ENABLE_DELAY);
|
||
|
||
isCheckingPlaybackStatus = false;
|
||
return;
|
||
}
|
||
|
||
// 继续检查,提高检查频率
|
||
mainHandler.postDelayed(() -> checkPlaybackStatus(), CHECK_INTERVAL);
|
||
} else {
|
||
isCheckingPlaybackStatus = false;
|
||
}
|
||
}
|
||
private void startPlaybackStatusCheck() {
|
||
isCheckingPlaybackStatus = true;
|
||
checkPlaybackStatus();
|
||
}
|
||
@Override
|
||
public void onConnected() {
|
||
Log.d(TAG, "连接成功");
|
||
runOnUiThread(() -> {
|
||
Toast.makeText(this, "连接成功", Toast.LENGTH_SHORT).show();
|
||
});
|
||
}
|
||
|
||
@Override
|
||
public void onDisconnected() {
|
||
Log.d(TAG, "连接断开");
|
||
runOnUiThread(() -> {
|
||
Toast.makeText(this, "连接已断开", Toast.LENGTH_SHORT).show();
|
||
endCall();
|
||
});
|
||
}
|
||
|
||
@Override
|
||
public void onError(String error) {
|
||
Log.e(TAG, "错误: " + error);
|
||
runOnUiThread(() -> {
|
||
Toast.makeText(this, "错误: " + error, Toast.LENGTH_SHORT).show();
|
||
});
|
||
}
|
||
|
||
@Override
|
||
public void onMessage(String message) {
|
||
try {
|
||
JSONObject jsonMessage = new JSONObject(message);
|
||
String type = jsonMessage.getString("type");
|
||
Log.d(TAG, "收到消息: " + message);
|
||
|
||
switch (type) {
|
||
case "stt":
|
||
// 处理语音识别结果
|
||
String recognizedText = jsonMessage.getString("text");
|
||
break;
|
||
|
||
case "tts":
|
||
break;
|
||
|
||
case "hello":
|
||
// 处理服务器的hello响应
|
||
if (!hasStartedCall) {
|
||
// 等待服务器返回session_id
|
||
if (jsonMessage.has("session_id")) {
|
||
sessionId = jsonMessage.getString("session_id");
|
||
hasStartedCall = true;
|
||
startCall();
|
||
}
|
||
}
|
||
break;
|
||
|
||
case "start":
|
||
// 收到start响应后,发送listen消息
|
||
if (jsonMessage.has("session_id")) {
|
||
sessionId = jsonMessage.getString("session_id");
|
||
sendListenMessage();
|
||
}
|
||
break;
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "处理消息失败", e);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onBinaryMessage(byte[] data) {
|
||
if (data == null || data.length == 0) return;
|
||
|
||
audioExecutor.execute(() -> {
|
||
try {
|
||
if (audioTrack == null || audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||
initAudioTrack();
|
||
}
|
||
|
||
// 解码并播放音频数据
|
||
int decodedSamples = opusUtils.decode(decoderHandle, data, decodedBuffer);
|
||
if (decodedSamples > 0) {
|
||
byte[] pcmData = new byte[decodedSamples * 2];
|
||
for (int i = 0; i < decodedSamples; i++) {
|
||
short sample = decodedBuffer[i];
|
||
pcmData[i * 2] = (byte) (sample & 0xff);
|
||
pcmData[i * 2 + 1] = (byte) ((sample >> 8) & 0xff);
|
||
}
|
||
|
||
// 更新最后一次收到音频数据的时间
|
||
lastAudioDataTime = System.currentTimeMillis();
|
||
|
||
// 如果之前不是AI说话状态,则立即切换状态并关闭麦克风
|
||
if (!isPlaying) {
|
||
isPlaying = true;
|
||
isRecording = false; // 立即关闭麦克风
|
||
Log.e(TAG,"AI正在说话...");
|
||
}
|
||
|
||
int written = audioTrack.write(pcmData, 0, pcmData.length, AudioTrack.WRITE_BLOCKING);
|
||
if (written < 0) {
|
||
Log.e(TAG, "音频播放失败: " + written);
|
||
}
|
||
|
||
// // 更新AI波形图
|
||
// float[] amplitudes = new float[decodedSamples];
|
||
// for (int i = 0; i < decodedSamples; i++) {
|
||
// amplitudes[i] = decodedBuffer[i] / 32768f;
|
||
// }
|
||
// updateAiWaveform(amplitudes);
|
||
|
||
// 确保状态检查在运行
|
||
if (!isCheckingPlaybackStatus) {
|
||
startPlaybackStatusCheck();
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "处理音频数据失败", e);
|
||
}
|
||
});
|
||
}
|
||
} |