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

news2024/11/28 14:44:26

目录

📂 前言

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.coloradmin.cn/o/2060280.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文通透mamba2:力证Transformer are SSM——从SSM、半可分矩阵、SSD到mamba2

前言 实话说,过去一两月一直忙着我司两大类项目的推进 一类是正在逐一上线基于大模型的论文翻译、论文审稿、论文对话、论文修订/润色、论文idea提炼等等一类是正在抓紧做面向一个个工厂的具身智能机器人的解决方案,且很快会分别在我司在各地的办公室(…

day06_算法训练

一. Stream流 1.1 Stream流概述 概念: jdk1.8以后提供的新的API, 主要用于批量操作数据(集合的另外一种操作方式),代码非常简洁 流式处理思想: 2.2 Stream对象获取 1.单列集合的Stream流对象获取 2.双列集合的Stream流对象获取 3.数组的Stream流对象获取 4.散装数据的St…

数据结构day03(栈 Stack 顺序栈、链式栈 )内含具体详细代码实现

目录 【1】栈 Stack 1》栈的定义 2》顺序栈 2》链式栈 4》顺序栈的链式栈的区别 【1】栈 Stack 1》栈的定义 栈:是只允许在一端进行插入或删除的线性表,首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。 栈顶&…

《Python编程:从入门到实践》笔记(一)

一、字符串 1.修改字符串大小写 title()以首字母大写的方式显示每个单词,即将每个单词的首字母都改为大写,其他的改为小写。 upper()将字母都改为大写,lower()将字母都改为小写。 2.合并(拼接)字符串 Python使用加号()来合并字符串。这种合…

Java—认识异常 ( ̄▽ ̄)~*

目录: 一、异常的概念和体系结构: 1、异常的概念: 2、异常的体系: 3、异常的分类: 二、异常的处理: 1、防御式编程: 1)、 事前防御型(LBYL) : 2)、事后…

C语言典型例题47

《C程序设计教程(第四版)——谭浩强》 习题3.7 输入4个整数,要求按照从小到大的顺序输出 4个数之间进行比较,冒泡排序最最最详细过程,如果想更改为任意数之间相互比较,只需要修改两个地方(数组大…

cocoscreator怪物实时寻路AI

这章主要介绍怪物AI 1:环境 cococreator2.4.* 2:规则 当前规则很简单,就是跳上,跳下 一个土块 3:上代码 // Learn cc.Class: // - https://docs.cocos.com/creator/manual/en/scripting/class.html // Learn Attri…

C++智能指针配合STL模板类

代码 #include <unordered_map> #include <set> #include <memory> class ResID { public:using SP std::shared_ptr<ResID>;ResID() default;ResID(const std::string& id, const std::string& type): m_id(id), m_type(type){}public:~Re…

SAM 2——视频和图像实时实例分割的全新开源模型

引言 源码地址&#xff1a;https://github.com/facebookresearch/segment-anything-2 过去几年&#xff0c;人工智能领域在文本处理的基础人工智能方面取得了显著进步&#xff0c;这些进步改变了从客户服务到法律分析等各个行业。然而&#xff0c;在图像处理方面&#xff0c;我…

Python高手如何做到一键更新代码?

声明&#xff1a;此篇为 ai123.cn 原创文章&#xff0c;转载请标明出处链接&#xff1a;https://ai123.cn/2186.html ​Hey&#xff0c;Python搞手们&#xff0c;&#x1f44b;&#xff0c;你们是否也头疼代码维护的重重难关&#xff1f;别担心&#xff0c;今天就给你们带来一手…

【功能】Lua层的全局事件管理系统

1.EventManager 全局的管理类 2.EventType 事件类型 3..Lua层Common工具目录去require对应文件目录的脚本文件 --事件类型 local EventType {TestMsg 1&#xff0c; }return EventType local EventManager class(); EventManager.msgMap {}local function HaveSameFunc(…

Linux 搜索历史命令Ctrl+R

最近使用CtrlR来搜索历史命令&#xff0c;对比速度比history 快一下&#xff0c;且看起来高级。记录如下&#xff1a;命令1&#xff1a;history 功能&#xff1a;显示当前Linux终端输入过的历史命令。案例&#xff1a;使用history 出来的结果很多&#xff0c;通常和grep 过滤&a…

抗菌肽LL-37;LLGDFFRKSKEKIGKEFKRIVQRIKDFLRNLVPRTES;CAS:154947-66-7

【抗菌肽LL-37 简介】 LL-37是一种由37个氨基酸残基组成的抗菌肽&#xff0c;它是人类cathelicidin家族中的唯一成员。LL-37具有广泛的抗菌活性&#xff0c;能够对抗革兰氏阳性菌和革兰氏阴性菌&#xff0c;以及真菌和病毒。除了直接的抗菌作用&#xff0c;LL-37还具有免疫调节…

你是如何克服编程学习中的挫折感的?(-@-^-0-)

在编程学习中遇到挫折感是极为常见且正常的现象&#xff0c;因为编程往往涉及解决复杂问题、理解抽象概念以及不断试错的过程。 以下是一些建议&#xff0c;帮助你在面对挫折时调整心态&#xff0c;继续前行&#xff1a; 接受失败是成长的一部分&#xff1a;首先要认识到&#…

linux之网络子系统-tcpdump 原理

一、tcpdump 的用途 tcpdump是Linux系统抓包工具&#xff0c;tcpdump基于libpcap库&#xff0c;根据使用者的定义对网络上的数据包进行截获&#xff0c;tcpdump可以将网络中传送的数据包中的"头"完全截获下来提供分析&#xff0c;支持针对网络层、协议、主机、网络或…

刷题篇 - 03

题目一&#xff1a; 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; public ListNode removeElements(ListNode head, int val) {//1. 如果链表为null&#xff0c;直接返回headif (head null) {return head;}//2. 定义快慢指针ListNode pre head;ListNode del …

工业数据采集网关简介-天拓四方

随着工业4.0和物联网&#xff08;IoT&#xff09;技术的深入发展&#xff0c;工业数据采集网关作为连接现场设备与上层管理系统的关键节点&#xff0c;其在智能工厂中的作用愈发凸显。本文将深入探讨工业数据采集网关的功能、特点、应用场景及其实操性&#xff0c;以期为读者提…

【JPCS出版】第三届机械、航天技术与材料应用国际学术会议 (MATMA 2024)

第三届机械、航天技术与材料应用国际学术会议(MATMA 2024)定于2024年08月30-9月1日在中国呼和浩特隆重举行。 本会议由内蒙古工业大学主办&#xff0c;主要围绕“机械工程”、“航天技术”与“材料应用”等最新研究领域展开研讨&#xff0c;为来自国内外高等院校、科学研究所、…

【SpringCloud】(一文通)SpringCloud 环境和工程搭建

目 录 一. 开发环境安装二. 案例介绍2.1 需求2.2 服务拆分 三. 数据准备四. 工程搭建4.1 构建父子工程4.1.1 创建父工程4.1.2 创建子项目-订单服务4.1.3 创建子项目-商品服务 4.2 完善订单服务4.2.1 完善启动类, 配置文件4.2.2 业务代码4.2.3 测试 4.3 完善商品服务4.3.1 完善启…

Cesium实现单个无人机飞行

通过一组坐标&#xff0c;实现平滑的无人机飞行效果 测试步骤&#xff1a; 1、手动填写坐标并记录坐标(可通过点击球面查看坐标信息) 2、点击初始化&#xff0c;载入相应的坐标信息 3、点击漫游&#xff0c;镜头会平滑演进 低配置云服务器&#xff0c;首次加载速度较慢&…