|
|
@ -15,12 +15,13 @@ import com.deep_voice.speech.tts.AudioDataListener |
|
|
import com.deep_voice.speech.tts.AudioOutputDevice |
|
|
import com.deep_voice.speech.tts.AudioOutputDevice |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 高性能音频播放器 - 16kHz采样率 |
|
|
* 语音通信音频播放器 - 16kHz采样率 |
|
|
* 设计要点: |
|
|
* 设计要点: |
|
|
* • 永远使用 USAGE_MEDIA + MODE_NORMAL 媒体播放通路 |
|
|
* • 使用 USAGE_VOICE_COMMUNICATION + MODE_IN_COMMUNICATION 通信通路 |
|
|
* • 利用 AudioDeviceCallback 监听设备热插拔 |
|
|
* • 利用 AudioDeviceCallback 监听设备热插拔 |
|
|
* • API 31+ 使用 setPreferredDevice() 精准路由 |
|
|
* • API 31+ 使用 setPreferredDevice() 精准路由 |
|
|
* • 有耳机自动从耳机播放,无耳机自动切到扬声器 |
|
|
* • 有耳机自动从耳机播放,无耳机自动切到扬声器 |
|
|
|
|
|
* • 内置回声消除和噪音抑制功能 |
|
|
*/ |
|
|
*/ |
|
|
class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, AudioDeviceCallback() { |
|
|
class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, AudioDeviceCallback() { |
|
|
companion object { |
|
|
companion object { |
|
|
@ -35,6 +36,35 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au |
|
|
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 audioFocusRequest: Any? = null // 兼容不同API版本 |
|
|
|
|
|
|
|
|
|
|
|
// 音频焦点变化监听器 |
|
|
|
|
|
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange -> |
|
|
|
|
|
when (focusChange) { |
|
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS -> { |
|
|
|
|
|
// 永久失去焦点,停止播放 |
|
|
|
|
|
Log.d(TAG, "音频焦点丢失") |
|
|
|
|
|
stop() |
|
|
|
|
|
} |
|
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { |
|
|
|
|
|
// 暂时失去焦点,暂停播放 |
|
|
|
|
|
Log.d(TAG, "音频焦点暂时丢失") |
|
|
|
|
|
audioTrack?.pause() |
|
|
|
|
|
} |
|
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { |
|
|
|
|
|
// 暂时失去焦点但可降低音量,这里选择暂停 |
|
|
|
|
|
Log.d(TAG, "音频焦点暂时丢失(可降低音量)") |
|
|
|
|
|
audioTrack?.pause() |
|
|
|
|
|
} |
|
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN -> { |
|
|
|
|
|
// 重新获得焦点,恢复播放 |
|
|
|
|
|
Log.d(TAG, "音频焦点恢复") |
|
|
|
|
|
if (sessionActive) { |
|
|
|
|
|
audioTrack?.play() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 回调 |
|
|
// 回调 |
|
|
private var onPlayStarted: (() -> Unit)? = null |
|
|
private var onPlayStarted: (() -> Unit)? = null |
|
|
@ -62,6 +92,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au |
|
|
init { |
|
|
init { |
|
|
// 注册设备热插拔监听 |
|
|
// 注册设备热插拔监听 |
|
|
audioManager.registerAudioDeviceCallback(this, Handler(Looper.getMainLooper())) |
|
|
audioManager.registerAudioDeviceCallback(this, Handler(Looper.getMainLooper())) |
|
|
|
|
|
// 请求音频焦点 |
|
|
|
|
|
requestAudioFocus() |
|
|
buildTrack(selectOutputDevice()) // 首次创建 |
|
|
buildTrack(selectOutputDevice()) // 首次创建 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -126,7 +158,7 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au |
|
|
audioTrack = AudioTrack.Builder() |
|
|
audioTrack = AudioTrack.Builder() |
|
|
.setAudioAttributes( |
|
|
.setAudioAttributes( |
|
|
AudioAttributes.Builder() |
|
|
AudioAttributes.Builder() |
|
|
.setUsage(AudioAttributes.USAGE_MEDIA) // 统一媒体通路 |
|
|
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) // 语音通信通路 |
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
.build() |
|
|
.build() |
|
|
) |
|
|
) |
|
|
@ -159,8 +191,8 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au |
|
|
track.play() |
|
|
track.play() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 配置音频管理器 - 统一使用 MODE_NORMAL |
|
|
// 配置音频管理器 - 使用通信模式以启用回声消除和噪音抑制 |
|
|
audioManager.mode = AudioManager.MODE_NORMAL |
|
|
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION |
|
|
// 切换扬声器开关仅在无耳机时打开 |
|
|
// 切换扬声器开关仅在无耳机时打开 |
|
|
audioManager.isSpeakerphoneOn = (device == null) |
|
|
audioManager.isSpeakerphoneOn = (device == null) |
|
|
|
|
|
|
|
|
@ -168,6 +200,71 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au |
|
|
Log.d(TAG, "AudioTrack已创建 - 输出设备: $deviceName, 采样率: ${SAMPLE_RATE}Hz") |
|
|
Log.d(TAG, "AudioTrack已创建 - 输出设备: $deviceName, 采样率: ${SAMPLE_RATE}Hz") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 请求音频焦点 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun requestAudioFocus() { |
|
|
|
|
|
try { |
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
|
|
// Android O及以上使用新的API |
|
|
|
|
|
val focusRequest = android.media.AudioFocusRequest.Builder( |
|
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK |
|
|
|
|
|
) |
|
|
|
|
|
.setAudioAttributes( |
|
|
|
|
|
AudioAttributes.Builder() |
|
|
|
|
|
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) |
|
|
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) |
|
|
|
|
|
.build() |
|
|
|
|
|
) |
|
|
|
|
|
.setAcceptsDelayedFocusGain(true) |
|
|
|
|
|
.setOnAudioFocusChangeListener(audioFocusChangeListener) |
|
|
|
|
|
.build() |
|
|
|
|
|
|
|
|
|
|
|
val result = audioManager.requestAudioFocus(focusRequest) |
|
|
|
|
|
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { |
|
|
|
|
|
audioFocusRequest = focusRequest |
|
|
|
|
|
Log.d(TAG, "音频焦点请求成功") |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.w(TAG, "音频焦点请求失败") |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// Android O以下使用旧的API |
|
|
|
|
|
@Suppress("DEPRECATION") |
|
|
|
|
|
val result = audioManager.requestAudioFocus( |
|
|
|
|
|
audioFocusChangeListener, |
|
|
|
|
|
AudioManager.STREAM_VOICE_CALL, |
|
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK |
|
|
|
|
|
) |
|
|
|
|
|
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { |
|
|
|
|
|
Log.d(TAG, "音频焦点请求成功(旧API)") |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.w(TAG, "音频焦点请求失败(旧API)") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "请求音频焦点异常: ${e.message}") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 释放音频焦点 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun releaseAudioFocus() { |
|
|
|
|
|
try { |
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
|
|
|
|
audioFocusRequest?.let { |
|
|
|
|
|
audioManager.abandonAudioFocusRequest(it as android.media.AudioFocusRequest) |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
@Suppress("DEPRECATION") |
|
|
|
|
|
audioManager.abandonAudioFocus(audioFocusChangeListener) |
|
|
|
|
|
} |
|
|
|
|
|
Log.d(TAG, "音频焦点已释放") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "释放音频焦点异常: ${e.message}") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 开始新的播放会话 |
|
|
* 开始新的播放会话 |
|
|
*/ |
|
|
*/ |
|
|
@ -262,7 +359,10 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener, Au |
|
|
stop() |
|
|
stop() |
|
|
audioTrack?.release() |
|
|
audioTrack?.release() |
|
|
audioTrack = null |
|
|
audioTrack = null |
|
|
|
|
|
releaseAudioFocus() |
|
|
audioManager.unregisterAudioDeviceCallback(this) |
|
|
audioManager.unregisterAudioDeviceCallback(this) |
|
|
|
|
|
// 恢复音频模式 |
|
|
|
|
|
audioManager.mode = AudioManager.MODE_NORMAL |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
|