wolfplus2048 1 year ago
parent
commit
e79f6ccf3b
  1. 5
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
  2. 315
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt
  3. 3
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  4. 1
      local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/TtsEvents.kt

5
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

@ -232,11 +232,6 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
manager.isSpeakerphoneOn = false
// 注意:Android不能强制路由到耳机,只能在耳机已连接时使用
}
AudioOutputDevice.EARPIECE -> {
// 强制使用听筒
manager.mode = AudioManager.MODE_IN_COMMUNICATION
manager.isSpeakerphoneOn = false
}
}
}
}

315
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt

@ -1,12 +1,16 @@
package com.deep_voice.bytedance_speech
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioAttributes
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import android.os.Build
import android.util.Log
import com.deep_voice.speech.tts.AudioDataListener
import com.deep_voice.speech.tts.AudioOutputDevice
@ -29,6 +33,11 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
private var audioOutputDevice = AudioOutputDevice.DEFAULT // 音频输出设备
private var audioManager: AudioManager? = null
// 设备监听相关
private var deviceCallback: AudioDeviceCallback? = null
private var noisyReceiver: BroadcastReceiver? = null
private var isMonitoringDevices = false
// 回调
private var onPlayStarted: (() -> Unit)? = null
private var onPlayCompleted: (() -> Unit)? = null
@ -36,7 +45,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
// 播放位置监听器
private val playbackListener = object : AudioTrack.OnPlaybackPositionUpdateListener {
override fun onMarkerReached(track: AudioTrack) {
Log.d(TAG, "播放到达标记位置: ${track.playbackHeadPosition}")
onPlayCompleted?.invoke()
}
@ -46,7 +54,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
}
init {
Log.d(TAG, "BytedanceAudioPlayer 初始化")
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
initAudioTrack()
}
@ -56,24 +63,19 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 开始新的播放会话
*/
fun startSession() {
Log.d(TAG, "开始新会话")
// 重置状态
isFirstData = true
totalBytesWritten = 0
sessionActive = true
// 重置 AudioTrack
audioTrack?.let { track ->
if (track.state == AudioTrack.STATE_INITIALIZED) {
// 停止并清空缓冲区
track.pause()
track.flush()
// 重新开始播放
track.play()
Log.d(TAG, "AudioTrack 已重置")
}
} ?: initAudioTrack() // 如果没有初始化,则初始化
} ?: initAudioTrack()
startDeviceMonitoring()
}
/**
@ -82,28 +84,20 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
fun endSession() {
if (!sessionActive) return
Log.d(TAG, "数据流结束,总共写入字节数: $totalBytesWritten")
sessionActive = false
audioTrack?.let { track ->
if (totalBytesWritten > 0) {
// 计算总帧数(16-bit 单声道,每帧2字节)
val totalFrames = totalBytesWritten / 2
// 设置标记位置
try {
track.setNotificationMarkerPosition(totalFrames)
Log.d(TAG, "设置播放完成标记位置: $totalFrames")
} catch (e: Exception) {
Log.e(TAG, "设置标记失败: ${e.message}")
// 设置失败时,使用延迟触发作为后备
val durationMs = (totalFrames * 1000L) / SAMPLE_RATE
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
onPlayCompleted?.invoke()
}, durationMs + 500)
}
} else {
// 没有数据,直接触发完成
onPlayCompleted?.invoke()
}
}
@ -113,37 +107,26 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 接收音频数据
*/
override fun onAudioData(data: ByteArray) {
if (data.isEmpty()) return
if (!sessionActive) {
Log.w(TAG, "收到音频数据但会话未激活,忽略数据")
return
}
if (audioTrack == null || audioTrack?.state != AudioTrack.STATE_INITIALIZED) {
Log.w(TAG, "收到音频数据但 AudioTrack 未初始化或状态不正确,忽略数据")
return
}
if (data.isEmpty() || !sessionActive) return
if (audioTrack?.state != AudioTrack.STATE_INITIALIZED) return
try {
audioTrack?.let { track ->
if (track.state == AudioTrack.STATE_INITIALIZED) {
val bytesWritten = track.write(data, 0, data.size)
val bytesWritten = track.write(data, 0, data.size)
if (bytesWritten > 0) {
if (isFirstData) {
isFirstData = false
onPlayStarted?.invoke()
}
if (bytesWritten > 0) {
// 第一次写入数据时自动触发开始回调
if (isFirstData) {
isFirstData = false
onPlayStarted?.invoke()
Log.d(TAG, "播放开始")
}
// 累计写入字节数
if (sessionActive) {
totalBytesWritten += bytesWritten
}
if (sessionActive) {
totalBytesWritten += bytesWritten
}
}
}
} catch (e: Exception) {
Log.e(TAG, "写入音频数据失败: ${e.message}")
// 忽略写入失败
}
}
@ -151,8 +134,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 停止播放
*/
fun stop() {
Log.d(TAG, "停止播放")
sessionActive = false
audioTrack?.let { track ->
@ -162,7 +143,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
}
}
// 如果已经开始播放,触发完成回调
stopDeviceMonitoring()
if (!isFirstData) {
onPlayCompleted?.invoke()
}
@ -172,12 +154,10 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 释放资源
*/
fun release() {
Log.d(TAG, "释放资源")
stop()
audioTrack?.release()
audioTrack = null
stopDeviceMonitoring()
}
/**
@ -214,13 +194,14 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
fun setAudioOutputDevice(device: AudioOutputDevice) {
if (audioOutputDevice != device) {
audioOutputDevice = device
// 如果AudioTrack已初始化,需要重新创建以应用新的输出设备设置
if (audioTrack != null) {
val wasPlaying = isPlaying()
initAudioTrack()
if (wasPlaying) {
audioTrack?.play()
}
// 应用新的音频路由设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// API 31+ 可以动态更改设备
setPreferredDeviceForTrack()
} else {
// API 30 及以下需要重新配置
configureAudioRouting()
}
}
}
@ -229,43 +210,19 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 初始化 AudioTrack
*/
private fun initAudioTrack() {
// 释放旧实例
audioTrack?.release()
// 计算缓冲区大小
val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
val bufferSize = minBufferSize * 2
// 根据输出设备配置AudioAttributes
val audioAttributes = when (audioOutputDevice) {
AudioOutputDevice.EARPIECE -> {
// 听筒模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
} else {
null
}
}
else -> {
// 默认、耳机、扬声器模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AudioTrack.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
} else {
null
}
}
}
// 创建 AudioTrack
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && audioAttributes != null) {
AudioTrack.Builder()
.setAudioAttributes(audioAttributes)
)
.setAudioFormat(
AudioFormat.Builder()
.setEncoding(AUDIO_FORMAT)
@ -278,12 +235,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
.build()
} else {
@Suppress("DEPRECATION")
val streamType = when (audioOutputDevice) {
AudioOutputDevice.EARPIECE -> AudioManager.STREAM_VOICE_CALL
else -> AudioManager.STREAM_MUSIC
}
AudioTrack(
streamType,
AudioManager.STREAM_MUSIC,
SAMPLE_RATE,
CHANNEL_CONFIG,
AUDIO_FORMAT,
@ -292,46 +245,202 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
)
}
// 设置播放位置监听器
audioTrack?.setPlaybackPositionUpdateListener(playbackListener)
// 配置音频路由
configureAudioRouting()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setPreferredDeviceForTrack()
} else {
configureAudioRouting()
}
// 开始播放
audioTrack?.play()
}
/**
* API 31+ 使用 setPreferredDevice 设置音频输出设备
*/
private fun setPreferredDeviceForTrack() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return
Log.d(TAG, "AudioTrack 初始化成功")
audioManager?.let { manager ->
audioTrack?.let { track ->
when (audioOutputDevice) {
AudioOutputDevice.DEFAULT -> {
track.setPreferredDevice(null)
manager.isSpeakerphoneOn = false
manager.mode = AudioManager.MODE_NORMAL
}
AudioOutputDevice.SPEAKER -> {
val speaker = manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }
speaker?.let { track.setPreferredDevice(it) }
}
AudioOutputDevice.HEADPHONES -> {
val headphones = manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
.firstOrNull { device ->
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
}
headphones?.let { track.setPreferredDevice(it) }
?: track.setPreferredDevice(null)
}
}
}
}
}
/**
* 配置音频路由
* 配置音频路由(API 30 及以下)
*/
private fun configureAudioRouting() {
audioManager?.let { manager ->
when (audioOutputDevice) {
AudioOutputDevice.DEFAULT -> {
// 默认模式:系统自动选择
manager.mode = AudioManager.MODE_NORMAL
manager.isSpeakerphoneOn = false
}
AudioOutputDevice.SPEAKER -> {
// 强制使用扬声器
manager.mode = AudioManager.MODE_NORMAL
manager.isSpeakerphoneOn = true
}
AudioOutputDevice.HEADPHONES -> {
// 强制使用耳机(如果已连接)
manager.mode = AudioManager.MODE_NORMAL
manager.isSpeakerphoneOn = false
// 注意:Android不能强制路由到耳机,只能在耳机已连接时使用
}
AudioOutputDevice.EARPIECE -> {
// 强制使用听筒
manager.mode = AudioManager.MODE_IN_COMMUNICATION
manager.isSpeakerphoneOn = false
}
}
}
/**
* 开始监听设备变化
*/
private fun startDeviceMonitoring() {
if (isMonitoringDevices) return
// API 23+ 使用 AudioDeviceCallback
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deviceCallback = object : AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
super.onAudioDevicesAdded(addedDevices)
handleDeviceAdded(addedDevices)
}
override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) {
super.onAudioDevicesRemoved(removedDevices)
handleDeviceRemoved(removedDevices)
}
}
audioManager?.registerAudioDeviceCallback(deviceCallback, null)
}
// 所有版本都监听 NOISY 广播(耳机拔出)
noisyReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) {
handleNoisyAudioEvent()
}
}
}
context.registerReceiver(noisyReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
isMonitoringDevices = true
}
/**
* 停止监听设备变化
*/
private fun stopDeviceMonitoring() {
if (!isMonitoringDevices) return
// 注销 AudioDeviceCallback
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deviceCallback?.let {
audioManager?.unregisterAudioDeviceCallback(it)
}
deviceCallback = null
}
// 注销广播接收器
noisyReceiver?.let {
try {
context.unregisterReceiver(it)
} catch (e: Exception) {
// 忽略已注销的异常
}
}
noisyReceiver = null
isMonitoringDevices = false
}
/**
* 处理设备添加
*/
private fun handleDeviceAdded(devices: Array<AudioDeviceInfo>) {
val hasHeadphones = devices.any { device ->
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
}
if (hasHeadphones) {
switchToDefaultMode()
}
}
/**
* 处理设备移除
*/
private fun handleDeviceRemoved(devices: Array<AudioDeviceInfo>) {
val hasHeadphones = devices.any { device ->
device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
}
if (hasHeadphones) {
switchToSpeakerMode()
}
}
/**
* 处理音频变得嘈杂事件(通常是耳机拔出)
*/
private fun handleNoisyAudioEvent() {
switchToSpeakerMode()
}
/**
* 切换到 DEFAULT 模式
*/
private fun switchToDefaultMode() {
audioOutputDevice = AudioOutputDevice.DEFAULT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setPreferredDeviceForTrack()
} else {
configureAudioRouting()
}
}
/**
* 切换到扬声器模式
*/
private fun switchToSpeakerMode() {
audioOutputDevice = AudioOutputDevice.SPEAKER
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setPreferredDeviceForTrack()
} else {
configureAudioRouting()
}
}
}

3
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt

@ -218,7 +218,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
mcpConfigJson = mcpServer
// 异步初始化MCP客户端
launch {
initializeMcpClient(mcpServer)
// initializeMcpClient(mcpServer)
initializeMcpClient("{}")
}
isInitialized = apiKey.isNotEmpty()

1
local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/TtsEvents.kt

@ -67,5 +67,4 @@ enum class AudioOutputDevice {
DEFAULT, // 默认(如果有耳机选耳机,否则使用系统扬声器)
HEADPHONES, // 强制使用耳机
SPEAKER, // 强制使用扬声器
EARPIECE // 强制使用听筒
}
Loading…
Cancel
Save