Browse Source

add

newdev_shunjiawei
wolfplus2048 1 year ago
parent
commit
d7e4dfa6c5
  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 manager.isSpeakerphoneOn = false
// 注意:Android不能强制路由到耳机,只能在耳机已连接时使用 // 注意: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 package com.deep_voice.bytedance_speech
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioFormat import android.media.AudioFormat
import android.media.AudioManager import android.media.AudioManager
import android.media.AudioTrack import android.media.AudioTrack
import android.os.Build import android.os.Build
import android.util.Log
import com.deep_voice.speech.tts.AudioDataListener import com.deep_voice.speech.tts.AudioDataListener
import com.deep_voice.speech.tts.AudioOutputDevice 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 audioOutputDevice = AudioOutputDevice.DEFAULT // 音频输出设备
private var audioManager: AudioManager? = null 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 onPlayStarted: (() -> Unit)? = null
private var onPlayCompleted: (() -> Unit)? = null private var onPlayCompleted: (() -> Unit)? = null
@ -36,7 +45,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
// 播放位置监听器 // 播放位置监听器
private val playbackListener = object : AudioTrack.OnPlaybackPositionUpdateListener { private val playbackListener = object : AudioTrack.OnPlaybackPositionUpdateListener {
override fun onMarkerReached(track: AudioTrack) { override fun onMarkerReached(track: AudioTrack) {
Log.d(TAG, "播放到达标记位置: ${track.playbackHeadPosition}")
onPlayCompleted?.invoke() onPlayCompleted?.invoke()
} }
@ -46,7 +54,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
} }
init { init {
Log.d(TAG, "BytedanceAudioPlayer 初始化")
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
initAudioTrack() initAudioTrack()
} }
@ -56,24 +63,19 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 开始新的播放会话 * 开始新的播放会话
*/ */
fun startSession() { fun startSession() {
Log.d(TAG, "开始新会话")
// 重置状态
isFirstData = true isFirstData = true
totalBytesWritten = 0 totalBytesWritten = 0
sessionActive = true sessionActive = true
// 重置 AudioTrack
audioTrack?.let { track -> audioTrack?.let { track ->
if (track.state == AudioTrack.STATE_INITIALIZED) { if (track.state == AudioTrack.STATE_INITIALIZED) {
// 停止并清空缓冲区
track.pause() track.pause()
track.flush() track.flush()
// 重新开始播放
track.play() track.play()
Log.d(TAG, "AudioTrack 已重置")
} }
} ?: initAudioTrack() // 如果没有初始化,则初始化 } ?: initAudioTrack()
startDeviceMonitoring()
} }
/** /**
@ -82,28 +84,20 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
fun endSession() { fun endSession() {
if (!sessionActive) return if (!sessionActive) return
Log.d(TAG, "数据流结束,总共写入字节数: $totalBytesWritten")
sessionActive = false sessionActive = false
audioTrack?.let { track -> audioTrack?.let { track ->
if (totalBytesWritten > 0) { if (totalBytesWritten > 0) {
// 计算总帧数(16-bit 单声道,每帧2字节)
val totalFrames = totalBytesWritten / 2 val totalFrames = totalBytesWritten / 2
// 设置标记位置
try { try {
track.setNotificationMarkerPosition(totalFrames) track.setNotificationMarkerPosition(totalFrames)
Log.d(TAG, "设置播放完成标记位置: $totalFrames")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "设置标记失败: ${e.message}")
// 设置失败时,使用延迟触发作为后备
val durationMs = (totalFrames * 1000L) / SAMPLE_RATE val durationMs = (totalFrames * 1000L) / SAMPLE_RATE
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
onPlayCompleted?.invoke() onPlayCompleted?.invoke()
}, durationMs + 500) }, durationMs + 500)
} }
} else { } else {
// 没有数据,直接触发完成
onPlayCompleted?.invoke() onPlayCompleted?.invoke()
} }
} }
@ -113,37 +107,26 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 接收音频数据 * 接收音频数据
*/ */
override fun onAudioData(data: ByteArray) { override fun onAudioData(data: ByteArray) {
if (data.isEmpty()) return if (data.isEmpty() || !sessionActive) return
if (!sessionActive) { if (audioTrack?.state != AudioTrack.STATE_INITIALIZED) return
Log.w(TAG, "收到音频数据但会话未激活,忽略数据")
return
}
if (audioTrack == null || audioTrack?.state != AudioTrack.STATE_INITIALIZED) {
Log.w(TAG, "收到音频数据但 AudioTrack 未初始化或状态不正确,忽略数据")
return
}
try { try {
audioTrack?.let { track -> 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 (sessionActive) {
// 第一次写入数据时自动触发开始回调 totalBytesWritten += bytesWritten
if (isFirstData) {
isFirstData = false
onPlayStarted?.invoke()
Log.d(TAG, "播放开始")
}
// 累计写入字节数
if (sessionActive) {
totalBytesWritten += bytesWritten
}
} }
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "写入音频数据失败: ${e.message}") // 忽略写入失败
} }
} }
@ -151,8 +134,6 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 停止播放 * 停止播放
*/ */
fun stop() { fun stop() {
Log.d(TAG, "停止播放")
sessionActive = false sessionActive = false
audioTrack?.let { track -> audioTrack?.let { track ->
@ -162,7 +143,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
} }
} }
// 如果已经开始播放,触发完成回调 stopDeviceMonitoring()
if (!isFirstData) { if (!isFirstData) {
onPlayCompleted?.invoke() onPlayCompleted?.invoke()
} }
@ -172,12 +154,10 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 释放资源 * 释放资源
*/ */
fun release() { fun release() {
Log.d(TAG, "释放资源")
stop() stop()
audioTrack?.release() audioTrack?.release()
audioTrack = null audioTrack = null
stopDeviceMonitoring()
} }
/** /**
@ -214,13 +194,14 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
fun setAudioOutputDevice(device: AudioOutputDevice) { fun setAudioOutputDevice(device: AudioOutputDevice) {
if (audioOutputDevice != device) { if (audioOutputDevice != device) {
audioOutputDevice = device audioOutputDevice = device
// 如果AudioTrack已初始化,需要重新创建以应用新的输出设备设置
if (audioTrack != null) { // 应用新的音频路由设置
val wasPlaying = isPlaying() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
initAudioTrack() // API 31+ 可以动态更改设备
if (wasPlaying) { setPreferredDeviceForTrack()
audioTrack?.play() } else {
} // API 30 及以下需要重新配置
configureAudioRouting()
} }
} }
} }
@ -229,43 +210,19 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
* 初始化 AudioTrack * 初始化 AudioTrack
*/ */
private fun initAudioTrack() { private fun initAudioTrack() {
// 释放旧实例
audioTrack?.release() audioTrack?.release()
// 计算缓冲区大小
val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
val bufferSize = minBufferSize * 2 val bufferSize = minBufferSize * 2
// 根据输出设备配置AudioAttributes audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val audioAttributes = when (audioOutputDevice) { AudioTrack.Builder()
AudioOutputDevice.EARPIECE -> { .setAudioAttributes(
// 听筒模式
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) {
AudioAttributes.Builder() AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA) .setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build() .build()
} else { )
null
}
}
}
// 创建 AudioTrack
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && audioAttributes != null) {
AudioTrack.Builder()
.setAudioAttributes(audioAttributes)
.setAudioFormat( .setAudioFormat(
AudioFormat.Builder() AudioFormat.Builder()
.setEncoding(AUDIO_FORMAT) .setEncoding(AUDIO_FORMAT)
@ -278,12 +235,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
.build() .build()
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
val streamType = when (audioOutputDevice) {
AudioOutputDevice.EARPIECE -> AudioManager.STREAM_VOICE_CALL
else -> AudioManager.STREAM_MUSIC
}
AudioTrack( AudioTrack(
streamType, AudioManager.STREAM_MUSIC,
SAMPLE_RATE, SAMPLE_RATE,
CHANNEL_CONFIG, CHANNEL_CONFIG,
AUDIO_FORMAT, AUDIO_FORMAT,
@ -292,46 +245,202 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
) )
} }
// 设置播放位置监听器
audioTrack?.setPlaybackPositionUpdateListener(playbackListener) audioTrack?.setPlaybackPositionUpdateListener(playbackListener)
// 配置音频路由 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
configureAudioRouting() setPreferredDeviceForTrack()
} else {
configureAudioRouting()
}
// 开始播放
audioTrack?.play() 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() { private fun configureAudioRouting() {
audioManager?.let { manager -> audioManager?.let { manager ->
when (audioOutputDevice) { when (audioOutputDevice) {
AudioOutputDevice.DEFAULT -> { AudioOutputDevice.DEFAULT -> {
// 默认模式:系统自动选择
manager.mode = AudioManager.MODE_NORMAL manager.mode = AudioManager.MODE_NORMAL
manager.isSpeakerphoneOn = false manager.isSpeakerphoneOn = false
} }
AudioOutputDevice.SPEAKER -> { AudioOutputDevice.SPEAKER -> {
// 强制使用扬声器
manager.mode = AudioManager.MODE_NORMAL manager.mode = AudioManager.MODE_NORMAL
manager.isSpeakerphoneOn = true manager.isSpeakerphoneOn = true
} }
AudioOutputDevice.HEADPHONES -> { AudioOutputDevice.HEADPHONES -> {
// 强制使用耳机(如果已连接)
manager.mode = AudioManager.MODE_NORMAL manager.mode = AudioManager.MODE_NORMAL
manager.isSpeakerphoneOn = false 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 mcpConfigJson = mcpServer
// 异步初始化MCP客户端 // 异步初始化MCP客户端
launch { launch {
initializeMcpClient(mcpServer) // initializeMcpClient(mcpServer)
initializeMcpClient("{}")
} }
isInitialized = apiKey.isNotEmpty() 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, // 默认(如果有耳机选耳机,否则使用系统扬声器) DEFAULT, // 默认(如果有耳机选耳机,否则使用系统扬声器)
HEADPHONES, // 强制使用耳机 HEADPHONES, // 强制使用耳机
SPEAKER, // 强制使用扬声器 SPEAKER, // 强制使用扬声器
EARPIECE // 强制使用听筒
} }
Loading…
Cancel
Save