HarmonyOS Developer 媒体开发指导

丶龙八夷
发布于 2023-3-26 20:37
浏览
0收藏

音频开发概述

音频模块支持音频业务的开发,提供音频相关的功能,主要包括音频播放、音量管理等。

基本概念

  • 采样
    采样是指将连续时域上的模拟信号按照一定的时间间隔采样,获取到离散时域上离散信号的过程。
  • 采样率
    采样率为每秒从连续信号中提取并组成离散信号的采样次数,单位用赫兹(Hz)来表示。通常人耳能听到频率范围大约在20Hz~20kHz之间的声音。常用的音频采样频率有:8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等。
  • 声道
    声道是指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,所以声道数也就是声音录制时的音源数量或回放时相应的扬声器数量。
  • 音频帧
    音频数据是流式的,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗成取2.5ms~60ms为单位的数据量为一帧音频。这个时间被称之为“采样时间”,其长度没有特别的标准,它是根据编解码器和具体应用的需求来决定的。
  • PCM
    PCM(Pulse Code Modulation),即脉冲编码调制,是一种将模拟信号数字化的方法,是将时间连续、取值连续的模拟信号转换成时间离散、抽样值离散的数字信号的过程。

音频播放开发指导

简介

音频播放的主要工作是将音频数据转码为可听见的音频模拟信号,并通过输出设备进行播放,同时对播放任务进行管理,包括开始播放、暂停播放、停止播放、释放资源、设置音量、跳转播放位置、获取轨道信息等功能控制。

运作机制

该模块提供了音频播放状态变化示意图和音频播放外部模块交互图。

图1 音频播放状态变化示意图

HarmonyOS Developer 媒体开发指导-鸿蒙开发者社区

注意:当前为Idle状态,设置src不会改变状态;且src设置成功后,不能再次设置其它src,需调用reset()接口后,才能重新设置src。

图2 音频播放外部模块交互图

HarmonyOS Developer 媒体开发指导-鸿蒙开发者社区

说明:三方应用通过调用JS接口层提供的js接口实现相应功能时,框架层会通过Native Framework的媒体服务,调用音频部件,将软件解码后的音频数据输出至硬件接口层的音频HDI,实现音频播放功能。

开发指导

详细API含义可参考:​​媒体服务API文档AudioPlayer​

说明

path路径在FA模型和Stage模型下的获取方式不同,示例代码中仅给出pathDir示例,具体的path路径请开发者根据实际情况获取。获取方式请参考​​应用沙箱路径使用说明​​。

全流程场景

音频播放的全流程场景包含:创建实例,设置uri,播放音频,跳转播放位置,设置音量,暂停播放,获取轨道信息,停止播放,重置,释放资源等流程。

AudioPlayer支持的src媒体源输入类型可参考:​​src属性说明​

import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'

// 打印码流轨道信息
function printfDescription(obj) {
    for (let item in obj) {
        let property = obj[item];
        console.info('audio key is ' + item);
        console.info('audio value is ' + property);
    }
}

// 设置播放器回调函数
function setCallBack(audioPlayer) {
    audioPlayer.on('dataLoad', () => { // 设置'dataLoad'事件回调,src属性设置成功后,触发此回调
        console.info('audio set source success');
        audioPlayer.play(); // 需等待'dataLoad'事件回调完成后,才可调用play进行播放,触发'play'事件回调
    });
    audioPlayer.on('play', () => { // 设置'play'事件回调
        console.info('audio play success');
        audioPlayer.pause(); // 触发'pause'事件回调,暂停播放
    });
    audioPlayer.on('pause', () => { // 设置'pause'事件回调
        console.info('audio pause success');
        audioPlayer.seek(5000); // 触发'timeUpdate'事件回调,seek到5000ms处播放
    });
    audioPlayer.on('stop', () => { // 设置'stop'事件回调
        console.info('audio stop success');
        audioPlayer.reset(); // 触发'reset'事件回调后,重新设置src属性,可完成切歌
    });
    audioPlayer.on('reset', () => { // 设置'reset'事件回调
        console.info('audio reset success');
        audioPlayer.release(); // audioPlayer资源被销毁
        audioPlayer = undefined;
    });
    audioPlayer.on('timeUpdate', (seekDoneTime) => { // 设置'timeUpdate'事件回调
        if (typeof(seekDoneTime) == 'undefined') {
            console.info('audio seek fail');
            return;
        }
        console.info('audio seek success, and seek time is ' + seekDoneTime);
        audioPlayer.setVolume(0.5); // 触发'volumeChange'事件回调
    });
    audioPlayer.on('volumeChange', () => { // 设置'volumeChange'事件回调
        console.info('audio volumeChange success');
        audioPlayer.getTrackDescription((error, arrlist) => { // 通过回调方式获取音频轨道信息
            if (typeof (arrlist) != 'undefined') {
                for (let i = 0; i < arrlist.length; i++) {
                    printfDescription(arrlist[i]);
                }
            } else {
                console.log(`audio getTrackDescription fail, error:${error.message}`);
            }
            audioPlayer.stop(); // 触发'stop'事件回调,停止播放
        });
    });
    audioPlayer.on('finish', () => { // 设置'finish'事件回调,播放完成触发
        console.info('audio play finish');
    });
    audioPlayer.on('error', (error) => { // 设置'error'事件回调
        console.info(`audio error called, errName is ${error.name}`);
        console.info(`audio error called, errCode is ${error.code}`);
        console.info(`audio error called, errMessage is ${error.message}`);
    });
}

async function audioPlayerDemo() {
    // 1. 创建实例
    let audioPlayer = media.createAudioPlayer();
    setCallBack(audioPlayer); // 设置事件回调
    // 2. 用户选择音频,设置uri
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el1/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el1/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上,其中ohos.acts.multimedia.audio.audioplayer需替换为实际的bundleName
    let path = pathDir  + '/01.mp3'
    let file = await fs.open(path);
    fdPath = fdPath + '' + file.fd;
    audioPlayer.src = fdPath; // 设置src属性,并触发'dataLoad'事件回调
}
正常播放场景

import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'

export class AudioDemo {
  // 设置播放器回调函数
  setCallBack(audioPlayer) {
    audioPlayer.on('dataLoad', () => { // 设置'dataLoad'事件回调,src属性设置成功后,触发此回调
      console.info('audio set source success');
      audioPlayer.play(); // 调用play方法开始播放,触发'play'事件回调
    });
    audioPlayer.on('play', () => { // 设置'play'事件回调
      console.info('audio play success');
    });
    audioPlayer.on('finish', () => { // 设置'finish'事件回调,播放完成触发
      console.info('audio play finish');
      audioPlayer.release(); // audioPlayer资源被销毁
      audioPlayer = undefined;
    });
  }

  async audioPlayerDemo() {
    let audioPlayer = media.createAudioPlayer(); // 创建一个音频播放实例
    this.setCallBack(audioPlayer); // 设置事件回调
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el1/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el1/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上,其中ohos.acts.multimedia.audio.audioplayer需替换为实际的bundleName
    let path = pathDir  + '/01.mp3'
    let file = await fs.open(path);
    fdPath = fdPath + '' + file.fd;
    audioPlayer.src = fdPath; // 设置src属性,并触发'dataLoad'事件回调
  }
}
切歌场景

import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'

export class AudioDemo {
// 设置播放器回调函数
  private isNextMusic = false;
  setCallBack(audioPlayer) {
    audioPlayer.on('dataLoad', () => { // 设置'dataLoad'事件回调,src属性设置成功后,触发此回调
      console.info('audio set source success');
      audioPlayer.play(); // 调用play方法开始播放,触发'play'事件回调
    });
    audioPlayer.on('play', () => { // 设置'play'事件回调
      console.info('audio play success');
      audioPlayer.reset(); // 调用reset方法,触发'reset'事件回调
    });
    audioPlayer.on('reset', () => { // 设置'reset'事件回调
      console.info('audio play success');
      if (!this.isNextMusic) { // 当isNextMusic 为false时,实现切歌功能
        this.nextMusic(audioPlayer); // 实现切歌功能
      } else {
        audioPlayer.release(); // audioPlayer资源被销毁
        audioPlayer = undefined;
      }
    });
  }

  async nextMusic(audioPlayer) {
    this.isNextMusic = true;
    let nextFdPath = 'fd://'
    let pathDir = "/data/storage/el1/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el1/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上,其中ohos.acts.multimedia.audio.audioplayer需替换为实际的bundleName
    let nextpath = pathDir  + '/02.mp3'
    let file = await fs.open(path);
    nextFdPath = nextFdPath + '' + file.fd;
    audioPlayer.src = nextFdPath; // 设置src属性,并重新触发触发'dataLoad'事件回调
  }

  async audioPlayerDemo() {
    let audioPlayer = media.createAudioPlayer();       // 创建一个音频播放实例
    this.setCallBack(audioPlayer);                     // 设置事件回调
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el1/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el1/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上,其中ohos.acts.multimedia.audio.audioplayer需替换为实际的bundleName
    let path = pathDir  + '/01.mp3'
    let file = await fs.open(path);
    fdPath = fdPath + '' + file.fd;
    audioPlayer.src = fdPath; // 设置src属性,并触发'dataLoad'事件回调
  }
}
单曲循环场景

import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'

export class AudioDemo {
  // 设置播放器回调函数
  setCallBack(audioPlayer) {
    audioPlayer.on('dataLoad', () => { // 设置'dataLoad'事件回调,src属性设置成功后,触发此回调
      console.info('audio set source success');
      audioPlayer.loop = true; // 设置循环播放属性
      audioPlayer.play(); // 调用play方法开始播放,触发'play'事件回调
    });
    audioPlayer.on('play', () => { // 设置'play'事件回调,开始循环播放
      console.info('audio play success');
    });
  }

  async audioPlayerDemo() {
    let audioPlayer = media.createAudioPlayer(); // 创建一个音频播放实例
    this.setCallBack(audioPlayer); // 设置事件回调
    let fdPath = 'fd://'
    let pathDir = "/data/storage/el1/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
    // path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el1/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上,其中ohos.acts.multimedia.audio.audioplayer需替换为实际的bundleName
    let path = pathDir  + '/01.mp3'
    let file = await fs.open(path);
    fdPath = fdPath + '' + file.fd;
    audioPlayer.src = fdPath; // 设置src属性,并触发'dataLoad'事件回调
  }
}

音频录制开发指导

简介

音频录制的主要工作是捕获音频信号,完成音频编码并保存到文件中,帮助开发者轻松实现音频录制功能。该模块允许调用者指定音频录制的采样率、声道数、编码格式、封装格式、输出文件的路径等参数。

运作机制

该模块提供了音频录制状态变化示意图和音频录制外部模块交互图。

图1 音频录制状态变化变化示意图

HarmonyOS Developer 媒体开发指导-鸿蒙开发者社区

图2 音频录制外部模块交互图

HarmonyOS Developer 媒体开发指导-鸿蒙开发者社区

说明:三方录音应用或录音机通过调用JS接口层提供的js接口实现相应功能时,框架层会通过Native Framework的媒体服务,调用音频部件获取通过音频HDI捕获的音频数据,再通过软件编码输出编码封装后的音频数据保存至文件中,实现音频录制功能。

约束与限制

开发者在进行录制功能开发前,需要先对所开发的应用配置麦克风权限(ohos.permission.MICROPHONE),权限配置相关内容可参考:​​访问控制权限申请指导​

开发指导

详细API含义可参考:​​媒体服务API文档AudioRecorder​

全流程场景

音频录制的全流程场景包含:创建实例,设置录制参数,开始录制,暂停录制,恢复录制,停止录制,释放资源等流程。

import media from '@ohos.multimedia.media'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
export class AudioRecorderDemo {
  private testFdNumber; // 用于保存fd地址

  // 设置音频录制相关回调函数
  setCallBack(audioRecorder) {
    audioRecorder.on('prepare', () => {                                             // 设置'prepare'事件回调
      console.log('prepare success');
      audioRecorder.start();                                                     // 调用start方法开始录制,并触发start回调
    });
    audioRecorder.on('start', () => {                                                // 设置'start'事件回调
      console.log('audio recorder start success');
      audioRecorder.pause();                                                     // 调用pause方法暂停录制,并触发pause回调
    });
    audioRecorder.on('pause', () => {                                             // 设置'pause'事件回调
      console.log('audio recorder pause success');
      audioRecorder.resume();                                                    // 调用resume方法恢复录制,并触发resume回调
    });
    audioRecorder.on('resume', () => {                                             // 设置'resume'事件回调
      console.log('audio recorder resume success');
      audioRecorder.stop();                                                      // 调用stop方法停止录制,并触发stop回调
    });
    audioRecorder.on('stop', () => {                                             // 设置'stop'事件回调
      console.log('audio recorder stop success');
      audioRecorder.reset();                                                     // 调用reset方法重置录制,并触发reset回调
    });
    audioRecorder.on('reset', () => {                                             // 设置'reset'事件回调
      console.log('audio recorder reset success');
      audioRecorder.release();                                                   // 调用release方法,释放资源,并触发release回调
    });
    audioRecorder.on('release', () => {                                             // 设置'release'事件回调
      console.log('audio recorder release success');
      audioRecorder = undefined;
    });
    audioRecorder.on('error', (error) => {                                         // 设置'error'事件回调
      console.info(`audio error called, errName is ${error.name}`);
      console.info(`audio error called, errCode is ${error.code}`);
      console.info(`audio error called, errMessage is ${error.message}`);
    });
  }

  // pathName是传入的录制文件名,例如:01.mp3
  // 使用mediaLibrary需要添加以下权限, ohos.permission.MEDIA_LOCATION、ohos.permission.WRITE_MEDIA、ohos.permission.READ_MEDIA
  async getFd(pathName) {
    let displayName = pathName;
    const mediaTest = mediaLibrary.getMediaLibrary(globalThis.Context);
    let fileKeyObj = mediaLibrary.FileKey;
    let mediaType = mediaLibrary.MediaType.VIDEO;
    let publicPath = await mediaTest.getPublicDirectory(mediaLibrary.DirectoryType.DIR_VIDEO);
    let dataUri = await mediaTest.createAsset(mediaType, displayName, publicPath);
    if (dataUri != undefined) {
      let args = dataUri.id.toString();
      let fetchOp = {
        selections : fileKeyObj.ID + "=?",
        selectionArgs : [args],
      }
      let fetchFileResult = await mediaTest.getFileAssets(fetchOp);
      let fileAsset = await fetchFileResult.getAllObject();
      let fdNumber = await fileAsset[0].open('rw');
      this.testFdNumber = "fd://" + fdNumber.toString();
    }
  }

  async audioRecorderDemo() {
    // 1.创建实例
    let audioRecorder = media.createAudioRecorder();
    // 2.设置回调
    this.setCallBack(audioRecorder);
    await this.getFd('01.mp3');                             // 调用getFd方法获取需要录制文件的fd地址
    // 3.设置录制参数
    let audioRecorderConfig = {
      audioEncodeBitRate : 22050,
      audioSampleRate : 22050,
      numberOfChannels : 2,
      uri : this.testFdNumber,                                 // testFdNumber由getFd生成
      location : { latitude : 30, longitude : 130},
      audioEncoderMime : media.CodecMimeType.AUDIO_AAC,
      fileFormat : media.ContainerFormatType.CFT_MPEG_4A,
    }
    audioRecorder.prepare(audioRecorderConfig);             // 调用prepare方法,触发prepare回调函数
  }
}

正常录制场景

与全流程场景不同,不包括暂停录制,恢复录制的过程。

import media from '@ohos.multimedia.media'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
export class AudioRecorderDemo {
  private testFdNumber; // 用于保存fd地址

  // 设置音频录制相关回调函数
  setCallBack(audioRecorder) {
    audioRecorder.on('prepare', () => {                                         // 设置'prepare'事件回调
      console.log('prepare success');
      audioRecorder.start();                                                     // 调用start方法开始录制,并触发start回调
    });
    audioRecorder.on('start', () => {                                             // 设置'start'事件回调
      console.log('audio recorder start success');
      audioRecorder.stop();                                                      // 调用stop方法停止录制,并触发stop回调
    });
    audioRecorder.on('stop', () => {                                             // 设置'stop'事件回调
      console.log('audio recorder stop success');
      audioRecorder.release();                                                   // 调用release方法,释放资源,并触发release回调
    });
    audioRecorder.on('release', () => {                                             // 设置'release'事件回调
      console.log('audio recorder release success');
      audioRecorder = undefined;
    });
    audioRecorder.on('error', (error) => {                                         // 设置'error'事件回调
      console.info(`audio error called, errName is ${error.name}`);
      console.info(`audio error called, errCode is ${error.code}`);
      console.info(`audio error called, errMessage is ${error.message}`);
    });
  }

  // pathName是传入的录制文件名,例如:01.mp3
  // 使用mediaLibrary需要添加以下权限, ohos.permission.MEDIA_LOCATION、ohos.permission.WRITE_MEDIA、ohos.permission.READ_MEDIA
  async getFd(pathName) {
    let displayName = pathName;
    const mediaTest = mediaLibrary.getMediaLibrary(globalThis.Context);
    let fileKeyObj = mediaLibrary.FileKey;
    let mediaType = mediaLibrary.MediaType.VIDEO;
    let publicPath = await mediaTest.getPublicDirectory(mediaLibrary.DirectoryType.DIR_VIDEO);
    let dataUri = await mediaTest.createAsset(mediaType, displayName, publicPath);
    if (dataUri != undefined) {
      let args = dataUri.id.toString();
      let fetchOp = {
        selections : fileKeyObj.ID + "=?",
        selectionArgs : [args],
      }
      let fetchFileResult = await mediaTest.getFileAssets(fetchOp);
      let fileAsset = await fetchFileResult.getAllObject();
      let fdNumber = await fileAsset[0].open('rw');
      this.testFdNumber = "fd://" + fdNumber.toString();
    }
  }

  async audioRecorderDemo() {
    // 1.创建实例
    let audioRecorder = media.createAudioRecorder();
    // 2.设置回调
    this.setCallBack(audioRecorder);
    await this.getFd('01.mp3');                             // 调用getFd方法获取需要录制文件的fd地址
    // 3.设置录制参数
    let audioRecorderConfig = {
      audioEncodeBitRate : 22050,
      audioSampleRate : 22050,
      numberOfChannels : 2,
      uri : this.testFdNumber,                                 // testFdNumber由getFd生成
      location : { latitude : 30, longitude : 130},
      audioEncoderMime : media.CodecMimeType.AUDIO_AAC,
      fileFormat : media.ContainerFormatType.CFT_MPEG_4A,
    }
    audioRecorder.prepare(audioRecorderConfig);             // 调用prepare方法,触发prepare回调函数
  }
}

音频渲染开发指导

简介

AudioRenderer提供了渲染音频文件和控制播放的接口,开发者可以通过本指导,了解如何在输出设备中播放音频文件并管理播放任务。开发者在调用AudioRenderer提供的各个接口时,需要理解以下名词:

  • 状态检查:在进行应用开发的过程中,建议开发者通过on('stateChange')方法订阅AudioRenderer的状态变更。因为针对AudioRenderer的某些操作,仅在音频播放器在固定状态时才能执行。如果应用在音频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。
  • 异步操作:为保证UI线程不被阻塞,大部分AudioRenderer调用都是异步的。对于每个API均提供了callback函数和Promise函数,以下示例均采用Promise函数,更多方式可参考​​音频管理API文档AudioRenderer​​。

运作机制

该模块提供了音频渲染模块的状态变化示意

图1 音频渲染状态示意图

HarmonyOS Developer 媒体开发指导-鸿蒙开发者社区

  • PREPARED状态:通过调用create()方法进入到该状态。
  • RUNNING状态:正在进行音频数据播放,可以在prepared状态通过调用start()方法进入此状态,也可以在pause状态和stopped状态通过调用start()方法进入此状态。
  • PAUSED状态:在running状态可以通过pause()方法暂停音频数据的播放,暂停播放之后可以通过调用start()方法继续音频数据播放。
  • STOPPED状态:在paused状态可以通过调用stop()方法停止音频数据的播放,在running状态可以通过stop()方法停止音频数据的播放。
  • RELEASED状态:在prepared、paused、stop等状态,用户均可通过release()方法释放掉所有占用的硬件和软件资源,并且不会再进入到其他的任何一种状态了。

开发指导

详细API含义可参考:​​音频管理API文档AudioRenderer​

  1. 使用createAudioRenderer()创建一个AudioRenderer实例。
    在audioRendererOptions中设置相关参数。该实例可用于音频渲染、控制和获取渲染状态,以及注册通知回调。

 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 // 0是音频渲染器的扩展标志位,默认为0
 }
 let audioRendererOptions = {
     streamInfo: audioStreamInfo,
     rendererInfo: audioRendererInfo
 }

 let audioRenderer = await audio.createAudioRenderer(audioRendererOptions);
 console.log("Create audio renderer success.");
  1. 调用start()方法来启动/恢复播放任务。

async function startRenderer() {
  let state = audioRenderer.state;
  // Renderer start时的状态应该是STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一.
  if (state != audio.AudioState.STATE_PREPARED && state != audio.AudioState.STATE_PAUSED &&
    state != audio.AudioState.STATE_STOPPED) {
    console.info('Renderer is not in a correct state to start');
    return;
  }

  await audioRenderer.start();

  state = audioRenderer.state;
  if (state == audio.AudioState.STATE_RUNNING) {
    console.info('Renderer started');
  } else {
    console.error('Renderer start failed');
  }
}

启动完成后,渲染器状态将变更为STATE_RUNNING,然后应用可以开始读取缓冲区。

  1. 调用write()方法向缓冲区写入数据。

将需要播放的音频数据读入缓冲区,重复调用write()方法写入。

import fs from '@ohos.file.fs';
import audio from '@ohos.multimedia.audio';

async function writeBuffer(buf) {
  // 写入数据时,渲染器的状态必须为STATE_RUNNING
  if (audioRenderer.state != audio.AudioState.STATE_RUNNING) {
    console.error('Renderer is not running, do not write');
    return;
  }
  let writtenbytes = await audioRenderer.write(buf);
  console.info(`Actual written bytes: ${writtenbytes} `);
  if (writtenbytes < 0) {
    console.error('Write buffer failed. check the state of renderer');
  }
}

// 此处是渲染器的合理的最小缓冲区大小(也可以选择其它大小的缓冲区)
const bufferSize = await audioRenderer.getBufferSize();
let dir = globalThis.fileDir; //不可直接访问,没权限,切记!!!一定要使用沙箱路径
const filePath = dir + '/file_example_WAV_2MG.wav'; // 需要渲染的音乐文件 实际路径为:/data/storage/el2/base/haps/entry/files/file_example_WAV_2MG.wav
console.info(`file filePath: ${ filePath}`);

let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
let stat = await fs.stat(filePath); //音乐文件信息
let buf = new ArrayBuffer(bufferSize);
let len = stat.size % this.bufferSize == 0 ? Math.floor(stat.size / this.bufferSize) : Math.floor(stat.size / this.bufferSize + 1);
for (let i = 0;i < len; i++) {
  let options = {
    offset: i * this.bufferSize,
    length: this.bufferSize
  }
  let readsize = await fs.read(file.fd, buf, options)
  let writeSize = await new Promise((resolve,reject)=>{
    this.audioRenderer.write(buf,(err,writeSize)=>{
      if(err){
        reject(err)
      }else{
        resolve(writeSize)
      }
    })
  })    
}
fs.close(file)
await audioRenderer.stop(); //停止渲染
await audioRenderer.release(); //释放资源
  1. (可选)调用pause()方法或stop()方法暂停/停止渲染音频数据。

 async function pauseRenderer() {
   let state = audioRenderer.state;
   // 只有渲染器状态为STATE_RUNNING的时候才能暂停
   if (state != audio.AudioState.STATE_RUNNING) {
     console.info('Renderer is not running');
     return;
   }

   await audioRenderer.pause();
   state = audioRenderer.state;
   if (state == audio.AudioState.STATE_PAUSED) {
     console.info('Renderer paused');
   } else {
     console.error('Renderer pause failed');
   }
 }

 async function stopRenderer() {
   let state = audioRenderer.state;
   // 只有渲染器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止
   if (state != audio.AudioState.STATE_RUNNING && state != audio.AudioState.STATE_PAUSED) {
     console.info('Renderer is not running or paused');
     return;
   }

   await audioRenderer.stop();
   state = audioRenderer.state;
   if (state == audio.AudioState.STATE_STOPPED) {
     console.info('Renderer stopped');
   } else {
     console.error('Renderer stop failed');
   }
 }
  1. (可选)调用drain()方法清空缓冲区。

 async function drainRenderer() {
   let state = audioRenderer.state;
   // 只有渲染器状态为STATE_RUNNING的时候才能使用drain()
   if (state != audio.AudioState.STATE_RUNNING) {
     console.info('Renderer is not running');
     return;
   }

   await audioRenderer.drain();
   state = audioRenderer.state;
 }
  1. 任务完成,调用release()方法释放相关资源。

AudioRenderer会使用大量的系统资源,所以请确保完成相关任务后,进行资源释放。

 async function releaseRenderer() {
   let state = audioRenderer.state;
   // 渲染器状态不是STATE_RELEASED或STATE_NEW状态,才能release
   if (state == audio.AudioState.STATE_RELEASED || state == audio.AudioState.STATE_NEW) {
     console.info('Renderer already released');
     return;
   }
   await audioRenderer.release();

   state = audioRenderer.state;
   if (state == audio.AudioState.STATE_RELEASED) {
     console.info('Renderer released');
   } else {
     console.info('Renderer release failed');
   }
 }
  1. (可选)获取渲染器相关信息

通过以下代码,可以获取渲染器的相关信息。

// 获取当前渲染器状态
let state = audioRenderer.state;

// 获取渲染器信息
let audioRendererInfo : audio.AudioRendererInfo = await audioRenderer.getRendererInfo();

// 获取音频流信息
let audioStreamInfo : audio.AudioStreamInfo = await audioRenderer.getStreamInfo();

// 获取音频流ID
let audioStreamId : number = await audioRenderer.getAudioStreamId();

// 获取纳秒形式的Unix时间戳
let audioTime : number = await audioRenderer.getAudioTime();

// 获取合理的最小缓冲区大小
let bufferSize : number = await audioRenderer.getBufferSize();

// 获取渲染速率
let renderRate : audio.AudioRendererRate = await audioRenderer.getRenderRate();
  1. (可选)设置渲染器相关信息

通过以下代码,可以设置渲染器的相关信息。

// 设置渲染速率为正常速度
let renderRate : audio.AudioRendererRate = audio.AudioRendererRate.RENDER_RATE_NORMAL;
await audioRenderer.setRenderRate(renderRate);

// 设置一个流的音量为0.5
let volume : number = 0.5;
await audioRenderer.setVolume(volume);
  1. (可选)使用on('stateChange')方法订阅渲染器音频状态变化事件。

注册stateChange监听后,当渲染器的状态发生改变时,会触发回调并返回当前渲染器的状态。

audioRenderer.on('stateChange', (audioState) => {
  console.info('State change event Received');
  console.info(`Current renderer state is: ${audioState}`);
});
  1. (可选)对on()方法的异常处理。

在使用on()方法时,如果传入的字符串错误或传入的参数类型错误,程序会抛出异常,需要用try catch来捕获。

try {
  audioRenderer.on('invalidInput', () => { // 字符串不匹配
  })
} catch (err) {
  console.info(`Call on function error,  ${err}`); // 程序抛出401异常
}
try {
  audioRenderer.on(1, () => { // 入参类型错误
  })
} catch (err) {
  console.info(`Call on function error,  ${err}`); // 程序抛出6800101异常
}

音频采集开发指导

简介

AudioCapturer提供了用于获取原始音频文件的方法。开发者可以通过本指导了解应用如何通过AudioCapturer接口的调用实现音频数据的采集。

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

运作机制

该模块提供了音频采集模块的状态变化示意图。

图1 音频采集状态变化示意图

HarmonyOS Developer 媒体开发指导-鸿蒙开发者社区

  • PREPARED状态:通过调用create()方法进入到该状态。
  • RUNNING状态:正在进行音频数据播放,可以在prepared状态通过调用start()方法进入此状态,也可以在stopped状态通过调用start()方法进入此状态。
  • STOPPED状态:在running状态可以通过stop()方法停止音频数据的播放。
  • RELEASED状态:在prepared和stop状态,用户均可通过release()方法释放掉所有占用的硬件和软件资源,并且不会再进入到其他的任何一种状态了。

约束与限制

开发者在进行音频数据采集功能开发前,需要先对所开发的应用配置麦克风权限(ohos.permission.MICROPHONE),权限配置相关内容可参考:​​访问控制授权申请指导​

开发指导

详细API含义可参考:​​音频管理API文档AudioCapturer​

  1. 使用createAudioCapturer()创建一个AudioCapturer实例。
    在audioCapturerOptions中设置音频采集器的相关参数。该实例可用于音频采集、控制和获取采集状态,以及注册通知回调。

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 audioCapturerInfo = {
  source: audio.SourceType.SOURCE_TYPE_MIC,
  capturerFlags: 0 // 0是音频采集器的扩展标志位,默认为0
}

let audioCapturerOptions = {
  streamInfo: audioStreamInfo,
  capturerInfo: audioCapturerInfo
}

let audioCapturer = await audio.createAudioCapturer(audioCapturerOptions);
console.log('AudioRecLog: Create audio capturer success.');
  1. 调用start()方法来启动/恢复采集任务。

启动完成后,采集器状态将变更为STATE_RUNNING,然后应用可以开始读取缓冲区。

import audio from '@ohos.multimedia.audio';

async function startCapturer() {
  let state = audioCapturer.state;
  // Capturer start时的状态应该是STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一.
  if (state != audio.AudioState.STATE_PREPARED || state != audio.AudioState.STATE_PAUSED ||
    state != audio.AudioState.STATE_STOPPED) {
    console.info('Capturer is not in a correct state to start');
    return;
  }
  await audioCapturer.start();

  state = audioCapturer.state;
  if (state == audio.AudioState.STATE_RUNNING) {
    console.info('AudioRecLog: Capturer started');
  } else {
    console.error('AudioRecLog: Capturer start failed');
  }
}
  1. 读取采集器的音频数据并将其转换为字节流。重复调用read()方法读取数据,直到应用准备停止采集。

参考以下示例,将采集到的数据写入文件。

import fs from '@ohos.file.fs';

 let state = audioCapturer.state;
 // 只有状态为STATE_RUNNING的时候才可以read.
 if (state != audio.AudioState.STATE_RUNNING) {
   console.info('Capturer is not in a correct state to read');
   return;
 }

const path = '/data/data/.pulse_dir/capture_js.wav'; // 采集到的音频文件存储路径
let file = fs.openSync(filePath, 0o2);
let fd = file.fd;
if (file !== null) {
  console.info('AudioRecLog: file created');
} else {
  console.info('AudioRecLog: file create : FAILED');
  return;
}

if (fd !== null) {
  console.info('AudioRecLog: file fd opened in append mode');
}

let numBuffersToCapture = 150; // 循环写入150次
let count = 0;
while (numBuffersToCapture) {
  let bufferSize = await audioCapturer.getBufferSize();
  let buffer = await audioCapturer.read(bufferSize, true);
  let options = {
    offset: count * this.bufferSize,
    length: this.bufferSize
  }
  if (typeof(buffer) == undefined) {
    console.info('AudioRecLog: read buffer failed');
  } else {
    let number = fs.writeSync(fd, buffer, options);
    console.info(`AudioRecLog: data written: ${number}`);
  } 
  numBuffersToCapture--;
  count++;
}
  1. 采集完成后,调用stop方法,停止录制。

 async function StopCapturer() {
   let state = audioCapturer.state;
   // 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止
   if (state != audio.AudioState.STATE_RUNNING && state != audio.AudioState.STATE_PAUSED) {
     console.info('AudioRecLog: Capturer is not running or paused');
     return;
   }

   await audioCapturer.stop();

   state = audioCapturer.state;
   if (state == audio.AudioState.STATE_STOPPED) {
     console.info('AudioRecLog: Capturer stopped');
   } else {
     console.error('AudioRecLog: Capturer stop failed');
   }
 }
  1. 任务结束,调用release()方法释放相关资源。

 async function releaseCapturer() {
   let state = audioCapturer.state;
   // 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release
   if (state == audio.AudioState.STATE_RELEASED || state == audio.AudioState.STATE_NEW) {
     console.info('AudioRecLog: Capturer already released');
     return;
   }

   await audioCapturer.release();

   state = audioCapturer.state;
   if (state == audio.AudioState.STATE_RELEASED) {
     console.info('AudioRecLog: Capturer released');
   } else {
     console.info('AudioRecLog: Capturer release failed');
   }
 }
  1. (可选)获取采集器相关信息

通过以下代码,可以获取采集器的相关信息。

// 获取当前采集器状态
let state = audioCapturer.state;

// 获取采集器信息
let audioCapturerInfo : audio.AuduioCapturerInfo = await audioCapturer.getCapturerInfo();

// 获取音频流信息
let audioStreamInfo : audio.AudioStreamInfo = await audioCapturer.getStreamInfo();

// 获取音频流ID
let audioStreamId : number = await audioCapturer.getAudioStreamId();

// 获取纳秒形式的Unix时间戳
let audioTime : number = await audioCapturer.getAudioTime();

// 获取合理的最小缓冲区大小
let bufferSize : number = await audioCapturer.getBufferSize();
  1. 如果应用需要在采集器状态更新时进行一些操作,可以订阅该事件,当采集器状态更新时,会受到一个包含有事件类型的回调。

audioCapturer.on('stateChange', (state) => {
  console.info(`AudioCapturerLog: Changed State to : ${state}`)
  switch (state) {
    case audio.AudioState.STATE_PREPARED:
      console.info('--------CHANGE IN AUDIO STATE----------PREPARED--------------');
      console.info('Audio State is : Prepared');
      break;
    case audio.AudioState.STATE_RUNNING:
      console.info('--------CHANGE IN AUDIO STATE----------RUNNING--------------');
      console.info('Audio State is : Running');
      break;
    case audio.AudioState.STATE_STOPPED:
      console.info('--------CHANGE IN AUDIO STATE----------STOPPED--------------');
      console.info('Audio State is : stopped');
      break;
    case audio.AudioState.STATE_RELEASED:
      console.info('--------CHANGE IN AUDIO STATE----------RELEASED--------------');
      console.info('Audio State is : released');
      break;
    default:
      console.info('--------CHANGE IN AUDIO STATE----------INVALID--------------');
      console.info('Audio State is : invalid');
      break;
  }
});




文章转载自:​​https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/audio-capturer-0000001477981049-V3?catalogVersion=V3​

标签
收藏
回复
举报
回复