当前位置: 首页 > news >正文

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.wav    let 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状态,才能release    if (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状态,才能release    if (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 通话&#xff1a;VOIP&#xff08;Voice over Internet Protocol&#xff09;通话是指基于互联网协议&#xff08;IP&#xff09;进行通讯的一种语音通话技术。VOIP 通话会将通话信息打包成数据包&#xff0c;通过网络进…...

LeetCode讲解篇之面试题 01.08. 零矩阵

文章目录 题目描述题解思路题解代码 题目描述 题解思路 遍历矩阵&#xff0c;若当前元素为零&#xff0c;则将该行和该列的第一个元素置零 遍历第一行&#xff0c;若当前元素为零&#xff0c;则将当前列置零 遍历第一列&#xff0c;若当前元素为零&#xff0c;则将当前行置零 …...

安装python虚拟环境

什么是虚拟环境&#xff1a; 虚拟环境的意义&#xff0c;就如同 虚拟机 一样&#xff0c;它可以实现不同环境中Python依赖包相互独立&#xff0c;互不干扰。 环境准备 安装python &#xff08;到官网下载Download Python​配置环境变量&#xff0c;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篇完结&#xff0c;对数据的转换、聚合、窗口等&#xff0c;都是基于DataStream的&#xff0c;称DataStreamAPI&#xff0c;如图&#xff1a; 在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]…...

账号运营的底层逻辑---获客思维

什么是运营&#xff1f; 运营是做什么的&#xff1f; 什么是内容运营&#xff1f; 什么是活动运营&#xff1f; 一篇带你搞清楚所有的底层逻辑&#xff01;...

Pinia中如何实现数据持久化操作

使用vue3中的pinia&#xff0c;我们可以在多个页面间共享数据&#xff0c;但是一旦我们关闭或刷新页面&#xff0c;这些数据就会丢失&#xff0c;因此&#xff0c;我们需要有一种数据持久化的解决方案。在记录vue3 使用vue3中的pinia&#xff0c;我们可以在多个页面间共享数据&…...

【owt-server】RTC视频接收调用流程学习笔记1: Call::CreateVideoReceiveStream 前后

WebRTC源码分析——Call模块 大神提到,call模块是在worker线程创建的。主要创建接收、发送流Call模块是WebRTC会话中不可缺少的一个模块,一个Call对象可以包含多个发送/接收流,且这些流对应同一个远端端点,并共享码率估计。 call中通过webrtc::VideoReceiveStream::Config …...

淘宝商品链接获取淘宝商品评论数据(用 Python实现淘宝商品评论信息抓取)

在网页抓取方面&#xff0c;可以使用 Python、Java 等编程语言编写程序&#xff0c;通过模拟 HTTP 请求&#xff0c;获取淘宝多网站上的商品详情页面评论内容。在数据提取方面&#xff0c;可以使用正则表达式、XPath 等方式从 HTML 代码中提取出有用的信息。值得注意的是&#…...

十九、类型信息(1)

本章概要 为什么需要 RTTI RTTI&#xff08;RunTime Type Information&#xff0c;运行时类型信息&#xff09;能够在程序运行时发现和使用类型信息 RTTI 把我们从只能在编译期进行面向类型操作的禁锢中解脱了出来&#xff0c;并且让我们可以使用某些非常强大的程序。对 RTTI …...

十八、字符串(3)

本章概要 正则表达式 基础创建正则表达式量词CharSequencePattern 和 Matcherfinde()组&#xff08;Groups&#xff09;start() 和 end()Pattern 标记split()替换操作reset()正则表达式与 Java I/0 正则表达式 很久之前&#xff0c;_正则表达式_就已经整合到标准 Unix 工具…...

基于SSM的酒店预约及管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…...

MIxformerV2的onnx和tensorrt加速

MIxformerV2的onnx和tensorrt加速 注意事项 地址&#xff1a;github地址 注意事项 转换成onnx模型之前&#xff0c;最好现简化算法的源代码&#xff0c;使其结构干净。因为在进行onnx转换后&#xff0c;可能在进行onnx→trt时算子不匹配&#xff0c;这时就需要去查看模型的源…...

Kotlin 中let 、run 、with、apply、also的用法与区别

实例代码 User(val userName:String,val age:Int){fun printName(){println(userName)}fun getUserName():String{return userName}} let 函数 let 函数常用来与对象的空判断一起用&#xff0c;起到作用于的限定效果。let 函数最后一行返回值。&#xff08;比如实例需要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盘&#xff0c;为了保证传输和安装速度请确保U盘的质量。 2.鼠标右键点击U盘&#xff0c;进行格式化&#xff1a; 3.下载微PE工具箱&#xff1a; 微PE工具箱 - 下载 4.安装微PE工具箱&#xff1a;选择安装到U盘 5.选择U盘后&#xff0c;开始安装&#xf…...

漏洞复现-jquery-picture-cut 任意文件上传_(CVE-2018-9208)

jquery-picture-cut 任意文件上传_&#xff08;CVE-2018-9208&#xff09; 漏洞信息 jQuery Picture Cut v1.1以下版本中存在安全漏洞CVE-2018-9208文件上传漏洞 描述 ​ picture cut是一个jquery插件&#xff0c;以友好和简单的方式处理图像&#xff0c;具有基于bootstrap…...

Golang Websocket框架:实时通信的新选择

前言 在现代应用程序中&#xff0c;实时通信已经成为了一种必需的特性。而Websocket是一种在客户端和服务器之间建立持久连接的协议&#xff0c;可以实现实时的双向通信。Golang作为一门高效且简洁的语言&#xff0c;也提供了一些优秀的Websocket框架&#xff0c;方便开发者构…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战&#xff0c;克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...