1# Using AudioRenderer for Audio Playback 2 3The AudioRenderer is used to play Pulse Code Modulation (PCM) audio data. Unlike the [AVPlayer](../media/using-avplayer-for-playback.md), the AudioRenderer can perform data preprocessing before audio input. Therefore, the AudioRenderer is more suitable if you have extensive audio development experience and want to implement more flexible playback features. 4 5## Development Guidelines 6 7The full rendering process involves creating an **AudioRenderer** instance, configuring audio rendering parameters, starting and stopping rendering, and releasing the instance. In this topic, you will learn how to use the AudioRenderer to render audio data. Before the development, you are advised to read [AudioRenderer](../../reference/apis-audio-kit/js-apis-audio.md#audiorenderer8) for the API reference. 8 9The figure below shows the state changes of the AudioRenderer. After an **AudioRenderer** instance is created, different APIs can be called to switch the AudioRenderer to different states and trigger the required behavior. If an API is called when the AudioRenderer is not in the given state, the system may throw an exception or generate other undefined behavior. Therefore, you are advised to check the AudioRenderer state before triggering state transition. 10 11To prevent the UI thread from being blocked, most **AudioRenderer** calls are asynchronous. Each API provides the callback and promise functions. The following examples use the callback functions. 12 13**Figure 1** AudioRenderer state transition 14 15 16 17During application development, you are advised to use [on('stateChange')](../../reference/apis-audio-kit/js-apis-audio.md#onstatechange-8) to subscribe to state changes of the AudioRenderer. This is because some operations can be performed only when the AudioRenderer is in a given state. If the application performs an operation when the AudioRenderer is not in the given state, the system may throw an exception or generate other undefined behavior. 18 19- **prepared**: The AudioRenderer enters this state by calling [createAudioRenderer()](../../reference/apis-audio-kit/js-apis-audio.md#audiocreateaudiorenderer8). 20 21- **running**: The AudioRenderer enters this state by calling [start()](../../reference/apis-audio-kit/js-apis-audio.md#start8) when it is in the **prepared**, **paused**, or **stopped** state. 22 23- **paused**: The AudioRenderer enters this state by calling [pause()](../../reference/apis-audio-kit/js-apis-audio.md#pause8) when it is in the **running** state. When the audio playback is paused, it can call [start()](../../reference/apis-audio-kit/js-apis-audio.md#start8) to resume the playback. 24 25- **stopped**: The AudioRenderer enters this state by calling [stop()](../../reference/apis-audio-kit/js-apis-audio.md#stop8) when it is in the **paused** or **running** state. 26 27- **released**: The AudioRenderer enters this state by calling [release()](../../reference/apis-audio-kit/js-apis-audio.md#release8) when it is in the **prepared**, **paused**, or **stopped** state. In this state, the AudioRenderer releases all occupied hardware and software resources and will not transit to any other state. 28 29### How to Develop 30 311. Set audio rendering parameters and create an **AudioRenderer** instance. For details about the parameters, see [AudioRendererOptions](../../reference/apis-audio-kit/js-apis-audio.md#audiorendereroptions8). 32 33 ```ts 34 import { audio } from '@kit.AudioKit'; 35 36 let audioStreamInfo: audio.AudioStreamInfo = { 37 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // Sampling rate. 38 channels: audio.AudioChannel.CHANNEL_2, // Channel. 39 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // Sampling format. 40 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // Encoding format. 41 }; 42 43 let audioRendererInfo: audio.AudioRendererInfo = { 44 usage: audio.StreamUsage.STREAM_USAGE_MUSIC, 45 rendererFlags: 0 46 }; 47 48 let audioRendererOptions: audio.AudioRendererOptions = { 49 streamInfo: audioStreamInfo, 50 rendererInfo: audioRendererInfo 51 }; 52 53 audio.createAudioRenderer(audioRendererOptions, (err, data) => { 54 if (err) { 55 console.error(`Invoke createAudioRenderer failed, code is ${err.code}, message is ${err.message}`); 56 return; 57 } else { 58 console.info('Invoke createAudioRenderer succeeded.'); 59 let audioRenderer = data; 60 } 61 }); 62 ``` 63 642. Call **on('writeData')** to subscribe to the callback for audio data writing. You are advised to use this function in API version 12, since it returns a callback result. 65 66 - From API version 12, this function returns a callback result, enabling the system to determine whether to play the data in the callback based on the value returned. 67 68 > **NOTE** 69 > 70 > - When the amount of data is sufficient to meet the required buffer length of the callback, you should return **audio.AudioDataCallbackResult.VALID**, and the system uses the entire data buffer for playback. Do not return **audio.AudioDataCallbackResult.VALID** in this case, as this leads to audio artifacts such as noise and playback stuttering. 71 > 72 > - When the amount of data is insufficient to meet the required buffer length of the callback, you are advised to return **audio.AudioDataCallbackResult.INVALID**. In this case, the system does not process this portion of audio data but requests data from the application again. Once the buffer is adequately filled, you can return **audio.AudioDataCallbackResult.VALID**. 73 > 74 > - Once the callback function finishes its execution, the audio service queues the data in the buffer for playback. Therefore, do not change the buffered data outside the callback. Regarding the last frame, if there is insufficient data to completely fill the buffer, you must concatenate the available data with padding to ensure that the buffer is full. This prevents any residual dirty data in the buffer from adversely affecting the playback effect. 75 76 ```ts 77 import { audio } from '@kit.AudioKit'; 78 import { BusinessError } from '@kit.BasicServicesKit'; 79 import { fileIo as fs } from '@kit.CoreFileKit'; 80 81 class Options { 82 offset?: number; 83 length?: number; 84 } 85 86 let bufferSize: number = 0; 87 let path = getContext().cacheDir; 88 // Ensure that the resource exists in the path. 89 let filePath = path + '/StarWars10s-2C-48000-4SW.wav'; 90 let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_ONLY); 91 92 let writeDataCallback = (buffer: ArrayBuffer) => { 93 let options: Options = { 94 offset: bufferSize, 95 length: buffer.byteLength 96 }; 97 98 try { 99 fs.readSync(file.fd, buffer, options); 100 bufferSize += buffer.byteLength; 101 // The system determines that the buffer is valid and plays the data normally. 102 return audio.AudioDataCallbackResult.VALID; 103 } catch (error) { 104 console.error('Error reading file:', error); 105 // The system determines that the buffer is invalid and does not play the data. 106 return audio.AudioDataCallbackResult.INVALID; 107 } 108 }; 109 110 audioRenderer.on('writeData', writeDataCallback); 111 ``` 112 113 - In API version 11, this function does not return a callback result, and the system treats all data in the callback as valid by default. 114 115 > **NOTE** 116 > 117 > - Ensure that the callback's data buffer is completely filled to the necessary length to prevent issues such as audio noise and playback stuttering. 118 > 119 > - If the amount of data is insufficient to fill the data buffer, you are advised to temporarily halt data writing (without pausing the audio stream), block the callback function, and wait until enough data accumulates before resuming writing, thereby ensuring that the buffer is fully filled. If you need to call AudioRenderer APIs after the callback function is blocked, unblock the callback function first. 120 > 121 > - If you do not want to play the audio data in this callback function, you can nullify the data block in the callback function. (Once nullified, the system still regards this as part of the written data, leading to silent frames during playback). 122 > 123 > - Once the callback function finishes its execution, the audio service queues the data in the buffer for playback. Therefore, do not change the buffered data outside the callback. Regarding the last frame, if there is insufficient data to completely fill the buffer, you must concatenate the available data with padding to ensure that the buffer is full. This prevents any residual dirty data in the buffer from adversely affecting the playback effect. 124 125 ```ts 126 import { BusinessError } from '@kit.BasicServicesKit'; 127 import { fileIo as fs } from '@kit.CoreFileKit'; 128 129 class Options { 130 offset?: number; 131 length?: number; 132 } 133 134 let bufferSize: number = 0; 135 let path = getContext().cacheDir; 136 // Ensure that the resource exists in the path. 137 let filePath = path + '/StarWars10s-2C-48000-4SW.wav'; 138 let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_ONLY); 139 let writeDataCallback = (buffer: ArrayBuffer) => { 140 // If you do not want to play a particular portion of the buffer, you can add a check and clear that specific section of the buffer. 141 let options: Options = { 142 offset: bufferSize, 143 length: buffer.byteLength 144 }; 145 fs.readSync(file.fd, buffer, options); 146 bufferSize += buffer.byteLength; 147 }; 148 149 audioRenderer.on('writeData', writeDataCallback); 150 ``` 151 1523. Call **start()** to switch the AudioRenderer to the **running** state and start rendering. 153 154 ```ts 155 import { BusinessError } from '@kit.BasicServicesKit'; 156 157 audioRenderer.start((err: BusinessError) => { 158 if (err) { 159 console.error(`Renderer start failed, code is ${err.code}, message is ${err.message}`); 160 } else { 161 console.info('Renderer start success.'); 162 } 163 }); 164 ``` 165 1664. Call **stop()** to stop rendering. 167 168 ```ts 169 import { BusinessError } from '@kit.BasicServicesKit'; 170 171 audioRenderer.stop((err: BusinessError) => { 172 if (err) { 173 console.error(`Renderer stop failed, code is ${err.code}, message is ${err.message}`); 174 } else { 175 console.info('Renderer stopped.'); 176 } 177 }); 178 ``` 179 1805. Call **release()** to release the instance. 181 182 ```ts 183 import { BusinessError } from '@kit.BasicServicesKit'; 184 185 audioRenderer.release((err: BusinessError) => { 186 if (err) { 187 console.error(`Renderer release failed, code is ${err.code}, message is ${err.message}`); 188 } else { 189 console.info('Renderer released.'); 190 } 191 }); 192 ``` 193 194### Selecting the Correct Stream Usage 195 196When developing a media player, it is important to correctly set the stream usage type according to the intended use case. This will ensure that the player behaves as expected in different scenarios. 197 198The recommended use cases are described in [StreamUsage](../../reference/apis-audio-kit/js-apis-audio.md#streamusage). For example, **STREAM_USAGE_MUSIC** is recommended for music scenarios, **STREAM_USAGE_MOVIE** is recommended for movie or video scenarios, and **STREAM_USAGE_GAME** is recommended for gaming scenarios. 199 200An incorrect configuration of **StreamUsage** may cause unexpected behavior. Example scenarios are as follows: 201 202- When **STREAM_USAGE_MUSIC** is incorrectly used in a game scenario, the game cannot be played simultaneously with music applications. However, games usually can coexist with music playback. 203- When **STREAM_USAGE_MUSIC** is incorrectly used in a navigation scenario, any playing music is interrupted when the navigation application provides audio guidance. However, it is generally expected that the music keeps playing at a lower volume while the navigation is active. 204 205### Sample Code 206 207Refer to the sample code below to render an audio file using AudioRenderer. 208 209```ts 210import { audio } from '@kit.AudioKit'; 211import { BusinessError } from '@kit.BasicServicesKit'; 212import { fileIo as fs } from '@kit.CoreFileKit'; 213 214const TAG = 'AudioRendererDemo'; 215 216class Options { 217 offset?: number; 218 length?: number; 219} 220 221let bufferSize: number = 0; 222let renderModel: audio.AudioRenderer | undefined = undefined; 223let audioStreamInfo: audio.AudioStreamInfo = { 224 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // Sampling rate. 225 channels: audio.AudioChannel.CHANNEL_2, // Channel. 226 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // Sampling format. 227 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // Encoding format. 228}; 229let audioRendererInfo: audio.AudioRendererInfo = { 230 usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // Audio stream usage type. 231 rendererFlags: 0 // AudioRenderer flag. 232}; 233let audioRendererOptions: audio.AudioRendererOptions = { 234 streamInfo: audioStreamInfo, 235 rendererInfo: audioRendererInfo 236}; 237let path = getContext().cacheDir; 238// Ensure that the resource exists in the path. 239let filePath = path + '/StarWars10s-2C-48000-4SW.wav'; 240let file: fs.File = fs.openSync(filePath, fs.OpenMode.READ_ONLY); 241let writeDataCallback = (buffer: ArrayBuffer) => { 242 let options: Options = { 243 offset: bufferSize, 244 length: buffer.byteLength 245 }; 246 247 try { 248 fs.readSync(file.fd, buffer, options); 249 bufferSize += buffer.byteLength; 250 // This function does not return a callback result in API version 11, but does so in API version 12 and later versions. 251 return audio.AudioDataCallbackResult.VALID; 252 } catch (error) { 253 console.error('Error reading file:', error); 254 // This function does not return a callback result in API version 11, but does so in API version 12 and later versions. 255 return audio.AudioDataCallbackResult.INVALID; 256 } 257}; 258 259// Create an AudioRenderer instance, and set the events to listen for. 260function init() { 261 audio.createAudioRenderer(audioRendererOptions, (err, renderer) => { // Create an AudioRenderer instance. 262 if (!err) { 263 console.info(`${TAG}: creating AudioRenderer success`); 264 renderModel = renderer; 265 if (renderModel !== undefined) { 266 (renderModel as audio.AudioRenderer).on('writeData', writeDataCallback); 267 } 268 } else { 269 console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`); 270 } 271 }); 272} 273 274// Start audio rendering. 275function start() { 276 if (renderModel !== undefined) { 277 let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED]; 278 if (stateGroup.indexOf((renderModel as audio.AudioRenderer).state.valueOf()) === -1) { // Rendering can be started only when the AudioRenderer is in the prepared, paused, or stopped state. 279 console.error(TAG + 'start failed'); 280 return; 281 } 282 // Start rendering. 283 (renderModel as audio.AudioRenderer).start((err: BusinessError) => { 284 if (err) { 285 console.error('Renderer start failed.'); 286 } else { 287 console.info('Renderer start success.'); 288 } 289 }); 290 } 291} 292 293// Pause the rendering. 294function pause() { 295 if (renderModel !== undefined) { 296 // Rendering can be paused only when the AudioRenderer is in the running state. 297 if ((renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_RUNNING) { 298 console.info('Renderer is not running'); 299 return; 300 } 301 // Pause the rendering. 302 (renderModel as audio.AudioRenderer).pause((err: BusinessError) => { 303 if (err) { 304 console.error('Renderer pause failed.'); 305 } else { 306 console.info('Renderer pause success.'); 307 } 308 }); 309 } 310} 311 312// Stop rendering. 313async function stop() { 314 if (renderModel !== undefined) { 315 // Rendering can be stopped only when the AudioRenderer is in the running or paused state. 316 if ((renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_RUNNING && (renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_PAUSED) { 317 console.info('Renderer is not running or paused.'); 318 return; 319 } 320 // Stop rendering. 321 (renderModel as audio.AudioRenderer).stop((err: BusinessError) => { 322 if (err) { 323 console.error('Renderer stop failed.'); 324 } else { 325 fs.close(file); 326 console.info('Renderer stop success.'); 327 } 328 }); 329 } 330} 331 332// Release the instance. 333async function release() { 334 if (renderModel !== undefined) { 335 // The AudioRenderer can be released only when it is not in the released state. 336 if (renderModel.state.valueOf() === audio.AudioState.STATE_RELEASED) { 337 console.info('Renderer already released'); 338 return; 339 } 340 // Release the resources. 341 (renderModel as audio.AudioRenderer).release((err: BusinessError) => { 342 if (err) { 343 console.error('Renderer release failed.'); 344 } else { 345 console.info('Renderer release success.'); 346 } 347 }); 348 } 349} 350``` 351 352When audio streams with the same or higher priority need to use the output device, the current audio playback will be interrupted. The application can respond to and handle the interruption event. For details, see [Processing Audio Interruption Events](audio-playback-concurrency.md). 353