Programmierhinweise zu OpenSL ES

WARNUNG: OpenSL ES wurde eingestellt. Entwickler sollten die Open-Source-Oboe-Bibliothek verwenden, die auf GitHub verfügbar ist. Oboe ist ein C++-Wrapper, der eine API bietet, die AAudio sehr ähnelt. Oboe ruft AAudio auf, wenn AAudio verfügbar ist, und verwendet OpenSL ES, wenn AAudio nicht verfügbar ist.

Die Hinweise in diesem Abschnitt ergänzen die OpenSL ES 1.0.1-Spezifikation.

Objekte und Benutzeroberfläche initialisieren

Zwei Aspekte des OpenSL ES-Programmiermodells, die neuen Entwicklern möglicherweise noch nicht vertraut sind, sind die Unterscheidung zwischen Objekten und Schnittstellen sowie die Initialisierungssequenz.

Kurz gesagt ähnelt ein OpenSL ES-Objekt dem Objektkonzept in Programmiersprachen wie Java und C++, mit der Ausnahme, dass ein OpenSL ES-Objekt nur über die zugehörigen Schnittstellen sichtbar ist. Dazu gehört auch die ursprüngliche Benutzeroberfläche für alle Objekte, die SLObjectItf genannt wird. Es gibt kein Handle für ein Objekt selbst, sondern nur ein Handle für die SLObjectItf-Schnittstelle des Objekts.

Ein OpenSL ES-Objekt wird zuerst erstellt, wodurch eine SLObjectItf zurückgegeben wird, und dann realisiert. Das ähnelt dem gängigen Programmiermuster, bei dem zuerst ein Objekt erstellt wird (was nur bei fehlendem Arbeitsspeicher oder ungültigen Parametern fehlschlagen sollte) und dann die Initialisierung abgeschlossen wird (was aufgrund von fehlenden Ressourcen fehlschlagen kann). Der Schritt „Umsetzen“ bietet der Implementierung einen logischen Ort, an dem bei Bedarf zusätzliche Ressourcen zugewiesen werden können.

Im Rahmen der API zum Erstellen eines Objekts gibt eine Anwendung ein Array der gewünschten Schnittstellen an, die sie später erwerben möchte. Hinweis: Mit diesem Array werden die Schnittstellen nicht automatisch erworben. Es gibt lediglich an, dass sie in Zukunft erworben werden sollen. Schnittstellen werden als implizit oder explizit unterschieden. Eine explizite Schnittstelle muss im Array aufgeführt sein, wenn sie später abgerufen wird. Eine implizite Schnittstelle muss nicht im Array „object create“ aufgeführt sein, es schadet aber auch nicht, sie dort zu listen. OpenSL ES hat noch eine weitere Schnittstellenart namens dynamisch, die nicht im Array zum Erstellen von Objekten angegeben werden muss und später nach dem Erstellen des Objekts hinzugefügt werden kann. Die Android-Implementierung bietet eine praktische Funktion, um diese Komplexität zu vermeiden. Weitere Informationen finden Sie unter Dynamische Benutzeroberflächen bei der Objekterstellung.

Nachdem das Objekt erstellt und realisiert wurde, sollte die Anwendung Schnittstellen für jede erforderliche Funktion erwerben, indem GetInterface auf dem ursprünglichen SLObjectItf verwendet wird.

Anschließend kann das Objekt über seine Schnittstellen verwendet werden. Einige Objekte erfordern jedoch eine weitere Einrichtung. Insbesondere ein Audioplayer mit URI-Datenquelle erfordert etwas mehr Vorbereitung, um Verbindungsfehler zu erkennen. Weitere Informationen finden Sie im Abschnitt Audioplayer-Prefetch.

Wenn Ihre Anwendung das Objekt nicht mehr benötigt, sollten Sie es explizit löschen. Weitere Informationen finden Sie unten im Abschnitt Destroy.

Audioplayer-Vorabruf

Bei einem Audioplayer mit URI-Datenquelle weist Object::Realize Ressourcen zu, stellt aber keine Verbindung zur Datenquelle her (vorbereiten) und beginnt nicht mit dem Vorab-Abrufen von Daten. Diese werden ausgelöst, wenn der Spielerstatus auf SL_PLAYSTATE_PAUSED oder SL_PLAYSTATE_PLAYING festgelegt ist.

Einige Informationen sind möglicherweise noch relativ spät in der Sequenz unbekannt. Insbesondere wird bei Player::GetDuration zuerst SL_TIME_UNKNOWN zurückgegeben und MuteSolo::GetChannelCount gibt entweder erfolgreich „0“ zurück oder das Fehlerergebnis SL_RESULT_PRECONDITIONS_VIOLATED. Diese APIs geben die korrekten Werte zurück, sobald sie bekannt sind.

Zu den weiteren Eigenschaften, die anfänglich unbekannt sind, gehören die Abtastrate und der tatsächliche Medieninhaltstyp, basierend auf der Untersuchung des Headers des Inhalts (im Gegensatz zum von der Anwendung angegebenen MIME-Typ und Containertyp). Diese werden auch später bei der Vorbereitung/Vorab-Ladephase ermittelt, es gibt jedoch keine APIs zum Abrufen.

Die Prefetch-Statusschnittstelle ist nützlich, um zu erkennen, ob alle Informationen verfügbar sind oder Ihre Anwendung regelmäßig Abfragen durchführen kann. Einige Informationen, z. B. die Dauer eines gestreamten MP3-Titels, sind möglicherweise nie verfügbar.

Die Benutzeroberfläche für den prefetch-Status ist auch nützlich, um Fehler zu erkennen. Registrieren Sie einen Callback und aktivieren Sie mindestens die Ereignisse SL_PREFETCHEVENT_FILLLEVELCHANGE und SL_PREFETCHEVENT_STATUSCHANGE. Wenn beide Ereignisse gleichzeitig gesendet werden und PrefetchStatus::GetFillLevel eine Nullebene und PrefetchStatus::GetPrefetchStatus SL_PREFETCHSTATUS_UNDERFLOW meldet, weist das auf einen nicht wiederherstellbaren Fehler in der Datenquelle hin. Dazu gehört auch, dass keine Verbindung zur Datenquelle hergestellt werden kann, weil der lokale Dateiname nicht vorhanden ist oder der Netzwerk-URI ungültig ist.

Die nächste Version von OpenSL ES wird voraussichtlich eine explizitere Unterstützung für die Fehlerbehandlung in der Datenquelle bieten. Aus Gründen der zukünftigen Binärkompatibilität werden wir die aktuelle Methode zur Meldung eines nicht wiederherstellbaren Fehlers jedoch weiterhin unterstützen.

Zusammenfassend ist dies die empfohlene Codesequenz:

  1. Engine::CreateAudioPlayer
  2. Object:Realize
  3. Object::GetInterface für SL_IID_PREFETCHSTATUS
  4. PrefetchStatus::SetCallbackEventsMask
  5. PrefetchStatus::SetFillUpdatePeriod
  6. PrefetchStatus::RegisterCallback
  7. Object::GetInterface für SL_IID_PLAY
  8. Play::SetPlayState bis SL_PLAYSTATE_PAUSED oder SL_PLAYSTATE_PLAYING

Hinweis : Die Vorbereitung und der Vorabruf erfolgen hier. Während dieser Zeit wird Ihr Callback mit regelmäßigen Statusaktualisierungen aufgerufen.

Zerstören

Destruieren Sie alle Objekte, wenn Sie die Anwendung beenden. Objekte sollten in umgekehrter Reihenfolge ihrer Erstellung zerstört werden, da ein Objekt mit abhängigen Objekten nicht sicher zerstört werden kann. Beispiel: Audioplayer und -rekorder, Ausgabemix und dann die Engine.

OpenSL ES unterstützt keine automatische Speicherbereinigung oder Referenzzählung von Schnittstellen. Nachdem Sie Object::Destroy aufgerufen haben, werden alle vorhandenen Schnittstellen, die vom zugehörigen Objekt abgeleitet sind, undefiniert.

Die Android OpenSL ES-Implementierung erkennt die falsche Verwendung solcher Schnittstellen nicht. Wenn Sie solche Schnittstellen weiterhin verwenden, nachdem das Objekt zerstört wurde, kann Ihre Anwendung abstürzen oder sich unvorhersehbar verhalten.

Wir empfehlen, sowohl die primäre Objektschnittstelle als auch alle zugehörigen Schnittstellen im Rahmen der Objektzerstörungssequenz explizit auf NULL festzulegen. So wird verhindert, dass ein veraltete Objekt-Handle versehentlich missbraucht wird.

Stereopanorama

Wenn Volume::EnableStereoPosition verwendet wird, um Stereo-Schwenken einer Monoquelle zu aktivieren, wird die Gesamtschallleistung um 3 dB reduziert. Dies ist erforderlich, damit die Gesamtlautstärke konstant bleibt, wenn die Quelle von einem Kanal zum anderen gewechselt wird. Aktivieren Sie daher die Stereopositionierung nur, wenn Sie sie benötigen. Weitere Informationen finden Sie im Wikipedia-Artikel Audiopanorama.

Callbacks und Threads

Callback-Handler werden in der Regel synchron aufgerufen, wenn die Implementierung ein Ereignis erkennt. Dieser Punkt ist asynchron zur Anwendung. Sie sollten daher einen nicht blockierenden Synchronisierungsmechanismus verwenden, um den Zugriff auf alle Variablen zu steuern, die zwischen der Anwendung und dem Callback-Handler freigegeben werden. Im Beispielcode, z. B. für Pufferwarteschlangen, haben wir diese Synchronisierung entweder weggelassen oder zur Vereinfachung die blockierende Synchronisierung verwendet. Eine ordnungsgemäße, nicht blockierende Synchronisierung ist jedoch für jeden Produktionscode entscheidend.

Callback-Handler werden von internen Nicht-Anwendungs-Threads aufgerufen, die nicht an die Android-Laufzeit angehängt sind, daher können sie JNI nicht verwenden. Da diese internen Threads für die Integrität der OpenSL ES-Implementierung entscheidend sind, sollte ein Callback-Handler auch nicht blockieren oder zu viel Arbeit leisten.

Wenn Ihr Callback-Handler JNI verwenden oder Aufgaben ausführen muss, die nicht dem Callback entsprechen, sollte der Handler stattdessen ein Ereignis posten, das von einem anderen Thread verarbeitet wird. Beispiele für akzeptable Rückrufarbeitslasten sind das Rendern und Einreihen des nächsten Ausgabepuffers (für einen AudioPlayer), die Verarbeitung des gerade gefüllten Eingabepuffers und das Einfügen des nächsten leeren Zwischenspeichers (für einen AudioRecorder) oder einfache APIs wie die meisten der Get-Familie. Informationen zur Arbeitslast finden Sie unten im Abschnitt Leistung.

Beachten Sie, dass das Umgekehrte sicher ist: Ein Android-Anwendungs-Thread, der in die JNI aufgenommen wurde, darf OpenSL ES APIs direkt aufrufen, einschließlich derjenigen, die blockieren. Blockierende Aufrufe aus dem Hauptthread werden jedoch nicht empfohlen, da sie zu einem ANR-Fehler (App antwortet nicht) führen können.

Die Bestimmung des Threads, der einen Callback-Handler aufruft, wird weitgehend der Implementierung überlassen. Diese Flexibilität soll zukünftige Optimierungen ermöglichen, insbesondere auf Multi-Core-Geräten.

Der Thread, in dem der Rückruf-Handler ausgeführt wird, hat nicht garantiert dieselbe Identität bei verschiedenen Aufrufen. Verlassen Sie sich daher nicht darauf, dass der von pthread_self() zurückgegebene pthread_t oder das von gettid() zurückgegebene pid_t bei allen Aufrufen konsistent ist. Aus demselben Grund sollten Sie die TLS-APIs (Thread Local Storage) wie pthread_setspecific() und pthread_getspecific() nicht über einen Rückruf verwenden.

Die Implementierung garantiert, dass keine gleichzeitigen Rückrufe derselben Art für dasselbe Objekt erfolgen. In verschiedenen Threads sind jedoch gleichzeitige Callbacks verschiedener Typen für dasselbe Objekt möglich.

Leistung

Da OpenSL ES eine native C API ist, haben Anwendungsthreads, die nicht zur Laufzeit gehören und OpenSL ES aufrufen, keinen laufzeitbezogenen Aufwand wie Pausen der automatischen Speicherbereinigung. Bis auf eine unten beschriebene Ausnahme bietet die Verwendung von OpenSL ES keine weiteren Leistungsvorteile. Insbesondere ist durch die Verwendung von OpenSL ES keine Verbesserungen wie eine geringere Audiolatenz und eine höhere Planungspriorität als die, die die Plattform allgemein bietet, garantiert. Andererseits kann eine OpenSL ES-Anwendung im Zuge der Weiterentwicklung der Android-Plattform und bestimmter Geräteimplementierungen von künftigen Verbesserungen der Systemleistung profitieren.

Eine dieser Entwicklungen ist die Unterstützung einer reduzierten Audioausgabelatenz. Die Grundlagen für eine reduzierte Ausgabelatenz wurden erstmals in Android 4.1 (API-Ebene 16) eingeführt. In Android 4.2 (API-Ebene 17) wurden weitere Fortschritte erzielt. Diese Verbesserungen sind über OpenSL ES für Geräteimplementierungen verfügbar, die die Funktion android.hardware.audio.low_latency angeben. Wenn das Gerät diese Funktion nicht angibt, aber Android 2.3 (API-Level 9) oder höher unterstützt, können Sie die OpenSL ES APIs weiterhin verwenden. Die Ausgabelatenz ist dann jedoch möglicherweise höher. Der Pfad mit der niedrigeren Ausgabelatenz wird nur verwendet, wenn die Anwendung eine Puffergröße und eine Abtastrate anfordert, die mit der nativen Ausgabekonfiguration des Geräts kompatibel sind. Diese Parameter sind gerätespezifisch und sollten wie unten beschrieben abgerufen werden.

Ab Android 4.2 (API-Ebene 17) kann eine Anwendung die native oder optimale Ausgabeabtastrate und ‑puffergröße für den primären Ausgabestream des Geräts abfragen. In Kombination mit dem oben genannten Funktionstest kann sich eine App jetzt für eine Ausgabe mit niedrigerer Latenz auf Geräten konfigurieren, die die Unterstützung angeben.

Für Android 4.2 (API-Ebene 17) und niedriger ist für eine geringere Latenz eine Pufferanzahl von mindestens zwei erforderlich. Ab Android 4.3 (API-Ebene 18) reicht für eine geringere Latenz eine Pufferanzahl von 1 aus.

Alle OpenSL ES-Schnittstellen für Ausgabeeffekte schließen den Pfad mit der niedrigeren Latenz aus.

Wir empfehlen folgende Reihenfolge:

  1. Prüfen Sie, ob API-Level 9 oder höher verwendet wird, um die Verwendung von OpenSL ES zu bestätigen.
  2. Prüfe mit folgendem Code, ob die Funktion android.hardware.audio.low_latency verfügbar ist:

    Kotlin

    import android.content.pm.PackageManager
    ...
    val pm: PackageManager = context.packageManager
    val claimsFeature: Boolean = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY)

    Java

    import android.content.pm.PackageManager;
    ...
    PackageManager pm = getContext().getPackageManager();
    boolean claimsFeature = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
  3. Prüfen Sie, ob API-Level 17 oder höher verwendet wird, um die Verwendung von android.media.AudioManager.getProperty() zu bestätigen.
  4. Mit folgendem Code kannst du die native oder optimale Ausgabeabtastrate und ‑puffergröße für den primären Ausgabestream dieses Geräts abrufen:

    Kotlin

    import android.media.AudioManager
    ...
    val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    val sampleRate: String = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
    val framesPerBuffer: String = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)

    Java

    import android.media.AudioManager;
    ...
    AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
    String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
    Beachten Sie, dass sampleRate und framesPerBuffer Strings sind. Prüfen Sie zuerst, ob der Wert null ist, und konvertieren Sie ihn dann mit Integer.parseInt() in einen Ganzzahltyp.
  5. Erstellen Sie jetzt mit OpenSL ES einen AudioPlayer mit PCM-Puffer-Queue-Datensucher.

Hinweis : Mit der Test-App Audio Buffer Size können Sie die native Puffergröße und die Abtastrate für OpenSL ES-Audioanwendungen auf Ihrem Audiogerät ermitteln. Auf GitHub finden Sie auch Beispiele für die audio-buffer-size.

Die Anzahl der Audioplayer mit niedrigerer Latenz ist begrenzt. Wenn für Ihre Anwendung mehr als nur wenige Audioquellen erforderlich sind, sollten Sie das Audio auf Anwendungsebene mischen. Deaktivieren Sie Ihre Audioplayer, wenn Ihre Aktivität pausiert ist, da sie eine globale Ressource sind, die mit anderen Apps geteilt wird.

Der Callback-Handler für die Pufferwarteschlange muss innerhalb eines kurzen und vorhersehbaren Zeitfensters ausgeführt werden, um hörbare Störungen zu vermeiden. Das bedeutet in der Regel, dass keine unbegrenzte Blockierung von Mutexen, Bedingungen oder E/A-Vorgängen erfolgt. Verwenden Sie stattdessen TryLocks, Sperren und Wartezeiten mit Zeitüberschreitungen sowie nicht blockierende Algorithmen.

Die Berechnung, die zum Rendern des nächsten Buffers (für AudioPlayer) oder zum Verbrauchen des vorherigen Buffers (für AudioRecord) erforderlich ist, sollte für jeden Rückruf ungefähr gleich viel Zeit in Anspruch nehmen. Vermeiden Sie Algorithmen, die in einer nicht deterministischen Zeit ausgeführt werden oder bei denen die Berechnungen stochastisch sind. Eine Callback-Berechnung ist stoßlastig, wenn die CPU-Zeit, die in einem bestimmten Callback verbracht wird, deutlich über dem Durchschnitt liegt. Idealerweise sollte die CPU-Ausführungszeit des Handlers eine Abweichung von nahezu null haben und der Handler nicht unbeschränkt lange blockieren.

Eine geringere Latenz ist nur für diese Ausgabegeräte möglich:

  • On-Device-Lautsprecher
  • Kabelgebundene Kopfhörer
  • Kabelgebundene Headsets
  • Line-out
  • USB Digital Audio

Auf einigen Geräten ist die Sprecherlatenz höher als bei anderen Pfaden, da die digitale Signalverarbeitung zur Korrektur und zum Schutz der Lautsprecher erforderlich ist.

Ab Android 5.0 (API-Level 21) wird auf ausgewählten Geräten Audioeingabe mit niedrigerer Latenz unterstützt. Damit du diese Funktion nutzen kannst, musst du zuerst prüfen, ob eine Ausgabe mit niedrigerer Latenz wie oben beschrieben verfügbar ist. Die Funktion für eine Ausgabe mit niedriger Latenz ist eine Voraussetzung für die Funktion für eine Eingabe mit niedriger Latenz. Erstellen Sie dann einen AudioRecorder mit derselben Abtastrate und Puffergröße wie für die Ausgabe. OpenSL ES-Schnittstellen für Eingabeeffekte schließen den Pfad mit niedrigerer Latenz aus. Für eine geringere Latenz muss das Aufnahme-Preset SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION verwendet werden. Dieses Preset deaktiviert die gerätespezifische digitale Signalverarbeitung, die dem Eingabepfad eine Latenz hinzufügen kann. Weitere Informationen zu Voreinstellungen für Einträge finden Sie oben im Abschnitt Android-Konfigurationsoberfläche.

Für die gleichzeitige Eingabe und Ausgabe werden für jede Seite separate Buffer-Queue-Abschluss-Handler verwendet. Es gibt keine Garantie für die relative Reihenfolge dieser Callbacks oder die Synchronisierung der Audiouhren, selbst wenn beide Seiten dieselbe Abtastrate verwenden. Ihre Anwendung sollte die Daten mit einer ordnungsgemäßen Puffersynchronisierung puffern.

Eine Folge potenziell unabhängiger Audiotaktgeber ist die Notwendigkeit einer asynchronen Samplerate-Umwandlung. Eine einfache (wenn auch nicht ideale) Methode für die asynchrone Abtastratenumwandlung besteht darin, bei Bedarf Samples in der Nähe eines Nulldurchgangs zu duplizieren oder zu entfernen. Es sind auch komplexere Conversions möglich.

Leistungsmodi

Mit Android 7.1 (API-Level 25) wurde in OpenSL ES eine Möglichkeit eingeführt, einen Leistungsmodus für den Audiopfad anzugeben. Folgende Optionen sind verfügbar:

  • SL_ANDROID_PERFORMANCE_NONE: Keine spezifischen Leistungsanforderungen. Ermöglicht Hardware- und Softwareeffekte.
  • SL_ANDROID_PERFORMANCE_LATENCY: Priorität wird der Latenz gegeben. Keine Hardware- oder Softwareeffekte. Dies ist der Standardmodus.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: Die Latenz hat Priorität, Hardware- und Softwareeffekte sind aber weiterhin zulässig.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: Energieeinsparung hat Priorität. Ermöglicht Hardware- und Softwareeffekte.

Hinweis : Wenn Sie keinen Pfad mit niedriger Latenz benötigen und die integrierten Audioeffekte des Geräts nutzen möchten (z. B. um die akustische Qualität bei der Videowiedergabe zu verbessern), müssen Sie den Leistungsmodus explizit auf SL_ANDROID_PERFORMANCE_NONE festlegen.

Wenn Sie den Leistungsmodus festlegen möchten, müssen Sie SetConfiguration über die Android-Konfigurationsoberfläche aufrufen, wie unten dargestellt:

  // Obtain the Android configuration interface using a previously configured SLObjectItf.
  SLAndroidConfigurationItf configItf = nullptr;
  (*objItf)->GetInterface(objItf, SL_IID_ANDROIDCONFIGURATION, &configItf);

  // Set the performance mode.
  SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_NONE;
    result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
                                                     &performanceMode, sizeof(performanceMode));

Sicherheit und Berechtigungen

Die Sicherheit bei Android wird auf Prozessebene festgelegt. Java-Code kann nicht mehr als nativer Code und nativer Code kann nicht mehr als Java-Code tun. Die einzigen Unterschiede zwischen ihnen sind die verfügbaren APIs.

Anwendungen, die OpenSL ES verwenden, müssen die Berechtigungen anfordern, die sie für ähnliche nicht native APIs benötigen. Wenn Ihre Anwendung beispielsweise Audio aufnimmt, benötigt sie die Berechtigung android.permission.RECORD_AUDIO. Für Anwendungen mit Audioeffekten ist android.permission.MODIFY_AUDIO_SETTINGS erforderlich. Für Anwendungen, die Netzwerk-URI-Ressourcen abspielen, ist android.permission.NETWORK erforderlich. Weitere Informationen finden Sie unter Mit Systemberechtigungen arbeiten.

Je nach Plattformversion und Implementierung werden Parser für Medieninhalte und Software-Codecs möglicherweise im Kontext der Android-Anwendung ausgeführt, die OpenSL ES aufruft. Hardware-Codecs sind abstrakt, aber geräteabhängig. Falsch formatierte Inhalte, die Parser- und Codec-Sicherheitslücken ausnutzen sollen, sind ein bekannter Angriffsvektor. Wir empfehlen, Medien nur von vertrauenswürdigen Quellen abzuspielen oder Ihre Anwendung so zu partitionieren, dass Code, der Medien von nicht vertrauenswürdigen Quellen verarbeitet, in einer relativ Sandbox-Umgebung ausgeführt wird. Beispielsweise können Sie Medien aus nicht vertrauenswürdigen Quellen in einem separaten Prozess verarbeiten. Obwohl beide Prozesse weiterhin unter derselben UID ausgeführt werden, erschwert diese Trennung einen Angriff.