Controlador de audio personalizado

Visión general

Los SDK de vídeo para Android e iOS le permiten configurar un controlador de audio personalizado para editores y suscriptores. Puede utilizar un controlador de audio personalizado para personalizar el audio enviado a la secuencia de un editor. También puede personalizar la reproducción del audio de las secuencias suscritas.

Este tutorial repasará:

  • Realiza modificaciones en el controlador de audio de tu aplicación Vonage Video para Android.
  • Realiza modificaciones en el controlador de audio de tu aplicación de Vonage Video para iOS.

Android

Antes de empezar

Esta guía muestra el código necesario para añadir un controlador de audio personalizado. El código está disponible en el proyecto Basic-Audio-Driver-Java del proyecto opentok-android-sdk-samples repo. Si aún no lo has hecho, tendrás que clonar el repositorio en un directorio local. En la línea de comandos, ejecute:

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

Abra el Basic-Audio-Driver-Java en Android Studio.

Explorar el código

Esta aplicación de ejemplo utiliza el controlador de audio personalizado para publicar ruido blanco (una señal de audio aleatoria) en su flujo de audio. También utiliza el controlador de audio personalizado para capturar el audio de los flujos suscritos y guardarlo en un archivo.

Añadir un controlador de audio personalizado

Configuración del dispositivo de audio y del bus de audio

Al utilizar un controlador de audio personalizado, se define un controlador de audio personalizado y un bus de audio que utilizará la aplicación.

En NoiseAudioDevice define una interfaz básica de dispositivo de audio que utilizará la aplicación. Amplía la clase BaseAudioDevice definida por el SDK de Android. Para utilizar un controlador de audio personalizado, llame a la clase AudioDeviceManager.setAudioDevice() método. Este ejemplo establece el dispositivo de audio en una instancia del método NoiseAudioDevice clase:

AudioDeviceManager.setAudioDevice(noiseAudioDevice);

Utiliza el AudioSettings definida en el SDK de Android, para definir el formato de audio utilizado por el controlador de audio personalizado. La clase NoiseAudioDevice() crea dos instancias AudioSettings una para el capturador de audio personalizado y otra para el renderizador de audio personalizado. Establece la frecuencia de muestreo y el número de canales para cada uno:

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();
}

El constructor también establece algunas propiedades locales que informan si el dispositivo está capturando o renderizando. También establece una instancia de Handler para procesar el archivo capturer Objeto ejecutable.

En NoiseAudioDevice.getAudioBus() obtiene el método AudioBus que utiliza este dispositivo de audio, definida por la etiqueta NoiseAudioDevice.AudioBus clase. Utilice la AudioBus para enviar y recibir muestras de audio desde y hacia una sesión. El editor accederá a la instancia AudioBus para obtener las muestras de audio. Los suscriptores enviarán muestras de audio (de los flujos suscritos) al objeto AudioBus.

Captura de audio para uso editorial

En BaseAudioDevice.startCapturer() se llama cuando el dispositivo de audio debe empezar a capturar audio para publicarlo. El método NoiseAudioDevice de este método inicia el capturer hilo que se ejecutará en la cola después de 1 segundo:

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

En capturer produce un búfer que contiene muestras de datos aleatorios (ruido blanco). A continuación, llama a la función writeCaptureData() método del AudioBus que envía las muestras al bus de audio. El editor de la aplicación utiliza las muestras enviadas al bus de audio para transmitirlas como audio en el flujo publicado. Entonces, si todavía hay una captura en curso (si la aplicación está publicando), el objeto capturer se vuelve a ejecutar al cabo de otro segundo:

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);
        }
    }
};

En AudioDevice incluye otros métodos implementados por la clase NoiseAudioDevice clase. Sin embargo, esta muestra no hace nada interesante en estos métodos, por lo que no se incluyen en esta discusión.

Añadir un renderizador de audio personalizado

Implementará el renderizador de audio para el audio de los flujos suscritos.

En NoiseAudioDevice() configura un archivo para guardar el audio entrante en un archivo. Esto se hace para ilustrar un uso del renderizador de audio del controlador de audio personalizado. La aplicación requiere los siguientes permisos, definidos en la directiva AndroidManifest.xml archivo:

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

En BaseAudioDevice.initRenderer() cuando la aplicación inicializa el renderizador de audio. La dirección NoiseAudioDevice de este método crea un nuevo objeto File, en el que la aplicación escribirá los datos de 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;
}

En BaseAudioDevice.startRendering() se llama cuando el dispositivo de audio debe empezar a reproducir el audio de los flujos suscritos. El método NoiseAudioDevice de este método inicia el capturer hilo que se ejecutará en la cola después de 1 segundo:

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

En renderer obtiene 1 segundo de audio del bus de audio llamando a la función readRenderData() método del AudioBus objeto. A continuación, escribe los datos de audio en el archivo (con fines de muestreo). Y, si el dispositivo de audio todavía se está utilizando para renderizar muestras de audio, establece un temporizador para ejecutar la función rendererHandler después de 0,1 segundos:

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);
        }
    }
};

Este ejemplo es intencionalmente para propósitos instructivos -- escribe los datos de audio a un archivo. En un uso más práctico de un controlador de audio personalizado, podrías usar el controlador de audio personalizado para reproducir audio a un dispositivo Bluetooth o para procesar audio antes de reproducirlo.

iOS

Esta guía muestra el código necesario para añadir un controlador de audio personalizado. El código está disponible en la rama audio-driver del archivo repo learning-opentok-ios repo. Si aún no lo has hecho, tendrás que clonar el repositorio en un directorio local. En la línea de comandos, ejecute:

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

Abra el controlador de audio en Xcode.

Explorar el código

Esta aplicación de ejemplo utiliza el controlador de audio personalizado para publicar ruido blanco (una señal de audio aleatoria) en su flujo de audio. También utiliza el controlador de audio personalizado para capturar el audio de los flujos suscritos y guardarlo en un archivo.

Añadir un controlador de audio personalizado

Configuración del dispositivo de audio y del bus de audio

Al utilizar un controlador de audio personalizado, se define un controlador de audio personalizado y un bus de audio que utilizará la aplicación.

En OTKBasicAudioDevice define una interfaz básica de dispositivo de audio que utilizará la aplicación. Implementa el protocolo OTAudioDevice, definido por el SDK de iOS. Para utilizar un controlador de audio personalizado, llame a la clase [OTAudioDeviceManager setAudioDevice:] método. Este ejemplo establece el dispositivo de audio en una instancia de la clase OTKBasicAudioDevice:

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

Utilice la clase OTAudioFormat, definida en el SDK de iOS, para definir el formato de audio utilizado por el controlador de audio personalizado. La dirección [OTKBasicAudioDevice init] crea una instancia de la clase OTAudioFormat y establece la frecuencia de muestreo y el número de canales del formato de 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;
}

En init también establece algunas propiedades locales que informan de si el dispositivo está capturando, si se ha inicializado la captura, si está renderizando y si se ha inicializado la renderización:

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

En init también configura un archivo para guardar el audio entrante en un archivo. Esto se hace para ilustrar un uso del renderizador de audio del controlador de audio personalizado:

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

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

En [OTKBasicAudioDevice setAudioBus:] (definido por el protocolo OTAudioDevice) establece el bus de audio que utilizará el dispositivo de audio (definido por el protocolo OTAudioBus). El dispositivo de audio utiliza este objeto para enviar y recibir muestras de audio hacia y desde una sesión. Esta instancia del objeto se conserva durante toda la vida útil del objeto de implementación. El publicador accederá al objeto OTAudioBus para obtener las muestras de audio. Y los suscriptores enviarán muestras de audio (de los flujos suscritos) al objeto OTAudioBus. Aquí está la implementación OTKBasicAudioDevice del objeto [OTAudioDevice setAudioBus:] método:

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

En [OTKBasicAudioDevice setAudioBus:] (definido por el protocolo OTAudioDevice) establece el formato de representación de audio, la instancia OTAudioFormat que se creó en el método init método:

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

Añadir un renderizador de audio personalizado

En [OTAudioDevice startRendering:] es llamado cuando el dispositivo de audio debe comenzar a renderizar (reproducir) el audio de los flujos suscritos. La implementación OTKBasicAudioDevice de este método llama al método [self consumeSampleCapture] después de 0,1 segundos:

- (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;
}

En [OTKBasicAudioDevice consumeSampleCapture] obtiene 1000 muestras del bus de audio llamando al método [OTAudioBus readRenderData:buffer numberOfSamples:] (definido por el OpenTok iOS SDK). A continuación, escribe los datos de audio en el archivo (para fines de muestreo). Y, si el dispositivo de audio todavía se está utilizando para renderizar muestras de audio, establece un temporizador para llamar a consumeSampleCapture después de 0,1 segundos:

- (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];
        });
    }
}

Este ejemplo es intencionalmente para propósitos instructivos -- escribe los datos de audio a un archivo. En un uso más práctico de un controlador de audio personalizado, podrías usar el controlador de audio personalizado para reproducir audio a un dispositivo Bluetooth o para procesar audio antes de reproducirlo.

Captura de audio para uso editorial

En [OTAudioDevice startCapture:] es llamado cuando el dispositivo de audio debe comenzar a capturar audio para ser publicado. La implementación OTKBasicAudioDevice de este método llama al método [self produceSampleCapture] después de 0,1 segundos:

- (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;
}

En [OTKBasicAudioDevice produceSampleCapture] produce un búfer que contiene muestras de datos aleatorios (ruido blanco). A continuación, llama al método [OTAudioBus writeCaptureData: numberOfSamples:] del objeto OTAudioBus, que envía las muestras al bus de audio. El editor de la aplicación utiliza las muestras enviadas al bus de audio para transmitirlas como audio en el flujo publicado. A continuación, si todavía hay una captura en curso (si la aplicación está publicando), el método se llama a sí mismo de nuevo después de 0,1 segundos.

- (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];
        });
    }
}

El protocolo OTAudioDevice incluye otros métodos necesarios, que son implementados por la clase OTKBasicAudioDevice. Sin embargo, esta muestra no hace nada interesante en estos métodos, por lo que no se incluyen en esta discusión.

Ver también