惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
C
Check Point Blog
博客园_首页
B
Blog
D
Docker
U
Unit 42
量子位
I
InfoQ
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
GbyAI
GbyAI
L
LangChain Blog
云风的 BLOG
云风的 BLOG
博客园 - Franky
美团技术团队
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
Vercel News
Vercel News
Recent Announcements
Recent Announcements
雷峰网
雷峰网
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Google DeepMind News
Google DeepMind News

博客园 - webabcd

开天辟地 HarmonyOS(鸿蒙) - 工具: Targets, Products 开天辟地 HarmonyOS(鸿蒙) - 测试: 单元测试 开天辟地 HarmonyOS(鸿蒙) - 优化: HiDebug(用于获取 cpu, 内存等信息) 开天辟地 HarmonyOS(鸿蒙) - 优化: errorManager(捕获未处理异常) 开天辟地 HarmonyOS(鸿蒙) - 优化: HiAppEvent(事件日志) 开天辟地 HarmonyOS(鸿蒙) - 优化: hilog 日志 开天辟地 HarmonyOS(鸿蒙) - 安全: 用户认证 开天辟地 HarmonyOS(鸿蒙) - 安全: 申请权限 开天辟地 HarmonyOS(鸿蒙) - 安全: 关键资产 开天辟地 HarmonyOS(鸿蒙) - 安全: 加密解密 开天辟地 HarmonyOS(鸿蒙) - 信息: CommonEventManager(用于进程间通信,以及订阅系统事件) 开天辟地 HarmonyOS(鸿蒙) - 信息: emitter(用于同一进程内不同线程间或同一线程内发送和订阅事件) 开天辟地 HarmonyOS(鸿蒙) - 信息: 各种信息 开天辟地 HarmonyOS(鸿蒙) - 华为账号: 华为账号的默认登录 开天辟地 HarmonyOS(鸿蒙) - 元服务: 元服务 开天辟地 HarmonyOS(鸿蒙) - 媒体: AVImageGenerator(提取视频指定时间点的图像) 开天辟地 HarmonyOS(鸿蒙) - 媒体: AVMetadataExtractor(提取视频或音频的元数据信息) 开天辟地 HarmonyOS(鸿蒙) - 媒体: AVTranscoder(视频转码) 开天辟地 HarmonyOS(鸿蒙) - 媒体: AVPlayer(播放器,用于播放视频或音频)
开天辟地 HarmonyOS(鸿蒙) - 媒体: SoundPool(音效播放器)
webabcd · 2025-05-27 · via 博客园 - webabcd

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

开天辟地 HarmonyOS(鸿蒙) - 媒体: SoundPool(音效播放器)

示例如下:

pages\media\SoundPoolDemo.ets

/*
 * SoundPool - 音效播放器,用于低时延播放短音频(要求音频解码后在 1MB 以内)
 *
 * 大致的流程为:
 * 1、创建 SoundPool 实例,并监听相关事件
 * 2、加载指定的短音频文件,拿到对应的 soundId
 * 3、根据 soundId 播放短音频(如果指定的 soundId 正在播放,则会将其停止,然后重新播放),并拿到对应的 streamId
 * 4、根据 streamId 停止短音频的播放,根据 soundId 卸载短音频文件
 */

import { TitleBar } from '../TitleBar'
import { BusinessError } from '@kit.BasicServicesKit'
import { media } from '@kit.MediaKit'
import { LengthMetrics } from '@kit.ArkUI'
import { fileIo as fs } from '@kit.CoreFileKit';
import { audio } from '@kit.AudioKit';

@Entry
@Component
struct SoundPoolDemo {

  build() {
    Column({ space: 10 }) {
      TitleBar()
      Tabs() {
        TabContent() { MySample1() }.tabBar('基础').align(Alignment.Top)
      }
      .scrollable(true)
      .barMode(BarMode.Scrollable)
      .layoutWeight(1)
    }
  }
}

@Component
struct MySample1 {

  @State message: string = ""
  soundIdList: number[] = []
  streamIdList: Set<number> = new Set<number>()

  /*
   * SoundPool - 音效播放器
   *   media.createSoundPool() - 创建 SoundPool 对象
   *     maxStreams - 音频池内的最大的音频流的数量(1 - 32 之间)
   *     audioRenderInfo - 音频渲染器的信息
   *       usage - 音频流的类型,写死 audio.StreamUsage.STREAM_USAGE_MUSIC 即可
   *       rendererFlags - 音频渲染器标志,写死 0 即可
   *     注:api 18 或以上,一个进程最多可以创建 128 个 SoundPool 实例;api 18 以下,一个进程只能创建 1 个 SoundPool 实例
   *   on('loadComplete')/off('loadComplete') - 短音频文件加载完成时的回调
   *   on('playFinished')/off('playFinished') - 短音频播放完成时的回调
   *   on('playFinishedWithStreamId')/off('playFinishedWithStreamId')/ - 短音频播放完成时的回调,可以从回调参数中拿到 streamId
   *     ‘playFinishedWithStreamId’ 仅在 api 18 或以上有效
   *     如果 ‘playFinished’ 和 ‘playFinishedWithStreamId’ 都监听了,则只会触发 ‘playFinishedWithStreamId’
   *   on('error') - 发生异常时的回调
   *   load() - 通过 fd:// 协议的方式或 FileDescriptor 的方式加载短音频文件,返回的是 soundId
   *   play() - 根据 soundId 播放指定的短音频
   *     soundID - 需要播放的 soundId
   *     params - 播放参数(一个 PlayParameters 对象)
   *       loop - 循环次数(比如设置为 2 时,则代表一共播放 3 次)
   *       rate - 播放倍速(AudioRendererRate 枚举)
   *         RENDER_RATE_NORMAL - 正常倍速
   *         RENDER_RATE_DOUBLE - 2 倍速
   *         RENDER_RATE_HALF - 0.5 倍速
   *       leftVolume - 左声道音量(0 - 1 之间)
   *       rightVolume - 右声道音量(0 - 1 之间)
   *       priority - 优先级(0 代表最低优先级)
   *     callback - 从此回调中可以拿到 streamId
   *   stop() - 停止指定的 streamId 的播放
   *   unload() - 卸载指定的 soundId 的文件
   *   release() - 清理资源
   */
  soundPool: media.SoundPool | undefined

  async aboutToAppear() {
    await this.initSoundPool()
    await this.loadSound()
  }

  // 创建 SoundPool 实例,并监听相关事件
  async initSoundPool() {
    // 如果需要的话,可以通过 canIUse 判断当前设备是否支持 SoundPool
    let supported = canIUse('SystemCapability.Multimedia.Media.SoundPool')
    this.message += `是否支持 SoundPool: ${supported}\n`

    this.soundPool = await media.createSoundPool(6, {
      usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
      rendererFlags: 0
    })

    this.soundPool.on('loadComplete', (soundId: number) => {
      this.message += `loadComplete soundId:${soundId}\n`
    });

    this.soundPool.on('playFinished', () => {
      this.message += `playFinished\n`
    });
    /*
    this.soundPool.on('playFinishedWithStreamId', (streamId: number) => {
      this.message += `playFinishedWithStreamId streamId:${streamId}\n`
    });
    */
    this.soundPool.on('error', (error: BusinessError) => {
      this.message += `error errCode:${error.code}, errMessage:${error.message}\n`
    });
  }

  // 加载短音频文件
  async loadSound() {
    /*
    // 下面的注销的示例用于演示,如何通过 fd:// 协议的方式加载指定的沙箱地址中的短音频文件
    for (let i = 0; i < 6; i++) {
      // 从 rawfile 中复制文件到指定的沙箱地址
      let filePath = getContext(this).getApplicationContext().filesDir + `/${i +  1}.mp3`
      let myBuffer: ArrayBufferLike = (await getContext(this).resourceManager.getRawFileContent(`audio/${i +  1}.mp3`)).buffer
      let file = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
      await fs.write(file.fd, myBuffer)
      // 加载指定的沙箱地址中的短音频文件(通过 fd:// 协议的方式)
      let fdPath = 'fd://' + (file.fd).toString()
      let soundId = await this.soundPool?.load(fdPath)
      if (soundId) {
        this.soundIdList.push(soundId)
      }
      await fs.close(file)
    }
    */

    // 加载指定的 rawfile 中的短音频文件(通过 FileDescriptor 的方式)
    for (let i = 0; i < 6; i++) {
      let fileDescriptor = await getContext(this).resourceManager.getRawFd(`audio/${i +  1}.mp3`)
      let soundId = await this.soundPool?.load(fileDescriptor.fd,  fileDescriptor.offset,  fileDescriptor.length)
      if (soundId) {
        this.soundIdList.push(soundId)
      }
    }
  }

  // 播放指定的短音频
  playSound(soundId: number) {
    // 短音频的播放参数
    let playParameters: media.PlayParameters = {
      loop: 0, // 播放 1 此
      rate: audio.AudioRendererRate.RENDER_RATE_NORMAL,
      leftVolume: 0.5,
      rightVolume: 0.5,
      priority: 100,
    };

    // 根据 soundId 播放短音频(如果指定的 soundId 正在播放,则会将其停止,然后重新播放),并拿到对应的 streamId
    this.soundPool?.play(soundId, playParameters, (error, streamId: number) => {
      this.message += `play soundId:${soundId}, streamId:${streamId}\n`
      this.streamIdList.add(streamId)

      /*
      // 设置指定 streamId 的播放参数(播放参数与上面的 PlayParameters 是一样的)
      this.soundPool?.setLoop(streamId, 0)
      this.soundPool?.setRate(streamId, audio.AudioRendererRate.RENDER_RATE_NORMAL)
      this.soundPool?.setVolume(streamId, 0.5, 0.5)
      this.soundPool?.setPriority(streamId, 100)
      */
    });
  }

  // 清理资源
  async release() {
    this.soundPool?.off('loadComplete')
    this.soundPool?.off('playFinished')
    /*
    this.soundPool.on('playFinishedWithStreamId')
    */
    this.soundPool?.off('error')
    for (let streamId of this.streamIdList) {
      await this.soundPool?.stop(streamId)
    }
    for (let soundId of this.soundIdList) {
      await this.soundPool?.unload(soundId)
    }
    await this.soundPool?.release()
  }

  async aboutToDisappear() {
    await this.release()
  }

  build() {
    Column({ space: 10 }) {

      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceAround, space: { main: LengthMetrics.vp(10), cross: LengthMetrics.vp(10) } }) {
        Button("play 0").onClick(() => {
          this.playSound(this.soundIdList[0])
        })
        Button("play 1").onClick(() => {
          this.playSound(this.soundIdList[1])
        })
        Button("play 2").onClick(() => {
          this.playSound(this.soundIdList[2])
        })
        Button("play 3").onClick(() => {
          this.playSound(this.soundIdList[3])
        })
        Button("play 4").onClick(() => {
          this.playSound(this.soundIdList[4])
        })
        Button("play 5").onClick(() => {
          this.playSound(this.soundIdList[5])
        })
      }

      Text(this.message)
    }
  }
}

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd