wolfplus2048 1 year ago
parent
commit
a63217e5ca
  1. 8
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  2. 44
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
  3. 100
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt
  4. 6
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceSpeechPlugin.kt
  5. 24
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt
  6. 7
      local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt
  7. 16
      local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/TtsEvents.kt

8
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -158,6 +158,14 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
)
}
TtsEventType.PLAYBACK_STARTED -> mapOf(
"type" to "playback_started"
)
TtsEventType.PLAYBACK_COMPLETED -> mapOf(
"type" to "playback_completed"
)
TtsEventType.ERROR -> {
val params = event.params
mapOf(

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

@ -1,14 +1,16 @@
package com.yunqiinnovation.azure_speech
import android.content.Context
import android.media.AudioManager
import com.microsoft.cognitiveservices.speech.*
import com.microsoft.cognitiveservices.speech.audio.*
import com.yunqiinnovation.azure_speech.utils.FileLogger
import com.deep_voice.speech.tts.AudioDataListener
import com.deep_voice.speech.tts.AudioOutputDevice
import com.deep_voice.speech.tts.ITtsService
import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
import com.deep_voice.speech.tts.AudioDataListener
import kotlinx.coroutines.*
import java.io.ByteArrayInputStream
import java.io.InputStream
@ -203,6 +205,42 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
}
}
/**
* 设置音频输出设备
*
* @param device 音频输出设备类型
*/
override fun setAudioOutputDevice(device: AudioOutputDevice) {
// Azure TTS使用系统默认的音频路由
// 音频输出设备的控制需要通过Android的AudioManager实现
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
audioManager?.let { manager ->
when (device) {
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
}
}
}
}
/**
* 触发事件通知
*/
@ -228,6 +266,8 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
SynthesisStarted?.addEventListener { _, eventArgs ->
FileLogger.d(TAG, "语音合成开始: resultId=${eventArgs.result.resultId}")
notifyEvent(TtsEventType.SYNTHESIS_STARTED)
// Azure TTS 在使用默认音频输出时会立即开始播放
notifyEvent(TtsEventType.PLAYBACK_STARTED)
}
// 合成中事件(接收音频数据)
@ -253,6 +293,8 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
FileLogger.d(TAG, "语音合成完成: resultId=${eventArgs.result.resultId}, 音频长度=${eventArgs.result.audioLength} 字节")
isSpeaking = false
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED)
// Azure TTS 合成完成即播放完成
notifyEvent(TtsEventType.PLAYBACK_COMPLETED)
}
// 合成取消事件

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

@ -1,5 +1,6 @@
package com.deep_voice.bytedance_speech
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioManager
@ -7,12 +8,13 @@ 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
/**
* 简化的音频流播放器
* 基于位置标记触发播放完成回调
*/
class BytedanceAudioPlayer : AudioDataListener {
class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
companion object {
private const val TAG = "BytedanceAudioPlayer"
private const val SAMPLE_RATE = 24000
@ -24,6 +26,8 @@ class BytedanceAudioPlayer : AudioDataListener {
private var isFirstData = true // 是否是第一次接收数据
private var totalBytesWritten = 0 // 总共写入的字节数
private var sessionActive = true // 会话是否活跃
private var audioOutputDevice = AudioOutputDevice.DEFAULT // 音频输出设备
private var audioManager: AudioManager? = null
// 回调
private var onPlayStarted: (() -> Unit)? = null
@ -40,6 +44,12 @@ class BytedanceAudioPlayer : AudioDataListener {
// 不使用周期性通知
}
}
init {
Log.d(TAG, "BytedanceAudioPlayer 初始化")
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
initAudioTrack()
}
/**
@ -198,6 +208,23 @@ class BytedanceAudioPlayer : AudioDataListener {
return audioTrack?.playState == AudioTrack.PLAYSTATE_PLAYING
}
/**
* 设置音频输出设备
*/
fun setAudioOutputDevice(device: AudioOutputDevice) {
if (audioOutputDevice != device) {
audioOutputDevice = device
// 如果AudioTrack已初始化,需要重新创建以应用新的输出设备设置
if (audioTrack != null) {
val wasPlaying = isPlaying()
initAudioTrack()
if (wasPlaying) {
audioTrack?.play()
}
}
}
}
/**
* 初始化 AudioTrack
*/
@ -209,15 +236,36 @@ class BytedanceAudioPlayer : AudioDataListener {
val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
val bufferSize = minBufferSize * 2
// 创建 AudioTrack
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AudioTrack.Builder()
.setAudioAttributes(
// 根据输出设备配置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) {
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)
@ -230,8 +278,12 @@ class BytedanceAudioPlayer : AudioDataListener {
.build()
} else {
@Suppress("DEPRECATION")
val streamType = when (audioOutputDevice) {
AudioOutputDevice.EARPIECE -> AudioManager.STREAM_VOICE_CALL
else -> AudioManager.STREAM_MUSIC
}
AudioTrack(
AudioManager.STREAM_MUSIC,
streamType,
SAMPLE_RATE,
CHANNEL_CONFIG,
AUDIO_FORMAT,
@ -243,9 +295,43 @@ class BytedanceAudioPlayer : AudioDataListener {
// 设置播放位置监听器
audioTrack?.setPlaybackPositionUpdateListener(playbackListener)
// 配置音频路由
configureAudioRouting()
// 开始播放
audioTrack?.play()
Log.d(TAG, "AudioTrack 初始化成功")
}
/**
* 配置音频路由
*/
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
}
}
}
}
}

6
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceSpeechPlugin.kt

@ -255,6 +255,12 @@ class BytedanceSpeechPlugin : FlutterPlugin {
TtsEventType.SYNTHESIS_CANCELED -> {
sendTTSEvent("canceled", null)
}
TtsEventType.PLAYBACK_STARTED -> {
sendTTSEvent("playback_started", null)
}
TtsEventType.PLAYBACK_COMPLETED -> {
sendTTSEvent("playback_completed", null)
}
TtsEventType.ERROR -> {
val params = HashMap<String, Any>()
params["code"] = event.params["errorCode"] ?: "UNKNOWN_ERROR"

24
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt

@ -3,6 +3,7 @@ package com.deep_voice.bytedance_speech
import android.content.Context
import android.util.Log
import com.deep_voice.speech.tts.AudioDataListener
import com.deep_voice.speech.tts.AudioOutputDevice
import com.deep_voice.speech.tts.ITtsService
import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener
@ -98,7 +99,7 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
private val audioDataListeners = mutableListOf<AudioDataListener>()
// 内部音频播放器
private val audioPlayer = BytedanceAudioPlayer()
private val audioPlayer = BytedanceAudioPlayer(context)
// 是否使用内部播放器
private var useInternalPlayer = true
@ -159,14 +160,14 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
audioPlayer.setOnPlayStarted {
Log.d(TAG, "播放开始")
updateStatus(STATUS_SPEAKING)
notifyEvent(TtsEventType.SYNTHESIS_STARTED)
notifyEvent(TtsEventType.PLAYBACK_STARTED)
}
// 设置播放完成回调
audioPlayer.setOnPlayCompleted {
Log.d(TAG, "播放结束")
updateStatus(STATUS_READY)
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED)
notifyEvent(TtsEventType.PLAYBACK_COMPLETED)
}
// 设置错误回调
@ -421,6 +422,15 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
updateInternalPlayerUsage()
}
/**
* 设置音频输出设备
*/
override fun setAudioOutputDevice(device: AudioOutputDevice) {
if (useInternalPlayer) {
audioPlayer.setAudioOutputDevice(device)
}
}
/**
* 更新内部播放器的使用状态
*/
@ -650,7 +660,9 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
EVENT_SESSION_STARTED -> {
connectionAttempts = 0
// 会话开始时,如果使用内部播放器则启动播放器会话
// 会话开始时触发合成开始事件
notifyEvent(TtsEventType.SYNTHESIS_STARTED)
// 如果使用内部播放器则启动播放器会话
if (useInternalPlayer) {
audioPlayer.startSession()
}
@ -672,6 +684,10 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
EVENT_SESSION_FINISHED -> {
isSessionStarted = false
Log.i(TAG, "EVENT_SESSION_FINISHED, ${sessionId}")
// 触发合成完成事件
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED)
// 如果使用内部播放器则结束播放器会话
if (useInternalPlayer) {
audioPlayer.endSession()

7
local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt

@ -97,4 +97,11 @@ interface ITtsService {
* false: 仅通过音频数据监听器输出数据,不播放
*/
fun setUseInternalPlayer(useInternalPlayer: Boolean)
/**
* 设置音频输出设备
*
* @param device 音频输出设备类型
*/
fun setAudioOutputDevice(device: AudioOutputDevice)
}

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

@ -13,6 +13,12 @@ enum class TtsEventType {
/** 合成取消 */
SYNTHESIS_CANCELED,
/** 播放开始 */
PLAYBACK_STARTED,
/** 播放结束 */
PLAYBACK_COMPLETED,
/** 发生错误 */
ERROR
}
@ -52,4 +58,14 @@ interface AudioDataListener {
* @param data 音频数据字节数组
*/
fun onAudioData(data: ByteArray)
}
/**
* 音频输出设备类型
*/
enum class AudioOutputDevice {
DEFAULT, // 默认(如果有耳机选耳机,否则使用系统扬声器)
HEADPHONES, // 强制使用耳机
SPEAKER, // 强制使用扬声器
EARPIECE // 强制使用听筒
}
Loading…
Cancel
Save