Pilote audio personnalisé

Vue d'ensemble

Les SDK vidéo Android et iOS vous permettent de configurer un pilote audio personnalisé pour les éditeurs et les abonnés. Vous pouvez utiliser un pilote audio personnalisé pour personnaliser l'audio envoyé au flux d'un éditeur. Vous pouvez également personnaliser la lecture de l'audio des flux souscrits.

Ce mode d'emploi aborde les points suivants :

  • Apportez des modifications au pilote audio dans votre application Android Vonage Video.
  • Apportez des modifications au pilote audio dans votre application iOS Vonage Video.

Android

Avant de commencer

Ce how-to montre le code nécessaire pour ajouter un pilote audio personnalisé. Le code de ce how-to est disponible dans le projet Basic-Audio-Driver-Java du projet opentok-android-sdk-samples repo. Si vous ne l'avez pas encore fait, vous devrez cloner le repo dans un répertoire local. Sur la ligne de commande, exécutez :

git clone git@github.com:opentok/opentok-android-sdk-samples.git

Ouvrir le Pilote audio de base-Java dans Android Studio.

Explorer le code

Cet exemple d'application utilise le pilote audio personnalisé pour publier du bruit blanc (un signal audio aléatoire) dans son flux audio. Elle utilise également le pilote audio personnalisé pour capturer l'audio des flux souscrits et l'enregistrer dans un fichier.

Ajout d'un pilote audio personnalisé

Configuration de l'appareil audio et du bus audio

En utilisant un pilote audio personnalisé, vous définissez un pilote audio personnalisé et un bus audio à utiliser par l'application.

Les NoiseAudioDevice définit une interface audio de base à utiliser par l'application. Elle étend la classe BaseAudioDevice définie par le SDK Android. Pour utiliser un pilote audio personnalisé, appelez la commande AudioDeviceManager.setAudioDevice() méthode. Cet exemple définit le périphérique audio comme une instance de la méthode NoiseAudioDevice classe :

AudioDeviceManager.setAudioDevice(noiseAudioDevice);

Utiliser le AudioSettings définie dans le SDK Android, pour définir le format audio utilisé par le pilote audio personnalisé. La classe NoiseAudioDevice() instancie deux AudioSettings une pour le capteur audio personnalisé et une pour le moteur de rendu audio personnalisé. Il définit la fréquence d'échantillonnage et le nombre de canaux pour chacune d'entre elles :

public NoiseAudioDevice(Context context) {
    this.context = context;

    captureSettings = new AudioSettings(SAMPLING_RATE, NUM_CHANNELS_CAPTURING);
    rendererSettings = new AudioSettings(SAMPLING_RATE, NUM_CHANNELS_RENDERING);

    capturerStarted = false;
    rendererStarted = false;

    audioDriverPaused = false;

    capturerHandler = new Handler();
    rendererHandler = new Handler();
}

Le constructeur définit également quelques propriétés locales qui indiquent si le dispositif est en train de capturer ou d'effectuer un rendu. Il définit également une instance de Handler pour traiter la requête capturer Objet exécutable.

Les NoiseAudioDevice.getAudioBus() obtient la méthode AudioBus que ce périphérique audio utilise, définie par l'instance NoiseAudioDevice.AudioBus classe. Utiliser le AudioBus pour envoyer et recevoir des échantillons audio depuis et vers une session. L'éditeur accède à l'instance AudioBus pour obtenir les échantillons audio. Les abonnés envoient des échantillons audio (provenant des flux souscrits) à l'objet AudioBus.

Capturer de l'audio pour l'utiliser par un éditeur

Les BaseAudioDevice.startCapturer() est appelée lorsque l'appareil audio doit commencer à capturer les données audio à publier. La méthode NoiseAudioDevice de cette méthode démarre le capturer thread à exécuter dans la file d'attente après 1 seconde :

public boolean startCapturer() {
    capturerStarted = true;
    capturerHandler.postDelayed(capturer, capturerIntervalMillis);
    return true;
}

Les capturer produit un tampon contenant des échantillons de données aléatoires (bruit blanc). Il appelle ensuite la fonction writeCaptureData() de la méthode AudioBus qui envoie les échantillons au bus audio. L'éditeur de l'application utilise les échantillons envoyés au bus audio pour les transmettre en tant qu'audio dans le flux publié. Ensuite, si une capture est toujours en cours (si l'application est en cours de publication), l'objet capturer est relancé après une seconde :

private Runnable capturer = new Runnable() {
    @Override
    public void run() {
        capturerBuffer.rewind();

        Random rand = new Random();
        rand.nextBytes(capturerBuffer.array());

        getAudioBus().writeCaptureData(capturerBuffer, SAMPLING_RATE);

        if(capturerStarted && !audioDriverPaused) {
            capturerHandler.postDelayed(capturer, capturerIntervalMillis);
        }
    }
};

Les AudioDevice comprend d'autres méthodes qui sont mises en œuvre par la classe NoiseAudioDevice classe. Cependant, cet échantillon n'apporte rien d'intéressant à ces méthodes, qui ne sont donc pas incluses dans cette discussion.

Ajout d'un rendu audio personnalisé

Vous mettrez en œuvre un moteur de rendu audio pour les flux audio souscrits.

Les NoiseAudioDevice() met en place un fichier pour enregistrer l'audio entrant dans un fichier. Cela permet d'illustrer l'utilisation du moteur de rendu audio du pilote audio personnalisé. L'application requiert les permissions suivantes, définies dans le fichier AndroidManifest.xml fichier :

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Les BaseAudioDevice.initRenderer() est appelée lorsque l'application initialise le moteur de rendu audio. La méthode NoiseAudioDevice de cette méthode instancie un nouvel objet File, dans lequel l'application écrira les données audio :

@Override
public boolean initRenderer() {
    rendererBuffer = ByteBuffer.allocateDirect(SAMPLING_RATE * 2); // Each sample has 2 bytes
    File documentsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
    rendererFile = new File(documentsDirectory, "output.raw");

    if (!rendererFile.exists()) {
        try {
            rendererFile.getParentFile().mkdirs();
            rendererFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    return true;
}

Les BaseAudioDevice.startRendering() est appelée lorsque l'appareil audio doit commencer à restituer (lire) l'audio des flux souscrits. La méthode NoiseAudioDevice de cette méthode démarre le capturer thread à exécuter dans la file d'attente après 1 seconde :

    @Override
    public boolean startRenderer() {
        rendererStarted = true;
        rendererHandler.postDelayed(renderer, rendererIntervalMillis);
        return true;
    }

Les renderer obtient une seconde d'audio à partir du bus audio en appelant la fonction readRenderData() de la méthode AudioBus objet. Il écrit ensuite les données audio dans le fichier (à des fins d'échantillonnage). Et, si le périphérique audio est toujours utilisé pour restituer des échantillons audio, il met en place un minuteur pour exécuter l'objet rendererHandler après 0,1 seconde :

private Handler rendererHandler;

private Runnable renderer = new Runnable() {
    @Override
    public void run() {
        rendererBuffer.clear();
        getAudioBus().readRenderData(rendererBuffer, SAMPLING_RATE);

        try {
            FileOutputStream stream = new FileOutputStream(rendererFile);
            stream.write(rendererBuffer.array());
            stream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (rendererStarted && !audioDriverPaused) {
            rendererHandler.postDelayed(renderer, rendererIntervalMillis);
        }
    }
};

Cet exemple a été conçu à des fins didactiques : il écrit les données audio dans un fichier. Dans une utilisation plus pratique d'un pilote audio personnalisé, vous pourriez utiliser le pilote audio personnalisé pour lire l'audio sur un périphérique Bluetooth ou pour traiter l'audio avant de le lire.

iOS

Ce how-to montre le code nécessaire pour ajouter un pilote audio personnalisé. Le code de ce how-to est disponible dans la branche audio-driver de l'archive apprentissage-opentok-ios repo repo. Si vous ne l'avez pas encore fait, vous devrez cloner le repo dans un répertoire local. Sur la ligne de commande, exécutez :

git clone https://github.com/opentok/learning-opentok-ios.git

Ouvrir le pilote audio dans Xcode pour suivre le processus.

Explorer le code

Cet exemple d'application utilise le pilote audio personnalisé pour publier du bruit blanc (un signal audio aléatoire) dans son flux audio. Elle utilise également le pilote audio personnalisé pour capturer l'audio des flux souscrits et l'enregistrer dans un fichier.

Ajout d'un pilote audio personnalisé

Configuration de l'appareil audio et du bus audio

En utilisant un pilote audio personnalisé, vous définissez un pilote audio personnalisé et un bus audio à utiliser par l'application.

Les OTKBasicAudioDevice définit une interface de base pour les appareils audio à utiliser par l'application. Elle met en œuvre le protocole OTAudioDevice, défini par le SDK iOS. Pour utiliser un pilote audio personnalisé, appelez la classe [OTAudioDeviceManager setAudioDevice:] méthode. Cet exemple définit le périphérique audio comme une instance de la classe OTKBasicAudioDevice :

[OTAudioDeviceManager setAudioDevice:[[OTKBasicAudioDevice alloc] init]];

Utilisez la classe OTAudioFormat, définie dans le SDK iOS, pour définir le format audio utilisé par le pilote audio personnalisé. La classe [OTKBasicAudioDevice init] crée une instance de la classe OTAudioFormat et définit la fréquence d'échantillonnage et le nombre de canaux pour le format audio :

- (id)init
{
    self = [super init];
    if (self) {
        self = [super init];
        if (self) {
            _otAudioFormat = [[OTAudioFormat alloc] init];
            _otAudioFormat.sampleRate = kSampleRate;
            _otAudioFormat.numChannels = 1;
        }

        // ...
    }
    return self;
}

Les init définit également certaines propriétés locales qui indiquent si l'appareil capture, si la capture a été initialisée, s'il effectue le rendu et si le rendu a été initialisé :

_isDeviceCapturing = NO;
_isCaptureInitialized = NO;
_isDeviceRendering = NO;
_isRenderingInitialized = NO;

Les init met également en place un fichier pour enregistrer l'audio entrant dans un fichier. Cela permet d'illustrer l'utilisation du moteur de rendu audio du pilote audio personnalisé :

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                        NSUserDomainMask,
                                                        YES);
NSString *path = [paths[0] stringByAppendingPathComponent:kOutputFileSampleName];

[[NSFileManager defaultManager] createFileAtPath:path
                                        contents:nil
                                        attributes:nil];
_outFile = [NSFileHandle fileHandleForReadingAtPath:path];

Les [OTKBasicAudioDevice setAudioBus:] (définie par le protocole OTAudioDevice) définit le bus audio à utiliser par le périphérique audio (défini par le protocole OTAudioBus). Le périphérique audio utilise cet objet pour envoyer et recevoir des échantillons audio vers et depuis une session. Cette instance de l'objet est conservée pendant toute la durée de vie de l'objet de mise en œuvre. L'éditeur accède à l'objet OTAudioBus pour obtenir les échantillons audio. Et les abonnés enverront des échantillons audio (à partir des flux souscrits) à l'objet OTAudioBus. Voici l'implémentation OTKBasicAudioDevice de l'objet [OTAudioDevice setAudioBus:] méthode :

- (BOOL)setAudioBus:(id<OTAudioBus>)audioBus
{
    self.otAudioBus = audioBus;
    return YES;
}

Les [OTKBasicAudioDevice setAudioBus:] (définie par le protocole OTAudioDevice) définit le format de rendu audio, l'instance OTAudioFormat créée dans la méthode init méthode :

- (OTAudioFormat*)renderFormat
{
    return self.otAudioFormat;
}

Ajout d'un rendu audio personnalisé

Les [OTAudioDevice startRendering:] est appelée lorsque le périphérique audio doit commencer à restituer (lire) l'audio des flux souscrits. L'implémentation OTKBasicAudioDevice de cette méthode appelle la fonction [self consumeSampleCapture] après 0,1 seconde :

- (BOOL)startRendering
{
    self.isDeviceRendering = YES;
    dispatch_after(
        dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
        dispatch_get_main_queue(),
        ^{
        [self consumeSampleCapture];
    });
    return YES;
}

Les [OTKBasicAudioDevice consumeSampleCapture] obtient 1000 échantillons du bus audio en appelant la méthode [OTAudioBus readRenderData:buffer numberOfSamples:] (définie par le SDK iOS OpenTok). Elle écrit ensuite les données audio dans le fichier (à des fins d'échantillonnage). Et, si le périphérique audio est toujours utilisé pour rendre des échantillons audio, il met en place un minuteur pour appeler la méthode consumeSampleCapture après 0,1 seconde :

- (void)consumeSampleCapture
{
    static int num_samples = 1000;
    int16_t *buffer = malloc(sizeof(int16_t) * num_samples);

    uint32_t samples_get = [self.otAudioBus readRenderData:buffer numberOfSamples:num_samples];

    NSData *data = [NSData dataWithBytes:buffer
                                    length:(sizeof(int16_t) * samples_get)];
    [self.outFile seekToEndOfFile];
    [self.outFile writeData:data];

    free(buffer);

    if (self.isDeviceRendering) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
            (int64_t)(0.1 * NSEC_PER_SEC)),
            dispatch_get_main_queue(),
            ^{
            [self consumeSampleCapture];
        });
    }
}

Cet exemple a été conçu à des fins didactiques : il écrit les données audio dans un fichier. Dans une utilisation plus pratique d'un pilote audio personnalisé, vous pourriez utiliser le pilote audio personnalisé pour lire l'audio sur un périphérique Bluetooth ou pour traiter l'audio avant de le lire.

Capturer de l'audio pour l'utiliser par un éditeur

Les [OTAudioDevice startCapture:] est appelée lorsque le périphérique audio doit commencer à capturer des données audio à publier. L'implémentation OTKBasicAudioDevice de cette méthode appelle la fonction [self produceSampleCapture] après 0,1 seconde :

- (BOOL)startCapture
{
    self.isDeviceCapturing = YES;
    dispatch_after(
        dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
        dispatch_get_main_queue(),
        ^{
        [self produceSampleCapture];
    });

    return YES;
}

Les [OTKBasicAudioDevice produceSampleCapture] produit un tampon contenant des échantillons de données aléatoires (bruit blanc). Elle appelle ensuite la fonction [OTAudioBus writeCaptureData: numberOfSamples:] de l'objet OTAudioBus, qui envoie les échantillons au bus audio. L'éditeur de l'application utilise les échantillons envoyés au bus audio pour les transmettre en tant qu'audio dans le flux publié. Ensuite, si une capture est toujours en cours (si l'application est en cours de publication), la méthode s'appelle à nouveau après 0,1 seconde.

- (void)produceSampleCapture
{
    static int num_frames = 1000;
    int16_t *buffer = malloc(sizeof(int16_t) * num_frames);

    for (int frame = 0; frame < num_frames; ++frame) {
        Float32 sample = ((double)arc4random() / 0x100000000);
        buffer[frame] = (sample * 32767.0f);
    }

    [self.otAudioBus writeCaptureData:buffer numberOfSamples:num_frames];

    free(buffer);

    if (self.isDeviceCapturing) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
            (int64_t)(0.1 * NSEC_PER_SEC)),
            dispatch_get_main_queue(),
            ^{
            [self produceSampleCapture];
        });
    }
}

Le protocole OTAudioDevice comprend d'autres méthodes requises, qui sont implémentées par la classe OTKBasicAudioDevice. Cependant, cet exemple ne fait rien d'intéressant dans ces méthodes, elles ne sont donc pas incluses dans cette discussion.

Voir aussi