鸿蒙媒体开发系列04——音频播放

news2024/9/20 9:43:09

 如果你也对鸿蒙开发感兴趣,加入“Harmony自习室”吧!扫描下方名片,关注公众号,公众号更新更快,同时也有更多学习资料和技术讨论群。

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

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

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

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

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

  • 在音频播放中,应用时常需要用到一些急促简短的音效,如相机快门音效、按键音效、游戏射击音效等,当前只能使用AVPlayer播放音频文件替代实现,在HarmonyOS后续版本将会推出相关接口来支持该场景。

2、使用AVPlayer开发音频播放功能

使用AVPlayer可以实现端到端播放原始媒体资源,本文将以完整地播放一首音乐作为示例,讲解AVPlayer音频播放相关功能。

播放的全流程包含:创建AVPlayer,设置播放资源,设置播放参数(音量/倍速/焦点模式),播放控制(播放/暂停/跳转/停止),重置,销毁资源。

在进行应用开发的过程中,我们可以通过AVPlayer的state属性主动获取当前状态或使用on('stateChange')方法监听状态变化。如果应用在音频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。如下图所示:

图片

当播放处于prepared / playing / paused / completed状态时,播放引擎处于工作状态,这需要占用系统较多的运行内存。当客户端暂时不使用播放器时,调用reset()或release()回收内存资源,做好资源利用。

2.1、开发步骤及注意事项

  1. 创建实例createAVPlayer(),AVPlayer初始化idle状态。

  2. 设置业务需要的监听事件,搭配全流程场景使用。支持的监听事件如下:

图片

3. 设置资源:设置属性url,AVPlayer进入initialized状态。

4. 准备播放:调用prepare(),AVPlayer进入prepared状态,此时可以获取duration,设置音量。

5. 音频播控:播放play(),暂停pause(),跳转seek(),停止stop() 等操作。

6. (可选)更换资源:调用reset()重置资源,AVPlayer重新进入idle状态,允许更换资源url。

7. 退出播放:调用release()销毁实例,AVPlayer进入released状态,退出播放。

2.2、Demo

参考以下示例,完整地播放一首音乐。

import media from '@ohos.multimedia.media';import fs from '@ohos.file.fs';import common from '@ohos.app.ability.common';export class AVPlayerDemo {  private avPlayer;  private isSeek: boolean = true; // 用于区分模式是否支持seek操作  private count: number = 0;  // 注册avplayer回调函数  setAVPlayerCallback() {    // seek操作结果回调函数    this.avPlayer.on('seekDone', (seekDoneTime) => {      console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);    })    // error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程    this.avPlayer.on('error', (err) => {      console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);      this.avPlayer.reset(); // 调用reset重置资源,触发idle状态    })    // 状态机变化回调函数    this.avPlayer.on('stateChange', async (state, reason) => {      switch (state) {        case 'idle': // 成功调用reset接口后触发该状态机上报          console.info('AVPlayer state idle called.');          this.avPlayer.release(); // 调用release接口销毁实例对象          break;        case 'initialized': // avplayer 设置播放源后触发该状态上报          console.info('AVPlayerstate initialized called.');          this.avPlayer.prepare().then(() => {            console.info('AVPlayer prepare succeeded.');          }, (err) => {            console.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`);          });          break;        case 'prepared': // prepare调用成功后上报该状态机          console.info('AVPlayer state prepared called.');          this.avPlayer.play(); // 调用播放接口开始播放          break;        case 'playing': // play成功调用后触发该状态机上报          console.info('AVPlayer state playing called.');          if (this.count !== 0) {            console.info('AVPlayer start to seek.');            this.avPlayer.seek(this.avPlayer.duration); //seek到音频末尾          } else {            this.avPlayer.pause(); // 调用暂停接口暂停播放          }          this.count++;          break;        case 'paused': // pause成功调用后触发该状态机上报          console.info('AVPlayer state paused called.');          this.avPlayer.play(); // 再次播放接口开始播放          break;        case 'completed': // 播放结束后触发该状态机上报          console.info('AVPlayer state completed called.');          this.avPlayer.stop(); //调用播放结束接口          break;        case 'stopped': // stop接口成功调用后触发该状态机上报          console.info('AVPlayer state stopped called.');          this.avPlayer.reset(); // 调用reset接口初始化avplayer状态          break;        case 'released':          console.info('AVPlayer state released called.');          break;        default:          console.info('AVPlayer state unknown called.');          break;      }    })  }  // 以下demo为使用fs文件系统打开沙箱地址获取媒体文件地址并通过url属性进行播放示例  async avPlayerUrlDemo() {    // 创建avPlayer实例对象    this.avPlayer = await media.createAVPlayer();    // 创建状态机变化回调函数    this.setAVPlayerCallback();    let fdPath = 'fd://';    // 通过UIAbilityContext获取沙箱地址filesDir,以下为Stage模型获方式,如需在FA模型上获取请参考《访问应用沙箱》获取地址    let context = getContext(this) as common.UIAbilityContext;    let pathDir = context.filesDir;    let path = pathDir + '/01.mp3';    // 打开相应的资源文件地址获取fd,并为url赋值触发initialized状态机上报    let file = await fs.open(path);    fdPath = fdPath + '' + file.fd;    this.avPlayer.url = fdPath;  }  // 以下demo为使用资源管理接口获取打包在HAP内的媒体资源文件并通过fdSrc属性进行播放示例  async avPlayerFdSrcDemo() {    // 创建avPlayer实例对象    this.avPlayer = await media.createAVPlayer();    // 创建状态机变化回调函数    this.setAVPlayerCallback();    // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址    // 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度    let context = getContext(this) as common.UIAbilityContext;    let fileDescriptor = await context.resourceManager.getRawFd('01.mp3');    // 为fdSrc赋值触发initialized状态机上报    this.avPlayer.fdSrc = fileDescriptor;    this.isSeek = false; // 不支持seek操作  }}

3、使用AudioRenderer开发音频播放功能

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

3.1、开发指导

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

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

为保证UI线程不被阻塞,大部分AudioRenderer调用都是异步的。对于每个API均提供了callback函数和Promise函数,以下示例均采用callback函数。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()方法释放掉所有占用的硬件和软件资源,并且不会再进入到其他的任何一种状态了。

3.2、开发步骤及注意事项

1. 配置音频渲染参数并创建AudioRenderer实例。

import audio from '@ohos.multimedia.audio';let audioStreamInfo = {  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,  channels: audio.AudioChannel.CHANNEL_1,  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW};let audioRendererInfo = {  content: audio.ContentType.CONTENT_TYPE_SPEECH,  usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,  rendererFlags: 0};let 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. 调用start()方法进入running状态,开始渲染音频。​​​​​​​

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

3. 指定待渲染文件地址,打开文件调用write()方法向缓冲区持续写入音频数据进行渲染播放。如果需要对音频数据进行处理以实现个性化的播放,在写入之前操作即可。​​​​​​​

const bufferSize = await audioRenderer.getBufferSize();let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);let buf = new ArrayBuffer(bufferSize);let readsize = await fs.read(file.fd, buf);let writeSize = await new Promise((resolve, reject) => {  audioRenderer.write(buf, (err, writeSize) => {    if (err) {      reject(err);    } else {      resolve(writeSize);    }  });});

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

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

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

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

3.3、Demo

下面展示了使用AudioRenderer渲染音频文件的示例代码。​​​​​​​

import audio from '@ohos.multimedia.audio';import fs from '@ohos.file.fs';const TAG = 'AudioRendererDemo';export default class AudioRendererDemo {  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_MUSIC, // 媒体类型    usage: audio.StreamUsage.STREAM_USAGE_MEDIA, // 音频流使用类型    rendererFlags: 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 == 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) { // 当且仅当状态为prepared、paused和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 + '/test.wav'; // 使用沙箱路径获取文件,实际路径为/data/storage/el2/base/haps/entry/files/test.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) { // 如果渲染器状态为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() {    // 只有渲染器状态为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() {    // 只有渲染器状态为running或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() {    // 渲染器状态不是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.');    }  }}

当同优先级或高优先级音频流要使用输出设备时,当前音频流会被中断,应用可以自行响应中断事件并做出处理。

4、使用OpenSL ES开发音频播放功能

OpenSL ES全称为Open Sound Library for Embedded Systems,是一个嵌入式、跨平台、免费的音频处理库。为嵌入式移动多媒体设备上的应用开发者提供标准化、高性能、低延迟的API。HarmonyOS的Native API基于Khronos Group开发的OpenSL ES 1.0.1 API 规范实现,开发者可以通过和在HarmonyOS上使用相关API。

4.1、HarmonyOS上的OpenSL ES

OpenSL ES中提供了以下的接口,HarmonyOS当前仅实现了部分OpenSL ES接口,可以实现音频播放的基础功能。

调用未实现接口后会返回SL_RESULT_FEATURE_UNSUPPORTED,当前没有相关扩展可以使用。

以下列表列举了HarmonyOS上已实现的OpenSL ES的接口,具体说明请参考OpenSL ES规范:

  • HarmonyOS上支持的Engine接口:

    • SLresult (*CreateAudioPlayer) (SLEngineItf self, SLObjectItf * pPlayer, SLDataSource *pAudioSrc, SLDataSink *pAudioSnk, SLuint32 numInterfaces, const SLInterfaceID * pInterfaceIds, const SLboolean * pInterfaceRequired)

    • SLresult (*CreateAudioRecorder) (SLEngineItf self, SLObjectItf * pRecorder, SLDataSource *pAudioSrc, SLDataSink *pAudioSnk, SLuint32 numInterfaces, const SLInterfaceID * pInterfaceIds, const SLboolean * pInterfaceRequired)

    • SLresult (*CreateOutputMix) (SLEngineItf self, SLObjectItf * pMix, SLuint32 numInterfaces, const SLInterfaceID * pInterfaceIds, const SLboolean * pInterfaceRequired)

  • HarmonyOS上支持的Object接口:

    • SLresult (*Realize) (SLObjectItf self, SLboolean async)

    • SLresult (*GetState) (SLObjectItf self, SLuint32 * pState)

    • SLresult (*GetInterface) (SLObjectItf self, const SLInterfaceID iid, void * pInterface)

    • void (*Destroy) (SLObjectItf self)

  • HarmonyOS上支持的Playback接口:

    • SLresult (*SetPlayState) (SLPlayItf self, SLuint32 state)

    • SLresult (*GetPlayState) (SLPlayItf self, SLuint32 *pState)

  • HarmonyOS上支持的Volume控制接口:

    • SLresult (*SetVolumeLevel) (SLVolumeItf self, SLmillibel level)

    • SLresult (*GetVolumeLevel) (SLVolumeItf self, SLmillibel *pLevel)

    • SLresult (*GetMaxVolumeLevel) (SLVolumeItf self, SLmillibel *pMaxLevel)

  • HarmonyOS上支持的BufferQueue接口:

    以下接口需引入<OpenSLES_OpenHarmony.h>使用。

4.2、Demo

【来自官方API介绍】

  1. 添加头文件。

#include "SLES/OpenSLES.h"#include "SLES/OpenSLES_OpenHarmony.h"#include "SLES/OpenSLES_Platform.h"

2. 使用slCreateEngine接口和获取engine实例。​​​​​​​

SLObjectItf engineObject = nullptr;slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);

3. 获取接口SL_IID_ENGINE的engineEngine实例。​​​​​​​

SLEngineItf engineEngine = nullptr;(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);

4. 配置播放器信息,创建AudioPlayer。​​​​​​​

SLDataLocator_BufferQueue slBufferQueue = {    SL_DATALOCATOR_BUFFERQUEUE,    0};// 具体参数需要根据音频文件格式进行适配SLDataFormat_PCM pcmFormat = {    SL_DATAFORMAT_PCM,    2,                           // 通道数    SL_SAMPLINGRATE_48,          // 采样率    SL_PCMSAMPLEFORMAT_FIXED_16, // 音频采样格式    0,    0,    0};SLDataSource slSource = {&slBufferQueue, &pcmFormat};SLObjectItf pcmPlayerObject = nullptr;(*engineEngine)->CreateAudioPlayer(engineEngine, &pcmPlayerObject, &slSource, nullptr, 0, nullptr, nullptr);(*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);

5. 获取接口SL_IID_OH_BUFFERQUEUE的bufferQueueItf实例。​​​​​​​

SLOHBufferQueueItf bufferQueueItf;(*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_OH_BUFFERQUEUE, &bufferQueueItf);

6. 打开音频文件,注册BufferQueueCallback回调。​​​​​​​

static void BufferQueueCallback (SLOHBufferQueueItf bufferQueueItf, void *pContext, SLuint32 size){    SLuint8 *buffer = nullptr;    SLuint32 pSize;    (*bufferQueueItf)->GetBuffer(bufferQueueItf, &buffer, &pSize);    // 将待播放音频数据写入buffer    (*bufferQueueItf)->Enqueue(bufferQueueItf, buffer, size);}void *pContext; // 可传入自定义的上下文信息,会在Callback内收到(*bufferQueueItf)->RegisterCallback(bufferQueueItf, BufferQueueCallback, pContext);

7. 获取接口SL_PLAYSTATE_PLAYING的playItf实例,开始播放。​​​​​​​

SLPlayItf playItf = nullptr;(*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &playItf);(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);

8. 结束音频播放。​​​​​​​

(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);(*pcmPlayerObject)->Destroy(pcmPlayerObject);(*engineObject)->Destroy(engineObject);

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

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

相关文章

C++_map_set详解

关联容器的基本介绍 关联容器支持高效的关键字查找和访问。map和set是最主要关联容器。关联容器也是用来存储数据的&#xff0c;与序列式容器不同的是&#xff0c;其里面存储的是<key, value>结构的键值对&#xff0c;在数据检索时比序列式容器效率更高。C标准库中提供了…

如何关闭前端Chrome的debugger反调试

1、禁用浏览器断点 2. 把控制台独立一个窗口

如何优雅地处理返回值

我们已经知道了如何优雅的校验传入的参数了&#xff0c;那么后端服务器如何实现把数据返回给前端呢&#xff1f; 返回格式 后端返回给前端我们一般用 JSON 体方式&#xff0c;定义如下&#xff1a; {#返回状态码code:string, #返回信息描述message:string,#返回值data…

《Google软件测试之道》笔记

介绍 GTAC&#xff1a;Google Test Automation Conference&#xff0c;Google测试自动化大会。 本书出版之前还有一本《微软测试之道》&#xff0c;值得阅读。 质量不是被测试出来的&#xff0c;但未经测试也不可能开发出有质量的软件。质量是开发过程的问题&#xff0c;而不…

09年408考研真题解析-计算机网络

[题34]在无噪声情况下&#xff0c;若某通信链路的带宽为3kHz&#xff0c;采用4个相位&#xff0c;每个相位具有4种振幅的QAM调制技术,则该通信链路的最大数据传输速率是&#xff08;B&#xff09; A.12 kbps B.24 kbps C.48 kbps D.96 kbps 解析&#xff…

优惠充值话费api对接如何选择对接平台?

优惠充值话费接口通常由电信运营商、第三方支付平台或专业的充值服务提供商提供。这些平台通过API接口允许开发者将话费充值功能集成到应用程序或网站中。 选择哪个平台比较好&#xff0c;取决于以下几个因素&#xff1a; 覆盖范围&#xff1a;选择能够覆盖你需要服务的地区和…

49.面向对象综合训练-朋友

1.创建JavaBean类 public class Friend {//题目要求&#xff1a;定义数组存储4个朋友对象//属性&#xff1a;姓名&#xff0c;年龄&#xff0c;性别&#xff0c;爱好//计算出四位朋友的平均年龄//统计出比平均年龄低的朋友有几个&#xff0c;并把信息都打印出来private String…

openstack之keystone介绍

功能 keystone在OpenStack中负责&#xff1a; 管理&#xff1a;用户、租户和权限&#xff1b; 认证&#xff1a;组件相互访问的身份认证&#xff1b; 鉴权&#xff1a;提供 RBAC&#xff08;Role Based Access Control&#xff09; 权限体系&#xff1b; 服务注册与发现&#…

windows系统docker装milvus向量数据库

首先创建一个文件夹比如milvus,在创建如下文件 docker-compose.yml文件如下: version: 3.5services:etcd:container_name: milvus-etcdimage: quay.io/coreos/etcd:v3.5.5environment:- ETCD_AUTO_COMPACTION_MODErevision- ETCD_AUTO_COMPACTION_RETENTION1000- ETCD_QUOTA_B…

Redis重要知识点:哨兵是什么?哨兵如何选择Redis主服务器

引言 哨兵贼简单&#xff0c;就是一个节点去看守领一个节点有没有挂掉&#xff0c;挂掉的数量比较多那得新选主节点了。 1. Redis哨兵 1.1 哨兵作用 哨兵的含义是什么&#xff1f;我们来看看百度百科的解释。 哨兵&#xff0c;汉语词语&#xff0c;是指站岗、放哨、巡逻、稽…

关于less的基本使用

1、介绍及概述 1.1、解释 less 是方便开发人员书写CSS的一门预处理语言。浏览器只认识html /css /js格式的文件&#xff0c;所以直接引入.less文件&#xff0c;没有任何的效果&#xff0c;需要把less文件转换成css文件 1.2、概述 CSS弊端&#xff1a; 没有逻辑性、变量、函…

【Paper Reading】结合 NanoFlow 研究,优化大语言模型服务效率的探索

作者 王伟 PAI引擎团队 近年来&#xff0c;人工智能领域的快速发展推动了大型语言模型的广泛应用&#xff0c;随之而来的是对其服务效率的迫切需求。论文《NanoFlow&#xff1a;Towards Optimal Large Language Model Serving Throughput》提出了一种突破性的新型服务框架&…

刚接触无处下手?水下航行器AUV/UUV六自由度模型/控制器设计matlab/simulink参考代码,基础的/进阶的,入门到顺利毕业/完成课题/发表论文。

导师不管&#xff1f;无人指导&#xff1f;无代码可参考&#xff1f;毫无头绪&#xff1f;换条思路借鉴一下吧&#xff0c;金钱买不到时间&#xff0c;但可以让你更多的支配你自己的时间&#xff0c;没错的&#xff0c;条条大路通罗马&#xff0c;毕竟前程是自己的&#xff0c;…

时序必读论文11|ICLR23 TimesNet时间序列分析的二维变化建模

论文标题&#xff1a;TEMPORAL 2D-VARIATION MODELING FOR GENERAL TIME SERIES ANALYSIS 开源代码&#xff1a;https://github.com/Thinklab-SJTU/Crossformer 前言 时间序列分析中&#xff0c;如何有效地建模时序数据中的时间变化是关键&#xff0c;然而直接从一维时序数据…

css <样式一>

1. 盒子模型 1.1>boarder 在这里插入图片描述 boarder 相邻框合并问题 boarder-classpse 相同的边框会合并在一起 text-alicn center 文字居中对齐 ########### boarder 会撑大盒子的实际大小 一个盒子加了boarder之后会变大的我可以把我的盒子内容进行修改, 减少像素内…

细胞分裂检测系统源码分享

细胞分裂检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

大数据Flink(一百二十):Flink SQL自定义函数(UDF)

文章目录 Flink SQL自定义函数&#xff08;UDF&#xff09; 一、概述 二、​​​​​​​自定义标量函数&#xff08;UDSF&#xff09; 三、​​​​​​​​​​​​​​自定义聚合函数(UDAF) 四、 ​​​​​​​​​​​​​​自定义表值函数(UDTF) Flink SQL自定义函数…

re题(20)BUUCTF [GWCTF 2019]pyre

BUUCTF在线评测 (buuoj.cn) Python解包及反编译: PyInstaller Extractoruncompyle6 - 知乎 (zhihu.com) python撤消&#xff1a; Pycharm撤销操作和代码跳转后退回操作以及消除波浪线操作快捷键_pycharm怎么反撤销-CSDN博客 把.pyc文件变成py文件 把.py文件用记事本打开 cod…

supermap iclient3d for cesium模型沿路径移动

可以直接settimeout隔一段时间直接设置位置属性&#xff0c;但是得到的结果模型不是连续的移动&#xff0c;如果想要连续的移动&#xff0c;就需要设置一个时间轴&#xff0c;然后给模型传入不同时间时的位置信息&#xff0c;然后就可以了。 开启时间轴 let start Cesium.Jul…

负载均衡:从理论到实践 ---day04

负载均衡 负载均衡1.什么是负载均衡2.负载均衡的分类硬件负载均衡软件负载均衡选择 3.引入负载均衡的好处 第一个Ribbon实例步骤1&#xff1a;步骤2&#xff1a;步骤3&#xff1a;步骤4&#xff1a; 问题1. 负载均衡的主要目标是什么&#xff1f;2. 负载均衡器的作用是什么&…