Skip to main content
AOQ SDK Features

Custom audio capture

Use an external audio source instead of the device microphone

Describes how to implement custom audio capture with the AOQ Client SDK, including adding external audio streams, pushing PCM data, and managing stream lifecycle.

Overview

The AOQ Client SDK's built-in audio module covers basic audio needs, but in some scenarios the built-in capture module may not be sufficient. Custom audio capture is useful when you need to:
  • Work around audio capture device conflicts.
  • Feed audio data from a custom capture system or audio file into the SDK for transmission.
  • Publish AI TTS-generated audio through the SDK.
The AOQ Client SDK provides flexible custom capture support, letting you manage your own audio devices and sources based on your use case. External audio stream data is mixed with internally captured audio before being published.

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 or stop audio capture

Start audio capture first. External audio stream data is mixed with internal capture data before being published. If you don't need internal microphone capture, set isExternal=true to disable the internal capture device.
// Option 1: Internal capture — external audio stream data is mixed with microphone data
AoqClientEngine.AoqAudioCaptureConfig config = new AoqClientEngine.AoqAudioCaptureConfig();
config.isExternal = false; // Use internal microphone capture
config.isVoipMode = false;
engine.startAudioCapture(config);
// Option 2: No internal capture — only external audio stream data is published
AoqClientEngine.AoqAudioCaptureConfig config = new AoqClientEngine.AoqAudioCaptureConfig();
config.isExternal = true; // Do not open the microphone; external audio stream provides the data
engine.startAudioCapture(config);

2. After connecting, add the external audio stream

Once the onConnectionStatusChange callback reports AoqConnectionStatusConnected, call addAudioExternalStream to add the external audio stream. Assign a unique streamId that you will use to push data and manage the stream. If you need 3A processing (echo cancellation, noise suppression, automatic gain control), set the enable3A parameter in AoqAudioExternalStreamConfig.
// Add the stream after confirming connection in the onConnectionStatusChange callback
@Override
public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) {
  if (status == AoqClientEngine.AoqConnectionStatus.AoqConnectionStatusConnected) {
    addExternalAudioStream();
  }
}
private void addExternalAudioStream() {
  AoqClientEngine.AoqAudioExternalStreamConfig config = new AoqClientEngine.AoqAudioExternalStreamConfig();
  config.sampleRate = 48000;       // Sample rate — must match the actual audio data
  config.channels = 1;             // Channel count
  config.publishVolume = 100;      // Publishing volume [0-100]
  config.playoutVolume = 0;        // Local playback volume [0-100]; 0 = no local playback
  config.maxBufferDuration = 1000; // Maximum buffer duration (milliseconds)
  config.enable3A = true;          // Whether to apply 3A processing to input PCM
  String streamId = "external_audio_1";
  int ret = engine.addAudioExternalStream(streamId, config);
  if (ret == 0) {
    mExternalStreamId = streamId;
  }
}
Parameters:
ParameterTypeDefaultDescription
trackTypeAoqTrackTypeAoqTrackTypeAudioAudio track type
codecTypeAoqEncoderTypeAoqEncoderTypeAudioPCMAudio stream format
channelsint1Channel count
sampleRateint48000Sample rate (Hz)
playoutVolumeint100Playback volume [0-100]
publishVolumeint100Publishing volume [0-100]
maxBufferDurationint1000Maximum buffer duration (milliseconds)
enable3AbooleanfalseWhether to apply 3A processing to input PCM

3. Capture or obtain PCM data

Implement your own audio capture or data source for your use case, then feed the data into the SDK. Common data sources include:
  • Microphone capture: Use Android AudioRecord to capture PCM data.
  • File reading: Parse PCM data from a local PCM or WAV audio file.
  • AI TTS: Obtain PCM data from a speech synthesis engine.
  • Network stream: Decode PCM data from a network audio stream.
Audio data must be in PCM format. Record the sample rate, channel count, and other parameters to construct the AoqAudioFrameData object.

4. Push audio data to the SDK via the stream ID

Call pushAudioExternalStreamData to feed the captured PCM data into the SDK.
  • For hardware capture: use 10 ms per frame and call push whenever data is available.
  • For file-based input: use 40 ms per frame and sleep 30 ms between pushes.
  • Maintain a running flag. Exit the push loop when the engine exits or the stream ID is removed.
// Member variable: flag to control the push loop
private volatile boolean mPushRunning = false;
// Push a single audio frame
private void pushAudioData(byte[] audioData, int bytesRead) {
  if (engine == null || mExternalStreamId == null || bytesRead <= 0) {
    return;
  }
  int channels = 1;
  int bytesPerSample = 2; // 16-bit PCM
  int sampleRate = 48000;
  // Build the audio frame data object
  AoqClientEngine.AoqAudioFrameData frameData = new AoqClientEngine.AoqAudioFrameData();
  frameData.dataPtr = audioData;
  frameData.dataSize = bytesRead;
  frameData.numOfSamples = bytesRead / (channels * bytesPerSample);
  frameData.bytesPerSample = bytesPerSample;
  frameData.numOfChannels = channels;
  frameData.samplesPerSec = sampleRate;
  // Push data and handle buffer-full errors
  int ret;
  final int WAIT_MS = 30;
  do {
    // Check whether the running flag and stream ID are still valid
    if (!mPushRunning || mExternalStreamId == null) {
      break;
    }
    ret = engine.pushAudioExternalStreamData(mExternalStreamId, frameData);
    if (ret == 110) { // AoqErrorCodeAudioExternalBufferFull
      try {
        Thread.sleep(WAIT_MS);
      } catch (InterruptedException e) {
        break;
      }
    } else {
      break;
    }
  } while (true);
}
Important notes:
  • Start pushing data only after the connection is established and the external audio stream has been added.
  • Set AoqAudioFrameData's numOfSamples to match the actual length of the data.
  • pushAudioExternalStreamData may fail if the internal buffer is full (error code 110). Retry after a short delay.
  • For real-time capture, use 10 ms frames and call push whenever data is available. Handle error code 110.
  • For file-based input, use 40 ms frames and call push every 30 ms. Handle error code 110.
  • Before the engine exits (destroy) or a stream ID is removed, set mPushRunning = false to stop the push loop and avoid accessing released resources.

5. Remove the external audio stream

When custom capture is no longer needed, stop the push loop first, then call removeAudioExternalStream to remove the external audio stream.
// Stop pushing first
stopPushAudio();
// Then remove the external audio stream
engine.removeAudioExternalStream(mExternalStreamId);
mExternalStreamId = null;
Custom audio capture - QwenCloud