Low-Latency Audio Monitoring

Low-latency audio monitoring is supported since API version 20.

AudioLoopback is an audio monitoring tool that delivers audio to headphones with reduced latency in real time, enabling users to hear their own voice or other relevant sounds immediately.

It is commonly used in karaoke applications, where the recorded vocals and background music are sent to the headphones in real time. This allows users to adjust their performance based on the feedback, enhancing their experience.

When audio loopback is enabled, the system creates a low-latency renderer and capturer to implement low-latency in-ear monitoring. The audio captured is routed back to the renderer through an internal path. The renderer follows the audio focus strategy for STREAM_USAGE_MUSIC, whereas the capturer follows the strategy for SOURCE_TYPE_MIC.

The system automatically chooses the input and output devices. If these devices do not support low latency, audio loopback does not work. If another audio stream takes over the audio focus or if the input or output device changes to the one that does not support low latency, the system disables audio loopback automatically.

Prerequisites

  • Currently, low-latency audio monitoring is only supported through wired headphones, where audio is captured and played back via the wired headphones.

  • Low-power renderers and low-latency renderers cannot be used concurrently in API version 20. To enable a renderer, you are advised to use STREAM_USAGE_UNKNOWN. If STREAM_USAGE_MUSIC is used, the system creates a normal renderer.

Development Guidelines

Using AudioLoopback for audio monitoring involves querying the monitoring capability with isAudioLoopbackSupported, creating an AudioLoopback instance, setting the volume, listening for status changes, and enabling/disabling audio loopback. This guide walks you through the process of enabling audio monitoring using AudioLoopback, with a focus on how to use AudioLoopback for audio monitoring. You are advised to read this in conjunction with the AudioLoopback API documentation.

The following figure shows the status changes of AudioLoopback. After an instance is created, you can call the corresponding method to enter the specified state to implement the corresponding behavior.

If an API is called when the AudioLoopback is not in the given state, the system may throw an exception or generate other undefined behavior. Therefore, you are advised to check the AudioLoopback state before triggering state transition.

AudioLoopback status changes

AudioLoopback status change

The on('statusChange') API can be used to listen for AudioLoopback status changes. For details about the value and description of each status, see AudioLoopbackStatus.

How to Develop

The examples in each of the following steps are code snippets. You can click the link at the bottom right of the sample code to obtain the complete sample codes.

  1. Query the audio monitoring capability and create an AudioLoopback instance. For details about the AudioLoopback mode, see AudioLoopbackMode.

    NOTE

    You must request the ohos.permission.MICROPHONE permission for audio monitoring. For details, see Requesting User Authorization.

    import { audio } from '@kit.AudioKit'; // Import the audio module.
    import { BusinessError } from '@kit.BasicServicesKit'; // Import BusinessError.
    // ...
    let mode: audio.AudioLoopbackMode = audio.AudioLoopbackMode.HARDWARE;
    let audioLoopback: audio.AudioLoopback | undefined = undefined;
    // ...
      let isSupported = audio.getAudioManager().getStreamManager().isAudioLoopbackSupported(mode);
      if (isSupported) {
        audio.createAudioLoopback(mode).then((loopback) => {
          console.info('Invoke createAudioLoopback succeeded.');
          // ...
          audioLoopback = loopback;
        }).catch((err: BusinessError) => {
          console.error(`Invoke createAudioLoopback failed, code is ${err.code}, message is ${err.message}.`);
          // ...
        });
      } else {
        console.error('Audio loopback is unsupported.');
        // ...
      }
    
  2. Call getStatus to obtain the current audio loopback status.

    NOTE

    The audio loopback status is affected by factors such as audio focus, low-latency control, and capturer and renderer devices.

    import { BusinessError } from '@kit.BasicServicesKit'; // Import BusinessError.
    // ...
        audioLoopback.getStatus().then((status: audio.AudioLoopbackStatus) => {
          console.info(`getStatus success, status is ${status}.`);
          // ...
        }).catch((err: BusinessError) => {
          console.error(`getStatus failed, code is ${err.code}, message is ${err.message}.`);
          // ...
        })
    
  3. Call setVolume to set the audio loopback volume.

    NOTE

    • Setting the volume before enabling audio loopback will take effect after successful activation of audio loopback.
    • Setting the volume after enabling audio loopback will take effect immediately.
    • If the volume is not set before enabling audio loopback, the default volume of 0.5 is used upon activation of audio loopback.
    import { BusinessError } from '@kit.BasicServicesKit'; // Import BusinessError.
    // ...
        try {
          await audioLoopback.setVolume(volume);
          console.info(`Invoke setVolume ${volume} succeeded.`);
          // ...
        } catch (err) {
          console.error(`Invoke setVolume failed, code is ${err.code}, message is ${err.message}.`);
          // ...
        }
    
  4. Call setReverbPreset to set the reverb mode for audio loopback. This API is available from API version 21.

    NOTE

    • If you set the reverb mode before enabling loopback, the setting takes effect after audio loopback is successfully enabled.
    • If you set the reverb mode after enabling loopback, the setting takes effect immediately.
    • If you do not set the reverb mode before enabling loopback, the default mode THEATER is used upon activation of audio loopback.
    import { BusinessError } from '@kit.BasicServicesKit'; // Import BusinessError.
    // ...
        try {
          audioLoopback.setReverbPreset(preset);
          console.info(`setReverbPreset( ${preset} succeeded.`);
          // ...
          currentReverbPreset = audioLoopback.getReverbPreset(); // Obtain the current reverb mode to prevent setting failures.
        } catch (err) {
          console.error(`setReverbPreset( failed, code is ${err.code}, message is ${err.message}.`);
          // ...
        }
    
  5. Call getReverbPreset to obtain the current reverb mode of audio loopback. This API is available from API version 21.

    NOTE

    If no reverb mode has been set, the default mode THEATER is returned.

    import { BusinessError } from '@kit.BasicServicesKit'; // Import BusinessError.
    // ...
        try {
          let reverbPreset = audioLoopback.getReverbPreset();
        } catch (err) {
          console.error(`getReverbPreset:ERROR: ${err}`);
          // ...
        }
    
  6. Call setEqualizerPreset to set the equalizer type for audio loopback. This API is available from API version 21.

    NOTE

    • If you set the equalizer type before enabling loopback, the setting takes effect after audio loopback is successfully enabled.
    • If you set the equalizer type after enabling loopback, the setting takes effect immediately.
    • If you do not set the equalizer type before enabling loopback, the default mode FULL is used upon activation of audio loopback.
    import { BusinessError } from '@kit.BasicServicesKit';
    try {
      audioLoopback.setEqualizerPreset(audio.AudioLoopbackEqualizerPreset.FULL);
    } catch (err) {
      console.error(`setEqualizerPreset :ERROR: ${err}`);
    }
    
  7. Call getEqualizerPreset to obtain the current equalizer type of audio loopback. This API is available from API version 21.

    NOTE

    If no equalizer type has been set, the default mode FULL is returned.

    import { BusinessError } from '@kit.BasicServicesKit'; // Import BusinessError.
    // ...
        try {
          let equalizerPreset = audioLoopback.getEqualizerPreset();
        } catch (err) {
          console.error(`getEqualizerPreset:ERROR: ${err}`);
          // ...
        }
    
  8. Call enable to enable or disable audio loopback.

    import { BusinessError } from '@kit.BasicServicesKit'; // Import BusinessError.
    // ...
    // Set a listener and enable audio loopback.
    async function enable(updateCallback?: (msg: string, isError: boolean) => void): Promise<void> {
      if (audioLoopback !== undefined) {
        try {
          let status = await audioLoopback.getStatus();
          if (status == audio.AudioLoopbackStatus.AVAILABLE_IDLE) {
            // Register a listener.
            audioLoopback.on('statusChange', statusChangeCallback);
            // Enable audio loopback.
            let success = await audioLoopback.enable(true);
            if (success) {
              console.info('Invoke enable succeeded');
              // ...
            } else {
              status = await audioLoopback.getStatus();
              statusChangeCallback(status);
            }
          } else {
            statusChangeCallback(status);
          }
        } catch (err) {
          console.error(`Invoke enable failed, code is ${err.code}, message is ${err.message}.`);
          // ...
        }
      } else {
        console.error('Audio loopback not created.');
        // ...
      }
    }
    
    // Disable audio loopback and unregister the listener.
    async function disable(updateCallback?: (msg: string, isError: boolean) => void): Promise<void> {
      if (audioLoopback !== undefined) {
        try {
          let status = await audioLoopback.getStatus();
          if (status == audio.AudioLoopbackStatus.AVAILABLE_RUNNING) {
            // Disable audio loopback.
            let success = await audioLoopback.enable(false);
            if (success) {
              console.info('Invoke disable succeeded');
              // ...
              // Unregister the listener.
              audioLoopback.off('statusChange', statusChangeCallback);
            } else {
              status = await audioLoopback.getStatus();
              statusChangeCallback(status);
            }
          } else {
            statusChangeCallback(status);
          }
        } catch (err) {
          console.error(`Invoke disable failed, code is ${err.code}, message is ${err.message}.`);
          // ...
        }
      } else {
        console.error('Audio loopback not created.');
        // ...
      }
    }
    

Complete Sample Code

The following example demonstrates how to use AudioLoopback to enable low-latency audio monitoring:

import { audio } from '@kit.AudioKit'; // Import the audio module.
import { BusinessError } from '@kit.BasicServicesKit'; // Import BusinessError.
import { common, abilityAccessCtrl, PermissionRequestResult } from '@kit.AbilityKit'; // Import UIAbilityContext.

const TAG = 'AudioLoopbackDemo';
let mode: audio.AudioLoopbackMode = audio.AudioLoopbackMode.HARDWARE;
let audioLoopback: audio.AudioLoopback | undefined = undefined;
let currentReverbPreset: audio.AudioLoopbackReverbPreset = audio.AudioLoopbackReverbPreset.THEATER;
let currentEqualizerPreset: audio.AudioLoopbackEqualizerPreset = audio.AudioLoopbackEqualizerPreset.FULL;
// ...

// ...

// Query the capability and create an instance.
function init(updateCallback?: (msg: string, isError: boolean) => void): void {
  let isSupported = audio.getAudioManager().getStreamManager().isAudioLoopbackSupported(mode);
  if (isSupported) {
    audio.createAudioLoopback(mode).then((loopback) => {
      console.info('Invoke createAudioLoopback succeeded.');
      // ...
      audioLoopback = loopback;
    }).catch((err: BusinessError) => {
      console.error(`Invoke createAudioLoopback failed, code is ${err.code}, message is ${err.message}.`);
      // ...
    });
  } else {
    console.error('Audio loopback is unsupported.');
    // ...
  }
}

// Set the volume for audio loopback.
async function setVolume(volume: number, updateCallback?: (msg: string, isError: boolean) => void): Promise<void> {
  if (audioLoopback !== undefined) {
    try {
      await audioLoopback.setVolume(volume);
      console.info(`Invoke setVolume ${volume} succeeded.`);
      // ...
    } catch (err) {
      console.error(`Invoke setVolume failed, code is ${err.code}, message is ${err.message}.`);
      // ...
    }
  } else {
    console.error('Audio loopback not created.');
    // ...
  }
}

// Set the reverb mode for audio loopback.
async function setReverbPreset(preset: audio.AudioLoopbackReverbPreset, updateCallback?: (msg: string,
  isError: boolean) => void): Promise<void> {
  if (audioLoopback !== undefined) {
    try {
      audioLoopback.setReverbPreset(preset);
      console.info(`setReverbPreset( ${preset} succeeded.`);
      // ...
      currentReverbPreset = audioLoopback.getReverbPreset(); // Obtain the current reverb mode to prevent setting failures.
    } catch (err) {
      console.error(`setReverbPreset( failed, code is ${err.code}, message is ${err.message}.`);
      // ...
    }
  } else {
    console.error('Audio loopback not created.');
    // ...
  }
}

// Set the equalizer type for audio loopback.
async function setEqualizerPreset(preset: audio.AudioLoopbackEqualizerPreset, updateCallback?:
  (msg: string, isError: boolean) => void): Promise<void> {
  if (audioLoopback !== undefined) {
    try {
      audioLoopback.setEqualizerPreset(preset);
      console.info(`setEqualizerPreset ${preset} succeeded.`);
      // ...
      currentEqualizerPreset = audioLoopback.getEqualizerPreset(); // Obtain the current equalizer type to prevent setting failures.
    } catch (err) {
      console.error(`setEqualizerPreset failed, code is ${err.code}, message is ${err.message}.`);
      // ...
    }
  } else {
    console.error('Audio loopback not created.');
    // ...
  }
}

// Set a listener and enable audio loopback.
async function enable(updateCallback?: (msg: string, isError: boolean) => void): Promise<void> {
  if (audioLoopback !== undefined) {
    try {
      let status = await audioLoopback.getStatus();
      if (status == audio.AudioLoopbackStatus.AVAILABLE_IDLE) {
        // Register a listener.
        audioLoopback.on('statusChange', statusChangeCallback);
        // Enable audio loopback.
        let success = await audioLoopback.enable(true);
        if (success) {
          console.info('Invoke enable succeeded');
          // ...
        } else {
          status = await audioLoopback.getStatus();
          statusChangeCallback(status);
        }
      } else {
        statusChangeCallback(status);
      }
    } catch (err) {
      console.error(`Invoke enable failed, code is ${err.code}, message is ${err.message}.`);
      // ...
    }
  } else {
    console.error('Audio loopback not created.');
    // ...
  }
}

// Disable audio loopback and unregister the listener.
async function disable(updateCallback?: (msg: string, isError: boolean) => void): Promise<void> {
  if (audioLoopback !== undefined) {
    try {
      let status = await audioLoopback.getStatus();
      if (status == audio.AudioLoopbackStatus.AVAILABLE_RUNNING) {
        // Disable audio loopback.
        let success = await audioLoopback.enable(false);
        if (success) {
          console.info('Invoke disable succeeded');
          // ...
          // Unregister the listener.
          audioLoopback.off('statusChange', statusChangeCallback);
        } else {
          status = await audioLoopback.getStatus();
          statusChangeCallback(status);
        }
      } else {
        statusChangeCallback(status);
      }
    } catch (err) {
      console.error(`Invoke disable failed, code is ${err.code}, message is ${err.message}.`);
      // ...
    }
  } else {
    console.error('Audio loopback not created.');
    // ...
  }
}