AudioRenderer创建多个实例并轮询其状态来并发播放音乐
================================

#### 问题现象

AudioRenderer支持低时延播放，可以通过创建多个实例并轮询其状态来管理多个音频的播放，在一个实例空闲时使用它来播放下一个音效，从而有效地处理大量的短音频并发播放请求，具体该如何实现？

#### 背景知识

[AudioRenderer](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/using-audiorenderer-for-playback)是音频渲染器，用于播放PCM音频数据，需要应用持续写入音频数据进行工作，应用可以在输入前添加数据预处理，如设定音频文件的采样率、位宽等，要求开发者具备音频处理的基础知识，适用于更专业、更多样化的媒体播放应用开发。

#### 解决方案

通过循环创建多个AudioRenderer实例，通过周期性定时器不断轮询所有AudioRenderer实例的状态，取其中空闲状态的实例作为当前实例，用来播放当前音乐，当播放下一首音乐时，使用的是从所有AudioRenderer实例轮询出来的空闲实例，不影响当前音乐的播放，从而有效地处理大量的音频并发播放请求。

1.  循环创建多个AudioRenderer实例。
    
    _//_ _循环创建多个实例_
    for (let num \= 0; num < 4; num++) {
      audio.createAudioRenderer(this.audioRendererOptions, (err, data) \=\> {
        if (err) {
          console.error(TAG, \`Invoke createAudioRenderer failed, code is ${err.code}, message is ${err.message}\`);
          return;
        } else {
          console.info(TAG, 'Invoke createAudioRenderer succeeded.');
          this.audioRendererList.push(data); _// 创建的实例放到数组中_
        }
      });
    }
    
2.  通过周期性定时器不断轮询所有AudioRenderer实例的状态，取其中空闲状态的实例作为当前实例。
    
    _//_ _轮询audioRenderer实例数组中，空闲的实例，并设为当前实例，为后续播放做准备_
    setInterval(() \=\> {
      for (let num \= 0; num < 4; num++) {
        let renderModelTemp \= this.audioRendererList\[num\];
       _ // 当数组中audioRenderer实例状态为prepared时，将该实例赋给当前实例_
        if ((renderModelTemp as audio.AudioRenderer).state.valueOf() \== 1) {
          this.renderModel \= renderModelTemp;
          console.info(TAG \+ \`AudioRenderer state: ${renderModelTemp.state}\`);
        } else {
          console.info(TAG \+ \`AudioRenderer state: ${renderModelTemp.state}\`);
        }
      }
    }, 10);
    
3.  调用当前AudioRenderer实例来播放音乐。
    
    _//_ _开始一次音频渲染。_
    start() {
      if (this.renderModel !== undefined) {
        let stateGroup \= \[audio.AudioState.STATE\_PREPARED, audio.AudioState.STATE\_PAUSED, audio.AudioState.STATE\_STOPPED\];
        if (stateGroup.indexOf((this.renderModel as audio.AudioRenderer).state.valueOf()) \===
          -1) { _// 当前状态为prepared、paused和stopped之一时才能启动渲染。_
          console.error(TAG \+ 'start failed');
          return;
        }
      _  // 启动渲染。_
        (this.renderModel as audio.AudioRenderer).start((err: BusinessError) \=\> {
          if (err) {
            console.error('Renderer start failed.');
          } else {
            console.info('Renderer start success.');
          }
        });
      }
    }
    

完整示例参考如下：

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';

const TAG \= 'AudioRendererDemo';

class Options {
  offset?: number;
  length?: number;
}

@Entry
@Component
struct Index {
  context: Context \= this.getUIContext().getHostContext() as Context;
  @State renderModel: audio.AudioRenderer | undefined \= undefined;
  @State file: fs.File | undefined \= undefined;
  _// 此处仅作示例，如下pcm资源，实际使用时需要将文件替换为应用要播放的PCM文件，否则无法成功运行。_
  private fileStr: string\[\] \= \['XXXX.pcm', 'XXXX.pcm'\];
  private bufferSize: number \= 0;
  private audioRendererList: Array<audio.AudioRenderer\> = \[\];
  public audioStreamInfo: audio.AudioStreamInfo \= {
    samplingRate: audio.AudioSamplingRate.SAMPLE\_RATE\_48000,
    channels: audio.AudioChannel.CHANNEL\_2,
    sampleFormat: audio.AudioSampleFormat.SAMPLE\_FORMAT\_S16LE,
    encodingType: audio.AudioEncodingType.ENCODING\_TYPE\_RAW
  };
  public audioRendererInfo: audio.AudioRendererInfo \= {
    usage: audio.StreamUsage.STREAM\_USAGE\_MUSIC,
    rendererFlags: 0
  };
  public audioRendererOptions: audio.AudioRendererOptions \= {
    streamInfo: this.audioStreamInfo,
    rendererInfo: this.audioRendererInfo
  };

  aboutToAppear(): void {
 _   // 循环创建多个实例_
    for (let num \= 0; num < 4; num++) {
      audio.createAudioRenderer(this.audioRendererOptions, (err, data) \=\> {
        if (err) {
          console.error(TAG, \`Invoke createAudioRenderer failed, code is ${err.code}, message is ${err.message}\`);
          return;
        } else {
          console.info(TAG, 'Invoke createAudioRenderer succeeded.');
          this.audioRendererList.push(data); _//_ _创建的实例放到数组中_
        }
      });
    }

 _   // 轮询audioRenderer实例数组中，空闲的实例，并设为当前实例，为后续播放做准备_
    setInterval(() \=\> {
      for (let num \= 0; num < 4; num++) {
        let renderModelTemp \= this.audioRendererList\[num\];
    _    // 当数组中audioRenderer实例状态为prepared时，将该实例赋给当前实例_
        if ((renderModelTemp as audio.AudioRenderer).state.valueOf() \== 1) {
          this.renderModel \= renderModelTemp;
          console.info(TAG \+ \`AudioRenderer state: ${renderModelTemp.state}\`);
        } else {
          console.info(TAG \+ \`AudioRenderer state: ${renderModelTemp.state}\`);
        }
      }
    }, 10);
  }

 _ // 开始一次音频渲染。_
  start() {
    if (this.renderModel !== undefined) {
      let stateGroup \= \[audio.AudioState.STATE\_PREPARED, audio.AudioState.STATE\_PAUSED, audio.AudioState.STATE\_STOPPED\];
      if (stateGroup.indexOf((this.renderModel as audio.AudioRenderer).state.valueOf()) \===
        -1) { _// 当前状态为prepared、paused和stopped之一时才能启动渲染。_
        console.error(TAG \+ 'start failed');
        return;
      }
    _  // 启动渲染。_
      (this.renderModel as audio.AudioRenderer).start((err: BusinessError) \=\> {
        if (err) {
          console.error('Renderer start failed.');
        } else {
          console.info('Renderer start success.');
        }
      });
    }
  }

  _// 暂停渲染。_
  pause() {
    if (this.renderModel !== undefined) {
    _  // 只有渲染器状态为running的时候才能暂停。_
      if ((this.renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE\_RUNNING) {
        console.info('Renderer is not running');
        return;
      }
     _ // 暂停渲染。_
      (this.renderModel as audio.AudioRenderer).pause((err: BusinessError) \=\> {
        if (err) {
          console.error('Renderer pause failed.');
        } else {
          console.info('Renderer pause success.');
        }
      });
    }
  }

  _// 停止渲染。_
  async stop() {
    if (this.renderModel !== undefined) {
   _   // 只有渲染器状态为running或paused的时候才可以停止。_
      if ((this.renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE\_RUNNING &&
        (this.renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE\_PAUSED) {
        console.info('Renderer is not running or paused.');
        return;
      }
    _  // 停止渲染。_
      (this.renderModel as audio.AudioRenderer).stop((err: BusinessError) \=\> {
        if (err) {
          console.error('Renderer stop failed.');
        } else {
          fs.close(this.file);
          console.info('Renderer stop success.');
        }
      });
    }
  }

 _ // 销毁实例，释放资源。_
  async release() {
    if (this.renderModel !== undefined) {
   _   // 渲染器状态不是released状态，才能release。_
      if (this.renderModel.state.valueOf() \=== audio.AudioState.STATE\_RELEASED) {
        console.info('Renderer already released');
        return;
      }
    _  // 释放资源。_
      (this.renderModel as audio.AudioRenderer).release((err: BusinessError) \=\> {
        if (err) {
          console.error('Renderer release failed.');
        } else {
          console.info('Renderer release success.');
        }
      });
    }
  }

  build() {
    Column({ space: 20 }) {
      Button('播放音乐1').onClick(() \=\> {
        let path \= this.context.cacheDir;

        let filePath \= path \+ '/' \+ this.fileStr\[0\];
        let file: fs.File \= fs.openSync(filePath, fs.OpenMode.READ\_ONLY);
        this.file \= file;
        let writeDataCallback \= (buffer: ArrayBuffer) \=\> {
          let options: Options \= {
            offset: this.bufferSize,
            length: buffer.byteLength
          };
          try {
            fs.readSync(file.fd, buffer, options);
            this.bufferSize += buffer.byteLength;
            return audio.AudioDataCallbackResult.VALID;
          } catch (error) {
            console.error(\`Error reading file\`);
            return audio.AudioDataCallbackResult.INVALID;
          }
        };
        if (this.renderModel !== undefined) {
          (this.renderModel as audio.AudioRenderer).on('writeData', writeDataCallback);
          this.start();
        }
      });

      Button('播放音乐2').onClick(() \=\> {
        let path \= this.context.cacheDir;
        let filePath \= path \+ '/' \+ this.fileStr\[1\];
        let file: fs.File \= fs.openSync(filePath, fs.OpenMode.READ\_ONLY);
        this.file \= file;
        let writeDataCallback \= (buffer: ArrayBuffer) \=\> {
          let options: Options \= {
            offset: this.bufferSize,
            length: buffer.byteLength
          };

          try {
            fs.readSync(file.fd, buffer, options);
            this.bufferSize += buffer.byteLength;
            return audio.AudioDataCallbackResult.VALID;
          } catch (error) {
            console.error(\`Error reading file\`);
            return audio.AudioDataCallbackResult.INVALID;
          }
        };
        if (this.renderModel !== undefined) {
          (this.renderModel as audio.AudioRenderer).on('writeData', writeDataCallback);
          this.start();
        }
      });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}