HarmonyOS音訊開發指導:使用AudioRenderer開發音訊播放功能

HarmonyOS開發者社群發表於2023-10-23

AudioRenderer是音訊渲染器,用於播放PCM(Pulse Code Modulation)音訊資料,相比AVPlayer而言,可以在輸入前新增資料預處理,更適合有音訊開發經驗的開發者,以實現更靈活的播放功能。

開發指導

使用AudioRenderer播放音訊涉及到AudioRenderer例項的建立、音訊渲染引數的配置、渲染的開始與停止、資源的釋放等。本開發指導將以一次渲染音訊資料的過程為例,向開發者講解如何使用AudioRenderer進行音訊渲染,建議搭配 AudioRenderer的API說明閱讀。

下圖展示了AudioRenderer的狀態變化,在建立例項後,呼叫對應的方法可以進入指定的狀態實現對應的行為。需要注意的是在確定的狀態執行不合適的方法可能導致AudioRenderer發生錯誤,建議開發者在呼叫狀態轉換的方法前進行狀態檢查,避免程式執行產生預期以外的結果。

為保證UI執行緒不被阻塞,大部分AudioRenderer呼叫都是非同步的。對於每個API均提供了callback函式和Promise函式,以下示例均採用callback函式。

圖1 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()方法釋放掉所有佔用的硬體和軟體資源,並且不會再進入到其他的任何一種狀態了。

開發步驟及注意事項

1.      配置音訊渲染引數並建立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
};
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.');
  }
});

完整示例

下面展示了使用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.');
    }
  }
}

當同優先順序或高優先順序音訊流要使用輸出裝置時,當前音訊流會被中斷,應用可以自行響應中斷事件並做出處理。具體的音訊併發處理方式可參考 多音訊播放的併發策略


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70009402/viewspace-2990452/,如需轉載,請註明出處,否則將追究法律責任。

相關文章