Browse Source

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

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

64
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.media.AudioAttributes
import android.media.AudioDeviceInfo
import android.media.AudioFocusRequest
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
@ -49,6 +50,20 @@ class SimpleAudioPlayer(private val context: Context? = 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() {
@ -235,10 +250,13 @@ class SimpleAudioPlayer(private val context: Context? = null) {
configureAudioRouting(isBluetoothConnected)
}
// 请求 AudioFocus,让外部音乐暂停
requestAudioFocus()
// 开始播放
audioTrack?.play()
isPlaying = true
FileLogger.d(TAG, "AudioTrack开始播放")
}
} catch (e: Exception) {
@ -257,7 +275,8 @@ class SimpleAudioPlayer(private val context: Context? = null) {
audioTrack?.pause()
audioTrack?.flush()
isPlaying = false
abandonAudioFocus()
FileLogger.d(TAG, "AudioTrack停止播放")
}
} catch (e: Exception) {
@ -298,7 +317,10 @@ class SimpleAudioPlayer(private val context: Context? = null) {
isPlaying = false
isReleased = true
// 放弃 AudioFocus,让外部音乐恢复
abandonAudioFocus()
FileLogger.d(TAG, "AudioTrack释放资源")
}
} catch (e: Exception) {
@ -310,7 +332,41 @@ class SimpleAudioPlayer(private val context: Context? = null) {
* 是否正在播放
*/
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调用

56
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 originalAudioMode = AudioManager.MODE_NORMAL
// AudioFocus
private var audioFocusRequest: AudioFocusRequest? = null
// 录音文件处理
var recordfile: RecordFile? = null
var isRecord =false
@ -251,19 +254,7 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
Log.d("AudioMode", "检测到蓝牙设备,使用蓝牙音频")
}
// 请求音频焦点
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)
}
// Honor 专属:请求音频焦点(已移至 requestAudioFocus() 统一处理)
Log.d("AudioMode", "通话音频模式已设置: MODE_IN_COMMUNICATION")
@ -296,6 +287,8 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
try {
Log.d(TAG, "前台服务已启动,开始音频录制")
// 请求 AudioFocus,让外部音乐暂停(所有设备)
requestAudioFocus()
// 根据手机品牌决定是否设置通话音频模式
if (isHonorDevice()) {
Log.d(TAG, "检测到荣耀设备,启用通话音频模式")
@ -412,10 +405,11 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
// 停止录音
audioRecord?.stop()
abandonAudioFocus()
// 恢复音频模式 这里会导致关闭麦克风卡住待优化
//restoreCommunicationAudioMode()
} catch (e: Exception) {
Log.e(TAG, "停止麦克风捕获失败: ${e.message}")
}
@ -497,6 +491,38 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
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