Skip to main content

Captura de audio personalizada

Describe cómo implementar la captura de audio personalizada con el AOQ Client SDK, incluyendo la adición de flujos de audio externos, el envío de datos PCM y la gestión del ciclo de vida del flujo.

Visión general

El módulo de audio integrado del AOQ Client SDK cubre las necesidades básicas de audio, pero en algunos escenarios el módulo de captura integrado puede no ser suficiente. La captura de audio personalizada es útil cuando necesita:
  • Solucionar conflictos de dispositivos de captura de audio.
  • Alimentar datos de audio desde un sistema de captura personalizado o un archivo de audio en el SDK para su transmisión.
  • Publicar audio generado por TTS de IA a través del SDK.
El AOQ Client SDK proporciona soporte flexible para captura personalizada, lo que le permite gestionar sus propios dispositivos y fuentes de audio según su caso de uso. Los datos del flujo de audio externo se mezclan con el audio capturado internamente antes de publicarse.

Requisitos previos

  • Se ha creado una instancia del motor llamando a createEngine.
  • Se ha establecido una conexión con el servidor (la devolución de llamada onConnectionStatusChange ha reportado AoqConnectionStatusConnected).

Implementación

1. Iniciar o detener la captura de audio

Inicie primero la captura de audio. Los datos del flujo de audio externo se mezclan con los datos de captura interna antes de publicarse. Si no necesita la captura del micrófono interno, establezca isExternal=true para desactivar el dispositivo de captura interno.
// 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. Después de conectar, agregue el flujo de audio externo

Una vez que la devolución de llamada onConnectionStatusChange reporte AoqConnectionStatusConnected, llame a addAudioExternalStream para añadir el flujo de audio externo. Asigne un streamId único que utilizará para enviar datos y gestionar el flujo. Si necesita procesamiento 3A (cancelación de eco, supresión de ruido, control automático de ganancia), configure el parámetro enable3A en 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;
    }
}
Parámetros:

Parámetro

Tipo

Predeterminado

Descripción

trackType

AoqTrackType

AoqTrackTypeAudio

Tipo de pista de audio

codecType

AoqEncoderType

AoqEncoderTypeAudioPCM

Formato de flujo de audio

channels

int

1

Recuento de canales

sampleRate

int

48000

Frecuencia de muestreo (Hz)

playoutVolume

int

100

Volumen de reproducción [0–100]

publishVolume

int

100

Volumen de publicación [0–100]

maxBufferDuration

int

1000

Duración máxima del búfer (milisegundos)

enable3A

boolean

false

Si se debe aplicar el procesamiento 3A al PCM de entrada

3. Capturar u obtener datos PCM

Implemente su propia captura de audio o fuente de datos para su caso de uso y luego introduzca los datos en el SDK. Las fuentes de datos comunes incluyen:
  • Captura por micrófono: Use Android AudioRecord para capturar datos PCM.
  • Lectura de archivos: Analizar datos PCM desde un archivo de audio PCM o WAV local.
  • TTS de IA: Obtener datos PCM de un motor de síntesis de voz.
  • Flujo de red: Decodificar datos PCM desde un flujo de audio de red.
Los datos de audio deben estar en formato PCM. Registre la tasa de muestreo, el número de canales y otros parámetros para construir el objeto AoqAudioFrameData.

4. Enviar datos de audio al SDK mediante el ID de flujo

Llame a pushAudioExternalStreamData para alimentar los datos PCM capturados en el SDK.
  • Para captura por hardware: use 10 ms por trama y llame a push siempre que haya datos disponibles.
  • Para entrada basada en archivos: use 40 ms por trama y espere 30 ms entre envíos.
  • Mantenga un indicador running. Salga del bucle de envío cuando el motor se cierre o se elimine el ID de flujo.
// 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);
}
Notas importantes:
  • Comience a enviar datos solo después de que se haya establecido la conexión y se haya añadido el flujo de audio externo.
  • Establezca numOfSamples de AoqAudioFrameData para que coincida con la longitud real de los datos.
  • pushAudioExternalStreamData puede fallar si el búfer interno está lleno (código de error 110). Reintente después de un breve retraso.
  • Para la captura en tiempo real, utilice tramas de 10 ms y llame a push cada vez que haya datos disponibles. Maneje el código de error 110.
  • Para entrada basada en archivos, use tramas de 40 ms y llame a push cada 30 ms. Maneje el código de error 110.
  • Antes de que el motor finalice (destroy) o se elimine un ID de flujo, establezca mPushRunning = false para detener el bucle de envío y evitar el acceso a recursos liberados.

5. Eliminar el flujo de audio externo

Cuando ya no se necesite la captura personalizada, detenga primero el bucle de envío y luego llame a removeAudioExternalStream para eliminar el flujo de audio externo.
// Stop pushing first
stopPushAudio();
// Then remove the external audio stream
engine.removeAudioExternalStream(mExternalStreamId);
mExternalStreamId = null;