Browse Source

feat(audio): 添加音频焦点管理以优化音频播放和录制体验

weicu
liwei1dao 7 months ago
parent
commit
ca752f53bb
  1. 56
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioPlayer.kt
  2. 52
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt

56
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioPlayer.kt

@ -3,6 +3,7 @@ package com.yunqiinnovation.azure_speech.tools
import android.content.Context import android.content.Context
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.AudioDeviceInfo import android.media.AudioDeviceInfo
import android.media.AudioFocusRequest
import android.media.AudioFormat import android.media.AudioFormat
import android.media.AudioManager import android.media.AudioManager
import android.media.AudioTrack import android.media.AudioTrack
@ -50,6 +51,20 @@ class SimpleAudioPlayer(private val context: Context? = null) {
// 推送流 // 推送流
private var pushOutputStream: PushAudioOutputStream? = null private var pushOutputStream: PushAudioOutputStream? = null
// AudioFocus
private var audioFocusRequest: AudioFocusRequest? = null
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_LOSS,
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
lock.withLock { audioTrack?.pause() }
}
AudioManager.AUDIOFOCUS_GAIN -> {
lock.withLock { if (isPlaying) audioTrack?.play() }
}
}
}
// 自定义推送流回调 // 自定义推送流回调
private inner class AudioOutputCallback : PushAudioOutputStreamCallback() { private inner class AudioOutputCallback : PushAudioOutputStreamCallback() {
override fun write(dataBuffer: ByteArray): Int { override fun write(dataBuffer: ByteArray): Int {
@ -235,6 +250,9 @@ class SimpleAudioPlayer(private val context: Context? = null) {
configureAudioRouting(isBluetoothConnected) configureAudioRouting(isBluetoothConnected)
} }
// 请求 AudioFocus,让外部音乐暂停
requestAudioFocus()
// 开始播放 // 开始播放
audioTrack?.play() audioTrack?.play()
isPlaying = true isPlaying = true
@ -257,6 +275,7 @@ class SimpleAudioPlayer(private val context: Context? = null) {
audioTrack?.pause() audioTrack?.pause()
audioTrack?.flush() audioTrack?.flush()
isPlaying = false isPlaying = false
abandonAudioFocus()
FileLogger.d(TAG, "AudioTrack停止播放") FileLogger.d(TAG, "AudioTrack停止播放")
} }
@ -299,6 +318,9 @@ class SimpleAudioPlayer(private val context: Context? = null) {
isPlaying = false isPlaying = false
isReleased = true isReleased = true
// 放弃 AudioFocus,让外部音乐恢复
abandonAudioFocus()
FileLogger.d(TAG, "AudioTrack释放资源") FileLogger.d(TAG, "AudioTrack释放资源")
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -311,6 +333,40 @@ class SimpleAudioPlayer(private val context: Context? = null) {
*/ */
fun isPlaying(): Boolean = isPlaying fun isPlaying(): Boolean = isPlaying
private fun requestAudioFocus() {
val am = audioManager ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val attrs = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
val req = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(attrs)
.setOnAudioFocusChangeListener(audioFocusChangeListener)
.build()
audioFocusRequest = req
am.requestAudioFocus(req)
} else {
@Suppress("DEPRECATION")
am.requestAudioFocus(
audioFocusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
)
}
}
private fun abandonAudioFocus() {
val am = audioManager ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioFocusRequest?.let { am.abandonAudioFocusRequest(it) }
audioFocusRequest = null
} else {
@Suppress("DEPRECATION")
am.abandonAudioFocus(audioFocusChangeListener)
}
}
/** /**
* 写入音频数据 * 写入音频数据
* 内部方法,由PushAudioOutputStreamCallback调用 * 内部方法,由PushAudioOutputStreamCallback调用

52
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt

@ -58,6 +58,9 @@ class SimpleAudioReceiver(private val context: Context) {
private var audioManager: AudioManager? = null private var audioManager: AudioManager? = null
private var originalAudioMode = AudioManager.MODE_NORMAL private var originalAudioMode = AudioManager.MODE_NORMAL
// AudioFocus
private var audioFocusRequest: AudioFocusRequest? = null
// 录音文件处理 // 录音文件处理
var recordfile: RecordFile? = null var recordfile: RecordFile? = null
var isRecord =false var isRecord =false
@ -251,19 +254,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
Log.d("AudioMode", "检测到蓝牙设备,使用蓝牙音频") Log.d("AudioMode", "检测到蓝牙设备,使用蓝牙音频")
} }
// 请求音频焦点 // Honor 专属:请求音频焦点(已移至 requestAudioFocus() 统一处理)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.build()
audioManager?.requestAudioFocus(focusRequest)
}
Log.d("AudioMode", "通话音频模式已设置: MODE_IN_COMMUNICATION") Log.d("AudioMode", "通话音频模式已设置: MODE_IN_COMMUNICATION")
@ -296,6 +287,8 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
try { try {
Log.d(TAG, "前台服务已启动,开始音频录制") Log.d(TAG, "前台服务已启动,开始音频录制")
// 请求 AudioFocus,让外部音乐暂停(所有设备)
requestAudioFocus()
// 根据手机品牌决定是否设置通话音频模式 // 根据手机品牌决定是否设置通话音频模式
if (isHonorDevice()) { if (isHonorDevice()) {
Log.d(TAG, "检测到荣耀设备,启用通话音频模式") Log.d(TAG, "检测到荣耀设备,启用通话音频模式")
@ -412,6 +405,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
// 停止录音 // 停止录音
audioRecord?.stop() audioRecord?.stop()
abandonAudioFocus()
// 恢复音频模式 这里会导致关闭麦克风卡住待优化 // 恢复音频模式 这里会导致关闭麦克风卡住待优化
//restoreCommunicationAudioMode() //restoreCommunicationAudioMode()
@ -497,6 +491,38 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
return isWriting.get() return isWriting.get()
} }
private fun requestAudioFocus() {
val am = audioManager ?: (context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.also { audioManager = it } ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val req = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.build()
audioFocusRequest = req
am.requestAudioFocus(req)
} else {
@Suppress("DEPRECATION")
am.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
}
Log.d(TAG, "AudioFocus 已请求")
}
private fun abandonAudioFocus() {
val am = audioManager ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioFocusRequest?.let { am.abandonAudioFocusRequest(it) }
audioFocusRequest = null
} else {
@Suppress("DEPRECATION")
am.abandonAudioFocus(null)
}
Log.d(TAG, "AudioFocus 已释放")
}
/** /**
* 连续识别回调接口 * 连续识别回调接口
*/ */

Loading…
Cancel
Save