Skip to main content
AOQ SDK Features

Custom audio playback

Play audio returned by AOQ using an external player

The AOQ Client SDK supports custom audio playback. Through the audio frame callback mechanism, decoded PCM data is delivered to the application layer, letting you implement your own audio rendering logic.

Overview

By default, the AOQ Client SDK plays received remote audio data through the system speaker or earpiece. In some scenarios, the SDK's built-in audio playback module may not meet your needs. Custom audio playback is useful when you need to:
  • Output received audio data to a custom playback device or audio processing pipeline.
  • Post-process received audio data -- for example, for AI speech recognition or audio effects.
  • Work around audio playback device conflicts.
The AOQ Client SDK provides flexible custom playback support. Through the audio frame callback mechanism, decoded PCM data is delivered to the application layer for you to render and play back.

Sample code

Coming soon.

Prerequisites

  • An engine instance has been created by calling createEngine.
  • A connection to the server has been established (the onConnectionStatusChange callback has reported AoqConnectionStatusConnected).

Implementation

1. Start audio playback in external mode

When calling startAudioPlayer, set isExternal=true to disable the SDK's internal audio rendering device. The application layer is then responsible for playing back the audio.
AoqClientEngine.AoqAudioPlaybackConfig config = new AoqClientEngine.AoqAudioPlaybackConfig();
config.isExternal = true;  // Disable SDK internal playback; application layer renders audio
config.channel = 1;        // Number of channels
engine.startAudioPlayer(config);
Parameters:
ParameterTypeDefaultDescription
isVoipModebooleanfalseWhether to enable VoIP mode (hardware AEC). Valid on mobile.
isDefaultSpeakerbooleantrueWhether to use the speaker by default. Valid on mobile.
isExternalbooleanfalseWhether to use external playback mode. When true, the SDK does not open the playback device.
channelint1Number of channels

2. Set the audio frame callback listener

Call setAudioFrameObserver to register the audio frame data callback listener. Implement the onPlaybackAudioFrame callback method to receive the playback PCM data.
engine.setAudioFrameObserver(new AoqClientListener.AoqAudioFrameListener() {
  @Override
  public void onPlaybackAudioFrame(@NonNull AoqClientEngine.AoqAudioFrameData frame) {
    // Process the received playback audio data here
    // frame.dataPtr: PCM data
    // frame.numOfSamples: number of samples
    // frame.numOfChannels: number of channels
    // frame.samplesPerSec: sample rate
    // frame.bytesPerSample: bytes per sample
    playPcmData(frame);
  }
});

3. Enable playback data callbacks

Call enableAudioFrameObserver to enable audio frame callbacks at the playback position. Specify the data source as AoqAudioSourcePlayback.
AoqClientEngine.AoqAudioObserverConfig observerConfig = new AoqClientEngine.AoqAudioObserverConfig();
observerConfig.sampleRate = 48000;  // Callback audio sample rate
observerConfig.channels = 1;       // Callback audio channel count
observerConfig.mode = AoqClientEngine.AoqAudioObserverMode.AoqAudioObserverModeReadOnly; // Read-only mode
engine.enableAudioFrameObserver(
  true,  // Enable callbacks
  AoqClientEngine.AoqAudioSource.AoqAudioSourcePlayback,  // Playback data source
  observerConfig
);
Parameters:
ParameterTypeDefaultDescription
sampleRateint48000Callback audio sample rate (Hz)
channelsint1Callback audio channel count
modeAoqAudioObserverModeAoqAudioObserverModeReadOnlyRead/write mode

4. Implement custom audio rendering

When PCM data arrives in the onPlaybackAudioFrame callback, the application layer handles rendering and playback. Common approaches include:
  • Android AudioTrack: Write the PCM data to the system audio device using AudioTrack.
  • AI speech recognition: Pass the PCM data to an ASR engine for speech recognition.
  • Audio effects: Apply audio effects to the PCM data before playback.
  • File storage: Save the received audio data to a local file.
// Example: Playback using Android AudioTrack
private AudioTrack mAudioTrack;
private volatile boolean mPlayRunning = false;
private void initAudioTrack(int sampleRate, int channels) {
  int channelConfig = (channels == 2)
    ? AudioFormat.CHANNEL_OUT_STEREO
    : AudioFormat.CHANNEL_OUT_MONO;
  int bufferSize = AudioTrack.getMinBufferSize(
    sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
  mAudioTrack = new AudioTrack(
    AudioManager.STREAM_VOICE_CALL,
    sampleRate,
    channelConfig,
    AudioFormat.ENCODING_PCM_16BIT,
    bufferSize,
    AudioTrack.MODE_STREAM);
  mAudioTrack.play();
  mPlayRunning = true;
}
private void playPcmData(AoqClientEngine.AoqAudioFrameData frame) {
  if (!mPlayRunning || mAudioTrack == null) {
    return;
  }
  if (frame.dataPtr != null && frame.dataSize > 0) {
    mAudioTrack.write(frame.dataPtr, 0, frame.dataSize);
  }
}
  • The onPlaybackAudioFrame callback fires on an SDK internal thread. The frame.dataPtr is only valid during the callback. If you need to use the data asynchronously, copy it first.
  • AudioTrack.write is a blocking operation. Writing directly inside the callback is fine because the SDK delivers callbacks at a controlled rate.
  • Maintain the mPlayRunning flag so you can stop processing when the engine exits or playback stops.

5. Stop custom playback

When custom playback is no longer needed, disable the audio frame callback first, then stop the playback device and release the AudioTrack resources.
// 1. Disable audio frame callbacks at the playback position
AoqClientEngine.AoqAudioObserverConfig observerConfig = new AoqClientEngine.AoqAudioObserverConfig();
engine.enableAudioFrameObserver(
  false,  // Disable callbacks
  AoqClientEngine.AoqAudioSource.AoqAudioSourcePlayback,
  observerConfig
);
// 2. Remove the audio frame callback listener
engine.setAudioFrameObserver(null);
// 3. Stop SDK audio playback
engine.stopAudioPlayer();
// 4. Release AudioTrack resources
mPlayRunning = false;
if (mAudioTrack != null) {
  mAudioTrack.stop();
  mAudioTrack.release();
  mAudioTrack = null;
}
Custom audio playback - QwenCloud