Browse Source

add

newdev_shunjiawei
wolfplus2048 1 year ago
parent
commit
dc9c3a4276
  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 -> { TtsEventType.ERROR -> {
val params = event.params val params = event.params
mapOf( 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 package com.yunqiinnovation.azure_speech
import android.content.Context import android.content.Context
import android.media.AudioManager
import com.microsoft.cognitiveservices.speech.* import com.microsoft.cognitiveservices.speech.*
import com.microsoft.cognitiveservices.speech.audio.* import com.microsoft.cognitiveservices.speech.audio.*
import com.yunqiinnovation.azure_speech.utils.FileLogger 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.ITtsService
import com.deep_voice.speech.tts.TtsEvent import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType import com.deep_voice.speech.tts.TtsEventType
import com.deep_voice.speech.tts.AudioDataListener
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream 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 -> SynthesisStarted?.addEventListener { _, eventArgs ->
FileLogger.d(TAG, "语音合成开始: resultId=${eventArgs.result.resultId}") FileLogger.d(TAG, "语音合成开始: resultId=${eventArgs.result.resultId}")
notifyEvent(TtsEventType.SYNTHESIS_STARTED) 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} 字节") FileLogger.d(TAG, "语音合成完成: resultId=${eventArgs.result.resultId}, 音频长度=${eventArgs.result.audioLength} 字节")
isSpeaking = false isSpeaking = false
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED) 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 package com.deep_voice.bytedance_speech
import android.content.Context
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.AudioFormat import android.media.AudioFormat
import android.media.AudioManager import android.media.AudioManager
@ -7,12 +8,13 @@ import android.media.AudioTrack
import android.os.Build import android.os.Build
import android.util.Log 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
/** /**
* 简化的音频流播放器 * 简化的音频流播放器
* 基于位置标记触发播放完成回调 * 基于位置标记触发播放完成回调
*/ */
class BytedanceAudioPlayer : AudioDataListener { class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
companion object { companion object {
private const val TAG = "BytedanceAudioPlayer" private const val TAG = "BytedanceAudioPlayer"
private const val SAMPLE_RATE = 24000 private const val SAMPLE_RATE = 24000
@ -24,6 +26,8 @@ class BytedanceAudioPlayer : AudioDataListener {
private var isFirstData = true // 是否是第一次接收数据 private var isFirstData = true // 是否是第一次接收数据
private var totalBytesWritten = 0 // 总共写入的字节数 private var totalBytesWritten = 0 // 总共写入的字节数
private var sessionActive = true // 会话是否活跃 private var sessionActive = true // 会话是否活跃
private var audioOutputDevice = AudioOutputDevice.DEFAULT // 音频输出设备
private var audioManager: AudioManager? = null
// 回调 // 回调
private var onPlayStarted: (() -> Unit)? = 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 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 * 初始化 AudioTrack
*/ */
@ -209,15 +236,36 @@ class BytedanceAudioPlayer : AudioDataListener {
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
// 创建 AudioTrack // 根据输出设备配置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)
@ -230,8 +278,12 @@ class BytedanceAudioPlayer : 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(
AudioManager.STREAM_MUSIC, streamType,
SAMPLE_RATE, SAMPLE_RATE,
CHANNEL_CONFIG, CHANNEL_CONFIG,
AUDIO_FORMAT, AUDIO_FORMAT,
@ -243,9 +295,43 @@ class BytedanceAudioPlayer : AudioDataListener {
// 设置播放位置监听器 // 设置播放位置监听器
audioTrack?.setPlaybackPositionUpdateListener(playbackListener) audioTrack?.setPlaybackPositionUpdateListener(playbackListener)
// 配置音频路由
configureAudioRouting()
// 开始播放 // 开始播放
audioTrack?.play() audioTrack?.play()
Log.d(TAG, "AudioTrack 初始化成功") 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 -> { TtsEventType.SYNTHESIS_CANCELED -> {
sendTTSEvent("canceled", null) sendTTSEvent("canceled", null)
} }
TtsEventType.PLAYBACK_STARTED -> {
sendTTSEvent("playback_started", null)
}
TtsEventType.PLAYBACK_COMPLETED -> {
sendTTSEvent("playback_completed", null)
}
TtsEventType.ERROR -> { TtsEventType.ERROR -> {
val params = HashMap<String, Any>() val params = HashMap<String, Any>()
params["code"] = event.params["errorCode"] ?: "UNKNOWN_ERROR" 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.content.Context
import android.util.Log 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.ITtsService import com.deep_voice.speech.tts.ITtsService
import com.deep_voice.speech.tts.TtsEvent import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener 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 audioDataListeners = mutableListOf<AudioDataListener>()
// 内部音频播放器 // 内部音频播放器
private val audioPlayer = BytedanceAudioPlayer() private val audioPlayer = BytedanceAudioPlayer(context)
// 是否使用内部播放器 // 是否使用内部播放器
private var useInternalPlayer = true private var useInternalPlayer = true
@ -159,14 +160,14 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
audioPlayer.setOnPlayStarted { audioPlayer.setOnPlayStarted {
Log.d(TAG, "播放开始") Log.d(TAG, "播放开始")
updateStatus(STATUS_SPEAKING) updateStatus(STATUS_SPEAKING)
notifyEvent(TtsEventType.SYNTHESIS_STARTED) notifyEvent(TtsEventType.PLAYBACK_STARTED)
} }
// 设置播放完成回调 // 设置播放完成回调
audioPlayer.setOnPlayCompleted { audioPlayer.setOnPlayCompleted {
Log.d(TAG, "播放结束") Log.d(TAG, "播放结束")
updateStatus(STATUS_READY) updateStatus(STATUS_READY)
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED) notifyEvent(TtsEventType.PLAYBACK_COMPLETED)
} }
// 设置错误回调 // 设置错误回调
@ -421,6 +422,15 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
updateInternalPlayerUsage() 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 -> { EVENT_SESSION_STARTED -> {
connectionAttempts = 0 connectionAttempts = 0
// 会话开始时,如果使用内部播放器则启动播放器会话 // 会话开始时触发合成开始事件
notifyEvent(TtsEventType.SYNTHESIS_STARTED)
// 如果使用内部播放器则启动播放器会话
if (useInternalPlayer) { if (useInternalPlayer) {
audioPlayer.startSession() audioPlayer.startSession()
} }
@ -672,6 +684,10 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
EVENT_SESSION_FINISHED -> { EVENT_SESSION_FINISHED -> {
isSessionStarted = false isSessionStarted = false
Log.i(TAG, "EVENT_SESSION_FINISHED, ${sessionId}") Log.i(TAG, "EVENT_SESSION_FINISHED, ${sessionId}")
// 触发合成完成事件
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED)
// 如果使用内部播放器则结束播放器会话 // 如果使用内部播放器则结束播放器会话
if (useInternalPlayer) { if (useInternalPlayer) {
audioPlayer.endSession() audioPlayer.endSession()

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

@ -97,4 +97,11 @@ interface ITtsService {
* false: 仅通过音频数据监听器输出数据,不播放 * false: 仅通过音频数据监听器输出数据,不播放
*/ */
fun setUseInternalPlayer(useInternalPlayer: Boolean) 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, SYNTHESIS_CANCELED,
/** 播放开始 */
PLAYBACK_STARTED,
/** 播放结束 */
PLAYBACK_COMPLETED,
/** 发生错误 */ /** 发生错误 */
ERROR ERROR
} }
@ -52,4 +58,14 @@ interface AudioDataListener {
* @param data 音频数据字节数组 * @param data 音频数据字节数组
*/ */
fun onAudioData(data: ByteArray) fun onAudioData(data: ByteArray)
}
/**
* 音频输出设备类型
*/
enum class AudioOutputDevice {
DEFAULT, // 默认(如果有耳机选耳机,否则使用系统扬声器)
HEADPHONES, // 强制使用耳机
SPEAKER, // 强制使用扬声器
EARPIECE // 强制使用听筒
} }
Loading…
Cancel
Save