【鸿蒙开发】第四十九章 Audio Kit简介

目录

1 概述

1.1 亮点/特征

1.2 开发说明

1.3 音频流介绍

1.3.1 音频流使用场景信息

1.4 支持的音频格式

 2 使用合适的音频流类型

2.1 常用的音频流类型及其适用场景

2.1.1 播放音频流类型

2.1.2 录制音频流类型

2.2 流类型对音频业务的影响

2.2.1 音量控制

2.2.2 音频焦点调整

2.2.3 输入/输出设备选择

2.3 设置音频流类型

3 音频焦点和音频会话介绍

3.1 音频焦点

3.1.1 申请音频焦点

3.1.2 释放音频焦点

3.1.3 音频焦点策略

3.1.4 焦点模式

3.1.5 处理音频焦点变化

3.2 使用AudioSession管理音频焦点

3.2.1 音频会话(AudioSession)使用流程

3.2.2 音频会话策略(AudioSessionStrategy)

3.2.3 监听AudioSession停用事件

3.3 典型场景

 4 使用AudioSession管理应用音频焦点(ArkTS)

4.1 获取音频会话管理器

4.2 激活音频会话

4.3 查询音频会话是否已激活

4.4 停用音频会话

4.5 监听音频会话停用事件

4.6 取消监听音频会话停用事件

5 音频播放

5.1 音频播放开发概述

5.1.1 如何选择音频播放开发方式

5.1.2 后台播放或熄屏播放开发须知

5.2 使用AudioRenderer开发音频播放功能

5.2.1 开发指导

1、开发步骤及注意事项

2、选择正确的StreamUsage

5.3 响应音频流输出设备变更

5.3.1 音频流输出设备信息

5.3.2 音频流输出设备变更原因

5.3.3 参考示例

5.4 使用AudioHaptic开发音振协同播放功能

5.4.1 开发指导

5.4.2 权限申请

5.4.3 开发步骤及注意事项

5.5 播放音量管理

5.5.1 系统音量

5.5.2 监听系统音量变化

5.5.3 使用音量面板调节系统音量

5.5.4 音频流音量

5.6 音效管理

5.6.1 播放实例音效管理

5.6.2 获取播放实例

5.6.3 查询当前播放实例的音效模式

5.6.4 设置当前播放实例的音效模式

5.6.5 全局查询音效模式

5.6.6 获取音频流管理接口

5.6.7 查询对应场景的音效模式

5.7 音频播放流管理

5.7.1 读取或监听应用内音频流状态变化

5.7.2 读取或监听所有音频流的变化

5.7.3 开发步骤及注意事项

 5.8 管理全局音频输出设备

5.8.1 创建AudioRoutingManager实例

5.8.2 支持的音频输出设备类型

5.8.3 获取输出设备信息

5.8.4 监听设备连接状态变化

5.8.5 获取最高优先级输出设备信息

5.8.6 监听最高优先级输出设备变化

5.9 音频录制

5.9.1 音频录制开发概述

1、如何选择音频录制开发方式

2、开发音频录制应用须知

5.9.2  使用AudioCapturer开发音频录制功能

1、开发指导

2、开发步骤及注意事项

5.9.3 管理麦克风

1、开发步骤及注意事项

5.9.4 音频录制流管理

1、读取或监听应用内音频流状态变化

2、读取或监听所有录制流的变化

3、开发步骤及注意事项

5.9.5 管理全局音频输入设备 

1、创建AudioRoutingManager实例

2、支持的音频输入设备类型

3、获取输入设备信息

4、监听设备连接状态变化

5.10 音频通话

5.10.1 音频通话开发概述

5.10.2 音频场景模式

5.10.3 铃声模式

5.10.4 通话场景音频设备切换

5.10.5 开发音频通话功能

1、使用AudioRenderer播放对端的通话声音

2、使用AudioCapturer录制本端的通话声音


1 概述

Audio Kit(音频服务)旨在提供场景化的音频播放和录制接口,助力迅速构建音频高清采集及沉浸式播放能力。

1.1 亮点/特征

  • 低时延播放

    提供统一音频低时延/非低时延播放能力接口,通过垂直打通硬件,达成最低的音频输出时延。在游戏、提示/告警音、K歌等场景下,可以通过低时延接口,实现音频快速流畅播放。

  • 音效模式

    提供系统音效模式设置,应用可以按需开/关系统音效,确保最佳音效输出体验。

    系统默认为音乐、听书、影院等不同场景进行相应音效处理,但应用内部自身也存在一些定制化音效,为确保最终音效不产生冲突,系统提供音效模式配置开关,允许应用按需开/关系统音效。

  • 音振协同

    提供音振协同能力接口,实现音频及振动流的低时延同步控制。达成在输入法中开启音频和振动效果,打字输入时音振协同、节奏一致,来电铃声和振动同时响起,铃音和振动节奏同步一致的体验。

1.2 开发说明

在每个功能中,会介绍多种实现方式以应对不同的使用场景,以及该场景相关的子功能点。比如在音频播放功能内,会同时介绍音频的并发策略、音量管理和输出设备等在操作系统中的处理方式,帮助开发者能够开发出功能覆盖更全面的应用。

仅针对音频播放或录制本身,Audio Kit提供相关能力

在开发音频功能之前,尤其是要实现处理音频数据的功能前,建议开发者先了解声学相关的知识,帮助理解操作系统提供的API是如何控制音频系统,从而开发出更易用、体验更好的音视频类应用。建议了解的相关概念包括但不限于:

  • 音频量化的过程:采样 > 量化 > 编码

  • 音频量化过程的相关概念:模拟信号和数字信号、采样率、声道、采样格式、位宽、码率、常见编码格式(如AAC、MP3、PCM、WMA等)、常见封装格式(如WAV、MPA、FLAC、AAC、OGG等)

1.3 音频流介绍

音频流,是指音频系统中一个具备音频格式和音频使用场景信息的独立音频数据处理单元。可以表示播放,也可以表示录制,并且具备独立音量调节和音频设备路由切换能力。

音频流基础信息通过AudioStreamInfo表示,包含采样、声道、位宽、编码信息,是创建音频播放或录制流的必要参数,描述了音频数据的基本属性。在配置时开发者需要保证基础信息与传输的音频数据相匹配,音频系统才能正确处理数据。

1.3.1 音频流使用场景信息

除了基本属性,音频流还需要具备使用场景信息。基础信息只能对音频数据进行描述,但在实际的使用过程中,不同的音频流,在音量大小、设备路由、并发策略上是有区别的。系统就是通过音频流所附带的使用场景信息,为不同的音频流制定合适的处理策略,以达到更好的音频用户体验。

  • 播放场景

    音频播放场景的信息,通过StreamUsage进行描述。

    StreamUsage指音频流本身的用途类型,包括媒体、语音通信、语音播报、通知、铃声等。

  • 录制场景

    音频流录制场景的信息,通过SourceType进行描述。

    SourceType指音频流中录音源的类型,包括麦克风音频源、语音识别音频源、语音通话音频源等。

1.4 支持的音频格式

audio模块下的接口支持PCM编码,包括AudioRenderer、AudioCapturer、TonePlayer、OpenSL ES等。

音频格式说明

  • 支持的常用的音频采样率(Hz):8000、11025、12000、16000、22050、24000、32000、44100、48000、64000、8820012+、96000,17640012+、19200012+具体参考枚举AudioSamplingRate

    不同设备支持的采样率规格会存在差异。

  • 支持单声道、双声道,具体参考AudioChannel

  • 支持的采样格式:U8(无符号8位整数)、S16LE(带符号的16位整数,小尾数)、S24LE(带符号的24位整数,小尾数)、S32LE(带符号的32位整数,小尾数)、F32LE(带符号的32位浮点数,小尾数),具体参考AudioSampleFormat

    由于系统限制,S24LE、S32LE、F32LE仅部分设备支持,请根据实际情况使用。

    小尾数指的是小端模式,即数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。这种存储模式将地址的高低和数据的位权有效结合起来,高地址部分权值高,低地址部分权值低。

 2 使用合适的音频流类型

音频流类型是定义频数据播放和录制方式的关键属性。对于播放流,其类型由StreamUsage确定;对于录制流,则由SourceType决定。音频流类型对音量控制、音频焦点管理以及输入/输出设备的选择具有决定性影响。

2.1 常用的音频流类型及其适用场景

2.1.1 播放音频流类型

下表中列举常用的播放音频流类型,由StreamUsage定义。

音频流使用类型(StreamUsage)适用场景
STREAM_USAGE_MUSIC适用于播放音乐,同样适用于其他媒体场景,如使用SoundPool播放简短音效等。
STREAM_USAGE_MOVIE适用于播放短视频、电影、电视剧等各类视频内容。
STREAM_USAGE_AUDIOBOOK适用于播放有声读物、新闻、播客等。
STREAM_USAGE_GAME适用于游戏内配乐、配音,后台音乐不会被打断;游戏内语音,建议使用STREAM_USAGE_VOICE_COMMUNICATION。
STREAM_USAGE_NAVIGATION适用于导航场景的语音播报功能。
STREAM_USAGE_VOICE_MESSAGE适用于播放语音短消息。
STREAM_USAGE_VOICE_COMMUNICATION适用于VoIP语音通话。
STREAM_USAGE_ALARM适用于播放闹铃。
STREAM_USAGE_RINGTONE适用于VoIP来电响铃等。
STREAM_USAGE_NOTIFICATION适用于播放通知音、提示音。

2.1.2 录制音频流类型

下表中列举常用的录制音频流类型,由SourceType定义。

音频流使用类型(StreamUsage)适用场景
SOURCE_TYPE_MIC适用于普通录音。
SOURCE_TYPE_VOICE_COMMUNICATION适用于VoIP语音通话。
SOURCE_TYPE_VOICE_MESSAGE适用于录制语音短消息。

2.2 流类型对音频业务的影响

不同的流类型会影响用户在控制音量时的体验,以及系统在调整音频焦点和选择输入/输出设备时的表现。

2.2.1 音量控制

播放流类型(StreamUsage)决定了音频流所属的音量类型(AudioVolumeType),各类音量类型(如媒体、铃声、闹钟、通话等)拥有独立的音量值,在用户界面上可独立调节,相互之间不会影响。

常见的播放流类型与音量类型的对应关系为:

音频流使用类型(StreamUsage)音量类型(AudioVolumeType)
MUSIC、MOVIE、AUDIOBOOK、GAME媒体音量(MEDIA)
RINGTONE、NOTIFICATION铃声音量(RINGTONE)
VOICE_COMMUNICATION通话音量(VOICE_CALL)
ALARM闹钟音量(ALARM)

2.2.2 音频焦点调整

音频流类型在音频焦点管理中扮演着关键角色,不同类型的音频流具有不同的默认优先级和处理方式。

当应用启动音频播放或录制时,系统会根据音频流类型自动申请焦点,这可能会中断其他音频或降低其音量。音频焦点的具体介绍可参考音频焦点和音频会话介绍

此处仅说明常见的音频流类型影响音频焦点的表现

  • 启动导航(Navigation)时,正在播放的音乐(Music)音量会自动调低,待导航(Navigation)结束后,音乐(Music)音量将自动恢复。

  • 开始播放视频(Movie)时,将会停止正在播放的音乐(Music);当视频(Movie)播放结束后,音乐(Music)播放不会自动恢复,对应的应用也不会收到任何恢复通知。

  • 开始语音通话(VoiceCommunication)时,将会暂停正在播放的音乐(Music);当语音通话(VoiceCommunication)结束后,播放音乐(Music)的应用将收到恢复播放的通知。

  • 音乐(Music)和游戏音频(Game)可以混音播放,两者互不影响。

  • 开始录制语音短消息(VoiceMessage)时,会自动暂停正在播放的音乐(Music);当语音短消息(VoiceMessage)录制结束后,播放音乐(Music)的应用将收到恢复播放的通知。

2.2.3 输入/输出设备选择

对于不同类型的音频流,系统会为其选定相应的输入/输出设备。

此处仅说明常见的音频流类型对应的输入/输出设备。

  • 音乐(Music)类型音频流的默认输出设备为扬声器。

  • 语音通话(VoiceCommunication)类型音频流的默认输入设备为麦克风,默认输出设备为听筒。

  • 闹铃(Alarm)类型音频流的默认输出设备为扬声器‌。若先连接蓝牙耳机,再开始播放Alarm音频,则扬声器和蓝牙耳机会同时播放。

若默认的输入/输出设备不符合使用诉求,应用也可以调用相关接口主动修改。应用使用AudioRenderer开发音频播放功能时,可以调用setDefaultOutputDevice接口,设置默认发声设备。

2.3 设置音频流类型

应用可采用多种方法实现音频播放或录音功能,因此,设置音频流类型的方式也各不相同。

常见的设置播放音频流类型的方法有

常见的设置录制音频流类型的方法有

3 音频焦点和音频会话介绍

3.1 音频焦点

系统预设了默认的音频焦点策略,根据音频流的类型及启动的先后顺序,对所有播放和录制音频流进行统一管理。

在启动播放或录制功能前,应用需要先申请音频焦点;而在播放或录制结束后,应适时释放音频焦点。在播放或录制的过程中,可能会因其他音频流的介入而失去焦点,此时,应用需依据焦点变化采取相应措施

对于应用而言,为了确保为用户提供优质的音频焦点体验,应当注意以下几点:

3.1.1 申请音频焦点

当应用开始播放或录制音频时,系统将自动为相应的音频流申请音频焦点。

例如,应用使用AudioRenderer开发音频播放功能,当调用AudioRenderer的start时,系统会自动为应用请求音频焦点。

若音频焦点请求成功,音频流将正常启动;反之,若音频焦点请求被拒绝,音频流将无法开始播放或录制。

建议应用主动监听音频焦点事件,一旦音频焦点请求被拒绝,应用将接收到音频焦点事件(InterruptEvent)

特殊场景:

  1. 短音播放:若应用使用SoundPool开发音频播放功能,且StreamUsage指定为Music、Movie、AudioBook等类型,播放短音,则其申请焦点时默认为并发模式,不会影响其他音频。

  2. 静音播放:若应用以静音状态开始播放音频(或视频),并且希望静音阶段不影响其他音频,当后续解除静音的时候,再以正常策略申请音频焦点,则可以调用静音并发播放模式的相关接口。具体可参考:

3.1.2 释放音频焦点

当应用结束播放或录制音频时,系统会自动为相应的音频流释放音频焦点。

例如,应用使用AudioRenderer开发音频播放功能,当调用AudioRenderer的pausestoprelease等时,系统会为其释放音频焦点。

当音频流释放音频焦点时,若存在受其影响的其他音频流(如音量被调低或被暂停的流),将触发恢复操作。

若应用不希望在音频流停止时立即释放音频焦点,可使用音频会话(AudioSession)的相关接口,实现音频焦点释放的延迟效果。

3.1.3 音频焦点策略

当音频流申请或释放音频焦点时,系统依据音频焦点策略,对所有音频流(包括播放和录制)实施焦点管理,决定哪些音频流可正常运行,哪些需被打断或执行其他操作。

系统预设的默认音频焦点策略,主要依据音频流类型(即播放流的StreamUsage和录制流的SourceType)及音频流启动的顺序进行决策。

为防止焦点变化不符合预期,应用在启动播放或录制前,应根据音频流的用途,准确设置StreamUsage或SourceType。关于各类型的详细说明,请参考使用合适的音频流类型

常见的音频焦点场景示例如下:

  • 开始播放Movie音频流时,将导致正在播放的Music音频流暂停,但Movie播放停止后,Music不会收到恢复播放的通知。
  • 开始Navigation音频流时,会自动降低正在播放的Music音频流音量,Navigation停止后,Music音量将恢复至原样。
  • Music音频流与Game音频流可并发混音播放,相互之间不会影响音量或播放状态。
  • VoiceCommunication开始播放时,将暂停正在播放的Music音频流,VoiceCommunication停止后,Music将收到恢复播放的通知。
  • 开始录制VoiceMessage时,Music音频流会被暂停,VoiceMessage录制停止后,Music将收到恢复播放的通知。

若默认的音频焦点策略无法满足特定场景的需求,应用程序可利用音频会话(AudioSession),调整本应用音频流所采用的音频焦点策略。

3.1.4 焦点模式

针对同一应用创建的多个音频流,应用可通过设置焦点模式(InterruptMode),选择由应用自主管控,或由系统统一管理。

系统预设了两种焦点模式

  • 共享焦点模式(SHARE_MODE):同一应用创建的多个音频流共享一个音频焦点。这些音频流之间的并发规则由应用自行决定,音频焦点策略不会介入。仅当其他应用创建的音频流与该应用的音频流同时播放时,才会触发音频焦点策略的管理。

  • 独立焦点模式(INDEPENDENT_MODE):应用创建的每个音频流均独立拥有一个音频焦点,多个音频流同时播放时,将触发音频焦点策略的管理。

应用可根据需求选择合适的焦点模式。在创建音频流时,系统默认采用共享焦点模式(SHARE_MODE),应用可主动设置所需模式。

设置焦点模式的方法

3.1.5 处理音频焦点变化

在应用播放或录制音频的过程中,若有其他音频流申请焦点,系统会根据焦点策略进行焦点处理。若判定本音频流的焦点有变化,需要执行暂停、继续、降低音量、恢复音量等操作,则系统会自动执行一些必要的操作,并通过音频焦点事件(InterruptEvent)通知应用。

因此,为了维持应用和系统的状态一致性,保证良好的用户体验,推荐应用监听音频焦点事件,并在焦点发生变化时,根据InterruptEvent做出必要的响应。

使用不同方式开发时,如何监听音频焦点事件:

应用在收到音频焦点事件(InterruptEvent)时,需要根据其中信息,做出相应的处理,以保持应用与系统状态一致,带给用户良好的音频体验。

在音频焦点事件中,应用应重点关注两个信息:打断类型(InterruptForceType)和打断提示(InterruptHint)。

  • 打断类型(InterruptForceType):

    InterruptForceType参数提示应用该焦点变化是否已由系统强制操作:

    • 强制打断类型(INTERRUPT_FORCE):由系统进行操作,强制执行。应用需要做一些必要的处理,例如更新状态、更新界面显示等。

    • 共享打断类型(INTERRUPT_SHARE):由应用进行操作,应用可以选择响应或忽略,系统不会干涉。

    系统默认优先采用强制打断类型(INTERRUPT_FORCE),应用无法更改。

    注意

    对于一些系统无法强制执行的操作(例如INTERRUPT_HINT_RESUME),会采用共享打断类型(INTERRUPT_SHARE)。

  • 打断提示(InterruptHint):

    InterruptHint参数用于提示应用音频流的状态:

    • 继续(INTERRUPT_HINT_RESUME):音频流可恢复播放或录制,仅会接收到PAUSE(暂停提示)之后收到。

      此操作无法由系统强制执行,其对应的InterruptForceType一定为INTERRUPT_SHARE类型。

    • 暂停(INTERRUPT_HINT_PAUSE):音频暂停,暂时失去音频焦点。后续待焦点可用时,会再收到INTERRUPT_HINT_RESUME。

    • 停止(INTERRUPT_HINT_STOP):音频停止,彻底失去音频焦点。

    • 降低音量(INTERRUPT_HINT_DUCK):音频降低音量播放,而不会停止。默认降低至正常音量的20%。

    • 恢复音量(INTERRUPT_HINT_UNDUCK):音频恢复正常音量。

处理音频焦点示例:

为了带给用户更好的音频体验,针对不同的音频焦点事件内容,应用需要做出相应的处理操作。此处以使用AudioRenderer开发音频播放功能为例,展示推荐应用采取的处理方法,提供伪代码供开发者参考。

若使用其他接口开发音频播放或音频录制功能,处理方法类似,具体的代码实现,开发者可结合实际情况编写,处理方法也可自行调整。

import { audio } from '@kit.AudioKit';  // 导入audio模块。
import { BusinessError } from '@kit.BasicServicesKit'; // 导入BusinessError。

let isPlay: boolean; // 是否正在播放,实际开发中,对应与音频播放状态相关的模块。
let isDucked: boolean; //是否降低音量,实际开发中,对应与音频音量相关的模块。
let started: boolean; // 标识符,记录“开始播放(start)”操作是否成功。

async function onAudioInterrupt(): Promise<void> {
  // 此处以使用AudioRenderer开发音频播放功能举例,变量audioRenderer即为播放时创建的AudioRenderer实例。
  audioRenderer.on('audioInterrupt', async(interruptEvent: audio.InterruptEvent) => {
    // 在发生音频焦点变化时,audioRenderer收到interruptEvent回调,此处根据其内容做相应处理。
    // 1. 可选:读取interruptEvent.forceType的类型,判断系统是否已强制执行相应操作。
    // 注:默认焦点策略下,INTERRUPT_HINT_RESUME为INTERRUPT_SHARE类型,其余hintType均为INTERRUPT_FORCE类型。因此对forceType可不做判断。
    // 2. 必选:读取interruptEvent.hintType的类型,做出相应的处理。
    if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
      // 强制打断类型(INTERRUPT_FORCE):音频相关处理已由系统执行,应用需更新自身状态,做相应调整。
       switch (interruptEvent.hintType) {
        case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
          // 此分支表示系统已将音频流暂停(临时失去焦点),为保持状态一致,应用需切换至音频暂停状态。
          // 临时失去焦点:待其他音频流释放音频焦点后,本音频流会收到resume对应的音频焦点事件,到时可自行继续播放。
          isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作。
          break;
        case audio.InterruptHint.INTERRUPT_HINT_STOP:
          // 此分支表示系统已将音频流停止(永久失去焦点),为保持状态一致,应用需切换至音频暂停状态。
          // 永久失去焦点:后续不会再收到任何音频焦点事件,若想恢复播放,需要用户主动触发。
          isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作。
          break;
        case audio.InterruptHint.INTERRUPT_HINT_DUCK:
          // 此分支表示系统已将音频音量降低(默认降到正常音量的20%)。
          isDucked = true; // 此句为简化处理,代表应用切换至降低音量播放状态的若干操作。
          break;
        case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
          // 此分支表示系统已将音频音量恢复正常。
          isDucked = false; // 此句为简化处理,代表应用切换至正常音量播放状态的若干操作。
          break;
        default:
          break;
      }
    } else if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_SHARE) {
      // 共享打断类型(INTERRUPT_SHARE):应用可自主选择执行相关操作或忽略音频焦点事件。
      switch (interruptEvent.hintType) {
        case audio.InterruptHint.INTERRUPT_HINT_RESUME:
          // 此分支表示临时失去焦点后被暂停的音频流此时可以继续播放,建议应用继续播放,切换至音频播放状态。
          // 若应用此时不想继续播放,可以忽略此音频焦点事件,不进行处理即可。
          // 继续播放,此处主动执行start(),以标识符变量started记录start()的执行结果。
          await audioRenderer.start().then(() => {
            started = true; // start()执行成功。
          }).catch((err: BusinessError) => {
            started = false; // start()执行失败。
          });
          // 若start()执行成功,则切换至音频播放状态。
          if (started) {
            isPlay = true; // 此句为简化处理,代表应用切换至音频播放状态的若干操作。
          } else {
            // 音频继续播放的操作执行失败。
          }
          break;
        default:
          break;
      }
   }
  });
}

3.2 使用AudioSession管理音频焦点

应用可以使用音频会话AudioSession)的相关接口,自定义本应用音频流焦点策略。在系统进行焦点管理时,只要条件允许,本应用的所有音频流将优先遵循这一策略。

使用音频会话(AudioSession)相关接口,主要可以实现以下功能:

  • 应用激活音频会话(AudioSession)并指定音频会话策略(AudioSessionStrategy)后,本应用的所有音频流在参与焦点管理时,会优先使用该策略。

    典型场景:应用播放短视频时,会打断后台音乐,应用希望自身的音频流停止后,后台的音乐可以自动恢复。

  • 音频会话(AudioSession)处于激活状态下,本应用的音频流全部停止时,不会立刻释放音频焦点,系统会保持音频焦点,直到音频会话停用时再释放音频焦点,或是直到该应用有新的音频流申请焦点。

    典型场景:应用连续播放多个音频时,在多个音频衔接的间隙,不希望后台被影响的其他音频自动恢复,希望整个播放过程保持音频焦点的连贯性。

3.2.1 音频会话(AudioSession)使用流程

音频会话(AudioSession)使用流程示意图:

  1. 音频业务开始之前,需要先获取AudioSessionManager实例。

    具体方法可参考获取音频会话管理器(ArkTS)获取音频会话管理器(C/C++)

  2. 在音频业务开始前,还需要激活当前应用的AudioSession,并根据实际需要指定AudioSessionStrategy

    具体方法可参考激活音频会话(ArkTS)激活音频会话(C/C++)

    说明

    • 激活AudioSession时传入的策略会被保存,在此之后,本应用的音频流参与焦点管理时(比如申请焦点、释放焦点等),会优先使用本策略。
    • 允许重复激活AudioSession,重复激活时系统保存的策略会被更新,焦点管理时会使用最新的策略。

    AudioSession处于激活状态时,有以下特性:

    • 若应用一直没有音频流处于运行状态,则系统会在1分钟后自动停用AudioSession。
    • 应用的音频流停止时,若该应用无其他音频流运行,则不会立刻释放焦点,而是会保持焦点(进入静默等待状态),直到AudioSession停用或该应用有新的音频流申请焦点。
  3. 应用正常开始播放、录制等音频业务。系统会在音频流开始时,申请音频焦点。本应用的所有音频流在参与焦点处理时,会优先使用AudioSession指定的策略。

  4. 音频业务结束之后,停用AudioSession。系统会在音频流停止且AudioSession停用时,释放音频焦点。

    应用需要在音频业务结束之后,主动停用AudioSession。

    应用在停用AudioSession时,如果该应用的所有音频流已全部停止(即处于保持焦点的静默等待状态),则会立刻释放音频焦点;如果该应用仍有音频流在运行,则它的音频流仍然会持有焦点,直到音频流停止时才释放。

    具体方法可参考停用音频会话(ArkTS)停用音频会话(C/C++)

在使用AudioSession的过程中,推荐应用监听AudioSession停用事件,当AudioSession被停用时,应用可以及时收到AudioSession停用事件

3.2.2 音频会话策略(AudioSessionStrategy)

应用在激活AudioSession时,需指定音频会话策略(AudioSessionStrategy),其中包含音频并发模式(AudioConcurrencyMode)参数,用于声明不同的音频并发策略。

系统预设了以下四种音频并发模式

  • 默认模式(CONCURRENCY_DEFAULT):即系统默认的音频焦点策略

  • 并发模式(CONCURRENCY_MIX_WITH_OTHERS):和其它音频流并发。

  • 降低音量模式(CONCURRENCY_DUCK_OTHERS):和其他音频流并发,并且降低其他音频流的音量。

  • 暂停模式(CONCURRENCY_PAUSE_OTHERS):暂停其他音频流,待释放焦点后通知其他音频流恢复。

注意

  • 当应用通过AudioSession使用上述各种模式时,系统将尽量满足其焦点策略,但在所有场景下可能无法保证完全满足。
  • 如使用CONCURRENCY_PAUSE_OTHERS模式时,Movie流申请音频焦点,如果Music流正在播放,则Music流会被暂停。但是如果VoiceCommunication流正在播放,则VoiceCommunication流不会被暂停。

3.2.3 监听AudioSession停用事件

应用在使用AudioSession的过程中,推荐应用监听音频会话停用事件(AudioSessionDeactivatedEvent)。当AudioSession被停用(非主动停用)时,应用会收到此事件通知。应用可根据自身业务需求,做相应的处理,例如释放相应资源、重新激活AudioSession等。

音频会话停用事件(AudioSessionDeactivatedEvent)包含参数音频会话停用原因(AudioSessionDeactivatedReason),该参数表示AudioSession被停用的原因,主要有两种:

  • 应用焦点被抢占(DEACTIVATED_LOWER_PRIORITY):该应用所有的音频流全部被其他音频流打断,丢失焦点,AudioSession被同时停用。

  • 超时(DEACTIVATED_TIMEOUT):若AudioSession处于激活状态,但该应用没有音频流在运行状态,则AudioSession会在1分钟之后被超时停用。

注意

当AudioSession因超时而停用时,被其压低音量(Duck)的音频会触发恢复音量(Unduck)操作,被其暂停(Pause)的音频流会触发停止(Stop)操作。

3.3 典型场景

以下列举一些典型的焦点适配场景。

先播放的音频类型推荐流类型后播放的音频类型推荐流类型推荐体验适配方案
音乐STREAM_USAGE_MUSIC音乐STREAM_USAGE_MUSIC后播音乐正常播放,先播音乐停止播放,UI变成停止播放状态。先播音乐应用注册焦点事件监听,接收到INTERRUPT_HINT_STOP事件时,停止音乐播放,并更新UI界面。
音乐STREAM_USAGE_MUSIC导航STREAM_USAGE_NAVIGATION导航正常播放,音乐降低音量播放。当导航结束后,音乐恢复正常音量。音乐应用注册焦点事件监听,接收到INTERRUPT_HINT_DUCK和INTERRUPT_HINT_UNDUCK事件时,可以选择更新UI界面。
视频STREAM_USAGE_MOVIE闹铃STREAM_USAGE_ALARM闹铃响起后,视频暂停播放;闹钟结束后,视频继续播放。视频应用注册焦点事件监听,接收到INTERRUPT_HINT_PAUSE事件时,直接暂停视频播放,并更新UI界面。当闹铃结束后,视频应用接收到INTERRUPT_HINT_RESUME事件,重新启动播放。
音乐STREAM_USAGE_MUSIC来电铃声STREAM_USAGE_RINGTONE开始响铃,音乐暂停播放;不接通或者接通再挂断后,音乐恢复播放。音乐应用注册焦点事件监听,接收到INTERRUPT_HINT_PAUSE事件时,直接暂停音乐播放,并更新UI界面。当电话结束后,音频应用接收到INTERRUPT_HINT_RESUME事件,重新启动播放。
音乐STREAM_USAGE_MUSICVoIP通话STREAM_USAGE_VOICE_COMMUNICATION通话接通时,音乐暂停播放;通话挂断后,音乐恢复播放。音乐应用注册焦点事件监听,接收到INTERRUPT_HINT_PAUSE事件时,直接暂停音乐播放,并更新UI界面。当通话结束后,音乐应用接收到INTERRUPT_HINT_RESUME事件,重新启动播放。

 4 使用AudioSession管理应用音频焦点(ArkTS)

应用可利用音频会话管理(AudioSessionManager)提供的接口,通过AudioSession主动管理应用内音频流的焦点,自定义本应用音频流的焦点策略,调整本应用音频流释放音频焦点的时机,从而贴合应用特定的使用需求。

4.1 获取音频会话管理器

创建AudioSessionManager实例。在使用AudioSessionManager的API前,需要先通过getSessionManager创建实例。

import { audio } from '@kit.AudioKit';

let audioSessionManager: audio.AudioSessionManager = audioManager.getSessionManager();

4.2 激活音频会话

应用可以通过AudioSessionManager.activateAudioSession接口激活当前应用的音频会话。

应用在激活AudioSession时,需指定音频会话策略(AudioSessionStrategy)。策略中包含参数concurrencyMode,其类型为AudioConcurrencyMode,用于声明音频并发策略。

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

let strategy: audio.AudioSessionStrategy = {
  concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_MIX_WITH_OTHERS
};

audioSessionManager.activateAudioSession(strategy).then(() => {
  console.info('activateAudioSession SUCCESS');
}).catch((err: BusinessError) => {
  console.error(`ERROR: ${err}`);
});

4.3 查询音频会话是否已激活

应用可以通过isAudioSessionActivated接口检查当前应用的音频会话是否已激活。

let isActivated = audioSessionManager.isAudioSessionActivated();

4.4 停用音频会话

应用可以通过deactivateAudioSession接口停用当前应用的音频会话。

import { BusinessError } from '@kit.BasicServicesKit';

audioSessionManager.deactivateAudioSession().then(() => {
  console.info('deactivateAudioSession SUCCESS');
}).catch((err: BusinessError) => {
  console.error(`ERROR: ${err}`);
});

4.5 监听音频会话停用事件

应用可以通过on('audioSessionDeactivated')接口监听音频会话停用事件(AudioSessionDeactivatedEvent)

当AudioSession被停用(非主动停用)时,应用会收到音频会话停用事件(AudioSessionDeactivatedEvent),其中包含音频会话停用原因(AudioSessionDeactivatedReason)

在收到AudioSessionDeactivatedEvent时,应用可根据自身业务需求,做相应的处理,例如释放相应资源、重新激活AudioSession等。

import { audio } from '@kit.AudioKit';

audioSessionManager.on('audioSessionDeactivated', (audioSessionDeactivatedEvent: audio.AudioSessionDeactivatedEvent) => {
  console.info(`reason of audioSessionDeactivated: ${audioSessionDeactivatedEvent.reason} `);
});

4.6 取消监听音频会话停用事件

应用可以通过off('audioSessionDeactivated')接口取消监听音频会话停用事件。

audioSessionManager.off('audioSessionDeactivated');

5 音频播放

5.1 音频播放开发概述

5.1.1 如何选择音频播放开发方式

系统提供了多样化的API,来帮助完成音频播放的开发,不同的API适用于不同音频数据格式、音频资源来源、音频使用场景,甚至是不同开发语言。因此,选择合适的音频播放API,有助于降低开发工作量,实现更佳的音频播放效果。

  • AudioRenderer:用于音频输出的ArkTS/JS API,仅支持PCM格式,需要应用持续写入音频数据进行工作。应用可以在输入前添加数据预处理,如设定音频文件的采样率、位宽等,要求开发者具备音频处理的基础知识,适用于更专业、更多样化的媒体播放应用开发。

  • AudioHaptic:用于音振协同播放的ArkTS/JS API,适用于需要在播放音频时同步发起振动的场景,如来电铃声随振、键盘按键反馈、消息通知反馈等。

  • OpenSL ES:一套跨平台标准化的音频Native API,同样提供音频输出能力,仅支持PCM格式,适用于从其他嵌入式平台移植,或依赖在Native层实现音频输出功能的播放应用使用。

  • OHAudio:用于音频输出的Native API,此API在设计上实现归一,同时支持普通音频通路和低时延通路。仅支持PCM格式,适用于依赖Native层实现音频输出功能的场景。

除上述方式外,也可以通过Media Kit中的AVPlayer和SoundPool实现音频播放。

  • AVPlayer:用于音频播放的ArkTS/JS API,集成了流媒体和本地资源解析、媒体资源解封装、音频解码和音频输出功能。可用于直接播放mp3、m4a等格式的音频文件,不支持直接播放PCM格式文件。

  • SoundPool:低时延的短音播放ArkTS/JS API,适用于播放急促简短的音效,如相机快门音效、按键音效、游戏射击音效等。

5.1.2 后台播放或熄屏播放开发须知

应用如果要实现后台播放或熄屏播放,需要同时满足:

  1. 使用媒体会话(AVSession)功能注册到系统内统一管理。具体参考AVSession Kit开发指导

    注意:若应用没有注册AVSession,且应用进入后台,则系统会对其音频行为做强制管控,主要包括:

    • 应用在进入后台时,其正在播放的音频流将被强制停止或被强制静音。
    • 应用在后台状态启动音频流时,该音频流会被禁止启动或被强制静音。
  2. 申请长时任务避免进入挂起(Suspend)状态。具体参考长时任务开发指导

当应用进入后台,播放被中断,如果被媒体会话管控,将打印日志“pause id”;如果没有该日志,则说明被长时任务管控。

5.2 使用AudioRenderer开发音频播放功能

AudioRenderer是音频渲染器,用于播放PCM(Pulse Code Modulation)音频数据,相比AVPlayer而言,可以在输入前添加数据预处理,更适合有音频开发经验的开发者,以实现更灵活的播放功能。

5.2.1 开发指导

使用AudioRenderer播放音频涉及到AudioRenderer实例的创建、音频渲染参数的配置、渲染的开始与停止、资源的释放等。本开发指导将以一次渲染音频数据的过程为例,向开发者讲解如何使用AudioRenderer进行音频渲染,建议搭配AudioRenderer的API说明阅读。

下图展示了AudioRenderer的状态变化,在创建实例后,调用对应的方法可以进入指定的状态实现对应的行为。需要注意的是在确定的状态执行不合适的方法可能导致AudioRenderer发生错误,建议开发者在调用状态转换的方法前进行状态检查,避免程序运行产生预期以外的结果。

为保证UI线程不被阻塞,大部分AudioRenderer调用都是异步的。对于每个API均提供了callback函数和Promise函数,以下示例均采用callback函数。

图1 AudioRenderer状态变化示意图

在进行应用开发的过程中,建议开发者通过on('stateChange')方法订阅AudioRenderer的状态变更。因为针对AudioRenderer的某些操作,仅在音频播放器在固定状态时才能执行。如果应用在音频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。

  • prepared状态: 通过调用createAudioRenderer()方法进入到该状态。

  • running状态: 正在进行音频数据播放,可以在prepared状态通过调用start()方法进入此状态,也可以在paused状态和stopped状态通过调用start()方法进入此状态。

  • paused状态: 在running状态可以通过调用pause()方法暂停音频数据的播放并进入paused状态,暂停播放之后可以通过调用start()方法继续音频数据播放。

  • stopped状态: 在paused/running状态可以通过stop()方法停止音频数据的播放。

  • released状态: 在prepared、paused、stopped等状态,用户均可通过release()方法释放掉所有占用的硬件和软件资源,并且不会再进入到其他的任何一种状态了。

1、开发步骤及注意事项
  1. 配置音频渲染参数并创建AudioRenderer实例,音频渲染参数的详细信息可以查看AudioRendererOptions

import { audio } from '@kit.AudioKit';

let audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
  channels: audio.AudioChannel.CHANNEL_2, // 通道。
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
};

let audioRendererInfo: audio.AudioRendererInfo = {
  usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。
  rendererFlags: 0 // 音频渲染器标志。
};

let audioRendererOptions: audio.AudioRendererOptions = {
  streamInfo: audioStreamInfo,
  rendererInfo: audioRendererInfo
};

audio.createAudioRenderer(audioRendererOptions, (err, data) => {
  if (err) {
    console.error(`Invoke createAudioRenderer failed, code is ${err.code}, message is ${err.message}`);
    return;
  } else {
    console.info('Invoke createAudioRenderer succeeded.');
    let audioRenderer = data;
  }
});

2. 调用on('writeData')方法,订阅监听音频数据写入回调,推荐使用API version 12支持返回回调结果的方式。

  • API version 12开始该方法支持返回回调结果,系统可以根据开发者返回的值来决定此次回调中的数据是否播放。

注意

  • 能填满回调所需长度数据的情况下,返回audio.AudioDataCallbackResult.VALID,系统会取用完整长度的数据缓冲进行播放。请不要在未填满数据的情况下返回audio.AudioDataCallbackResult.VALID,否则会导致杂音、卡顿等现象。

  • 在无法填满回调所需长度数据的情况下,建议开发者返回audio.AudioDataCallbackResult.INVALID,系统不会处理该段音频数据,然后会再次向应用请求数据,确认数据填满后返回audio.AudioDataCallbackResult.VALID。

  • 回调函数结束后,音频服务会把缓冲中数据放入队列里等待播放,因此请勿在回调外再次更改缓冲中的数据。对于最后一帧,如果数据不够填满缓冲长度,开发者需要使用剩余数据拼接空数据的方式,将缓冲填满,避免缓冲内的历史脏数据对播放效果产生不良的影响。

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';

class Options {
  offset?: number;
  length?: number;
}

let bufferSize: number = 0;
let path = getContext().cacheDir;
// 确保该沙箱路径下存在该资源。
let filePath = path + '/StarWars10s-2C-48000-4SW.wav';
let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_ONLY);

let writeDataCallback = (buffer: ArrayBuffer) => {
  let options: Options = {
    offset: bufferSize,
    length: buffer.byteLength
  };

  try {
    fs.readSync(file.fd, buffer, options);
    bufferSize += buffer.byteLength;
    // 系统会判定buffer有效,正常播放。
    return audio.AudioDataCallbackResult.VALID;
  } catch (error) {
    console.error('Error reading file:', error);
    // 系统会判定buffer无效,不播放。
    return audio.AudioDataCallbackResult.INVALID;
  }
};

audioRenderer.on('writeData', writeDataCallback);

3. 调用start()方法进入running状态,开始渲染音频。

import { BusinessError } from '@kit.BasicServicesKit';

audioRenderer.start((err: BusinessError) => {
  if (err) {
    console.error(`Renderer start failed, code is ${err.code}, message is ${err.message}`);
  } else {
    console.info('Renderer start success.');
  }
});

4. 调用stop()方法停止渲染。

import { BusinessError } from '@kit.BasicServicesKit';

audioRenderer.stop((err: BusinessError) => {
  if (err) {
    console.error(`Renderer stop failed, code is ${err.code}, message is ${err.message}`);
  } else {
    console.info('Renderer stopped.');
  }
});

5. 调用release()方法销毁实例,释放资源。

import { BusinessError } from '@kit.BasicServicesKit';

audioRenderer.release((err: BusinessError) => {
  if (err) {
    console.error(`Renderer release failed, code is ${err.code}, message is ${err.message}`);
  } else {
    console.info('Renderer released.');
  } 
});
2、选择正确的StreamUsage

创建播放器时候,开发者需要根据应用场景指定播放器的StreamUsage,选择正确的StreamUsage可以避免用户遇到不符合预期的行为。

在音频API文档StreamUsage介绍中,列举了每一种类型推荐的应用场景。例如音乐场景推荐使用STREAM_USAGE_MUSIC,电影或者视频场景推荐使用STREAM_USAGE_MOVIE,游戏场景推荐使用STREAM_USAGE_GAME,等等。

如果开发者配置了不正确的StreamUsage,可能带来一些不符合预期的行为。例如以下场景。

  • 游戏场景错误使用STREAM_USAGE_MUSIC类型,游戏应用将无法和其他音乐应用并发播放,而游戏场景通常可以与其他音乐应用并发播放。
  • 导航场景错误使用STREAM_USAGE_MUSIC类型,导航应用播报时候会导致正在播放的音乐停止播放,而导航场景我们通常期望正在播放的音乐仅仅降低音量播放。

5.3 响应音频流输出设备变更

5.3.1 音频流输出设备信息

outputDeviceChangeWithInfo返回的音频流设备变更信息中,包含当前音频流输出设备信息,以数组形式发送,一般该列表仅包含一个设备信息,具体可参考AudioDeviceDescriptors(设备信息列表)。

5.3.2 音频流输出设备变更原因

说明

当发生下述四种情况(AudioStreamDeviceChangeReason)时,系统将向应用发送设备变更回调。

  • REASON_NEW_DEVICE_AVAILABLE:新设备可用。

    触发场景:

    普通蓝牙设备(耳机、眼镜、音箱、车机等)连接、支持佩戴检测的蓝牙设备(耳机、眼镜等)佩戴、有线设备(3.5mm耳机、Type-C耳机、USB耳机、USB音箱等)插入、分布式设备上线等。

  • REASON_OLD_DEVICE_UNAVAILABLE:旧设备不可用。

    当报告此原因时,应用程序应考虑暂停音频播放。

    触发场景:

    普通蓝牙设备(耳机、眼镜、音箱、车机等)断开、支持佩戴检测的蓝牙耳机双耳摘下、支持佩戴检测的蓝牙眼镜摘下、有线设备(3.5mm耳机、Type-C耳机、USB耳机、音箱等)拔出、分布式设备下线等。

    针对此场景,常用业务场景的处理建议如下:

    • 游戏场景:不暂停
    • 听书场景:暂停
    • 音乐场景:暂停
    • 视频场景:暂停
  • REASON_OVERRODE:用户强制选择设备。

  • 触发场景:

    用户从界面选择切换音频流输出设备、从外设选择接听蜂窝或VoIP来电。

  • REASON_UNKNOWN:未知原因。

5.3.3 参考示例

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

let audioRenderer: audio.AudioRenderer | undefined = undefined;
let audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
  channels: audio.AudioChannel.CHANNEL_2, // 通道。
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
};
let audioRendererInfo: audio.AudioRendererInfo = {
  usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。
  rendererFlags: 0 // 音频渲染器标志。
};
let audioRendererOptions: audio.AudioRendererOptions = {
  streamInfo: audioStreamInfo,
  rendererInfo: audioRendererInfo
};

// 创建AudioRenderer实例。
audio.createAudioRenderer(audioRendererOptions).then((data) => {
  audioRenderer = data;
  console.info('AudioFrameworkRenderLog: AudioRenderer Created : Success : Stream Type: SUCCESS');
}).catch((err: BusinessError) => {
  console.error(`AudioFrameworkRenderLog: AudioRenderer Created : ERROR : ${err}`);
});

if (audioRenderer) {
  // 订阅监听音频流输出设备变化及原因。
  (audioRenderer as audio.AudioRenderer).on('outputDeviceChangeWithInfo', async (deviceChangeInfo: audio.AudioStreamDeviceChangeInfo) => {
    switch (deviceChangeInfo.changeReason) {
      case audio.AudioStreamDeviceChangeReason.REASON_OLD_DEVICE_UNAVAILABLE:
        // 响应设备不可用事件,如果应用处于播放状态,应暂停播放,更新UX界面。
        // await audioRenderer.pause();
        break;
      case audio.AudioStreamDeviceChangeReason.REASON_NEW_DEVICE_AVAILABLE:
        // 应用根据业务情况响应设备可用事件。
        break;
      case audio.AudioStreamDeviceChangeReason.REASON_OVERRODE:
        // 应用根据业务情况响应设备强选事件。
        break;
      case audio.AudioStreamDeviceChangeReason.REASON_UNKNOWN:
        // 应用根据业务情况响应未知原因事件。
        break;
    }
  });
}

5.4 使用AudioHaptic开发音振协同播放功能

AudioHaptic11+提供音频与振动协同播放及管理的方法,适用于需要在播放音频时同步发起振动的场景,如来电铃声随振、键盘按键反馈、消息通知反馈等。

5.4.1 开发指导

使用AudioHaptic播放音频并同步开启振动,涉及到音频及振动资源的管理、音频时延模式及音频流使用类型的配置、音振播放器的创建及管理等。本开发指导将以一次音振协同播放的过程为例,向开发者讲解如何使用AudioHaptic进行音振协同播放,建议配合AudioHaptic的API说明阅读。

5.4.2 权限申请

如果应用创建的AudioHapticPlayer需要触发振动,则需要校验应用是否拥有该权限:ohos.permission.VIBRATE。

  1. 声明权限
  2. 向用户申请授权

5.4.3 开发步骤及注意事项

1. 获取音振管理器实例,并注册音频及振动资源,资源支持情况可以查看AudioHapticManager

import { audio, audioHaptic } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

let audioHapticManagerInstance: audioHaptic.AudioHapticManager = audioHaptic.getAudioHapticManager();

let audioUri = 'data/audioTest.wav'; // 需更改为目标音频资源的Uri。
let hapticUri = 'data/hapticTest.json'; // 需更改为目标振动资源的Uri。
let id = 0;

audioHapticManagerInstance.registerSource(audioUri, hapticUri).then((value: number) => {
  console.info(`Promise returned to indicate that the source id of the registerd source ${value}.`);
  id = value;
}).catch ((err: BusinessError) => {
  console.error(`Failed to register source ${err}`);
});

2. 设置音振播放器参数,各参数作用可以查看AudioHapticManager

let latencyMode: audioHaptic.AudioLatencyMode = audioHaptic.AudioLatencyMode.AUDIO_LATENCY_MODE_FAST;
audioHapticManagerInstance.setAudioLatencyMode(id, latencyMode);

let usage: audio.StreamUsage = audio.StreamUsage.STREAM_USAGE_NOTIFICATION;
audioHapticManagerInstance.setStreamUsage(id, usage);

3. 创建AudioHapticPlayer实例。

let options: audioHaptic.AudioHapticPlayerOptions = {muteAudio: false, muteHaptics: false};
let audioHapticPlayer: audioHaptic.AudioHapticPlayer | undefined = undefined;

audioHapticManagerInstance.createPlayer(id, options).then((value: audioHaptic.AudioHapticPlayer) => {
  console.info(`Promise returned to indicate that the audio haptic player instance.`);
  audioHapticPlayer = value;
}).catch ((err: BusinessError) => {
  console.error(`Failed to create player ${err}`);
});
console.info(`Create the audio haptic player successfully.`);

4. 调用start()方法,开启音频播放并同步开启振动。

audioHapticPlayer.start().then(() => {
  console.info(`Promise returned to indicate that start playing successfully.`);
}).catch ((err: BusinessError) => {
  console.error(`Failed to start playing. ${err}`);
});

5. 调用stop()方法,停止音频播放并同步停止振动。

audioHapticPlayer.stop().then(() => {
  console.info(`Promise returned to indicate that stop playing successfully.`);
}).catch ((err: BusinessError) => {
  console.error(`Failed to stop playing. ${err}`);
});

6. 释放AudioHapticPlayer实例。

audioHapticPlayer.release().then(() => {
  console.info(`Promise returned to indicate that release the audio haptic player successfully.`);
}).catch ((err: BusinessError) => {
  console.error(`Failed to release the audio haptic player. ${err}`);
});

7. 将已注册的音频及振动资源移除注册

audioHapticManagerInstance.unregisterSource(id).then(() => {
  console.info(`Promise returned to indicate that unregister source successfully`);
}).catch ((err: BusinessError) => {
  console.error(`Failed to unregistere source ${err}`);
});

5.5 播放音量管理

播放音量的管理主要包括对系统音量的管理和对音频流音量的管理。系统音量与音频流音量分别是指HarmonyOS系统的总音量和指定音频流的音量,其中音频流音量的大小受制于系统音量,管理两者的接口不同。

5.5.1 系统音量

管理系统音量的接口是AudioVolumeManager,在使用之前,需要使用getVolumeManager()获取AudioVolumeManager实例。

通过AudioVolumeManager只能获取音量信息及监听音量变化,不能主动调节系统音量。如果应用需要调节系统音量,可以使用音量面板调节系统音量

import { audio } from '@kit.AudioKit';

let audioManager = audio.getAudioManager();
let audioVolumeManager = audioManager.getVolumeManager();

5.5.2 监听系统音量变化

通过设置监听事件,可以监听系统音量的变化:

import { audio } from '@kit.AudioKit';

audioVolumeManager.on('volumeChange', (volumeEvent: audio.VolumeEvent) => {
  console.info(`VolumeType of stream: ${volumeEvent.volumeType} `);
  console.info(`Volume level: ${volumeEvent.volume} `);
  console.info(`Whether to updateUI: ${volumeEvent.updateUi} `);
});

5.5.3 使用音量面板调节系统音量

应用无法直接调节系统音量,可以通过系统音量面板,让用户通过界面操作来调节音量。当用户通过应用内音量面板调节音量时,系统会展示音量提示界面,显性地提示用户系统音量发生改变。

5.5.4 音频流音量

管理音频流音量的接口是AVPlayer或AudioRenderer的setVolume()方法,使用AVPlayer设置音频流音量的示例代码如下:

let volume = 1.0;  // 指定的音量大小,取值范围为[0.00-1.00],1表示最大音量。
avPlayer.setVolume(volume);

使用AudioRenderer设置音频流音量的示例代码如下:

import { BusinessError } from '@kit.BasicServicesKit';

audioRenderer.setVolume(0.5).then(() => {  // 音量范围为[0.0-1.0]。
  console.info('Invoke setVolume succeeded.');
}).catch((err: BusinessError) => {  
  console.error(`Invoke setVolume failed, code is ${err.code}, message is ${err.message}`);
});

5.6 音效管理

音效管理主要包括播放实例音效管理和全局音效查询两部分,播放实例音效管理主要包括查询和设置当前音频播放流的音效模式,全局音效查询支持查询StreamUsage对应场景支持的音效模式。

5.6.1 播放实例音效管理

主要包括查询和设置当前音频播放流的音效模式,音效模式包括EFFECT_NONE关闭音效模式和EFFECT_DEFAULT默认音效模式。默认音效模式会根据创建音频流的StreamUsage自动加载对应场景的音效。

5.6.2 获取播放实例

管理播放实例音效的接口是getAudioEffectMode()查询当前音频播放流的音效模式和setAudioEffectMode(mode: AudioEffectMode)设置当前音频播放流的音效模式,在使用之前,需要使用createAudioRenderer(options: AudioRendererOptions)先创建音频播放流AudioRenderer实例。

1. 导入音频接口。

import { audio } from '@kit.AudioKit';

2. 配置音频渲染参数并创建AudioRenderer实例,音频渲染参数的详细信息可以查看AudioRendererOptions,创建AudioRenderer实例时会默认挂载EFFECT_DEFAULT模式音效。

import { BusinessError } from '@kit.BasicServicesKit';

let audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
  channels: audio.AudioChannel.CHANNEL_2, // 通道。
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
};

let audioRendererInfo: audio.AudioRendererInfo = {
  usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。
  rendererFlags: 0 // 音频渲染器标志。
};

let audioRendererOptions: audio.AudioRendererOptions = {
  streamInfo: audioStreamInfo,
  rendererInfo: audioRendererInfo
};
let audioRenderer: audio.AudioRenderer | undefined = undefined;

audio.createAudioRenderer(audioRendererOptions, (err: BusinessError, data: audio.AudioRenderer) => {
  if (err) {
    console.error(`Invoke createAudioRenderer failed, code is ${err.code}, message is ${err.message}`);
    return;
  } else {
    console.info('Invoke createAudioRenderer succeeded.');
    audioRenderer = data;
  }
});

5.6.3 查询当前播放实例的音效模式

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

audioRenderer.getAudioEffectMode((err: BusinessError, effectMode: audio.AudioEffectMode) => {
  if (err) {
    console.error(`Failed to get params, code is ${err.code}, message is ${err.message}`);
    return;    
  } else {
    console.info(`getAudioEffectMode: ${effectMode}`);
  }
});

5.6.4 设置当前播放实例的音效模式

关闭系统音效:

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

audioRenderer.setAudioEffectMode(audio.AudioEffectMode.EFFECT_NONE, (err: BusinessError) => {
  if (err) {
    console.error(`Failed to set params, code is ${err.code}, message is ${err.message}`);
    return;
  } else {
    console.info('Callback invoked to indicate a successful audio effect mode setting.');
  }
});

开启系统音效默认模式:

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

audioRenderer.setAudioEffectMode(audio.AudioEffectMode.EFFECT_DEFAULT, (err: BusinessError) => {
  if (err) {
    console.error(`Failed to set params, code is ${err.code}, message is ${err.message}`);
    return;
  } else {
    console.info('Callback invoked to indicate a successful audio effect mode setting.');
  }
});

5.6.5 全局查询音效模式

主要包括全局音效查询相应StreamUsage对应场景的音效模式。

对于播放音频类的应用,开发者需要关注该应用的音频流使用什么音效模式并做出相应的操作,比如音乐App播放时,应选择音乐场景下的模式。在使用查询接口前,开发者需要使用getStreamManager()创建一个AudioStreamManager音频流管理实例。

5.6.6 获取音频流管理接口

创建AudioStreamManager实例。在使用AudioStreamManager的API前,需要使用getStreamManager()创建一个AudioStreamManager实例。

import { audio } from '@kit.AudioKit';

let audioManager = audio.getAudioManager();
let audioStreamManager = audioManager.getStreamManager();

5.6.7 查询对应场景的音效模式

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

audioStreamManager.getAudioEffectInfoArray(audio.StreamUsage.STREAM_USAGE_MUSIC, async (err: BusinessError, audioEffectInfoArray: audio.AudioEffectInfoArray) => {
  if (err) {
    console.error('Failed to get effect info array');
    return;    
  } else {
    console.info(`getAudioEffectInfoArray: ${audioEffectInfoArray}`);
  }
});

5.7 音频播放流管理

5.7.1 读取或监听应用内音频流状态变化

参考使用AudioRenderer开发音频播放功能audio.createAudioRenderer,完成AudioRenderer的创建,然后可以通过以下两种方式查看音频流状态的变化:

  • 方法1:直接查看AudioRenderer的state

import { audio } from '@kit.AudioKit';

let audioRendererState: audio.AudioState = audioRenderer.state;
console.info(`Current state is: ${audioRendererState }`)
  • 方法2:注册stateChange监听AudioRenderer的状态变化:
import { audio } from '@kit.AudioKit';

audioRenderer.on('stateChange', (rendererState: audio.AudioState) => {
  console.info(`State change to: ${rendererState}`)
});

获取state后可对照AudioState来进行相应的操作,比如更改暂停播放按钮的显示等。

5.7.2 读取或监听所有音频流的变化

如果部分应用需要查询获取所有音频流的变化信息,可以通过AudioStreamManager读取或监听所有音频流的变化。

如下为音频流管理调用关系图:

在进行应用开发的过程中,开发者需要使用getStreamManager()创建一个AudioStreamManager实例,进而通过该实例管理音频流。开发者可通过调用on('audioRendererChange')监听音频流的变化,在音频流状态变化、设备变化时获得通知。同时可通过off('audioRendererChange')取消相关事件的监听。另外,开发者可以主动调用getCurrentAudioRendererInfoArray()来查询播放流的唯一ID、播放流客户端的UID、音频流状态等信息。

5.7.3 开发步骤及注意事项

1. 创建AudioStreamManager实例。

在使用AudioStreamManager的API前,需要使用getStreamManager()创建一个AudioStreamManager实例。

import { audio } from '@kit.AudioKit';

let audioManager = audio.getAudioManager();
let audioStreamManager = audioManager.getStreamManager();

2. 使用on('audioRendererChange')监听音频播放流的变化。 如果音频流监听应用需要在音频播放流状态变化、设备变化时获取通知,可以订阅该事件。

import { audio } from '@kit.AudioKit';

audioStreamManager.on('audioRendererChange',  (AudioRendererChangeInfoArray: audio.AudioRendererChangeInfoArray) => {
  for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) {
    let AudioRendererChangeInfo = AudioRendererChangeInfoArray[i];
    console.info(`## RendererChange on is called for ${i} ##`);
    console.info(`StreamId for ${i} is: ${AudioRendererChangeInfo.streamId}`);
    console.info(`Content ${i} is: ${AudioRendererChangeInfo.rendererInfo.content}`);
    console.info(`Stream ${i} is: ${AudioRendererChangeInfo.rendererInfo.usage}`);
    console.info(`Flag ${i} is: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`); 
    for (let j = 0;j < AudioRendererChangeInfo.deviceDescriptors.length; j++) {
      console.info(`Id: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].id}`);
      console.info(`Type: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceType}`);
      console.info(`Role: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceRole}`);
      console.info(`Name: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].name}`);
      console.info(`Address: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].address}`);
      console.info(`SampleRates: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]}`);
      console.info(`ChannelCount ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]}`);
      console.info(`ChannelMask: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelMasks}`);
    }
  }
});

3. (可选)使用off('audioRendererChange')取消监听音频播放流变化。

audioStreamManager.off('audioRendererChange');
console.info('RendererChange Off is called ');

4. (可选)使用getCurrentAudioRendererInfoArray()获取所有音频播放流的信息。

该接口可获取音频播放流唯一ID、音频播放客户端的UID、音频状态以及音频播放器的其他信息。

说明

对所有音频流状态进行监听的应用需要声明权限ohos.permission.USE_BLUETOOTH,否则无法获得实际的设备名称和设备地址信息,查询到的设备名称和设备地址(蓝牙设备的相关属性)将为空字符串。

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

async function getCurrentAudioRendererInfoArray(): Promise<void> {
  await audioStreamManager.getCurrentAudioRendererInfoArray().then((AudioRendererChangeInfoArray: audio.AudioRendererChangeInfoArray) => {
    console.info(`getCurrentAudioRendererInfoArray  Get Promise is called `);
    if (AudioRendererChangeInfoArray != null) {
      for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) {
        let AudioRendererChangeInfo = AudioRendererChangeInfoArray[i];
        console.info(`StreamId for ${i} is: ${AudioRendererChangeInfo.streamId}`);
        console.info(`Content ${i} is: ${AudioRendererChangeInfo.rendererInfo.content}`);
        console.info(`Stream ${i} is: ${AudioRendererChangeInfo.rendererInfo.usage}`);
        console.info(`Flag ${i} is: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`);  
        for (let j = 0;j < AudioRendererChangeInfo.deviceDescriptors.length; j++) {
          console.info(`Id: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].id}`);
          console.info(`Type: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceType}`);
          console.info(`Role: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceRole}`);
          console.info(`Name: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].name}`);
          console.info(`Address: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].address}`);
          console.info(`SampleRates: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]}`);
          console.info(`ChannelCount ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]}`);
          console.info(`ChannelMask: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelMasks}`);
        }
      }
    }
  }).catch((err: BusinessError ) => {
    console.error(`Invoke getCurrentAudioRendererInfoArray failed, code is ${err.code}, message is ${err.message}`);
  });
}

 5.8 管理全局音频输出设备

有时设备同时连接多个音频输出设备,需要指定音频输出设备进行音频播放,此时需要使用AudioRoutingManager接口进行输出设备的管理

5.8.1 创建AudioRoutingManager实例

在使用AudioRoutingManager管理音频设备前,需要先导入模块并创建实例。

import { audio } from '@kit.AudioKit';  // 导入audio模块。

let audioManager = audio.getAudioManager();  // 需要先创建AudioManager实例。

let audioRoutingManager = audioManager.getRoutingManager();  // 再调用AudioManager的方法创建AudioRoutingManager实例。

5.8.2 支持的音频输出设备类型

目前支持的音频输出设备见下表:

名称说明
EARPIECE1听筒。
SPEAKER2扬声器。
WIRED_HEADSET3有线耳机,带麦克风。
WIRED_HEADPHONES4有线耳机,无麦克风。
BLUETOOTH_SCO7蓝牙设备SCO(Synchronous Connection Oriented)连接。
BLUETOOTH_A2DP8蓝牙设备A2DP(Advanced Audio Distribution Profile)连接。
USB_HEADSET22USB耳机,带麦克风。

5.8.3 获取输出设备信息

使用getDevices()方法可以获取当前所有输出设备的信息。

import { audio } from '@kit.AudioKit';

audioRoutingManager.getDevices(audio.DeviceFlag.OUTPUT_DEVICES_FLAG).then((data: audio.AudioDeviceDescriptors) => {
  console.info('Promise returned to indicate that the device list is obtained.');
});

5.8.4 监听设备连接状态变化

可以设置监听事件来监听设备连接状态的变化,当有设备连接或断开时触发回调:

import { audio } from '@kit.AudioKit';

// 监听音频设备状态变化。
audioRoutingManager.on('deviceChange', audio.DeviceFlag.OUTPUT_DEVICES_FLAG, (deviceChanged: audio.DeviceChangeAction) => {
  console.info(`device change type : ${deviceChanged.type}`);  // 设备连接状态变化,0为连接,1为断开连接。
  console.info(`device descriptor size : ${deviceChanged.deviceDescriptors.length}`);
  console.info(`device change descriptor : ${deviceChanged.deviceDescriptors[0].deviceRole}`);  // 设备角色。
  console.info(`device change descriptor : ${deviceChanged.deviceDescriptors[0].deviceType}`);  // 设备类型。
});

// 取消监听音频设备状态变化。
audioRoutingManager.off('deviceChange');

5.8.5 获取最高优先级输出设备信息

使用getPreferOutputDeviceForRendererInfo()方法, 可以获取当前最高优先级的输出设备。

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

let rendererInfo: audio.AudioRendererInfo = {
    usage: audio.StreamUsage.STREAM_USAGE_MUSIC,// 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。
    rendererFlags: 0 // 音频渲染器标志。
};

async function getPreferOutputDeviceForRendererInfo() {
  audioRoutingManager.getPreferOutputDeviceForRendererInfo(rendererInfo).then((desc: audio.AudioDeviceDescriptors) => {
    console.info(`device descriptor: ${desc}`);
  }).catch((err: BusinessError) => {
    console.error(`Result ERROR: ${err}`);
  })
}

5.8.6 监听最高优先级输出设备变化

import { audio } from '@kit.AudioKit';

let rendererInfo: audio.AudioRendererInfo = {
    usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。
    rendererFlags: 0 // 音频渲染器标志。
};

// 监听最高优先级输出设备变化。
audioRoutingManager.on('preferOutputDeviceChangeForRendererInfo', rendererInfo, (desc: audio.AudioDeviceDescriptors) => {
    console.info(`device change descriptor : ${desc[0].deviceRole}`);  // 设备角色。
    console.info(`device change descriptor : ${desc[0].deviceType}`);  // 设备类型。
});

// 取消监听最高优先级输出设备变化。
audioRoutingManager.off('preferOutputDeviceChangeForRendererInfo');

5.9 音频录制

5.9.1 音频录制开发概述

1、如何选择音频录制开发方式

系统提供了多样化的API,来帮助开发者完成音频录制的开发,不同的API适用于不同录音输出格式、音频使用场景或不同开发语言。因此,选择合适的音频录制API,有助于降低开发工作量,实现更佳的音频录制效果。

  • AudioCapturer:用于音频输入的的ArkTS/JS API,仅支持PCM格式,需要应用持续读取音频数据进行工作。应用可以在音频输出后添加数据处理,要求开发者具备音频处理的基础知识,适用于更专业、更多样化的媒体录制应用开发。

  • OpenSL ES:一套跨平台标准化的音频Native API,同样提供音频输入原子能力,仅支持PCM格式,适用于从其他嵌入式平台移植,或依赖在Native层实现音频输入功能的录音应用使用。

  • OHAudio:用于音频输入的Native API,此API在设计上实现归一,同时支持普通音频通路和低时延通路。仅支持PCM格式,适用于依赖Native层实现音频输入功能的场景。

除上述方式外,也可以通过Media Kit中的AVRecorder实现音频录制。

  • AVRecorder:用于音频录制的ArkTS/JS API,集成了音频输入录制、音频编码和媒体封装的功能。开发者可以直接调用设备硬件如麦克风录音,并生成m4a音频文件。
2、开发音频录制应用须知
  • 应用可以调用麦克风录制音频,但该行为属于隐私敏感行为,在调用麦克风前,需要先向用户申请权限:ohos.permission.MICROPHONE。

    如何使用和管理麦克风请参考管理麦克风

  • 如果需要持续录制或后台录制,请申请长时任务避免进入挂起(Suspend)状态。具体参考长时任务开发指导

  • 录制需要在前台启动,启动后可以退后台。在后台启动录制将会失败。

5.9.2  使用AudioCapturer开发音频录制功能

AudioCapturer是音频采集器,用于录制PCM(Pulse Code Modulation)音频数据,适合有音频开发经验的开发者实现更灵活的录制功能。

1、开发指导

使用AudioCapturer录制音频涉及到AudioCapturer实例的创建、音频采集参数的配置、采集的开始与停止、资源的释放等。本开发指导将以一次录制音频数据的过程为例,向开发者讲解如何使用AudioCapturer进行音频录制,建议搭配AudioCapturer的API说明阅读。

下图展示了AudioCapturer的状态变化,在创建实例后,调用对应的方法可以进入指定的状态实现对应的行为。需要注意的是在确定的状态执行不合适的方法可能导致AudioCapturer发生错误,建议开发者在调用状态转换的方法前进行状态检查,避免程序运行产生预期以外的结果。

图1 AudioCapturer状态变化示意图

使用on('stateChange')方法可以监听AudioCapturer的状态变化,每个状态对应值与说明见AudioState

2、开发步骤及注意事项

1. 配置音频采集参数并创建AudioCapturer实例,音频采集参数的详细信息可以查看AudioCapturerOptions

说明

当设置Mic音频源(即SourceType为SOURCE_TYPE_MIC、SOURCE_TYPE_VOICE_RECOGNITION、SOURCE_TYPE_VOICE_COMMUNICATION、SOURCE_TYPE_VOICE_MESSAGE)时,需要申请麦克风权限ohos.permission.MICROPHONE,申请方式参考:向用户申请授权

 import { audio } from '@kit.AudioKit';
 
 let audioStreamInfo: audio.AudioStreamInfo = {
   samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
   channels: audio.AudioChannel.CHANNEL_2, // 通道。
   sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
   encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
 };
 
 let audioCapturerInfo: audio.AudioCapturerInfo = {
   source: audio.SourceType.SOURCE_TYPE_MIC, // 音源类型:Mic音频源。根据业务场景配置,参考SourceType。
   capturerFlags: 0 // 音频采集器标志。
 };
 
 let audioCapturerOptions: audio.AudioCapturerOptions = {
   streamInfo: audioStreamInfo,
   capturerInfo: audioCapturerInfo
 };
 
 audio.createAudioCapturer(audioCapturerOptions, (err, data) => {
   if (err) {
     console.error(`Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`);
   } else {
     console.info('Invoke createAudioCapturer succeeded.');
     let audioCapturer = data;
   }
 });

2. 调用on('readData')方法,订阅监听音频数据读入回调。

 import { BusinessError } from '@kit.BasicServicesKit';
 import { fileIo as fs } from '@kit.CoreFileKit';

 class Options {
   offset?: number;
   length?: number;
 }

 let bufferSize: number = 0;
 let path = getContext().cacheDir;
 let filePath = path + '/StarWars10s-2C-48000-4SW.pcm';
 let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
 let readDataCallback = (buffer: ArrayBuffer) => {
   let options: Options = {
     offset: bufferSize,
     length: buffer.byteLength
   }
   fs.writeSync(file.fd, buffer, options);
   bufferSize += buffer.byteLength;
 };

 audioCapturer.on('readData', readDataCallback);

3. 调用start()方法进入running状态,开始录制音频。

 import { BusinessError } from '@kit.BasicServicesKit';

 audioCapturer.start((err: BusinessError) => {
   if (err) {
     console.error(`Capturer start failed, code is ${err.code}, message is ${err.message}`);
   } else {
     console.info('Capturer start success.');
   }
 });

4. 调用stop()方法停止录制。

 import { BusinessError } from '@kit.BasicServicesKit';

 audioCapturer.stop((err: BusinessError) => {
   if (err) {
     console.error(`Capturer stop failed, code is ${err.code}, message is ${err.message}`);
   } else {
     console.info('Capturer stopped.');
   }
 });

5. 调用release()方法销毁实例,释放资源。

 import { BusinessError } from '@kit.BasicServicesKit';

 audioCapturer.release((err: BusinessError) => {
   if (err) {
     console.error(`capturer release failed, code is ${err.code}, message is ${err.message}`);
   } else {
     console.info('capturer released.');
   }
 });

5.9.3 管理麦克风

1、开发步骤及注意事项

在AudioVolumeGroupManager中提供了管理麦克风状态的方法,接口的详细说明请参考API文档

1. 创建audioVolumeGroupManager对象。

import { audio } from '@kit.AudioKit';

let audioVolumeGroupManager: audio.AudioVolumeGroupManager;

// 创建audioVolumeGroupManager对象。
async function loadVolumeGroupManager() {
  const groupid = audio.DEFAULT_VOLUME_GROUP_ID;
  audioVolumeGroupManager = await audio.getAudioManager().getVolumeManager().getVolumeGroupManager(groupid);
  console.info('audioVolumeGroupManager create success.');
}

2. 调用isMicrophoneMute查询麦克风当前静音状态,返回true为静音,false为非静音。

// 查询麦克风是否静音。
async function isMicrophoneMute() {
  await audioVolumeGroupManager.isMicrophoneMute().then((value: boolean) => {
    console.info(`isMicrophoneMute is: ${value}.`);
  });
}

5.9.4 音频录制流管理

1、读取或监听应用内音频流状态变化

参考使用AudioCapturer开发音频录制功能audio.createAudioCapturer,完成AudioCapturer的创建,然后可以通过以下两种方式查看音频流状态的变化:

  • 方法1:直接查看AudioCapturer的state

let audioCapturerState: audio.AudioState = audioCapturer.state;
console.info(`Current state is: ${audioCapturerState }`)
  • 方法2:注册stateChange监听AudioCapturer的状态变化:
audioCapturer.on('stateChange', (capturerState: audio.AudioState) => {
  console.info(`State change to: ${capturerState}`)
});

 

2、读取或监听所有录制流的变化

如果部分应用需要查询获取所有音频流的变化信息,可以通过AudioStreamManager读取或监听所有音频流的变化。

如下为音频流管理调用关系图:

在进行应用开发的过程中,开发者需要使用getStreamManager()创建一个AudioStreamManager实例,进而通过该实例管理音频流。开发者可通过调用on('audioCapturerChange')监听音频流的变化,在音频流状态变化、设备变化时获得通知,同时可通过off('audioCapturerChange')取消相关事件的监听。另外,开发者可以通过主动调用getCurrentAudioCapturerInfoArray()查询录制流的唯一ID、录制流客户端的UID、以及流状态等信息。

3、开发步骤及注意事项

1. 创建AudioStreamManager实例。

在使用AudioStreamManager的API前,需要使用getStreamManager()创建一个AudioStreamManager实例。

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

let audioManager = audio.getAudioManager();
let audioStreamManager = audioManager.getStreamManager();

2. 使用on('audioCapturerChange')监听音频录制流更改事件。 如果音频流监听应用需要在音频录制流状态变化、设备变化时获取通知,可以订阅该事件。

audioStreamManager.on('audioCapturerChange', (AudioCapturerChangeInfoArray: audio.AudioCapturerChangeInfoArray) =>  {
  for (let i = 0; i < AudioCapturerChangeInfoArray.length; i++) {
    console.info(`## CapChange on is called for element ${i} ##`);
    console.info(`StreamId for ${i} is: ${AudioCapturerChangeInfoArray[i].streamId}`);
    console.info(`Source for ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.source}`);
    console.info(`Flag  ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags}`);
    let devDescriptor: audio.AudioDeviceDescriptors = AudioCapturerChangeInfoArray[i].deviceDescriptors;
    for (let j = 0; j < AudioCapturerChangeInfoArray[i].deviceDescriptors.length; j++) {
      console.info(`Id: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].id}`);
      console.info(`Type: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceType}`);
      console.info(`Role: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceRole}`);
      console.info(`Name: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].name}`);
      console.info(`Address: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].address}`);
      console.info(`SampleRates: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].sampleRates[0]}`);
      console.info(`ChannelCounts ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelCounts[0]}`);
      console.info(`ChannelMask: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelMasks}`);
    }
  }
});

3. (可选)使用off('audioCapturerChange')取消监听音频录制流变化。

audioStreamManager.off('audioCapturerChange');
console.info('CapturerChange Off is called');

4. (可选)使用getCurrentAudioCapturerInfoArray()获取当前音频录制流的信息。

该接口可获取音频录制流唯一ID、音频录制客户端的UID、音频状态以及音频捕获器的其他信息。

async function getCurrentAudioCapturerInfoArray(){
  await audioStreamManager.getCurrentAudioCapturerInfoArray().then((AudioCapturerChangeInfoArray: audio.AudioCapturerChangeInfoArray) => {
    console.info('getCurrentAudioCapturerInfoArray  Get Promise Called ');
    if (AudioCapturerChangeInfoArray != null) {
      for (let i = 0; i < AudioCapturerChangeInfoArray.length; i++) {
        console.info(`StreamId for ${i} is: ${AudioCapturerChangeInfoArray[i].streamId}`);
        console.info(`Source for ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.source}`);
        console.info(`Flag  ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags}`);
        for (let j = 0; j < AudioCapturerChangeInfoArray[i].deviceDescriptors.length; j++) {
          console.info(`Id: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].id}`);
          console.info(`Type: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceType}`);
          console.info(`Role: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceRole}`);
          console.info(`Name: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].name}`);
          console.info(`Address: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].address}`);
          console.info(`SampleRates: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].sampleRates[0]}`);
          console.info(`ChannelCounts ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelCounts[0]}`);
          console.info(`ChannelMask: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelMasks}`);
        }
      }
    }
  }).catch((err: BusinessError) => {
    console.error(`Invoke getCurrentAudioCapturerInfoArray failed, code is ${err.code}, message is ${err.message}`);
  });
}

5.9.5 管理全局音频输入设备 

1、创建AudioRoutingManager实例

在使用AudioRoutingManager管理音频设备前,需要先导入模块并创建实例。

import { audio } from '@kit.AudioKit';  // 导入audio模块。

let audioManager = audio.getAudioManager();  // 需要先创建AudioManager实例。
let audioRoutingManager = audioManager.getRoutingManager();  // 再调用AudioManager的方法创建AudioRoutingManager实例。
2、支持的音频输入设备类型

目前支持的音频输入设备见下表:

名称说明
WIRED_HEADSET3有线耳机,带麦克风。
BLUETOOTH_SCO7蓝牙设备SCO(Synchronous Connection Oriented)连接。
MIC15麦克风。
USB_HEADSET22USB耳机,带麦克风。
3、获取输入设备信息

使用getDevices()方法可以获取当前所有输入设备的信息。

import { audio } from '@kit.AudioKit';

audioRoutingManager.getDevices(audio.DeviceFlag.INPUT_DEVICES_FLAG).then((data: audio.AudioDeviceDescriptors) => {
  console.info('Promise returned to indicate that the device list is obtained.');
});
4、监听设备连接状态变化

可以设置监听事件来监听设备连接状态的变化,当有设备连接或断开时触发回调:

import { audio } from '@kit.AudioKit';

// 监听音频设备状态变化。
audioRoutingManager.on('deviceChange', audio.DeviceFlag.INPUT_DEVICES_FLAG, (deviceChanged: audio.DeviceChangeAction) => {
  console.info('device change type : ' + deviceChanged.type);  // 设备连接状态变化,0为连接,1为断开连接。
  console.info('device descriptor size : ' + deviceChanged.deviceDescriptors.length);
  console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceRole);  // 设备角色。
  console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceType);  // 设备类型。
});

// 取消监听音频设备状态变化。
audioRoutingManager.off('deviceChange', (deviceChanged: audio.DeviceChangeAction) => {
  console.info('Should be no callback.');
});

5.10 音频通话

5.10.1 音频通话开发概述

常用的音频通话模式包括VoIP通话蜂窝通话

  • VoIP通话

    VoIP(Voice over Internet Protocol)通话是指基于互联网协议(IP)进行通讯的一种语音通话技术。VoIP通话会将通话信息打包成数据包,通过网络进行传输,因此VoIP通话对网络要求较高,通话质量与网络连接速度紧密相关。

  • 蜂窝通话(仅对系统应用开放)

    蜂窝通话是指传统的电话功能,由运营商提供服务,目前仅对系统应用开放,未向第三方应用提供开发接口。

5.10.2 音频场景模式

应用使用音频通话相关功能时,系统会切换至与通话相关的音频场景模式(AudioScene),当前预置了多种音频场景,包括响铃、通话、语音聊天等,在不同的场景下,系统会采用不同的策略来处理音频。

当前预置的音频场景:

  • AUDIO_SCENE_DEFAULT:默认音频场景,音频通话之外的场景均可使用。

  • AUDIO_SCENE_VOICE_CHAT:语音聊天音频场景,VoIP通话时使用。

应用可通过AudioManager的getAudioScene来获取当前的音频场景模式。当应用开始或结束使用音频通话相关功能时,可通过此方法检查系统是否已切换为合适的音频场景模式。

5.10.3 铃声模式

在用户进入到音频通话时,应用可以使用铃声或振动来提示用户。系统通过调整铃声模式(AudioRingMode),实现便捷地管理铃声音量,并调整设备的振动模式。

当前预置的三种铃声模式:

  • RINGER_MODE_SILENT:静音模式,此模式下铃声音量为零(即静音)。

  • RINGER_MODE_VIBRATE:振动模式,此模式下铃声音量为零,设备振动开启(即响铃时静音,触发振动)。

  • RINGER_MODE_NORMAL:响铃模式,此模式下铃声音量正常。

应用可以调用AudioVolumeGroupManager中的getRingerMode获取当前的铃声模式,以便采取合适的提示策略。

如果应用希望及时获取铃声模式的变化情况,可以通过AudioVolumeGroupManager中的on('ringerModeChange')监听铃声模式变化事件,使应用在铃声模式发生变化时及时收到通知,方便应用做出相应的调整。

5.10.4 通话场景音频设备切换

在通话场景下,系统会根据默认优先级选择合适的音频设备。应用可以根据需要,切换音频设备。

切换方式可参考AVSession Kit使用通话设备切换组件

5.10.5 开发音频通话功能

在音频通话场景下,音频输出(播放对端声音)和音频输入(录制本端声音)会同时进行,应用可以通过使用AudioRenderer来实现音频输出,通过使用AudioCapturer来实现音频输入,同时使用AudioRenderer和AudioCapturer即可实现音频通话功能。

在音频通话开始和结束时,应用可以自行检查当前的音频场景模式铃声模式,以便采取合适的音频管理及提示策略。

以下代码示范了同时使用AudioRenderer和AudioCapturer实现音频通话功能的基本过程,其中未包含音频通话数据的传输过程,实际开发中,需要将网络传输来的对端通话数据解码播放,此处仅以读取音频文件的数据代替;同时需要将本端录制的通话数据编码打包,通过网络发送给对端,此处仅以将数据写入音频文件代替。

1、使用AudioRenderer播放对端的通话声音

该过程与使用AudioRenderer开发音频播放功能过程相似,关键区别在于audioRendererInfo参数和音频数据来源。audioRendererInfo参数中,音频内容类型需设置为语音:CONTENT_TYPE_SPEECH,音频流使用类型需设置为VOIP通话:STREAM_USAGE_VOICE_COMMUNICATION。

import { audio } from '@kit.AudioKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG = 'VoiceCallDemoForAudioRenderer';
// 与使用AudioRenderer开发音频播放功能过程相似,关键区别在于audioRendererInfo参数和音频数据来源。
class Options {
  offset?: number;
  length?: number;
}

let bufferSize: number = 0;
let renderModel: audio.AudioRenderer | undefined = undefined;
let audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
  channels: audio.AudioChannel.CHANNEL_2, // 通道。
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
};
let audioRendererInfo: audio.AudioRendererInfo = {
  // 需使用通话场景相应的参数。
  usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION, // 音频流使用类型:VOIP通话。
  rendererFlags: 0 // 音频渲染器标志:默认为0即可。
};
let audioRendererOptions: audio.AudioRendererOptions = {
  streamInfo: audioStreamInfo,
  rendererInfo: audioRendererInfo
};
let path = getContext().cacheDir;
// 确保该沙箱路径下存在该资源。
let filePath = path + '/StarWars10s-2C-48000-4SW.wav';
let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
let writeDataCallback = (buffer: ArrayBuffer) => {
  let options: Options = {
    offset: bufferSize,
    length: buffer.byteLength
  };
  fs.readSync(file.fd, buffer, options);
  bufferSize += buffer.byteLength;
};

// 初始化,创建实例,设置监听事件。
audio.createAudioRenderer(audioRendererOptions, (err: BusinessError, renderer: audio.AudioRenderer) => { // 创建AudioRenderer实例。
  if (!err) {
    console.info(`${TAG}: creating AudioRenderer success`);
    renderModel = renderer;
    if (renderModel !== undefined) {
      renderModel.on('stateChange', (state: audio.AudioState) => { // 设置监听事件,当转换到指定的状态时触发回调。
        if (state == 1) {
          console.info('audio renderer state is: STATE_PREPARED');
        }
        if (state == 2) {
          console.info('audio renderer state is: STATE_RUNNING');
        }
      });
      renderModel.on('markReach', 1000, (position: number) => { // 订阅markReach事件,当渲染的帧数达到1000帧时触发回调。
        if (position == 1000) {
          console.info('ON Triggered successfully');
        }
      });
      renderModel.on('writeData', writeDataCallback);
    }
  } else {
    console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);
  }
});

// 开始一次音频渲染。
async function start() {
  if (renderModel !== undefined) {
    let stateGroup: number[] = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
    if (stateGroup.indexOf(renderModel.state.valueOf()) === -1) { // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动渲染。
      console.error(TAG + 'start failed');
      return;
    }
    renderModel.start((err: BusinessError) => {
      if (err) {
        console.error('Renderer start failed.');
      } else {
        console.info('Renderer start success.');
      }
    });
  }
}

// 暂停渲染。
async function pause() {
  if (renderModel !== undefined) {
    // 只有渲染器状态为STATE_RUNNING的时候才能暂停。
    if (renderModel.state.valueOf() !== audio.AudioState.STATE_RUNNING) {
      console.info('Renderer is not running');
      return;
    }
    await renderModel.pause(); // 暂停渲染。
    if (renderModel.state.valueOf() === audio.AudioState.STATE_PAUSED) {
      console.info('Renderer is paused.');
    } else {
      console.error('Pausing renderer failed.');
    }
  }
}

// 停止渲染。
async function stop() {
  if (renderModel !== undefined) {
    // 只有渲染器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止。
    if (renderModel.state.valueOf() !== audio.AudioState.STATE_RUNNING && renderModel.state.valueOf() !== audio.AudioState.STATE_PAUSED) {
      console.info('Renderer is not running or paused.');
      return;
    }
    await renderModel.stop(); // 停止渲染。
    if (renderModel.state.valueOf() === audio.AudioState.STATE_STOPPED) {
      console.info('Renderer stopped.');
    } else {
      console.error('Stopping renderer failed.');
    }
  }
}

// 销毁实例,释放资源。
async function release() {
  if (renderModel !== undefined) {
    // 渲染器状态不是STATE_RELEASED状态,才能release。
    if (renderModel.state.valueOf() === audio.AudioState.STATE_RELEASED) {
      console.info('Renderer already released');
      return;
    }
    await renderModel.release(); // 释放资源。
    if (renderModel.state.valueOf() === audio.AudioState.STATE_RELEASED) {
      console.info('Renderer released');
    } else {
      console.error('Renderer release failed.');
    }
  }
}

2、使用AudioCapturer录制本端的通话声音

该过程与使用AudioCapturer开发音频录制功能过程相似,关键区别在于audioCapturerInfo参数和音频数据流向。audioCapturerInfo参数中音源类型需设置为语音通话:SOURCE_TYPE_VOICE_COMMUNICATION。

所有录制均需要申请麦克风权限:ohos.permission.MICROPHONE,申请方式请参考向用户申请授权

import { audio } from '@kit.AudioKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG = 'VoiceCallDemoForAudioCapturer';
class Options {
  offset?: number;
  length?: number;
}

// 与使用AudioCapturer开发音频录制功能过程相似,关键区别在于audioCapturerInfo参数和音频数据流向。
let bufferSize: number = 0;
let audioCapturer: audio.AudioCapturer | undefined = undefined;
let audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
  channels: audio.AudioChannel.CHANNEL_2, // 通道。
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
};
let audioCapturerInfo: audio.AudioCapturerInfo = {
  // 需使用通话场景相应的参数。
  source: audio.SourceType.SOURCE_TYPE_VOICE_COMMUNICATION, // 音源类型:语音通话。
  capturerFlags: 0 // 音频采集器标志:默认为0即可。
};
let audioCapturerOptions: audio.AudioCapturerOptions = {
  streamInfo: audioStreamInfo,
  capturerInfo: audioCapturerInfo
};
let path = getContext().cacheDir;
let filePath = path + '/StarWars10s-2C-48000-4SW.wav';
let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
let readDataCallback = (buffer: ArrayBuffer) => {
  let options: Options = {
    offset: bufferSize,
    length: buffer.byteLength
  };
  fs.writeSync(file.fd, buffer, options);
  bufferSize += buffer.byteLength;
};

// 初始化,创建实例,设置监听事件。
async function init() {
  audio.createAudioCapturer(audioCapturerOptions, (err: BusinessError, capturer: audio.AudioCapturer) => { // 创建AudioCapturer实例。
    if (err) {
      console.error(`Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`);
      return;
    }
    console.info(`${TAG}: create AudioCapturer success`);
    audioCapturer = capturer;
    if (audioCapturer !== undefined) {
      audioCapturer.on('markReach', 1000, (position: number) => { // 订阅markReach事件,当采集的帧数达到1000帧时触发回调。
        if (position === 1000) {
          console.info('ON Triggered successfully');
        }
      });
      audioCapturer.on('periodReach', 2000, (position: number) => { // 订阅periodReach事件,当采集的帧数每达到2000时触发回调。
        if (position === 2000) {
          console.info('ON Triggered successfully');
        }
      });
      audioCapturer.on('readData', readDataCallback);
    }
  });
}

// 开始一次音频采集。
async function start() {
  if (audioCapturer !== undefined) {
    let stateGroup: number[] = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
    if (stateGroup.indexOf(audioCapturer.state.valueOf()) === -1) { // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动采集。
      console.error(`${TAG}: start failed`);
      return;
    }
    audioCapturer.start((err: BusinessError) => {
      if (err) {
        console.error('Capturer start failed.');
      } else {
        console.info('Capturer start success.');
      }
    });
  }
}

// 停止采集。
async function stop() {
  if (audioCapturer !== undefined) {
    // 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止。
    if (audioCapturer.state.valueOf() !== audio.AudioState.STATE_RUNNING && audioCapturer.state.valueOf() !== audio.AudioState.STATE_PAUSED) {
      console.info('Capturer is not running or paused');
      return;
    }
    await audioCapturer.stop(); // 停止采集。
    if (audioCapturer.state.valueOf() === audio.AudioState.STATE_STOPPED) {
      console.info('Capturer stopped');
    } else {
      console.error('Capturer stop failed');
    }
  }
}

// 销毁实例,释放资源。
async function release() {
  if (audioCapturer !== undefined) {
    // 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release。
    if (audioCapturer.state.valueOf() === audio.AudioState.STATE_RELEASED || audioCapturer.state.valueOf() === audio.AudioState.STATE_NEW) {
      console.info('Capturer already released');
      return;
    }
    await audioCapturer.release(); // 释放资源。
    if (audioCapturer.state.valueOf() === audio.AudioState.STATE_RELEASED) {
      console.info('Capturer released');
    } else {
      console.error('Capturer release failed');
    }
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值