HarmonyOS 音频通话开发指导
常用的音频通话模式包括 VOIP 通话和蜂窝通话。
● VOIP 通话:VOIP(Voice over Internet Protocol)通话是指基于互联网协议(IP)进行通讯的一种语音通话技术。VOIP 通话会将通话信息打包成数据包,通过网络进行传输,因此 VOIP 通话对网络要求较高,通话质量与网络连接速度紧密相关。
● 蜂窝通话(仅对系统应用开放)蜂窝通话是指传统的电话功能,由运营商提供服务,目前仅对系统应用开放,未向三方应用提供开发接口。
在开发音频通话相关功能时,开发者可以根据实际情况,检查当前的音频场景模式和铃声模式,以使用相应的音频处理策略。
音频场景模式
应用使用音频通话相关功能时,系统会切换至与通话相关的音频场景模式(AudioScene),当前预置了多种音频场景,包括响铃、通话、语音聊天等,在不同的场景下,系统会采用不同的策略来处理音频。
当前预置的音频场景:
● AUDIO_SCENE_DEFAULT:默认音频场景,音频通话之外的场景均可使用。
● AUDIO_SCENE_VOICE_CHAT:语音聊天音频场景,VOIP 通话时使用。
应用可通过AudioManager的 getAudioScene 来获取当前的音频场景模式。当应用开始或结束使用音频通话相关功能时,可通过此方法检查系统是否已切换为合适的音频场景模式。
铃声模式
在用户进入到音频通话时,应用可以使用铃声或振动来提示用户。系统通过调整铃声模式(AudioRingMode),实现便捷地管理铃声音量,并调整设备的振动模式。
当前预置的三种铃声模式:
● RINGER_MODE_SILENT:静音模式,此模式下铃声音量为零(即静音)。
● RINGER_MODE_VIBRATE:振动模式,此模式下铃声音量为零,设备振动开启(即响铃时静音,触发振动)。
● RINGER_MODE_NORMAL:响铃模式,此模式下铃声音量正常。
应用可以调用AudioVolumeGroupManager中的 getRingerMode 获取当前的铃声模式,以便采取合适的提示策略。
如果应用希望及时获取铃声模式的变化情况,可以通过 AudioVolumeGroupManager 中的 on('ringerModeChange')监听铃声模式变化事件,使应用在铃声模式发生变化时及时收到通知,方便应用做出相应的调整。
通话场景音频设备切换
在通话场景下,系统会根据默认优先级选择合适的音频设备。应用可以根据需要,自主切换音频设备。
通信设备类型(CommunicationDeviceType)是系统预置的可用于通话场景的设备,应用可以使用AudioRoutingManager的 isCommunicationDeviceActive 函数获取指定通信设备的激活状态,并且可以使用 AudioRoutingManager 的 setCommunicationDevice 设置通信设备的激活状态,通过激活设备来实现通话场景音频设备的切换。
在音频通话场景下,音频输出(播放对端声音)和音频输入(录制本端声音)会同时进行,应用可以通过使用 AudioRenderer 来实现音频输出,通过使用 AudioCapturer 来实现音频输入,同时使用 AudioRenderer 和 AudioCapturer 即可实现音频通话功能。
开发音视频通话功能
在音频通话开始和结束时,应用可以自行检查当前的音频场景模式和铃声模式,以便采取合适的音频管理及提示策略。
以下代码示范了同时使用 AudioRenderer 和 AudioCapturer 实现音频通话功能的基本过程,其中未包含音频通话数据的传输过程,实际开发中,需要将网络传输来的对端通话数据解码播放,此处仅以读取音频文件的数据代替;同时需要将本端录制的通话数据编码打包,通过网络发送给对端,此处仅以将数据写入音频文件代替。
使用 AudioRenderer 播放对端的通话声音
该过程与使用AudioRenderer开发音频播放功能过程相似,关键区别在于 audioRenderInfo 参数和音频数据来源。audioRenderInfo 参数中,音频内容类型需设置为语音,CONTENT_TYPE_SPEECH,音频流使用类型需设置为语音通信,STREAM_USAGE_VOICE_COMMUNICATION。
import audio from '@ohos.multimedia.audio';import fs from '@ohos.file.fs';const TAG = 'VoiceCallDemoForAudioRenderer';// 与使用AudioRenderer开发音频播放功能过程相似,关键区别在于audioRendererInfo参数和音频数据来源export default class VoiceCallDemoForAudioRenderer {private renderModel = undefined;private audioStreamInfo = {samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率channels: audio.AudioChannel.CHANNEL_2, // 通道sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式}private audioRendererInfo = {// 需使用通话场景相应的参数content: audio.ContentType.CONTENT_TYPE_SPEECH, // 音频内容类型:语音usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION, // 音频流使用类型:语音通信rendererFlags: 0 // 音频渲染器标志:默认为0即可}private audioRendererOptions = {streamInfo: this.audioStreamInfo,rendererInfo: this.audioRendererInfo}// 初始化,创建实例,设置监听事件init() {audio.createAudioRenderer(this.audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例if (!err) {console.info(`${TAG}: creating AudioRenderer success`);this.renderModel = renderer;this.renderModel.on('stateChange', (state) => { // 设置监听事件,当转换到指定的状态时触发回调if (state == 1) {console.info('audio renderer state is: STATE_PREPARED');}if (state == 2) {console.info('audio renderer state is: STATE_RUNNING');}});this.renderModel.on('markReach', 1000, (position) => { // 订阅markReach事件,当渲染的帧数达到1000帧时触发回调if (position == 1000) {console.info('ON Triggered successfully');}});} else {console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);}});}// 开始一次音频渲染async start() {let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];if (stateGroup.indexOf(this.renderModel.state) === -1) { // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动渲染console.error(TAG + 'start failed');return;}await this.renderModel.start(); // 启动渲染const bufferSize = await this.renderModel.getBufferSize();// 此处仅以读取音频文件的数据举例,实际音频通话开发中,需要读取的是通话对端传输来的音频数据let context = getContext(this);let path = context.filesDir;const filePath = path + '/voice_call_data.wav'; // 沙箱路径,实际路径为/data/storage/el2/base/haps/entry/files/voice_call_data.wavlet file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);let stat = await fs.stat(filePath);let buf = new ArrayBuffer(bufferSize);let len = stat.size % bufferSize === 0 ? Math.floor(stat.size / bufferSize) : Math.floor(stat.size / bufferSize + 1);for (let i = 0; i < len; i++) {let options = {offset: i * bufferSize,length: bufferSize};let readsize = await fs.read(file.fd, buf, options);// buf是要写入缓冲区的音频数据,在调用AudioRenderer.write()方法前可以进行音频数据的预处理,实现个性化的音频播放功能,AudioRenderer会读出写入缓冲区的音频数据进行渲染let writeSize = await new Promise((resolve, reject) => {this.renderModel.write(buf, (err, writeSize) => {if (err) {reject(err);} else {resolve(writeSize);}});});if (this.renderModel.state === audio.AudioState.STATE_RELEASED) { // 如果渲染器状态为STATE_RELEASED,停止渲染fs.close(file);await this.renderModel.stop();}if (this.renderModel.state === audio.AudioState.STATE_RUNNING) {if (i === len - 1) { // 如果音频文件已经被读取完,停止渲染fs.close(file);await this.renderModel.stop();}}}}// 暂停渲染async pause() {// 只有渲染器状态为STATE_RUNNING的时候才能暂停if (this.renderModel.state !== audio.AudioState.STATE_RUNNING) {console.info('Renderer is not running');return;}await this.renderModel.pause(); // 暂停渲染if (this.renderModel.state === audio.AudioState.STATE_PAUSED) {console.info('Renderer is paused.');} else {console.error('Pausing renderer failed.');}}// 停止渲染async stop() {// 只有渲染器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止if (this.renderModel.state !== audio.AudioState.STATE_RUNNING && this.renderModel.state !== audio.AudioState.STATE_PAUSED) {console.info('Renderer is not running or paused.');return;}await this.renderModel.stop(); // 停止渲染if (this.renderModel.state === audio.AudioState.STATE_STOPPED) {console.info('Renderer stopped.');} else {console.error('Stopping renderer failed.');}}// 销毁实例,释放资源async release() {// 渲染器状态不是STATE_RELEASED状态,才能releaseif (this.renderModel.state === audio.AudioState.STATE_RELEASED) {console.info('Renderer already released');return;}await this.renderModel.release(); // 释放资源if (this.renderModel.state === audio.AudioState.STATE_RELEASED) {console.info('Renderer released');} else {console.error('Renderer release failed.');}}}
使用 AudioCapturer 录制本端的通话声音
该过程与使用AudioCapturer开发音频录制功能过程相似,关键区别在于 audioCapturerInfo 参数和音频数据流向。audioCapturerInfo 参数中音源类型需设置为语音通话,SOURCE_TYPE_VOICE_COMMUNICATION。
import audio from '@ohos.multimedia.audio';import fs from '@ohos.file.fs';const TAG = 'VoiceCallDemoForAudioCapturer';// 与使用AudioCapturer开发音频录制功能过程相似,关键区别在于audioCapturerInfo参数和音频数据流向export default class VoiceCallDemoForAudioCapturer {private audioCapturer = undefined;private audioStreamInfo = {samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, // 采样率channels: audio.AudioChannel.CHANNEL_1, // 通道sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式}private audioCapturerInfo = {// 需使用通话场景相应的参数source: audio.SourceType.SOURCE_TYPE_VOICE_COMMUNICATION, // 音源类型:语音通话capturerFlags: 0 // 音频采集器标志:默认为0即可}private audioCapturerOptions = {streamInfo: this.audioStreamInfo,capturerInfo: this.audioCapturerInfo}// 初始化,创建实例,设置监听事件init() {audio.createAudioCapturer(this.audioCapturerOptions, (err, capturer) => { // 创建AudioCapturer实例if (err) {console.error(`Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`);return;}console.info(`${TAG}: create AudioCapturer success`);this.audioCapturer = capturer;this.audioCapturer.on('markReach', 1000, (position) => { // 订阅markReach事件,当采集的帧数达到1000时触发回调if (position === 1000) {console.info('ON Triggered successfully');}});this.audioCapturer.on('periodReach', 2000, (position) => { // 订阅periodReach事件,当采集的帧数达到2000时触发回调if (position === 2000) {console.info('ON Triggered successfully');}});});}// 开始一次音频采集async start() {let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];if (stateGroup.indexOf(this.audioCapturer.state) === -1) { // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动采集console.error(`${TAG}: start failed`);return;}await this.audioCapturer.start(); // 启动采集// 此处仅以将音频数据写入文件举例,实际音频通话开发中,需要将本端采集的音频数据编码打包,通过网络发送给通话对端let context = getContext(this);const path = context.filesDir + '/voice_call_data.wav'; // 采集到的音频文件存储路径let file = fs.openSync(path, 0o2 | 0o100); // 如果文件不存在则创建文件let fd = file.fd;let numBuffersToCapture = 150; // 循环写入150次let count = 0;while (numBuffersToCapture) {let bufferSize = await this.audioCapturer.getBufferSize();let buffer = await this.audioCapturer.read(bufferSize, true);let options = {offset: count * bufferSize,length: bufferSize};if (buffer === undefined) {console.error(`${TAG}: read buffer failed`);} else {let number = fs.writeSync(fd, buffer, options);console.info(`${TAG}: write date: ${number}`);}numBuffersToCapture--;count++;}}// 停止采集async stop() {// 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止if (this.audioCapturer.state !== audio.AudioState.STATE_RUNNING && this.audioCapturer.state !== audio.AudioState.STATE_PAUSED) {console.info('Capturer is not running or paused');return;}await this.audioCapturer.stop(); // 停止采集if (this.audioCapturer.state === audio.AudioState.STATE_STOPPED) {console.info('Capturer stopped');} else {console.error('Capturer stop failed');}}// 销毁实例,释放资源async release() {// 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能releaseif (this.audioCapturer.state === audio.AudioState.STATE_RELEASED || this.audioCapturer.state === audio.AudioState.STATE_NEW) {console.info('Capturer already released');return;}await this.audioCapturer.release(); // 释放资源if (this.audioCapturer.state == audio.AudioState.STATE_RELEASED) {console.info('Capturer released');} else {console.error('Capturer release failed');}}}
相关文章:
HarmonyOS 音频通话开发指导
常用的音频通话模式包括 VOIP 通话和蜂窝通话。 ● VOIP 通话:VOIP(Voice over Internet Protocol)通话是指基于互联网协议(IP)进行通讯的一种语音通话技术。VOIP 通话会将通话信息打包成数据包,通过网络进…...
LeetCode讲解篇之面试题 01.08. 零矩阵
文章目录 题目描述题解思路题解代码 题目描述 题解思路 遍历矩阵,若当前元素为零,则将该行和该列的第一个元素置零 遍历第一行,若当前元素为零,则将当前列置零 遍历第一列,若当前元素为零,则将当前行置零 …...
安装python虚拟环境
什么是虚拟环境: 虚拟环境的意义,就如同 虚拟机 一样,它可以实现不同环境中Python依赖包相互独立,互不干扰。 环境准备 安装python (到官网下载Download Python配置环境变量,cmd进入命令行输入 python…...
【App 抓包提示网络异常怎么破?】
背景 当你测试App的时候,想要通过Fiddler/Charles等工具抓包看下https请求的数据情况,发现大部分的App都提示网络异常/无数据等等信息。以“贝壳找房”为例: 455 x 705 Fiddler中看到的请求是这样的: 619 x 215 你可能开始找证书的问题:是不是Fiddler/Charles的证书没有…...
【开发篇】一、处理函数:定时器与定时服务
文章目录 1、基本处理函数2、定时器和定时服务3、KeyedProcessFunction下演示定时器4、process重获取当前watermark 前面API篇完结,对数据的转换、聚合、窗口等,都是基于DataStream的,称DataStreamAPI,如图: 在Flink…...
重入漏洞EtherStore
重入漏洞 // SPDX-License-Identifier: MIT pragma solidity ^0.8.13;contract EtherStore {mapping(address > uint) public balances;function deposit() public payable {balances[msg.sender] msg.value;}function withdraw() public {uint bal balances[msg.sender]…...
账号运营的底层逻辑---获客思维
什么是运营? 运营是做什么的? 什么是内容运营? 什么是活动运营? 一篇带你搞清楚所有的底层逻辑!...
Pinia中如何实现数据持久化操作
使用vue3中的pinia,我们可以在多个页面间共享数据,但是一旦我们关闭或刷新页面,这些数据就会丢失,因此,我们需要有一种数据持久化的解决方案。在记录vue3 使用vue3中的pinia,我们可以在多个页面间共享数据&…...
【owt-server】RTC视频接收调用流程学习笔记1: Call::CreateVideoReceiveStream 前后
WebRTC源码分析——Call模块 大神提到,call模块是在worker线程创建的。主要创建接收、发送流Call模块是WebRTC会话中不可缺少的一个模块,一个Call对象可以包含多个发送/接收流,且这些流对应同一个远端端点,并共享码率估计。 call中通过webrtc::VideoReceiveStream::Config …...
淘宝商品链接获取淘宝商品评论数据(用 Python实现淘宝商品评论信息抓取)
在网页抓取方面,可以使用 Python、Java 等编程语言编写程序,通过模拟 HTTP 请求,获取淘宝多网站上的商品详情页面评论内容。在数据提取方面,可以使用正则表达式、XPath 等方式从 HTML 代码中提取出有用的信息。值得注意的是&#…...
十九、类型信息(1)
本章概要 为什么需要 RTTI RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息 RTTI 把我们从只能在编译期进行面向类型操作的禁锢中解脱了出来,并且让我们可以使用某些非常强大的程序。对 RTTI …...
十八、字符串(3)
本章概要 正则表达式 基础创建正则表达式量词CharSequencePattern 和 Matcherfinde()组(Groups)start() 和 end()Pattern 标记split()替换操作reset()正则表达式与 Java I/0 正则表达式 很久之前,_正则表达式_就已经整合到标准 Unix 工具…...
基于SSM的酒店预约及管理系统设计与实现
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…...
MIxformerV2的onnx和tensorrt加速
MIxformerV2的onnx和tensorrt加速 注意事项 地址:github地址 注意事项 转换成onnx模型之前,最好现简化算法的源代码,使其结构干净。因为在进行onnx转换后,可能在进行onnx→trt时算子不匹配,这时就需要去查看模型的源…...
Kotlin 中let 、run 、with、apply、also的用法与区别
实例代码 User(val userName:String,val age:Int){fun printName(){println(userName)}fun getUserName():String{return userName}} let 函数 let 函数常用来与对象的空判断一起用,起到作用于的限定效果。let 函数最后一行返回值。(比如实例需要let函…...
PHP函数的定义与最简单后门原理
PHP函数的定义与最简单后门原理 文章目录 PHP函数的定义与最简单后门原理函数的定义函数调用的过程变量的范围局部变量全局变量 可变函数动态函数 PHP 最简单后门原理分析 函数的定义 使用function关键字来定义一个函数定义函数的函数名避开关键字形式参数是传递映射的实际参数…...
PlantSimulation访问本地Excel文件的方法
PlantSimulation访问本地Excel文件的方法 PlantSimulation访问本地Excel文件的方法PlantSimulation访问本地Excel文件的方法 //Param StatusTable,T_DataTable:object var T_DataTable:object:=DataTable IF NOT isComputerAccessPermittedMESSageBox("计算机访问被阻止,…...
使用微PE工具箱制作winU盘启动盘~重装系统
1.准备一个大于8G的U盘,为了保证传输和安装速度请确保U盘的质量。 2.鼠标右键点击U盘,进行格式化: 3.下载微PE工具箱: 微PE工具箱 - 下载 4.安装微PE工具箱:选择安装到U盘 5.选择U盘后,开始安装…...
漏洞复现-jquery-picture-cut 任意文件上传_(CVE-2018-9208)
jquery-picture-cut 任意文件上传_(CVE-2018-9208) 漏洞信息 jQuery Picture Cut v1.1以下版本中存在安全漏洞CVE-2018-9208文件上传漏洞 描述 picture cut是一个jquery插件,以友好和简单的方式处理图像,具有基于bootstrap…...
Golang Websocket框架:实时通信的新选择
前言 在现代应用程序中,实时通信已经成为了一种必需的特性。而Websocket是一种在客户端和服务器之间建立持久连接的协议,可以实现实时的双向通信。Golang作为一门高效且简洁的语言,也提供了一些优秀的Websocket框架,方便开发者构…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
