Programmierhinweise zu OpenSL ES

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

Objekte und Schnittstelleninitialisierung

Zwei Aspekte des OpenSL ES-Programmiermodells, die neuen Entwicklern möglicherweise nicht bekannt sind, sind der Unterschied zwischen Objekten und Schnittstellen sowie die Initialisierungssequenz.

Kurz gesagt: Ein OpenSL ES-Objekt ähnelt 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 erste Schnittstelle für alle Objekte namens SLObjectItf. Es gibt keinen Handle für ein Objekt selbst, sondern nur einen Handle für die SLObjectItf-Schnittstelle des Objekts.

Zuerst wird ein OpenSL ES-Objekt erstellt, das einen SLObjectItf zurückgibt und dann realisiert wird. Dies ähnelt dem üblichen Programmiermuster, mit dem zuerst ein Objekt erstellt wird (das nie fehlschlagen sollte, außer wegen fehlenden Arbeitsspeichers oder ungültiger Parameter) und dann die Initialisierung abschließen (was aufgrund fehlender Ressourcen fehlschlagen kann). Der Schritt „Realisieren“ gibt der Implementierung einen logischen Ort, an dem bei Bedarf zusätzliche Ressourcen zugewiesen werden können.

Als Teil der API zum Erstellen eines Objekts gibt eine Anwendung ein Array mit gewünschten Schnittstellen an, die später übernommen werden sollen. Dieses Array übernimmt die Schnittstellen nicht automatisch, sondern weist lediglich auf eine zukünftige Absicht hin, sie zu erfassen. Schnittstellen werden als implizit oder explizit unterschieden. Eine explizite Schnittstelle muss im Array aufgeführt werden, wenn sie später abgerufen wird. Eine implizite Schnittstelle muss nicht im Array zum Erstellen des Objekts aufgeführt werden. Eine Auflistung dieser Schnittstelle ist aber nutzlos. OpenSL ES hat eine weitere Art von Schnittstelle namens dynamic, die nicht im Array zur Objekterstellung angegeben werden muss und später hinzugefügt werden kann, nachdem das Objekt erstellt wurde. Die Android-Implementierung bietet eine praktische Funktion, um diese Komplexität zu vermeiden, die unter Dynamische Schnittstellen bei der Objekterstellung beschrieben wird.

Nachdem das Objekt erstellt und realisiert wurde, sollte die Anwendung Schnittstellen für jedes benötigte Feature mit GetInterface für die anfängliche SLObjectItf abrufen.

Schließlich kann das Objekt über seine Benutzeroberflächen verwendet werden. Beachten Sie jedoch, dass für einige Objekte zusätzliche Einrichtungsschritte erforderlich sind. Insbesondere muss ein Audioplayer mit URI-Datenquelle etwas vorbereitet werden, um Verbindungsfehler erkennen zu können. Weitere Informationen finden Sie im Abschnitt Audioplayer-Prefetch.

Nachdem die Anwendung mit dem Objekt fertig ist, sollten Sie es ausdrücklich zerstören. Weitere Informationen dazu finden Sie im Abschnitt Löschen unten.

Prefetch für Audioplayer

Bei einem Audioplayer mit URI-Datenquelle weist Object::Realize Ressourcen zu, stellt jedoch keine Verbindung zur Datenquelle her (prepare) und ruft auch keine Daten vorab ab. Diese treten auf, wenn der Player-Status entweder auf SL_PLAYSTATE_PAUSED oder SL_PLAYSTATE_PLAYING gesetzt ist.

Einige Informationen sind möglicherweise noch bis relativ spät in dieser Sequenz unbekannt. Insbesondere gibt Player::GetDuration anfänglich SL_TIME_UNKNOWN zurück und MuteSolo::GetChannelCount wird entweder erfolgreich mit der Kanalanzahl null oder dem Fehlerergebnis SL_RESULT_PRECONDITIONS_VIOLATED zurückgegeben. Diese APIs geben die richtigen Werte zurück, sobald sie bekannt sind.

Weitere Eigenschaften, die anfänglich unbekannt sind, sind unter anderem die Abtastrate und der tatsächliche Medieninhaltstyp, der auf der Untersuchung des Inhalts-Headers basiert (im Gegensatz zum anwendungsspezifischen MIME- und Containertyp). Diese werden auch später beim Vorbereiten/Vorabruf bestimmt, aber es gibt keine APIs, um sie abzurufen.

Die Schnittstelle für den Prefetch-Status ist nützlich, um zu erkennen, wann alle Informationen verfügbar sind. Alternativ kann Ihre Anwendung regelmäßig Abfragen ausführen. Einige Informationen wie die Dauer eines Streaming-MP3 sind möglicherweise nie bekannt.

Die Oberfläche für den Prefetch-Status ist auch für die Fehlererkennung nützlich. 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 die Ebene null und PrefetchStatus::GetPrefetchStatus den Wert SL_PREFETCHSTATUS_UNDERFLOW meldet, weist dies auf einen nicht behebbaren Fehler in der Datenquelle hin. Dazu gehört auch, dass keine Verbindung zur Datenquelle hergestellt werden kann, da der lokale Dateiname nicht existiert oder der Netzwerk-URI ungültig ist.

In der nächsten Version von OpenSL ES wird voraussichtlich die Fehlerbehandlung in der Datenquelle expliziter unterstützt. Wir beabsichtigen jedoch, die aktuelle Methode zum Melden eines nicht behebbaren Fehlers für die zukünftige Binärkompatibilität weiterhin zu unterstützen.

Zusammenfassend lässt sich sagen, dass eine empfohlene Codesequenz so aussieht:

  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 : Hier werden Vorbereitung und Vorabruf durchgeführt. Während dieser Zeit wird der Callback mit regelmäßigen Statusaktualisierungen aufgerufen.

Löschen

Löschen Sie unbedingt alle Objekte, wenn Sie Ihre Anwendung beenden. Objekte sollten in umgekehrter Reihenfolge gelöscht werden, da das Löschen von Objekten, die abhängige Objekte enthält, nicht sicher ist. Löschen Sie z. B. in dieser Reihenfolge: Audioplayer und -rekorder, Ausgabemix und schließlich die Engine.

OpenSL ES unterstützt weder die automatische automatische Speicherbereinigung noch die Referenzzählung von Schnittstellen. Nach dem Aufruf von Object::Destroy werden alle vorhandenen Schnittstellen, die vom verknüpften Objekt abgeleitet werden, nicht mehr definiert.

Die Android OpenSL ES-Implementierung erkennt die falsche Verwendung solcher Schnittstellen nicht. Wenn Sie solche Schnittstellen nach dem Löschen des Objekts weiterhin verwenden, kann Ihre Anwendung abstürzen oder sich unvorhersehbar verhalten.

Wir empfehlen, im Rahmen der Sequenz zum Löschen von Objekten sowohl die primäre Objektschnittstelle als auch alle zugehörigen Schnittstellen explizit auf NULL festzulegen. So wird der versehentliche Missbrauch einer veralteten Schnittstellen-Handle verhindert.

Stereo-Schwenken

Wenn Volume::EnableStereoPosition zum Stereo-Schwenken einer Monoquelle verwendet wird, verringert sich die Gesamtschallleistung um 3 dB. Dies ist erforderlich, damit die Gesamtschallleistung konstant bleibt, wenn die Quelle von einem Kanal zum anderen geschwenkt wird. Aktivieren Sie daher die Stereo-Positionierung nur, wenn Sie sie benötigen. Weitere Informationen finden Sie im Wikipedia-Artikel zum Audio-Schwenken.

Callbacks und Threads

Callback-Handler werden im Allgemeinen synchron aufgerufen, wenn die Implementierung ein Ereignis erkennt. Dieser Punkt ist in Bezug auf die Anwendung asynchron, daher sollten Sie einen nicht blockierenden Synchronisierungsmechanismus verwenden, um den Zugriff auf alle Variablen zu steuern, die von der Anwendung und dem Callback-Handler gemeinsam genutzt werden. Im Beispielcode, z. B. für Pufferwarteschlangen, haben wir diese Synchronisierung entweder weggelassen oder der Einfachheit halber blockiert. 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 nicht für JNI verwendet werden. Da diese internen Threads für die Integrität der OpenSL ES-Implementierung von entscheidender Bedeutung sind, sollte ein Callback-Handler auch nicht übermäßig viele Aufgaben blockieren oder ausführen.

Wenn Ihr Callback-Handler JNI verwenden oder Aufgaben ausführen muss, die nicht proportional zum Callback sind, sollte der Handler stattdessen ein Ereignis posten, das von einem anderen Thread verarbeitet werden soll. Beispiele für akzeptable Callback-Arbeitslast sind das Rendern und die Einreihen des nächsten Ausgabepuffers (für einen AudioPlayer), die Verarbeitung des gerade gefüllten Eingabepuffers und das Einbinden 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.

Das Gegenteil ist sicher: Ein Android-Anwendungs-Thread, der in JNI eingetreten ist, darf OpenSL ES APIs direkt aufrufen, einschließlich solcher, die blockieren. Das Blockieren von Aufrufen aus dem Hauptthread wird jedoch nicht empfohlen, da sie zu Application Not Responding (ANR) führen können.

Die Entscheidung über den Thread, der einen Callback-Handler aufruft, bleibt größtenteils der Implementierung überlassen. Diese Flexibilität ermöglicht künftige Optimierungen, insbesondere auf Mehrkerngeräten.

Der Thread, in dem der Callback-Handler ausgeführt wird, hat nicht zwangsläufig dieselbe Identität für verschiedene Aufrufe. Verlassen Sie sich daher nicht darauf, dass das von pthread_self() zurückgegebene pthread_t oder das von gettid() zurückgegebene pid_t für alle Aufrufe konsistent ist. Verwenden Sie aus demselben Grund nicht die lokalen Storage-APIs (TLS) für Threads wie pthread_setspecific() und pthread_getspecific() aus einem Callback.

Die Implementierung sorgt dafür, dass keine gleichzeitigen Callbacks derselben Art für dasselbe Objekt auftreten. Gleichzeitige Callbacks unterschiedlicher Arten für dasselbe Objekt sind jedoch in verschiedenen Threads möglich.

Leistung

Da es sich bei OpenSL ES um eine native C API handelt, fallen Anwendungs-Threads, die nicht zur Laufzeit gehören und OpenSL ES aufrufen, keinen laufzeitbezogenen Aufwand wie Pausen bei der automatischen Speicherbereinigung an. Mit einer der unten beschriebenen Ausnahmen bietet die Verwendung von OpenSL ES abgesehen von dieser zusätzlichen Leistungsvorteile. Insbesondere sind durch die Verwendung von OpenSL ES keine Verbesserungen wie eine geringere Audiolatenz und eine höhere Planungspriorität garantiert, die die Plattform im Allgemeinen bietet. Da sich die Android-Plattform und bestimmte Geräteimplementierungen jedoch weiter weiterentwickeln, kann eine OpenSL ES-Anwendung von künftigen Verbesserungen der Systemleistung profitieren.

Eine davon ist die Unterstützung für eine reduzierte Audioausgabelatenz. Die Grundlage für eine verminderte Ausgabelatenz wurde erstmals in Android 4.1 (API-Level 16) umgesetzt. In Android 4.2 (API-Level 17) wurde der Fortschritt jedoch fortgesetzt. Diese Verbesserungen sind über OpenSL ES für Geräteimplementierungen verfügbar, die die Funktion android.hardware.audio.low_latency beanspruchen. Wenn das Gerät diese Funktion nicht beansprucht, aber Android 2.3 (API-Level 9) oder höher unterstützt, können Sie die OpenSL ES APIs trotzdem verwenden, aber die Ausgabelatenz kann höher sein. Der Pfad mit der niedrigeren Ausgabelatenz wird nur verwendet, wenn die Anwendung eine Puffergröße und 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-Level 17) kann eine Anwendung die plattformnative oder optimale Ausgabeabtastrate und Zwischenspeichergröße für den primären Ausgabestream des Geräts abfragen. In Kombination mit dem gerade erwähnten Funktionstest kann eine App sich jetzt entsprechend konfigurieren, um eine Ausgabe mit geringerer Latenz auf Geräten zu ermöglichen, die Unterstützung fordern.

Unter Android 4.2 (API-Level 17) und niedriger ist eine Pufferanzahl von zwei oder mehr erforderlich, um die Latenz zu verringern. Ab Android 4.3 (API-Level 18) ist eine Pufferanzahl von 1 für eine niedrigere Latenz ausreichend.

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

Wir empfehlen folgende Reihenfolge:

  1. Prüfen Sie, ob API-Level 9 oder höher vorhanden ist, um die Verwendung von OpenSL ES zu bestätigen.
  2. Suchen Sie mit einem Code wie dem folgenden nach der Funktion android.hardware.audio.low_latency:

    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üfe die Verwendung von android.media.AudioManager.getProperty() auf API-Level 17 oder höher.
  4. Rufen Sie die native oder optimale Ausgabeabtastrate und -puffergröße für den primären Ausgabestream dieses Geräts mit Code wie diesem ab:

    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 auf null und konvertieren Sie dann mit Integer.parseInt() in eine Ganzzahl.
  5. Erstellen Sie nun mithilfe von OpenSL ES einen AudioPlayer mit PCM-Zwischenspeicher für Datensuche.

Hinweis : Mit der Test-App Audiozwischenspeicher können Sie die native Zwischenspeichergröße und Abtastrate für OpenSL ES-Audioanwendungen auf Ihrem Audiogerät ermitteln. Sie können auch GitHub besuchen, um Beispiele für Audiopuffer-Größe anzusehen.

Die Anzahl von Audioplayern mit niedrigerer Latenz ist begrenzt. Wenn für Ihre Anwendung mehr als nur wenige Audioquellen erforderlich sind, empfiehlt es sich, die Audiodaten auf Anwendungsebene zu mischen. Löschen Sie Ihre Audioplayer, wenn Ihre Aktivität pausiert ist, da sie eine globale Ressource sind, die mit anderen Apps gemeinsam genutzt wird.

Um akustische Störungen zu vermeiden, muss der Callback-Handler für die Zwischenspeicherwarteschlange innerhalb eines kurzen und vorhersehbaren Zeitfensters ausgeführt werden. Dies bedeutet normalerweise keine unbegrenzte Blockierung bei Mutexen, Bedingungen oder E/A-Vorgängen. Stattdessen können Sie Sperren, Sperren und Wartezeiten mit Zeitüberschreitungen sowie nicht blockierende Algorithmen in Betracht ziehen.

Die Berechnung, die zum Rendern des nächsten Zwischenspeichers (für AudioPlayer) oder zum Verbrauchen des vorherigen Zwischenspeichers (für AudioRecord) erforderlich ist, sollte bei jedem Rückruf ungefähr die gleiche Zeit in Anspruch nehmen. Vermeiden Sie Algorithmen, die in einem nicht deterministischen Zeitraum ausgeführt werden oder bei ihren Berechnungen schlagartig sind. Eine Callback-Berechnung erfolgt sporadisch, wenn die für einen bestimmten Callback aufgewendete CPU-Zeit deutlich größer als der Durchschnitt ist. Zusammenfassend lässt sich sagen, dass die CPU-Ausführungszeit des Handlers idealerweise eine Varianz nahe null hat und der Handler nicht für unbegrenzte Zeiten blockiert wird.

Audio mit geringerer Latenz ist nur für diese Ausgaben möglich:

  • Gerätelautsprecher.
  • Kabelgebundene Kopfhörer.
  • Kabelgebundene Headsets
  • Line out.
  • Digitales USB-Audio.

Auf einigen Geräten ist die Latenz des Lautsprechers aufgrund der digitalen Signalverarbeitung zur Lautsprecherkorrektur und zum Schutz höher als bei anderen Pfaden.

Ab Android 5.0 (API-Level 21) wird auf ausgewählten Geräten eine Audioeingabe mit niedriger Latenz unterstützt. Damit Sie diese Funktion nutzen können, müssen Sie zuerst prüfen, ob eine Ausgabe mit geringerer Latenz wie oben beschrieben verfügbar ist. Die Möglichkeit der Ausgabe mit geringerer Latenz ist eine Voraussetzung für das Eingabefeature mit geringerer Latenz. Erstellen Sie dann einen AudioRekorder mit derselben Abtastrate und Puffergröße, die auch für die Ausgabe verwendet werden. OpenSL ES-Schnittstellen für Eingabeeffekte schließen den Pfad mit niedrigerer Latenz aus. Die Voreinstellung SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION muss für eine niedrigere Latenz verwendet werden. Durch diese Voreinstellung wird die gerätespezifische digitale Signalverarbeitung deaktiviert, die eine Latenz im Eingabepfad verursachen 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 separate Handler zum Abschluss von Zwischenspeicherwarteschlangen für jede Seite 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 ordnungsgemäßer Puffersynchronisierung puffern.

Eine Folge potenziell unabhängiger Audiouhren ist die Notwendigkeit einer asynchronen Umwandlung der Abtastrate. Eine einfache, jedoch nicht für die Audioqualität ideale Methode zur Umwandlung asynchroner Abtastraten ist das Duplizieren oder Entfernen von Stichproben bei Bedarf in der Nähe eines Nullkreuzes. Komplexere Konvertierungen sind möglich.

Leistungsmodi

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

  • SL_ANDROID_PERFORMANCE_NONE: Keine besondere Leistungsanforderung. Ermöglicht Hardware- und Softwareeffekte.
  • SL_ANDROID_PERFORMANCE_LATENCY: Die Latenz hat Priorität. Keine Hardware- oder Softwareeffekte. Das ist der Standardmodus.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: Die Latenz hat Vorrang, während Hardware- und Softwareeffekte weiterhin berücksichtigt werden.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: Priorität für die Energieeinsparung. 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. zur Verbesserung der akustischen Qualität bei der Videowiedergabe), müssen Sie den Leistungsmodus explizit auf SL_ANDROID_PERFORMANCE_NONE setzen.

Zum Festlegen des Leistungsmodus müssen Sie SetConfiguration über die Android-Konfigurationsoberfläche aufrufen:

  // 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 in Android erfolgt auf Prozessebene. Mit dem Programmiersprache Java-Code kann nichts mehr als nativer Code und auch nativer Code kann nichts anderes als Java-Code. 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 aufzeichnet, ist die Berechtigung android.permission.RECORD_AUDIO erforderlich. Anwendungen, die Audioeffekte verwenden, benötigen android.permission.MODIFY_AUDIO_SETTINGS. Anwendungen, die Netzwerk-URI-Ressourcen wiedergeben, benötigen android.permission.NETWORK. Weitere Informationen finden Sie unter Mit Systemberechtigungen arbeiten.

Abhängig von der Plattformversion und Implementierung können Parser für Medieninhalte und Software-Codecs im Kontext der Android-Anwendung ausgeführt werden, die OpenSL ES aufruft. Hardware-Codecs sind abstrahiert, aber geräteabhängig. Fehlerhafte Inhalte, die darauf abzielen, Parser- und Codec-Sicherheitslücken auszunutzen, sind ein bekannter Angriffsvektor. Wir empfehlen, nur Medien von vertrauenswürdigen Quellen wiederzugeben oder Ihre Anwendung so zu partitionieren, dass Code, der Medien aus 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 unter derselben UID ausgeführt werden, erschwert diese Trennung einen Angriff.