文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android TTS播报音频并且配合AudioManager压低其他音频声音

2023-09-10 21:02

关注

文章目录

使用场景

1. 拦截三方导航返回的即将播放的文本信息,并且加以处理然后播报语音。2. 音频焦点管理,协调与其他音频的冲突,如后台播放音乐时导航播报3. 提示固定提示音,如超速等滴滴声音4. 控制导航语音音量大小

通过 TTS 播放文本信息

TTS 就是 TextToSpeech Google 提供的将文字转换为自然语言流的技术,就是通过接收一段文本,转换为声音。具体看百度百科

我这使用场景是在第三方返回语音信息时拦截,然后自己经过处理后播报出去。具体实现的核心简化版代码如下

创建 TTS

const val GOOGLE_TTS_ENGINE = "com.google.android.tts"        private fun createTts() {    // contextRef 的弱引用 防止内存泄漏        contextRef.get()?.let { context ->            tts = if (!isFollowSystem) {            // 我们这里项目做的不是国内的项目所以如果不跟随系统选择的默认的Google引擎                TextToSpeech(                        context,                        { status -> this@TtsManager.onInit(status) },                        GOOGLE_TTS_ENGINE)            } else {            // 手机默认引擎,这个和手机厂商定制应该有关系                 TextToSpeech(context) { p0 -> this@TtsManager.onInit(p0) }            }            // 设置语言播报速度            tts?.setSpeechRate(1f)            // setOnUtteranceProgressListener 设置的是语音播报的开始、结束、异常 等监听事件            processListenerRef?.get()?.let {                tts?.setOnUtteranceProgressListener(it)            }        }    }

TextToSpeech 第一个参数大家都知道,第二个是初始化成功还是失败的回调,第三个是可以指定引擎,一般手机厂商很多会自己内置自己的引擎,默认的话大多应该就是 Google 引擎了。this 里面第四个参数是 packagename 如果本地有其他的引擎可以指定包名,默认null。this 里面第五个参数是如果指定的引擎失败会走默认的引擎初始化。

 public TextToSpeech(Context context, OnInitListener listener, String engine) {        this(context, listener, engine, null, true);    }

我用的手机不是国行的手机,所以默认是 Google 的 tts 引擎。

下载 TTS

由于目标用户特性,我这里如果没有 Google 引擎,会提示下载 Google tts 引擎

fun isInstallGoogleTts():Boolean{        tts?.engines?.forEach{            if(it.name ==  "com.google.android.tts") {                return true            }        }        return false    }
private const val GOOGLE_TTS_URI = "market://details?id=com.google.android.tts"val intent = Intent(Intent.ACTION_VIEW)            intent.data = Uri.parse(GOOGLE_TTS_URI)            applicationContext.packageManager?.let {                if(intent.resolveActivity(it) != null) {                    startActivity(intent)                }            }

TTS 有 Voice 语音包

            tts?.voices?.forEach {                // 筛选美式英语                if (it.locale.language == English                    && (it.locale.country.toUpperCase(Locale.ROOT) == US|| it.locale.country.toUpperCase(Locale.ROOT) == UNITED_STATES)) {                    enVoices.add(it)                    //刷选美式西班牙语                } else if (it.locale.language == Spanish                    && (it.locale.country.toUpperCase(Locale.ROOT) == US|| it.locale.country.toUpperCase(Locale.ROOT) == UNITED_STATES)) {                    spVoices.add(it)                }            }

TTS 播放语音

fun speak(text: String, queueModel: Int) {        Log.d(TAG, "Voice message: $text")        utteranceId = TAG + messageId++        // QUEUE_FLUSH interrupts already speaking messages.        val error: Int = tts?.speak(text, queueModel, null, utteranceId) ?: -1        if (error != 0) {            Log.e(TAG, "Error when speaking using Android's TextToSpeech: $error")        }}
    public int speak(final CharSequence text,                     final int queueMode,                     final Bundle params,                     final String utteranceId) {        return runAction((ITextToSpeechService service) -> {        }, ERROR, "speak");    }
@param text :要讲的文本字符串,不超过 4000 个字符@param queueMode:要使用的队列策略,主要有两种- public static final int QUEUE_ADD = 1; 队列模式,在播放队列的末尾添加新条目。- public static final int QUEUE_FLUSH = 0; 主要意思就是前面的删除留下新的@param params:请求参数可以为null。就是内置的一些可配置的引擎参数。通过 bundle传入。@param utteranceId :此请求的唯一标识符。@ return :返回值是代表错误吗 0 成功 , -1 失败

音频焦点管理

音频焦点管理就是解决播报时是否有其他音频正在播报,比如导航中,后台放着音乐,前面右转播报时降低音乐音量保证用户可以听清楚导航声音。

通过 android,media 下的 AudioFocusRequest 的音频焦点管理类和 AudioManager 获取音频焦点。核心简化代码如下

    private var audioRequest: AudioFocusRequest? = null    private fun createFocusRequest(){        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {            audioRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)                .setAudioAttributes(audioAttributes)                .setOnAudioFocusChangeListener {                    Log.d(TAG, "init Audio Focus: $it")                }                .setAcceptsDelayedFocusGain(false)                .build()        }}

通过 AudioFocusRequest 的 Builder 来构建对象,audioAttributes 设置一些属性如下:

        private val audioAttributes: AudioAttributes by lazy {        audioAttr?:AudioAttributes.Builder()            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)            .build()    }
  • setUsage : 设置用途,因为我这里时导航,所以选择的 USAGE_ASSISTANCE_NAVIGATION_GUIDANCE 导航用途
    • USAGE_MEDIA:表示音频的用途是多媒体。
      USAGE_VOICE_COMMUNICATION:表示音频的用途是语音通信。
      USAGE_NOTIFICATION:表示音频的用途是通知。
      USAGE_ALARM:表示音频的用途是闹钟。
      USAGE_ASSISTANCE_ACCESSIBILITY:表示音频的用途是辅助功能和无障碍功能。
  • setContentType: 设置描述音频信号(如语音或音乐)的内容类型的属性。以下是其中一些类型
    • CONTENT_TYPE_MUSIC:表示音频的内容类型是音乐。
      CONTENT_TYPE_SPEECH:表示音频的内容类型是语音。
      CONTENT_TYPE_MOVIE:表示音频的内容类型是电影。
      CONTENT_TYPE_SONIFICATION:表示音频的内容类型是提示音或系统声音。
  • setFlags(int flags):设置音频的标志。

setAcceptsDelayedFocusGain() 是 AudioAttributes.Builder 类中的一个属性,用于指示音频源是否支持延迟获取焦点(delayed focus gain)。
在 Android 中,当多个应用程序同时请求使用音频焦点时,系统需要决定哪个应用程序具有焦点,以便控制音频的播放和暂停。默认情况下,如果一个应用程序请求获取音频焦点,系统会立即将焦点转移到该应用程序,但在某些情况下,这可能会引起不必要的中断和干扰。例如,如果用户正在使用语音助手(例如 Google Assistant)进行语音识别,那么在识别期间可能不希望其他应用程序突然播放声音并中断语音识别。为了避免这种情况,系统支持延迟获取焦点,即在请求获取焦点后,系统会等待一段时间以确定焦点是否真的需要转移,如果不需要,则会保留焦点。
如果一个应用程序支持延迟获取焦点,那么当它请求获取焦点时,系统会通知它是否已经获得了焦点,并告诉它何时将焦点转移到该应用程序。这使得应用程序可以在等待焦点时继续播放音频,而不必担心在焦点转移时被中断。
因此,setAcceptsDelayedFocusGain() 属性的意义是指示音频源是否支持延迟获取焦点。如果一个音频源支持延迟获取焦点,那么应该将该属性设置为 true,否则应该将其设置为 false。如果不确定是否需要支持延迟获取焦点,则可以使用默认值 false。

我这里因为要求实时性延迟的话会很危险,所以设置成了 false

    private val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager

请求音频焦点

private val utteranceProgressListener = object : UtteranceProgressListener() {        override fun onStart(utteranceId: String?) {        // 获取焦点            audioFocusManager.requestAudioFocus()            // 根据配置选择是否增加音量            increaseVolume()        }        override fun onDone(utteranceId: String?) {        // 释放焦点            audioFocusManager.abandonAudioFocus()            // 还原音量            recoverVolume()        }        @Deprecated("Deprecated in Java")        override fun onError(utteranceId: String?) {        }        override fun onError(utteranceId: String?, errorCode: Int) {            super.onError(utteranceId, errorCode)            // 释放焦点            audioFocusManager.abandonAudioFocus()             // 还原音量            recoverVolume()        }        override fun onStop(utteranceId: String?, interrupted: Boolean) {            super.onStop(utteranceId, interrupted)            //1.使用speak的QUEUE_FLUSH模式,打断上一个,上一个不会回调onDone,但是会回调onStop            //2.直接stop也会回调onStop            audioFocusManager.abandonAudioFocus()             // 还原音量            recoverVolume()        }    }
    fun requestAudioFocus(): Int {        try {            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {                audioRequest?.let {                    return audioManager.requestAudioFocus(it)                }                return AudioManager.AUDIOFOCUS_REQUEST_FAILED            } else {            // 这个已经弃用                return audioManager.requestAudioFocus(                    {                        Log.d(TAG, "requestAudioFocus Audio Focus: $it")                    },                    AudioManager.STREAM_ACCESSIBILITY,                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK                )            }        } catch (e: IllegalArgumentException) {            e.printStackTrace()        }        return AudioManager.AUDIOFOCUS_REQUEST_FAILED    }

释放焦点

        fun abandonAudioFocus() {        audioRequest?.let {            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {                audioManager.abandonAudioFocusRequest(it)            } else {            // 弃用的方法                audioManager.abandonAudioFocus{                    Log.d(TAG, "abandonAudioFocus Audio Focus: $it")                }            }        }    }

通过 AudioManager 增加或者减少音量

    private fun increaseVolume() {    // 判断用户开关是否开启        if (isVolumeUp()) {            //记录原始音量            recordOriginVolume = getVolume()            //静音情况下直接返回            if (recordOriginVolume <= 0) {                return            }            // 设置音量,固定增加量,我们这里增加了原来的百分之三十            setVolume(recordOriginVolume + increaseVolumeNum)            //记录修改后的音量            recordTargetVolume = getVolume()            isVolumeUpping = true        }    }
    private fun getVolume(): Int {        return try {            audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)        } catch (e: Exception) {            TPLogUtils.e(TAG, "getVolume() error : message:${e.message}")            0        }    }

audioManager 的 getStreamVolume() 的类型有以下几种可以根据需求选择

STREAM_VOICE_CALL:语音通话音频流。STREAM_SYSTEM:系统提示音和声音效果音频流。STREAM_RING:电话铃声音频流。STREAM_NOTIFICATION:通知提示音和通知音效音频流。STREAM_ALARM:闹钟提示音频流。STREAM_DTMF:拨号按键音频流。STREAM_ACCESSIBILITY:辅助功能音频流
    private fun setVolume(volumeIndex: Int, flags: Int = 0) {        try {            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volumeIndex, flags)        } catch (e: Exception) {            TPLogUtils.e(                TAG,                "setVolume() error : message:${e.message},volumeIndex:$volumeIndex,flags:$flags"            )        }    }

音量设置完成,播放完成以后记得设置回原来的值,以免越来越大。

AudioManager 常用的 API

setStreamVolume(int streamType, int index, int flags):设置指定流类型(streamType)的音量值(index),其中标志(flags)通常设置为 0getStreamVolume(int streamType):获取指定流类型(streamType)的当前音量值。setMode(int mode):设置音频模式(mode),例如音乐播放或电话通话。setSpeakerphoneOn(boolean on):设置是否使用扬声器(speakerphone)进行音频输出。isSpeakerphoneOn():检查当前是否使用扬声器(speakerphone)进行音频输出。setMicrophoneMute(boolean on):设置麦克风静音状态。isMicrophoneMute():检查当前麦克风是否处于静音状态。setWiredHeadsetOn(boolean on):设置是否插入有线耳机。isWiredHeadsetOn():检查当前是否插入有线耳机。setBluetoothScoOn(boolean on):设置是否使用蓝牙 SCO 音频通道。startBluetoothSco():开始使用蓝牙 SCO 音频通道。stopBluetoothSco():停止使用蓝牙 SCO 音频通道。isBluetoothScoOn():检查当前是否使用蓝牙 SCO 音频通道。setBluetoothScoHeadset(AudioDevice bluetoothScoHeadset):设置当前蓝牙耳机设备。isBluetoothScoAvailableOffCall():检查当前是否支持在非通话状态下使用蓝牙 SCO 音频通道。

通过这些 API ,可以满足大多数场景下的语音播报需求。

来源地址:https://blog.csdn.net/ldxlz224/article/details/130532614

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯