AR 眼镜之-系统应用音效-实现方案

目录

📂 前言

AR 眼镜系统版本

系统应用音效

1. 🔱 技术方案

1.1 技术方案概述

1.2 实现方案

1)初始化

2)播放音效

3)释放资源

2. 💠 播放音效

2.1 静音不播放

2.2 获取音效默认音量

3. ⚛️ 单声道空间音效

3.1 实现方案

3.2 封装播放单声道空间音效

4. ✅ 小结

附录1:StreamType 值

附录2:音效播放工具源码


📂 前言

AR 眼镜系统版本

        W517 Android9。

系统应用音效

        系统应用音效主要包括:通知音效、蓝牙电话音效、点击音效等。

1. 🔱 技术方案

1.1 技术方案概述

        系统应用音效都是比较短的,一般采用 Android 推荐的 ogg 格式,直接使用 SoundPool 播放即可。

1.2 实现方案

1)初始化

        在适当位置初始化 SoundPool 工具类(比如:在 Activity 的 onCreate 方法中初始化),包括:初始化相应 SoundPool、load 相应音效资源、保存音效资源 ID。

        1、初始化相应 SoundPool

private val phoneSP by lazy { SoundPool(20, AudioManager.STREAM_VOICE_CALL, 0) }
private val notifySP by lazy { SoundPool(20, AudioManager.STREAM_NOTIFICATION, 0) }
private val clickSP by lazy { SoundPool(20, AudioManager.STREAM_SYSTEM, 0) }

        SoundPool 构造第一个参数为 maxStreams,一般设置为1或10,本文设置为20是有一定风险的(主要是为了规避接通蓝牙电话时,点击蓝牙电话接听的系统音效被蓝牙电话音效通道抢占,导致点击蓝牙电话接听的系统音效无法播放的问题)。

        SoundPool 构造第二个参数是 streamType 值,具体值的含义可查看附录1。比如:本文的通知音效使用 STREAM_NOTIFICATION,蓝牙电话使用的 STREAM_VOICE_CALL,点击音效使用的 STREAM_SYSTEM。

        2、load 相应音效资源,保存音效资源 ID

/**
 * 加载音效ID
 */
private var hangupId: Int = -1
private var answerId: Int = -1
private var notifyComeId: Int = -1
private var notifyClearId: Int = -1
private var clickId: Int = -1

fun init(context: Context) {
    hangupId = phoneSP.load(context, R.raw.phone_hang_up, 3)
    answerId = phoneSP.load(context, R.raw.phone_answer, 3)
    notifyComeId = notifySP.load(context, R.raw.notification_message, 1)
    notifyClearId = notifySP.load(context, R.raw.notification_clear, 1)
    clickId = clickSP.load(context, R.raw.click, 1)
    Log.i(TAG, "init: hangupId = $hangupId,answerId = $answerId,notifyComeId = $notifyComeId,notifyClearId = $notifyClearId,clickId = $clickId")
}

        load 方法的第三个参数为声音的优先级,源码注释为:目前不起作用,默认使用1即可。

2)播放音效

        在触发音效播放处调用播放音效,包括播放音效通用方法、播放音效特定封装方法。

        1、播放音效通用方法

/**
 * 播放音效
 */
fun play(
    context: Context,
    soundPool: SoundPool,
    soundId: Int,
    resId: Int,
    leftVol: Float = -1f,
    rightVol: Float = -1f
) {
    ...
    // soundId:加载的音频资源的 ID。
    // leftVolume和rightVolume:左右声道的音量,范围为 0.0(静音)到 1.0(最大音量)。
    // priority:播放优先级,一般设为 1。
    // loop:是否循环播放,0 表示不循环,-1 表示无限循环。
    // rate:播放速率,1.0 表示正常速率,更大的值表示更快的播放速率,0.5 表示慢速播放。
    soundPool.play(soundId, leftVol, rightVol, 1, 0, 1.0f)
    ...
}

        2、播放音效特定封装方法

fun playHangup(context: Context) {
    Log.i(TAG, "playHangup: resId = ${R.raw.phone_hang_up}")
    play(context, phoneSP, hangupId, R.raw.phone_hang_up)
}
fun playAnswer(context: Context) {
    Log.i(TAG, "playAnswer: resId = ${R.raw.phone_answer}")
    play(context, phoneSP, answerId, R.raw.phone_answer)
}
fun playNotifyCome(context: Context) {
    Log.i(TAG, "playNotifyCome: resId = ${R.raw.notification_message}")
    play(context, notifySP, notifyComeId, R.raw.notification_message)
}
fun playNotifyClear(context: Context) {
    Log.i(TAG, "playNotifyClear: resId = ${R.raw.notification_clear}")
    play(context, notifySP, notifyClearId, R.raw.notification_clear)
}
3)释放资源

        在适当位置释放 SoundPool 对象(比如:在 Activity 的 onDestroy 方法中释放资源)

/**
 * 释放资源
 */
fun release() {
    phoneSP.release()
    notifySP.release()
}

2. 💠 播放音效

2.1 静音不播放

        判断当前系统对应音效是否静音,如果静音则不播放音效。

/**
 * 播放音效
 */
fun play(
    context: Context,
    soundPool: SoundPool,
    soundId: Int,
    resId: Int,
    leftVol: Float = -1f,
    rightVol: Float = -1f
) {
    // 1.静音不播放
    if (isSilent(context)) {
        Log.i(TAG, "_play: AudioManager RINGER_MODE_SILENT!")
        return
    }

    ...
}

/**
 * @return 是否静音
 */
private fun isSilent(context: Context) =
    (context.getSystemService(Context.AUDIO_SERVICE) as AudioManager).ringerMode == AudioManager.RINGER_MODE_SILENT

2.2 获取音效默认音量

        设定音效播放前,先获取到系统音效的默认音量大小,在播放时使用系统音效的默认音量值。

/**
 * 播放音效
 */
fun play(
    context: Context,
    soundPool: SoundPool,
    soundId: Int,
    resId: Int,
    leftVol: Float = -1f,
    rightVol: Float = -1f
) {
    ...
    // 2.获取音效默认音量
    val volFloat = getVol(context)
    ...
}

/**
 * @return 音效默认音量
 */
private fun getVol(context: Context) = 10.0.pow(
    (context.resources.getInteger(com.android.internal.R.integer.config_soundEffectVolumeDb)
        .toFloat() / 20).toDouble()
).toFloat()

3. ⚛️ 单声道空间音效

3.1 实现方案

  1. 屏蔽原生 View 点击音效:android:soundEffectsEnabled="false"

  2. 使用 SoundPool 单独设置左右声道音量,假如只播放右声道,则把左声道音量置为0。

3.2 封装播放单声道空间音效

fun playClickLeft(context: Context) {
    Log.i(TAG, "playClickLeft: resId = ${R.raw.click}")
    play(context, phoneSP, hangupId, R.raw.click, rightVol = 0f)
}
fun playClickRight(context: Context) {
    Log.i(TAG, "playClickRight: resId = ${R.raw.click}")
    play(context, phoneSP, hangupId, R.raw.click, 0f)
}

/**
 * 播放音效
 */
fun play(
    context: Context,
    soundPool: SoundPool,
    soundId: Int,
    resId: Int,
    leftVol: Float = -1f,
    rightVol: Float = -1f
) {
    ...
    // 2.获取音效默认音量
    val volFloat = getVol(context)
    val tempLeftVol = if (leftVol != -1f) leftVol else volFloat
    val tempRightVol = if (rightVol != -1f) rightVol else volFloat
    soundPool.play(soundId, tempLeftVol, tempRightVol, 1, 0, 1.0f)
    ...
}

4. ✅ 小结

        对于系统应用音效,本文只是一个基础实现方案,更多业务细节请参考产品逻辑去实现。

        另外,由于本人能力有限,如有错误,敬请批评指正,谢谢。


附录1:StreamType 值

/** Used to identify the default audio stream volume */
public static final int STREAM_DEFAULT = -1;
/** Used to identify the volume of audio streams for phone calls */
public static final int STREAM_VOICE_CALL = 0;
/** Used to identify the volume of audio streams for system sounds */
public static final int STREAM_SYSTEM = 1;
/** Used to identify the volume of audio streams for the phone ring and message alerts */
public static final int STREAM_RING = 2;
/** Used to identify the volume of audio streams for music playback */
public static final int STREAM_MUSIC = 3;
/** Used to identify the volume of audio streams for alarms */
public static final int STREAM_ALARM = 4;
/** Used to identify the volume of audio streams for notifications */
public static final int STREAM_NOTIFICATION = 5;
/** Used to identify the volume of audio streams for phone calls when connected on bluetooth */
public static final int STREAM_BLUETOOTH_SCO = 6;
/** Used to identify the volume of audio streams for enforced system sounds in certain
 * countries (e.g camera in Japan) */
public static final int STREAM_SYSTEM_ENFORCED = 7;
/** Used to identify the volume of audio streams for DTMF tones */
public static final int STREAM_DTMF = 8;
/** Used to identify the volume of audio streams exclusively transmitted through the
 *  speaker (TTS) of the device */
public static final int STREAM_TTS = 9;
/** Used to identify the volume of audio streams for accessibility prompts */
public static final int STREAM_ACCESSIBILITY = 10;

附录2:音效播放工具源码

/**
 * Description:    音效播放工具
 * CreateDate:     2024/6/26 15:47
 * Author:         agg
 */
object SoundPoolTools {

    private val TAG = SoundPoolTools::class.java.simpleName
    private val phoneSP by lazy { SoundPool(20, AudioManager.STREAM_VOICE_CALL, 0) }
    private val notifySP by lazy { SoundPool(20, AudioManager.STREAM_NOTIFICATION, 0) }
    private val clickSP by lazy { SoundPool(20, AudioManager.STREAM_SYSTEM, 0) }
    /**
     * 加载音效ID
     */
    private var hangupId: Int = -1
    private var answerId: Int = -1
    private var notifyComeId: Int = -1
    private var notifyClearId: Int = -1
    private var clickId: Int = -1

    fun init(context: Context) {
        hangupId = phoneSP.load(context, R.raw.phone_hang_up, 3)
        answerId = phoneSP.load(context, R.raw.phone_answer, 3)
        notifyComeId = notifySP.load(context, R.raw.notification_message, 1)
        notifyClearId = notifySP.load(context, R.raw.notification_clear, 1)
        clickId = clickSP.load(context, R.raw.click, 1)
        Log.i(TAG, "init: hangupId = $hangupId,answerId = $answerId,notifyComeId = $notifyComeId,notifyClearId = $notifyClearId,clickId = $clickId")
    }

    fun playClickLeft(context: Context) {
        Log.i(TAG, "playClickLeft: resId = ${R.raw.click}")
        play(context, phoneSP, hangupId, R.raw.click, rightVol = 0f)
    }

    fun playClickRight(context: Context) {
        Log.i(TAG, "playClickRight: resId = ${R.raw.click}")
        play(context, phoneSP, hangupId, R.raw.click, 0f)
    }

    fun playHangup(context: Context) {
        Log.i(TAG, "playHangup: resId = ${R.raw.phone_hang_up}")
        play(context, phoneSP, hangupId, R.raw.phone_hang_up)
    }

    fun playAnswer(context: Context) {
        Log.i(TAG, "playAnswer: resId = ${R.raw.phone_answer}")
        play(context, phoneSP, answerId, R.raw.phone_answer)
    }

    fun playNotifyCome(context: Context) {
        Log.i(TAG, "playNotifyCome: resId = ${R.raw.notification_message}")
        play(context, notifySP, notifyComeId, R.raw.notification_message)
    }

    fun playNotifyClear(context: Context) {
        Log.i(TAG, "playNotifyClear: resId = ${R.raw.notification_clear}")
        play(context, notifySP, notifyClearId, R.raw.notification_clear)
    }

    /**
     * 播放音效
     */
    fun play(
        context: Context,
        soundPool: SoundPool,
        soundId: Int,
        resId: Int,
        leftVol: Float = -1f,
        rightVol: Float = -1f
    ) {
        // 1.静音不播放
        if (isSilent(context)) {
            Log.i(TAG, "_play: AudioManager RINGER_MODE_SILENT!")
            return
        }
        // 2.获取音效默认音量
        val volFloat = getVol(context)
        val tempLeftVol = if (leftVol != -1f) leftVol else volFloat
        val tempRightVol = if (rightVol != -1f) rightVol else volFloat
        // 3.播放音效
        // soundId:加载的音频资源的 ID。
        // leftVolume和rightVolume:左右声道的音量,范围为 0.0(静音)到 1.0(最大音量)。
        // priority:播放优先级,一般设为 1。
        // loop:是否循环播放,0 表示不循环,-1 表示无限循环。
        // rate:播放速率,1.0 表示正常速率,更大的值表示更快的播放速率,0.5 表示慢速播放。
        if (soundId != -1) {
            Log.i(TAG, "_play: play [direct],soundId = $soundId,resId = $resId")
            soundPool.play(soundId, tempLeftVol, tempRightVol, 1, 0, 1.0f)
        } else {
            Log.i(TAG, "_play: play [load],soundId = $soundId,resId = $resId")
            soundPool.load(context, resId, 1)
            soundPool.setOnLoadCompleteListener { _, _, _ ->
                Log.i(TAG, "_play: play [load -> play],soundId = $soundId,resId = $resId")
                soundPool.play(soundId, tempLeftVol, tempRightVol, 1, 0, 1.0f)
            }
        }
    }

    /**
     * 释放资源
     */
    fun release() {
        phoneSP.release()
        notifySP.release()
    }

    /**
     * @return 是否静音
     */
    private fun isSilent(context: Context) =
        (context.getSystemService(Context.AUDIO_SERVICE) as AudioManager).ringerMode == AudioManager.RINGER_MODE_SILENT

    /**
     * @return 音效默认音量
     */
    private fun getVol(context: Context) = 10.0.pow(
        (context.resources.getInteger(com.android.internal.R.integer.config_soundEffectVolumeDb)
            .toFloat() / 20).toDouble()
    ).toFloat()

}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/872041.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QTCreator学习

1.新建程序 2. 设置项目名称 3. Build System选择qmake,若选择cmake则只会产生CmakeLists文件,不会产生pro文件。 4.Base class选择QDialog,表示该类继承于QDialog类 5.套件选择MinGW 32bit,取消掉其他的。 6. 双击ui文件,拖动可添加工具。 7.点击左…

C++模拟实现priority_queue(优先级队列)

一、priority_queue的函数接口 从上图我们可以看出&#xff0c; priority_queue也是一个容器适配器&#xff0c;我们使用vector容器来模拟实现priority_queue。 namespace bit{#include<vector>#include<functional>template <class T, class Container vector…

iOS App上架审核被拒——2.3.3 - Performance - Accurate Metadata

iOS上架审核被拒——Guideline 2.3.3 - Performance - Accurate Metadata 噢&#xff0c;又被拒了… 文章目录 iOS上架审核被拒——Guideline 2.3.3 - Performance - Accurate Metadata被拒原因解决 被拒原因 大概翻译了下&#xff1a;预览图问题&#xff0c;只因某张预览图加了…

正则表达式备查

一、常用 符号内容\将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如&#xff0c;“n”匹配字符“n”。“\n”匹配换行符。序列“\”匹配“\”&#xff0c;“(”匹配“(”。^匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性&#xff0c;^ 还…

CSS知识点详解:display+float

display&#xff1a;浮动 1.block&#xff1a;使元素呈现为块级元素&#xff0c;可设置宽高 display: block; 特点&#xff1a;使元素呈现为块级元素&#xff0c;即该元素会以新行开始&#xff0c;占据整行的宽度&#xff0c;即使其宽度未满。 例子&#xff1a; 2.inline&a…

答题小程序的轮播图管理与接入获取展示实现

实现了答题小程序的轮播图管理&#xff0c;包括上传图片、设置轮播图、操作上下线等功能&#xff0c;可用于管理各类答题小程序的轮播图。 轮播图前端接入代码 答题小程序内使用以下代码接入轮播图&#xff1a; WXML&#xff1a; <view style"width: 100%"> …

继承(下)【C++】

文章目录 子类继承父类之后&#xff0c;子类的默认成员函数的变化构造函数编译器自动生成的构造函数程序员手动写的构造函数 拷贝构造编译器自动生成的拷贝构造函数程序员手动写的拷贝构造函数 赋值重载编译器自动生成的赋值重载程序员手动写的赋值重载 析构函数 继承与友元菱形…

ISO 26262中的失效率计算:IEC 61709-Clause 17_Switches and push-buttons

概要 IEC 61709是国际电工委员会&#xff08;IEC&#xff09;制定的一个标准&#xff0c;即“电子元器件 可靠性 失效率的基准条件和失效率转换的应力模型”。主要涉及电学元器件的可靠性&#xff0c;包括失效率的基准条件和失效率转换的应力模型。本文介绍IEC 61709第十七章&…

Linux安装并配置Hadoop

目录 一、安装并配置JDK二、安装并配置Hadoop三、安装过程中遇到的问题总结 一、安装并配置JDK Linux上一般会安装Open JDK,关于OpenJDK和JDK的区别&#xff1a;http://www.cnblogs.com/sxdcgaq8080/p/7487369.html 准备Open JDK 1.8 查询可安装的java版本 yum -y list jav…

C语言第17篇

1.在C语言中,全局变量的存储类别是_________. A) static B) extern C) void D) register 提示&#xff1a;extern adj.外来的 register n.登记表&#xff0c;v.登记 提示与本题无关 2.在一个C源程序文件中,要定义一个只允许本源文件中所有函数使用的全局变…

【经典算法】BFS_最短路问题

目录 1. 最短路问题介绍2. 算法原理和代码实现(含题目链接)1926.迷宫中离入口最近的出口433.最小基因变化127.单词接龙675.为高尔夫比赛砍树 3. 算法总结 1. 最短路问题介绍 最短路径问题是图论中的一类十分重要的问题。本篇文章只介绍边权为1(或边权相同)的最简单的最短路径问…

ant design pro 中用户的表单如何控制多个角色

ant design pro 如何去保存颜色ant design pro v6 如何做好角色管理ant design 的 tree 如何作为角色中的权限选择之一ant design 的 tree 如何作为角色中的权限选择之二ant design pro access.ts 是如何控制多角色的权限的 看上面的图片 当创建或编辑一个用户时&#xff0c;…

自带灭火电池?深蓝SL03托底事故揭秘

近日&#xff0c;网络上的一段热传视频&#xff0c;让不少网友看得先是惊心动魄&#xff0c;然后却又啧啧称奇。 该视频显示&#xff0c;8月18日晚上19点28分&#xff0c;一辆深蓝SL03在行驶中意外遭遇严重托底事故&#xff0c;车辆瞬间腾空跳跃&#xff0c;紧接着底盘出现明火…

禁止浏览器默认填充密码 vue

禁止浏览器默认填充密码会和我的样式冲突 所以禁止 第一种&#xff1a; 通过给表单元素添加 autocomplete"off" 属性&#xff0c; 可以防止浏览器自动填充表单中的账号和密码。可以在 input 标签或整个 form 标签上使用&#xff1a; <template><a-form&g…

向量数据库中的PQ(Procduct Quantization)

为了加快向量之间距离计算和比较速度&#xff0c;有人发明的Product Quantization方法&#xff0c;这个方法并不是一种索引&#xff0c;所以它并不能减少目标向量&#xff08;要查找的向量&#xff09;&#xff0c;与数据库中向量的比较次数&#xff0c;但是它可以加快与每个数…

(第三十三天)

1. 设置主从从 mysql57 服务器 &#xff08; 1 &#xff09;配置主数据库 [rootmsater_5 ~] # systemctl stop filewalld [rootmsater_5 ~] # setenforce 0 [rootmsater_5 ~] # systemctl disable filewalld [rootmsater_5 ~] # ls anaconda-ks.cfg mysql-5.7.44-linux-g…

uniapp 页面跳转传参:父页面监听子页面传过来的数据

父页面 监听events事件 uni.navigateTo({url: "/components/watermark-camera",events: { // 重点重点重点重点重点重点重点重点getImages(data) { // 接收子页面抛出的 getImages 事件console.log("水印相机的照片&#xff1a;", data)}}})子页面 const …

<数据集>航拍牧场奶牛识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1805张 标注数量(xml文件个数)&#xff1a;1805 标注数量(txt文件个数)&#xff1a;1805 标注类别数&#xff1a;1 标注类别名称&#xff1a;[cow] 序号类别名称图片数框数1cow1805141337 使用标注工具&#xff…

World of Warcraft [CLASSIC] the Eye of Eternity [EOE] P1-P2

World of Warcraft [CLASSIC] the Eye of Eternity [EOE] 永恒之眼&#xff08;蓝龙&#xff09; 第一阶段 第二阶段 第三阶段 载具1-6技能介绍 World of Warcraft [CLASSIC] the Eye of Eternity [EOE]_永恒之眼 eoe-CSDN博客 永恒之眼怎么出副本呢&#xff0c;战斗结束&am…

makefile文件基本语法

一、makefile文件基本介绍 Makefile 文件是 make 工具使用的配置文件&#xff0c;它定义了如何自动化构建项目的规则和命令。Makefile 文件的主要作用是指定如何编译和链接程序&#xff0c;以及管理文件之间的依赖关系&#xff0c;从而实现高效的构建过程。 1.1 Makefile 的基…