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:
Crie um aplicativo Alibaba Real-Time Communication (ARTC) e obtenha um App ID e uma App Key no console do ApsaraVideo Live. Para obter instruções, consulte Criar um aplicativo.
Baixe e integre o ARTC SDK ao seu projeto e implemente chamadas básicas de áudio e vídeo em tempo real. Para obter instruções, consulte Baixar e integrar o ARTC SDK e Implementar uma chamada de áudio/vídeo.
Implementação
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).
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.
Quando chamar este método:
Se precisar usar 3A, recomendamos chamar
addExternalAudioStreamapó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 interfaceonAudioPublishStateChangedretornarnewStatecomoAliRtcStatsPublished (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).
Inicie o envio de dados somente após a publicação do fluxo de áudio (quando o callback
onAudioPublishStateChangedreportar o estadoAliRtcStatsPublished).Defina o parâmetro
numSamplesno objetoAliRtcAudioFramecom o comprimento real dos dados capturados. Métodos comoAudioRecord.readpodem 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
pushExternalAudioStreamRawDatapode falhar se o buffer interno estiver cheio. Sua aplicação deve tratar esse erro e implementar um mecanismo de nova tentativa.Recomendamos chamar
pushExternalAudioStreamRawDataa 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
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.