Utilisez le SDK ARTC pour capturer l'audio à partir de sources personnalisées au lieu du module de capture intégré.
Présentation
Le module audio intégré du SDK ARTC couvre la plupart des cas d'utilisation, mais une capture audio personnalisée s'avère nécessaire dans les scénarios suivants :
Lorsqu'un périphérique de capture audio est occupé par un autre processus.
Pour capturer l'audio depuis une source personnalisée, telle qu'un système propriétaire ou un fichier audio, et l'envoyer au SDK.
La capture audio personnalisée vous permet de gérer vos propres périphériques et sources audio.
Exemples de code
Android : Android/ARTCExample/AdvancedUsage/src/main/java/com/aliyun/artc/api/advancedusage/CustomAudioCaptureAndRender/CustomAudioCaptureActivity.java.
iOS : iOS/ARTCExample/AdvancedUsage/CustomAudioCapture/CustomAudioCaptureVC.swift.
Prérequis
Avant de commencer, assurez-vous d'avoir effectué les opérations suivantes :
Créez une application Alibaba Real-Time Communication (ARTC) et obtenez un App ID et une App Key depuis la console ApsaraVideo Live. Pour plus d'instructions, consultez Créer une application.
Intégrez le SDK ARTC à votre projet et implémentez les appels audio et vidéo en temps réel de base. Pour plus d'instructions, consultez Télécharger et intégrer le SDK ARTC et Implémenter un appel audio/vidéo.
Implémentation
1. Activer ou désactiver la capture interne
Pour utiliser la capture audio personnalisée, commencez par désactiver le module de capture interne du SDK en transmettant le paramètre extras lors de l'appel à getInstance pour créer le moteur :
user_specified_use_external_audio_record : Désactive la capture interne du SDK pour activer la capture audio personnalisée.
"TRUE": Utilise la capture audio personnalisée (désactive la capture interne)."FALSE": N'utilise pas la capture audio personnalisée (active la capture interne).
Le paramètre extras est une chaîne 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. Ajouter un flux audio externe
Appelez addExternalAudioStream pour ajouter un flux audio externe et obtenir son ID de flux. Pour activer le traitement audio 3A (annulation d'écho acoustique, contrôle automatique du gain et suppression du bruit), définissez le paramètre enable3A dans l'objet AliRtcExternalAudioStreamConfig.
Moment approprié pour appeler cette méthode :
Si vous devez utiliser la fonction 3A, nous vous recommandons d'appeler
addExternalAudioStreamaprès la publication réussie du flux audio et lorsque le module de capture personnalisé obtient la première trame audio. Autrement dit, vous devez appeler la méthode après que l'interfaceonAudioPublishStateChangeda renvoyénewStateavec la valeurAliRtcStatsPublished (3).Si vous n'avez pas besoin du traitement audio 3A (par exemple, lors de la diffusion d'audio à partir d'un fichier local, d'une source réseau ou de données générées par synthèse vocale) : Vous pouvez appeler cette méthode immédiatement après la création du moteur. Ensuite, commencez à pousser les données audio une fois le flux publié.
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. Implémenter un module de capture audio personnalisé
Vous devez implémenter la logique permettant de capturer et de traiter les données audio, puis de les envoyer au SDK.
Alibaba Cloud fournit un exemple de capture personnalisé qui montre comment lire des données au format PCM à partir d'un fichier local ou d'un microphone.
4. Pousser les données audio vers le SDK
Une fois le flux audio publié (le rappel onAudioPublishStateChanged indique AliRtcStatsPublished), appelez pushExternalAudioStreamRawData avec l'ID de flux obtenu à l'étape 2 pour envoyer les données audio capturées au SDK. Convertissez les données audio en un objet AliRtcAudioFrame avec les propriétés suivantes :
data : Les données audio.
numSamples : Le nombre d'échantillons par canal dans les données fournies.
bytesPerSample : Octets par échantillon (profondeur de bits / 8). Par exemple, cette valeur est de 2 pour un audio 16 bits.
numChannels : Nombre de canaux audio.
samplesPerSec : La fréquence d'échantillonnage, en Hz (par exemple, 16000 ou 48000).
Vous ne devez commencer à pousser les données qu'après la publication du flux audio (lorsque le rappel
onAudioPublishStateChangedsignale un étatAliRtcStatsPublished).Définissez le paramètre
numSamplesdans l'objetAliRtcAudioFramesur la longueur réelle des données capturées. Des méthodes telles queAudioRecord.readpeuvent renvoyer moins de données que la taille du tampon ; vous devez donc utiliser la valeur de retour de la méthode pour déterminer la longueur réelle des données.L'appel à
pushExternalAudioStreamRawDatapeut échouer si le tampon interne est plein. Votre application doit gérer cette erreur et mettre en œuvre un mécanisme de nouvelle tentative.Nous vous recommandons d'appeler
pushExternalAudioStreamRawDatatoutes les 10 ms pour envoyer les données.
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
Avant d'implémenter la capture personnalisée sur Windows, vous devez appeler la méthode QueryInterface pour obtenir l'objet du moteur multimé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. Supprimer le flux audio externe
Pour arrêter la publication de l'audio depuis la source personnalisée, appelez 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. (Facultatif) Activer ou désactiver dynamiquement la capture interne
Pour activer ou désactiver dynamiquement la capture interne du SDK pendant un appel, utilisez la méthode 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\"}}");
FAQ
-
Quelle est la fréquence recommandée pour appeler
pushExternalAudioStreamRawData?Nous vous recommandons de synchroniser les appels avec l'horloge du périphérique audio physique, en appelant la méthode chaque fois que le périphérique fournit un nouveau paquet de données.
Si aucune horloge de périphérique physique n'est disponible, nous vous recommandons d'envoyer les données toutes les 10 à 50 ms.
-
Puis-je utiliser le traitement audio 3A interne du SDK (AEC, AGC et ANS) avec la capture audio personnalisée ?
Oui. Comme décrit à l'étape 2, vous pouvez définir le paramètre enable3A lors de l'ajout du flux audio externe pour activer ou désactiver le traitement audio 3A interne du SDK.