Todos os produtos
Search
Central de documentação

ApsaraVideo Live:Captura de áudio personalizada

Última atualização: Jun 30, 2026

Utilize o ARTC SDK para capturar áudio de fontes personalizadas em vez do módulo de captura integrado.

Visão geral

O módulo de áudio integrado do ARTC SDK atende à maioria dos casos de uso, mas a captura de áudio personalizada é necessária em cenários como:

  • Quando um dispositivo de captura de áudio está ocupado por outro processo.

  • Para capturar áudio de uma source personalizada, como um sistema proprietário ou um arquivo de áudio, e enviá-lo ao SDK.

A captura de áudio personalizada permite que você gerencie seus próprios dispositivos e fontes de áudio.

Código de exemplo

Android: Android/ARTCExample/AdvancedUsage/src/main/java/com/aliyun/artc/api/advancedusage/CustomAudioCaptureAndRender/CustomAudioCaptureActivity.java.

iOS: iOS/ARTCExample/AdvancedUsage/CustomAudioCapture/CustomAudioCaptureVC.swift.

Pré-requisitos

Antes de começar, certifique-se de ter concluído as seguintes etapas:

Implementação

image

1. Ativar ou desativar a captura interna

Para usar a captura de áudio personalizada, primeiro desative o módulo de captura interna do SDK passando o parâmetro extras ao chamar getInstance para criar o mecanismo:

user_specified_use_external_audio_record: Desativa a captura interna do SDK para ative a captura de áudio personalizada.

  • "TRUE": Usa captura de áudio personalizada (desativa a captura interna).

  • "FALSE": Não usa captura de áudio personalizada (ativa a captura interna).

Nota

O parâmetro extras é uma string JSON.

Android

String extras = "{\"user_specified_use_external_audio_record\":\"TRUE\"}";
mAliRtcEngine = AliRtcEngine.getInstance(this, extras);

iOS

// Create and initialize the engine.
var customAudioCaptureConfig: [String: String] = [:]
// Use custom audio capture.
customAudioCaptureConfig["user_specified_use_external_audio_record"] = "TRUE"
// Serialize to JSON.
guard let jsonData = try? JSONSerialization.data(withJSONObject: customAudioCaptureConfig, options: []),
let extras = String(data: jsonData, encoding: .utf8) else {
     print("JSON serialization failed")
     return
 }
let engine = AliRtcEngine.sharedInstance(self, extras:extras)

Mac

NSString * extras = @"{\"user_specified_use_external_audio_record\":\"TRUE\"}";
mAliRtcEngine = [AliRtcEngine sharedInstance:self extras:extras];

Windows

/* Windows supports enabling or disabling audio capture during engine creation. */
/* Disable internal capture. */
char* extra = "{\"user_specified_enable_use_virtual_audio_device\":\"TRUE\", \"user_specified_use_external_audio_record\":\"TRUE\"}";
mAliRtcEngine = AliRtcEngine.Create(extra);

/* Enable internal capture. */
char* extra = "{\"user_specified_enable_use_virtual_audio_device\":\"FALSE\", \"user_specified_use_external_audio_record\":\"FALSE\"}";
mAliRtcEngine = AliRtcEngine.Create(extra);

2. Adicionar um fluxo de áudio externo

Chame addExternalAudioStream para adicionar um fluxo de áudio externo e obter seu ID de fluxo. Para ative o processamento de áudio 3A (cancelamento de eco acústico, controle automático de ganho e supressão de ruído), defina o parâmetro enable3A no objeto AliRtcExternalAudioStreamConfig.

Nota

Quando chamar este método:

  • Se precisar usar 3A, recomendamos chamar addExternalAudioStream após a publicação bem-sucedida do fluxo de áudio e o módulo de captura personalizada obter o primeiro quadro de áudio. Ou seja, chame o método depois que a interface onAudioPublishStateChanged retornar newState como AliRtcStatsPublished (3).

  • Se não precisar do processamento de áudio 3A (por exemplo, ao transmitir áudio de um arquivo local, source de rede ou dados gerados por TTS): Chame este método imediatamente após criar o mecanismo. Em seguida, inicie o envio de dados de áudio assim que o fluxo for publicado.

Android

AliRtcEngine.AliRtcExternalAudioStreamConfig config = new AliRtcEngine.AliRtcExternalAudioStreamConfig();
config.sampleRate = SAMPLE_RATE; // Sample rate
config.channels = CHANNEL; // Number of channels
// Publish volume
config.publishVolume = 100;
// Local playout volume
config.playoutVolume = isLocalPlayout ? 100 : 0;
config.enable3A = true;

int result = mAliRtcEngine.addExternalAudioStream(config);
if (result <= 0) {
    return;
}
// The return value is the stream ID. You need it to push data to the SDK.
mExternalAudioStreamId = result;

iOS

/* Set parameters based on your application's needs. */
AliRtcExternalAudioStreamConfig *config = [AliRtcExternalAudioStreamConfig new];
// This must match the number of channels of the external PCM audio stream. Set to 1 for mono or 2 for stereo.
config.channels = _pcmChannels;
// This must match the sample rate of the external PCM audio stream.
config.sampleRate = _pcmSampleRate;
config.playoutVolume = 0;
config.publishVolume = 100;
_externalPlayoutStreamId = [self.engine addExternalAudioStream:config];

Mac

/* Set parameters based on your application's needs. */
AliRtcExternalAudioStreamConfig *config = [AliRtcExternalAudioStreamConfig new];
config.channels = pcmChannels;
/** Sample rate. Default: 48000. Supported values: 8000, 12000, 16000, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000. */
config.sampleRate = pcmSampleRate;
config.playoutVolume = 0;
config.publishVolume = 100;
int ret = [self.engine addExternalAudioStream:config];

Windows

/* Get the media engine. */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;
    
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);
/* Set parameters based on your application's needs. */
AliEngineExternalAudioStreamConfig config;
config.playoutVolume = currentAudioPlayoutVolume;
config.publishVolume = currentAudioPublishVolume;
config.channels = 1;
config.sampleRate = 48000;
config.publishStream = 0;
audioStreamID = mAliRtcMediaEngine->AddExternalAudioStream(config);

mAliRtcMediaEngine->Release();

3. Implementar um módulo de captura de áudio personalizada

Implemente a lógica para capturar e processar dados de áudio e, em seguida, enviá-los ao SDK.

A Alibaba Cloud fornece um exemplo de captura personalizada que demonstra como ler dados formatados em PCM de um arquivo local ou de um microfone.

4. Enviar dados de áudio para o SDK

Após a publicação do fluxo de áudio (o callback onAudioPublishStateChanged reporta AliRtcStatsPublished), chame pushExternalAudioStreamRawData com o ID de fluxo obtido na Etapa 2 para enviar os dados de áudio capturados ao SDK. Converta os dados de áudio em um objeto AliRtcAudioFrame com as seguintes propriedades:

  • data: Os dados de áudio.

  • numSamples: O número de pontos de amostra por canal nos dados fornecidos.

  • bytesPerSample: Bytes por ponto de amostra (profundidade de bits / 8). Por exemplo, este valor é 2 para áudio de 16 bits.

  • numChannels: Número de canais de áudio.

  • samplesPerSec: A taxa de amostragem, em Hz (por exemplo, 16000 ou 48000).

Nota
  • Inicie o envio de dados somente após a publicação do fluxo de áudio (quando o callback onAudioPublishStateChanged reportar o estado AliRtcStatsPublished).

  • Defina o parâmetro numSamples no objeto AliRtcAudioFrame com o comprimento real dos dados capturados. Métodos como AudioRecord.read podem retornar menos dados do que o tamanho do buffer, portanto, use o valor de retorno do método para determinar o comprimento real dos dados.

  • A chamada pushExternalAudioStreamRawData pode falhar se o buffer interno estiver cheio. Sua aplicação deve tratar esse erro e implementar um mecanismo de nova tentativa.

  • Recomendamos chamar pushExternalAudioStreamRawData a cada 10 ms para enviar dados.

Android

// Assume the captured audio data is in `audioData`, the size is `bytesRead` bytes, and it represents 10 ms of data.
if (mAliRtcEngine != null && bytesRead > 0) {
    // Construct an AliRtcAudioFrame object. `bitsPerSample` is the bit depth, which is typically 16.
    AliRtcEngine.AliRtcAudioFrame sample = new AliRtcEngine.AliRtcAudioFrame();
    sample.data = audioData;
    sample.numSamples = bytesRead / (channels * (bitsPerSample / 8)); // Calculate the number of samples based on the actual number of bytes read.
    sample.numChannels = channels;
    sample.samplesPerSec = sampleRate;
    sample.bytesPerSample = bitsPerSample / 8;

    int ret = 0;
    // Retry the push operation if it fails because the buffer is full.
    int retryCount = 0;
    final int MAX_RETRY_COUNT = 20;
    final int BUFFER_WAIT_MS = 10;
    do {
        // Push the captured data to the SDK.
        ret = mAliRtcEngine.pushExternalAudioStreamRawData(mExternalAudioStreamId, sample);
        if(ret == ErrorCodeEnum.ERR_SDK_AUDIO_INPUT_BUFFER_FULL) {
            // Handle the buffer full scenario. Wait for a short period and retry.
            retryCount++;
            if(mExternalAudioStreamId <= 0 || retryCount >= MAX_RETRY_COUNT) {
                // The stream has been stopped or the maximum retry count is reached. Exit the loop.
                break;
            }

            try {
                // Pause for a short interval.
                Thread.sleep(BUFFER_WAIT_MS);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        } else {
            // Push succeeded or another error occurred. Exit the loop.
            break;
        }
    } while (retryCount < MAX_RETRY_COUNT);
}

iOS

// Construct an AliRtcAudioFrame object from the captured audio data.
let sample = AliRtcAudioFrame()
sample.dataPtr = UnsafeMutableRawPointer(mutating: pcmData)
sample.samplesPerSec = pcmSampleRate
sample.bytesPerSample = Int32(MemoryLayout<Int16>.size)
sample.numOfChannels = pcmChannels
sample.numOfSamples = numOfSamples

var retryCount = 0

while retryCount < 20 {
    if !(pcmInputThread?.isExecuting ?? false) {
        break
    }
    // Push the audio data to the SDK.
    let rc = rtcEngine?.pushExternalAudioStream(externalPublishStreamId, rawData: sample) ?? 0

    // Handle a full buffer.
    // 0x01070101 SDK_AUDIO_INPUT_BUFFER_FULL: The buffer is full. Retransmission is required.
    if rc == 0x01070101 && !(pcmInputThread?.isCancelled ?? true) {
        Thread.sleep(forTimeInterval: 0.03) // 30ms
        retryCount += 1;
    } else {
        if rc < 0 {
            "pushExternalAudioStream error, ret: \(rc)".printLog()
        }
        break
    }
}

Mac

while ( true ) {
    
    if (![pcmInputThread isExecuting]) {
        push_error = YES;
        break;
    }

    AliRtcAudioFrame *sample = [AliRtcAudioFrame new];
    sample.dataPtr = pcmData;
    sample.samplesPerSec = pcmSampleRate;
    sample.bytesPerSample = sizeof(int16_t);
    sample.numOfChannels = pcmChannels;
    sample.numOfSamples = numOfSamples;
    int rc = [self.engine pushExternalAudioStream:_externalPublishStreamId rawData:sample];
    count = count + 1;

    /* If the error is AliRtcErrAudioBufferFull, sleep for a moment and then continue pushing. */
    if ( rc == AliRtcErrAudioBufferFull && [pcmInputThread isCancelled ] == NO ) {
        [NSThread sleepForTimeInterval:0.04] ;
    }else {
        if ( rc < 0 ) {
            push_error = true ;
        }

        break ;
    }

}

Windows

Nota

Antes de implementar a captura personalizada no Windows, chame o método QueryInterface para obter o objeto do mecanismo de mídia.

/* Get the media engine. */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;   
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);

// Construct an audio frame from the data.
AliEngineAudioRawData rawData;
rawData.dataPtr = frameInfo.audio_data[0];
rawData.numOfSamples = (int) (frameInfo.audio_data[0].length / (2 * frameInfo.audio_channels));
rawData.bytesPerSample = 2;
rawData.numOfChannels = frameInfo.audio_channels;
rawData.samplesPerSec = frameInfo.audio_sample_rate;
// Push the data to the SDK.
int ret = mAliRtcMediaEngine->PushExternalAudioStreamRawData(audioStreamID, rawData);
// Handle buffer full and other errors.
if ( ret == AliEngineErrorAudioBufferFull ) {
    Sleep(40);
    continue ;
} 

// Release the media engine.
mAliRtcMediaEngine->Release();

5. Remover o fluxo de áudio externo

Para parar de publicar áudio da source personalizada, chame removeExternalAudioStream.

Android

mAliRtcEngine.removeExternalAudioStream(mExternalAudioStreamId);

iOS

[self.engine removeExternalAudioStream:_externalPublishStreamId];

Mac

[self.engine removeExternalAudioStream:_externalPublishStreamId];

Windows

/* Get the media engine. */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;
    
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);

mAliRtcMediaEngine->RemoveExternalAudioStream(audioStreamID);
mAliRtcMediaEngine->Release();

6. (Opcional) Ativar ou desativar dinamicamente a captura interna

Para ative ou desative dinamicamente a captura interna do SDK durante uma chamada, use o método setParameter.

Android

/* Dynamically disable internal capture. */
String parameter = "{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}";
mAliRtcEngine.setParameter(parameter);

/* Dynamically enable internal capture. */
String parameter = "{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}"; 
mAliRtcEngine.setParameter(parameter);

iOS

// Dynamically disable internal capture.
engine.setParameter("{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}")
// Dynamically enable internal capture.
engine.setParameter("{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}")

Mac

// Dynamically disable internal capture.
[self setParameter:@"{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}"];
// Dynamically enable internal capture.
[self setParameter:@"{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}"];

Windows

/* Dynamically disable internal capture. */
mAliRtcEngine->SetParameter("{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}");

/* Dynamically enable internal capture. */
mAliRtcEngine->SetParameter("{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}");

Perguntas frequentes

  • Qual é a frequência recomendada para chamar pushExternalAudioStreamRawData?

    • Recomendamos sincronizar as chamadas com o clock do dispositivo de áudio físico, chamando o método sempre que o dispositivo fornecer um novo pacote de dados.

    • Se nenhum clock de dispositivo físico estiver disponível, recomendamos enviar dados a cada 10 a 50 ms.

  • Posso usar o processamento de áudio 3A interno do SDK (AEC, AGC e ANS) com captura de áudio personalizada?

    • Sim. Conforme descrito na Etapa 2, defina o parâmetro enable3A ao adicionar o fluxo de áudio externo para ative ou desative o processamento de áudio 3A interno do SDK.