Android AWS KVS WebRTC 通话声道切换到媒体音乐声道

在基于AWS KVS开发音视频建立双向直播流的过程中碰到一个问题,APP端不需要发送手机端的音视频,只需要接收设备端的音视频,但是在APP中播放远端音频(Remote Audio Track)的时候,音频一直占用通话声道,而不是音乐通道,那如何解决这个问题?

有二种方式,第一种方式是修改源码,第二种方式则是不使用默认的JavaAudioDeviceModule自定义创建。这里主要讲第二种方式。

一、创建自定义JavaAudioDeviceModule

主要是修改audioAttributes, 代码如下:

kotlin 复制代码
import android.content.Context
import android.media.*
import android.util.Log
import org.webrtc.audio.JavaAudioDeviceModule

/**
 * @description 用于将 WebRTC 的音频输出切换到媒体声道(STREAM_MUSIC),而不是默认的通话声道(STREAM_VOICE_CALL)
 */
class MediaAudioManager(private val context: Context) {
    
    /**
     * 创建 JavaAudioDeviceModule,并强制使用媒体通道输出
     */
    fun createJavaAudioDeviceModule(): JavaAudioDeviceModule {
        val audioAttributes = AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build()

        val audioRecordErrorCallback = object : JavaAudioDeviceModule.AudioRecordErrorCallback {
            override fun onWebRtcAudioRecordInitError(errorMessage: String?) {
                Log.e("MediaAudioManager", "onWebRtcAudioRecordInitError: $errorMessage")
            }

            override fun onWebRtcAudioRecordStartError(
                errorCode: JavaAudioDeviceModule.AudioRecordStartErrorCode?,
                errorMessage: String?
            ) {
                Log.e("MediaAudioManager", "onWebRtcAudioRecordStartError: $errorMessage")
            }

            override fun onWebRtcAudioRecordError(errorMessage: String?) {
                Log.e("MediaAudioManager", "onWebRtcAudioRecordError: $errorMessage")
            }
        }

        val audioTrackErrorCallback = object : JavaAudioDeviceModule.AudioTrackErrorCallback {
            override fun onWebRtcAudioTrackInitError(errorMessage: String?) {
                Log.e("MediaAudioManager", "onWebRtcAudioTrackInitError: $errorMessage")
            }

            override fun onWebRtcAudioTrackStartError(
                errorCode: JavaAudioDeviceModule.AudioTrackStartErrorCode?,
                errorMessage: String?
            ) {
                Log.e("MediaAudioManager", "onWebRtcAudioTrackStartError: $errorMessage")
            }

            override fun onWebRtcAudioTrackError(errorMessage: String?) {
                Log.e("MediaAudioManager", "onWebRtcAudioTrackError: $errorMessage")
            }
        }

        // 创建 JavaAudioDeviceModule 并使用 MEDIA 声道
        return JavaAudioDeviceModule.builder(context)
            .setAudioAttributes(audioAttributes)
            .setUseHardwareAcousticEchoCanceler(true)
            .setUseHardwareNoiseSuppressor(true)
            .setAudioRecordErrorCallback(audioRecordErrorCallback)
            .setAudioTrackErrorCallback(audioTrackErrorCallback)
            .createAudioDeviceModule()
    }
}

在构建PeerConnectionFactory时使用:

kotlin 复制代码
PeerConnectionFactory.builder().apply {
    //解决声音播放占用通话通道的问题
    val mediaAudioManager = MediaAudioManager(mContext)
    val audioModule = mediaAudioManager.createJavaAudioDeviceModule()
    setAudioDeviceModule(audioModule)
    
    ...其他配置略...
    
  }.createPeerConnectionFactory()

二、声音通道的控制

一般情况下都会有一个按钮控制静音,静音或开启的控制逻辑需要区分用户是否佩戴耳机,佩戴耳机的情况下不能开启扬声器避免暴露隐私,具体代码如下:

kotlin 复制代码
/**
 * 开启或关闭声音
 */
fun enableAudio(enable: Boolean) {
    if (enable) {
        //没有佩戴耳机
        if (!isHeadphonesPlugged()) {
            audioManager?.mode = AudioManager.MODE_NORMAL
            setSpeakerphoneOn(true)
        } else {
            audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
            setSpeakerphoneOn(false)
        }
    } else {
        originalAudioMode?.let { audioManager?.mode = it }
        originalSpeakerphoneOn?.let { audioManager?.isSpeakerphoneOn = it }
    }
    try {
        remoteAudioTrack?.setEnabled(enable)
    } catch (ex: Exception) {
        Log.d(TAG,"enable Audio but remote Audio Track has been disposed")
    }
}

检测是否佩戴有线或蓝牙耳机:

kotlin 复制代码
/**
 * 判断是否佩戴耳机,佩戴耳机音频不外放
 */
private fun isHeadphonesPlugged(): Boolean {
    val audioDevices = audioManager?.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
    if (audioDevices != null) {
        for (deviceInfo in audioDevices) {
            if (deviceInfo.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
                || deviceInfo.type == AudioDeviceInfo.TYPE_WIRED_HEADSET
                || deviceInfo.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
            ) {
                return true
            }
        }
    }
    return false
}

最后就是打开手机扬声器的代码:

kotlin 复制代码
/**
 * 设置扬声器的开启和关闭
 */
@SuppressLint("NewApi")
private fun setSpeakerphoneOn(enabled: Boolean) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {   //Android 12及以上
        val preferredDevice = if (enabled) {
            // 获取扬声器设备
            val audioDevices = audioManager?.availableCommunicationDevices
            audioDevices?.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }
        } else {
            // 恢复默认设备
            null
        }
        preferredDevice?.let {
            audioManager?.setCommunicationDevice(it)
        } ?: run {
            audioManager?.clearCommunicationDevice()
        }
    } else {
        // 旧版本兼容方式 (Android 11及以下)
        audioManager?.mode = if (enabled) AudioManager.MODE_NORMAL else AudioManager.MODE_IN_COMMUNICATION
        audioManager?.isSpeakerphoneOn = enabled
    }
}

部分类或对象找不到需要参考AWS KVS的Demo。做这类需求的应该能看明白。

相关推荐
薛晓刚3 分钟前
MySQL的replace使用分析
android·adb
DengDongQi26 分钟前
Jetpack Compose 滚轮选择器
android
stevenzqzq26 分钟前
Android Studio Logcat 基础认知
android·ide·android studio·日志
代码不停35 分钟前
MySQL事务
android·数据库·mysql
朝花不迟暮40 分钟前
使用Android Studio生成apk,卡在Running Gradle task ‘assembleDebug...解决方法
android·ide·android studio
yngsqq1 小时前
使用VS(.NET MAUI)开发第一个安卓APP
android·.net
Android-Flutter1 小时前
android compose LazyVerticalGrid上下滚动的网格布局 使用
android·kotlin
Android-Flutter1 小时前
android compose LazyHorizontalGrid水平滚动的网格布局 使用
android·kotlin
千里马-horse1 小时前
RK3399E Android 11 将自己的库放到系统库方法
android·so·设置系统库
美狐美颜sdk1 小时前
Android直播美颜SDK:选择指南与开发方案
android·人工智能·计算机视觉·第三方美颜sdk·视频美颜sdk·人脸美型sdk