Benutzerdefinierter Audiotreiber
Übersicht
Mit den Android- und iOS-Video-SDKs können Sie einen benutzerdefinierten Audiotreiber für Publisher und Abonnenten einrichten. Sie können einen benutzerdefinierten Audiotreiber verwenden, um das an den Stream eines Publishers gesendete Audio anzupassen. Sie können auch die Audiowiedergabe von abonnierten Streams anpassen.
In dieser Anleitung wird das Thema behandelt:
- Nehmen Sie Änderungen am Audiotreiber in Ihrer Vonage Video Android-Anwendung vor.
- Nehmen Sie Änderungen am Audiotreiber in Ihrer Vonage Video iOS-Anwendung vor.
Android
Bevor Sie beginnen
Diese Anleitung zeigt den erforderlichen Code zum Hinzufügen eines benutzerdefinierten Audiotreibers. Der Code für diese Anleitung ist im Basic-Audio-Driver-Java-Projekt des opentok-android-sdk-Beispiele Repo. Wenn Sie dies noch nicht getan haben, müssen Sie das Projektarchiv in ein lokales Verzeichnis klonen. Führen Sie in der Befehlszeile Folgendes aus:
Öffnen Sie die Basis-Audio-Treiber-Java Projekt in Android Studio, um die Entwicklung zu verfolgen.
Erforschung des Codes
Diese Beispielanwendung verwendet den benutzerdefinierten Audiotreiber, um weißes Rauschen (ein zufälliges Audiosignal) im Audiostrom zu veröffentlichen. Außerdem wird der benutzerdefinierte Audiotreiber verwendet, um das Audio von abonnierten Streams zu erfassen und in einer Datei zu speichern.
Hinzufügen eines benutzerdefinierten Audiotreibers
Einrichten des Audiogeräts und des Audiobusses
Bei der Verwendung eines benutzerdefinierten Audiotreibers definieren Sie einen benutzerdefinierten Audiotreiber und einen Audiobus, der von der Anwendung verwendet werden soll.
Die NoiseAudioDevice Klasse definiert eine grundlegende Schnittstelle für Audiogeräte, die von der Anwendung verwendet werden soll. Sie erweitert die BaseAudioDevice Klasse, die vom Android SDK definiert wurde. Um einen eigenen Audiotreiber zu verwenden, rufen Sie die AudioDeviceManager.setAudioDevice() Methode. Dieses Beispiel setzt das Audiogerät auf eine Instanz der Methode NoiseAudioDevice Klasse:
AudioDeviceManager.setAudioDevice(noiseAudioDevice);
Verwenden Sie die AudioSettings Klasse, die im Android SDK definiert ist, um das vom benutzerdefinierten Audiotreiber verwendete Audioformat zu definieren. Die NoiseAudioDevice() Konstruktor instanziiert zwei AudioSettings Instanzen - eine für den benutzerdefinierten Audio-Capturer und eine für den benutzerdefinierten Audio-Renderer. Sie legt die Samplerate und die Anzahl der Kanäle für jede Instanz fest:
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();
}
Der Konstruktor richtet auch einige lokale Eigenschaften ein, die angeben, ob das Gerät aufnimmt oder rendert. Er legt auch eine Handler-Instanz fest, um die capturer Lauffähiges Objekt.
Die NoiseAudioDevice.getAudioBus() Methode erhält die AudioBus Instanz, die dieses Audiogerät verwendet, definiert durch den NoiseAudioDevice.AudioBus Klasse. Verwenden Sie die AudioBus Instanz zum Senden und Empfangen von Hörproben zu und von einer Sitzung. Der Herausgeber greift auf die AudioBus Objekt, um die Hörproben zu erhalten. Die Abonnenten senden Hörproben (aus abonnierten Streams) an das AudioBus-Objekt.
Aufzeichnung von Audio zur Verwendung durch einen Verlag
Die BaseAudioDevice.startCapturer() wird aufgerufen, wenn das Audiogerät mit der Aufnahme von Audiodaten beginnen soll, die veröffentlicht werden sollen. Die NoiseAudioDevice Implementierung dieser Methode startet die capturer Thread, der nach 1 Sekunde in der Warteschlange ausgeführt werden soll:
public boolean startCapturer() {
capturerStarted = true;
capturerHandler.postDelayed(capturer, capturerIntervalMillis);
return true;
}
Die capturer Thread erzeugt einen Puffer mit Stichproben von Zufallsdaten (weißes Rauschen). Dann ruft er die writeCaptureData() Methode der AudioBus Objekt, das die Samples an den Audiobus sendet. Der Publisher in der Anwendung verwendet die an den Audiobus gesendeten Samples, um sie als Audio im veröffentlichten Stream zu übertragen. Wenn dann noch eine Aufnahme läuft (wenn die Anwendung veröffentlicht), wird das capturer Thread wird nach einer weiteren Sekunde erneut ausgeführt:
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);
}
}
};
Die AudioDevice Klasse enthält weitere Methoden, die von der NoiseAudioDevice Klasse. In diesem Beispiel wird jedoch nichts Interessantes mit diesen Methoden gemacht, so dass sie in dieser Diskussion nicht berücksichtigt werden.
Hinzufügen eines benutzerdefinierten Audio-Renderers
Sie implementieren einen Audio-Renderer für den Ton der abonnierten Streams.
Die NoiseAudioDevice() Konstruktormethode richtet eine Datei ein, um das eingehende Audio in einer Datei zu speichern. Dies geschieht, um eine Verwendung des Audio-Renderers des benutzerdefinierten Audiotreibers zu veranschaulichen. Die Anwendung benötigt die folgenden Berechtigungen, die in der Datei AndroidManifest.xml Datei:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Die BaseAudioDevice.initRenderer() Methode wird aufgerufen, wenn die Anwendung den Audio-Renderer initialisiert. Die NoiseAudioDevice Implementierung dieser Methode instanziiert ein neues File-Objekt, in das die Anwendung Audiodaten schreiben wird:
@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;
}
Die BaseAudioDevice.startRendering() Methode wird aufgerufen, wenn das Audiogerät mit dem Rendern (Abspielen) von Audio aus abonnierten Streams beginnen soll. Die NoiseAudioDevice Implementierung dieser Methode startet die capturer Thread, der nach 1 Sekunde in der Warteschlange ausgeführt werden soll:
@Override
public boolean startRenderer() {
rendererStarted = true;
rendererHandler.postDelayed(renderer, rendererIntervalMillis);
return true;
}
Die renderer Thread holt sich 1 Sekunde Audio vom Audiobus, indem er die readRenderData() Methode der AudioBus Objekt. Dann schreibt es die Audiodaten in die Datei (für Samplezwecke). Und wenn das Audiogerät noch zum Rendern von Audiosamples verwendet wird, wird ein Timer gesetzt, um die rendererHandler Thread nach 0,1 Sekunden erneut:
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);
}
}
};
Dieses Beispiel dient der Veranschaulichung - es schreibt die Audiodaten in eine Datei. In einer praktischeren Anwendung eines benutzerdefinierten Audiotreibers könnten Sie den benutzerdefinierten Audiotreiber verwenden, um Audio auf einem Bluetooth-Gerät wiederzugeben oder um Audio vor der Wiedergabe zu verarbeiten.
iOS
Diese Anleitung zeigt den erforderlichen Code, um einen benutzerdefinierten Audiotreiber hinzuzufügen. Der Code für diese Anleitung ist im audio-driver-Zweig des lern-opentok-ios repo Repo. Wenn Sie dies noch nicht getan haben, müssen Sie das Projektarchiv in ein lokales Verzeichnis klonen. Führen Sie in der Befehlszeile Folgendes aus:
Öffnen Sie die Audiotreiber Zweig in Xcode zu folgen.
Erforschung des Codes
Diese Beispielanwendung verwendet den benutzerdefinierten Audiotreiber, um weißes Rauschen (ein zufälliges Audiosignal) im Audiostrom zu veröffentlichen. Außerdem wird der benutzerdefinierte Audiotreiber verwendet, um das Audio von abonnierten Streams zu erfassen und in einer Datei zu speichern.
Hinzufügen eines benutzerdefinierten Audiotreibers
Einrichten des Audiogeräts und des Audiobusses
Bei der Verwendung eines benutzerdefinierten Audiotreibers definieren Sie einen benutzerdefinierten Audiotreiber und einen Audiobus, der von der Anwendung verwendet werden soll.
Die OTKBasicAudioDevice Klasse definiert eine grundlegende Audiogeräteschnittstelle, die von der App verwendet werden soll. Sie implementiert das OTAudioDevice-Protokoll, das vom iOS SDK definiert wurde. Um einen eigenen Audiotreiber zu verwenden, rufen Sie die [OTAudioDeviceManager setAudioDevice:] Methode. Dieses Beispiel setzt das Audiogerät auf eine Instanz der Klasse OTKBasicAudioDevice:
[OTAudioDeviceManager setAudioDevice:[[OTKBasicAudioDevice alloc] init]];
Verwenden Sie die Klasse OTAudioFormat, die im iOS SDK definiert ist, um das vom benutzerdefinierten Audiotreiber verwendete Audioformat zu definieren. Die [OTKBasicAudioDevice init] Methode erzeugt eine Instanz der Klasse OTAudioFormat und legt die Abtastrate und die Anzahl der Kanäle für das Audioformat fest:
- (id)init
{
self = [super init];
if (self) {
self = [super init];
if (self) {
_otAudioFormat = [[OTAudioFormat alloc] init];
_otAudioFormat.sampleRate = kSampleRate;
_otAudioFormat.numChannels = 1;
}
// ...
}
return self;
}
Die init Methode richtet auch einige lokale Eigenschaften ein, die angeben, ob das Gerät aufnimmt, ob die Aufnahme initialisiert wurde, ob es rendert und ob das Rendering initialisiert wurde:
_isDeviceCapturing = NO;
_isCaptureInitialized = NO;
_isDeviceRendering = NO;
_isRenderingInitialized = NO;
Die init Methode richtet auch eine Datei ein, um das eingehende Audio in einer Datei zu speichern. Dies geschieht, um eine Verwendung des Audio-Renderers des benutzerdefinierten Audiotreibers zu veranschaulichen:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [paths[0] stringByAppendingPathComponent:kOutputFileSampleName];
[[NSFileManager defaultManager] createFileAtPath:path
contents:nil
attributes:nil];
_outFile = [NSFileHandle fileHandleForReadingAtPath:path];
Die [OTKBasicAudioDevice setAudioBus:] (definiert durch das OTAudioDevice-Protokoll) legt den Audiobus fest, der vom Audiogerät (definiert durch das OTAudioBus-Protokoll) verwendet werden soll. Das Audiogerät verwendet dieses Objekt zum Senden und Empfangen von Audiosamples an und von einer Sitzung. Diese Instanz des Objekts wird während der Lebensdauer des implementierenden Objekts beibehalten. Der Verleger greift auf das OTAudioBus-Objekt zu, um die Hörproben zu erhalten. Und die Abonnenten senden Hörproben (aus abonnierten Streams) an das OTAudioBus-Objekt. Hier ist die OTKBasicAudioDevice-Implementierung des [OTAudioDevice setAudioBus:] Methode:
- (BOOL)setAudioBus:(id<OTAudioBus>)audioBus
{
self.otAudioBus = audioBus;
return YES;
}
Die [OTKBasicAudioDevice setAudioBus:] Methode (definiert durch das OTAudioDevice-Protokoll) das Audio-Rendering-Format, die OTAudioFormat-Instanz, die in der Methode init Methode:
- (OTAudioFormat*)renderFormat
{
return self.otAudioFormat;
}
Hinzufügen eines benutzerdefinierten Audio-Renderers
Die [OTAudioDevice startRendering:] Methode wird aufgerufen, wenn das Audiogerät mit dem Rendern (Abspielen) von Audio aus abonnierten Streams beginnen soll. Die OTKBasicAudioDevice-Implementierung dieser Methode ruft die [self consumeSampleCapture] Methode nach 0,1 Sekunden:
- (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;
}
Die [OTKBasicAudioDevice consumeSampleCapture] Methode erhält 1000 Samples vom Audiobus, indem sie die [OTAudioBus readRenderData:buffer numberOfSamples:] Methode (definiert durch das OpenTok iOS SDK). Anschließend werden die Audiodaten in die Datei geschrieben (für Sample-Zwecke). Und wenn das Audiogerät noch zum Rendern von Audiosamples verwendet wird, wird ein Timer gesetzt, der die consumeSampleCapture Methode nach 0,1 Sekunden erneut:
- (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];
});
}
}
Dieses Beispiel dient der Veranschaulichung - es schreibt die Audiodaten in eine Datei. In einer praktischeren Anwendung eines benutzerdefinierten Audiotreibers könnten Sie den benutzerdefinierten Audiotreiber verwenden, um Audio auf einem Bluetooth-Gerät wiederzugeben oder um Audio vor der Wiedergabe zu verarbeiten.
Aufzeichnung von Audio zur Verwendung durch einen Verlag
Die [OTAudioDevice startCapture:] wird aufgerufen, wenn das Audiogerät mit der Aufnahme von Audiodaten beginnen soll, die veröffentlicht werden sollen. Die OTKBasicAudioDevice-Implementierung dieser Methode ruft die [self produceSampleCapture] Methode nach 0,1 Sekunden:
- (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;
}
Die [OTKBasicAudioDevice produceSampleCapture] Methode erzeugt einen Puffer mit Stichproben von Zufallsdaten (weißes Rauschen). Dann ruft sie die [OTAudioBus writeCaptureData: numberOfSamples:] Methode des OTAudioBus-Objekts, die die Samples an den Audiobus sendet. Der Publisher in der Anwendung verwendet die an den Audiobus gesendeten Samples, um sie als Audio im veröffentlichten Stream zu übertragen. Wenn dann noch eine Aufnahme im Gange ist (wenn die Anwendung veröffentlicht), ruft sich die Methode nach 0,1 Sekunden erneut auf.
- (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];
});
}
}
Das OTAudioDevice-Protokoll umfasst weitere erforderliche Methoden, die von der Klasse OTKBasicAudioDevice implementiert werden. In diesem Beispiel sind diese Methoden jedoch uninteressant, so dass sie in dieser Diskussion nicht berücksichtigt werden.