Programmierhinweise zu OpenSL ES

WARNUNG: OpenSL ES wurde eingestellt. Entwickler sollten die Open-Source- Oboenbibliothek, die auf GitHub verfügbar ist Oboe ist ein C++-Wrapper, der eine API bietet, die der AAudio: Oboe ruft AAudio auf, wenn AAudio verfügbar. Falls AAudio nicht verfügbar ist, wird OpenSL ES verwendet.

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

Objekt- und Schnittstelleninitialisierung

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

Kurz gesagt, ähnelt ein OpenSL ES-Objekt dem Objektkonzept in Programmiersprachen wie Java und C++, außer ein OpenSL ES-Objekt ist nur über die zugehörigen Schnittstellen sichtbar. Dazu gehören die erste Schnittstelle für alle Objekte, die SLObjectItf heißt. Es gibt kein Handle für ein Objekt. nur ein Handle zur SLObjectItf-Schnittstelle des Objekts.

Zuerst wird ein OpenSL ES-Objekt erstellt, das ein SLObjectItf zurückgibt. 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 „Realisieren“ gibt eine logische Stelle zu implementieren, um bei Bedarf zusätzliche Ressourcen zuzuweisen.

Als Teil der API zum Erstellen eines Objekts gibt eine Anwendung ein Array der gewünschten Schnittstellen an die es 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. Benutzeroberflächen unterscheiden sich implizit oder explizit. Eine explizite Schnittstelle muss im Array aufgeführt sein, wenn später übernommen werden. Eine implizite Schnittstelle muss nicht im Array erstellen, aber es schadet nicht, sie dort zu veröffentlichen. OpenSL ES hat eine weitere Art von Schnittstellen, Dynamic, die nicht im Objekt angegeben werden muss Array erstellen und kann hinzugefügt werden, nachdem das Objekt erstellt wurde. Die Android-Implementierung bietet eine praktische Funktion, vermeiden, die in den Dynamische Schnittstellen bei der Objekterstellung

Nachdem das Objekt erstellt und realisiert wurde, sollte die Anwendung Schnittstellen für alle Funktion, die sie benötigt, unter Verwendung von GetInterface für die anfängliche SLObjectItf.

Anschließend kann das Objekt über seine Schnittstellen verwendet werden. Einige Objekte erfordern jedoch eine weitere Einrichtung. Ein Audioplayer mit URI-Datenquelle benötigt eine intensivere Vorbereitung um Verbindungsfehler zu erkennen. Weitere Informationen finden Sie in der Im Abschnitt Vorabruf des Audioplayers finden Sie weitere Informationen.

Nachdem Ihre Anwendung mit dem Objekt fertig ist, sollten Sie es explizit löschen. sieh dir die Vernichten weiter unten.

Vorabruf des Audioplayers

Bei einem Audioplayer mit URI-Datenquelle weist Object::Realize Ressourcen, aber nicht eine Verbindung zur Datenquelle herstellen (vorbereiten) oder mit dem Vorabruf von Daten beginnen. Diese treten auf, sobald der Der Player-Status ist entweder auf SL_PLAYSTATE_PAUSED oder SL_PLAYSTATE_PLAYING festgelegt.

Einige Informationen sind möglicherweise noch relativ spät in der Sequenz unbekannt. Insbesondere wird bei Player::GetDuration 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 richtigen Werte sobald sie bekannt sind.

Zu den weiteren Eigenschaften, die anfänglich unbekannt sind, gehören die Stichprobenrate und tatsächlicher Medieninhaltstyp auf der Überschrift des Inhalts (im Gegensatz zu den anwendungsspezifischen MIME-Typ und Containertyp). Diese werden auch später im Verlauf Vorabrufen vorbereiten, aber es gibt keine APIs, um sie abzurufen.

Über die Oberfläche für den Prefetch-Status lässt sich erkennen, verfügbar ist, oder Ihr Anwendung regelmäßig abfragen kann. Beachten Sie, dass einige Informationen wie der Dauer eines Streamings MP3-Format, sind möglicherweise nie bekannt.

Die Benutzeroberfläche für den Prefetch-Status ist auch nützlich, um Fehler zu erkennen. Callback registrieren und aktivieren Sie mindestens SL_PREFETCHEVENT_FILLLEVELCHANGE und SL_PREFETCHEVENT_STATUSCHANGE Ereignisse. Wenn beide Ereignisse gleichzeitig ausgeliefert werden und PrefetchStatus::GetFillLevel gibt eine Ebene von null an und PrefetchStatus::GetPrefetchStatus meldet SL_PREFETCHSTATUS_UNDERFLOW, dann das weist auf einen nicht behebbaren Fehler in der Datenquelle hin. Dazu gehört auch die Unfähigkeit, verbinden Sie sich mit dem Datenquelle, da der lokale Dateiname nicht vorhanden ist oder der Netzwerk-URI ungültig ist.

Die nächste Version von OpenSL ES soll voraussichtlich mehr expliziten Support für der Fehlerbehandlung Datenquelle verwendet werden. Für die zukünftige Kompatibilität mit Binärprogrammen beabsichtigen wir jedoch, um die aktuelle Situation zu unterstützen, Methode zum Melden eines nicht behebbaren Fehlers.

Eine empfohlene Codesequenz sieht so aus:

  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: Vorbereitung und Vorabruf erfolgen hier. während dieser Zeit wird Ihr Rückruf mit regelmäßige Statusupdates zu erhalten.

Vernichten

Achten Sie darauf, alle Objekte zu löschen, wenn Sie Ihre Anwendung verlassen. Objekte sollten in umgekehrte Reihenfolge ihrer Erstellung, da es nicht sicher ist, ein Objekt zu zerstören, Objekte. Zerstören wir z. B. diese Reihenfolge: Audioplayer und -rekorder, Ausgabemix und dann und schließlich den Motor.

OpenSL ES unterstützt nicht die automatische automatische Speicherbereinigung oder Referenz Zählung der Schnittstellen. Nach dem Anruf von Object::Destroy, alle vorhandenen Schnittstellen, die die vom verknüpften Objekt abgeleitet wurden, nicht definiert werden.

Die Android OpenSL ES-Implementierung erkennt die falsche Verwendung solcher Schnittstellen nicht. Wenn Sie solche Schnittstellen weiterhin verwenden, nachdem das Objekt gelöscht wurde, kann Ihre Anwendung zu Abstürzen oder unvorhersehbarem Verhalten.

Es empfiehlt sich, sowohl die primäre Objektschnittstelle als auch alle zugehörigen im Rahmen der Sequenz zum Löschen von Objekten zu NULL, wodurch eine versehentliche Missbrauch eines veralteten Schnittstellen-Handles.

Stereo-Schwenken

Wenn Volume::EnableStereoPosition verwendet wird, um das Stereo-Schwenken einer Monoquelle zu aktivieren, Die Gesamtmenge ist um 3 dB reduziert. Schallleistung Level auf. Dies ist erforderlich, damit der gesamte Schallleistungspegel konstant bleibt, ist die Quelle die von einem Kanal zum anderen geschwenkt wurden. Aktivieren Sie daher die Stereo-Positionierung nur, . Weitere Informationen findest du im Wikipedia-Artikel Audio-Schwenken.

Callbacks und Threads

Callback-Handler werden im Allgemeinen synchron aufgerufen, wenn die Implementierung ein . 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 entweder dies weggelassen. oder aus Gründen der Einfachheit eine blockierende Synchronisierung verwendet. Echte, nicht aus Das Blockieren der Synchronisierung ist für jeglichen Produktionscode von entscheidender Bedeutung.

Callback-Handler werden von internen Nicht-Anwendungs-Threads aufgerufen, die nicht an die Android-Laufzeit, daher können sie JNI nicht verwenden. Da diese internen Threads entscheidend für der Integrität der OpenSL ES-Implementierung gerechtfertigt ist, sollte ein Callback-Handler ebenfalls keine oder übermäßige Arbeit.

Wenn Ihr Callback-Handler JNI verwenden oder Arbeiten ausführen muss, die nicht proportional zur -Rückruf zurück, sollte der Handler stattdessen ein Ereignis für einen anderen zu verarbeitenden Thread posten. Beispiele für zulässige Callback-Arbeitslasten sind das Rendern und Einfügen des nächsten Ausgabepuffers (für einen Audioplayer), die Verarbeitung des gerade gefüllten Eingabepuffers und das Einfügen des nächsten leeren Puffers (für einen Audiorekorder) oder einfache APIs wie die meisten Get-APIs. Weitere Informationen zur Arbeitslast finden Sie unten im Abschnitt Leistung.

Beachten Sie, dass das Gegenteil sicher ist: ein Android-Anwendungs-Thread, der in die JNI eingedrungen ist. darf OpenSL ES APIs direkt aufrufen, einschließlich derjenigen, die blockieren. Das Blockieren von Anrufen ist jedoch nicht aus dem Hauptthread empfohlen, da dies zu App antwortet nicht (ANR).

Die Feststellung in Bezug auf den Thread, der einen Rückruf-Handler aufruft, bleibt größtenteils dem Implementierung. Der Grund für diese Flexibilität besteht darin, zukünftige Optimierungen zu ermöglichen, vor allem auf Multi-Core-Geräte.

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 auf den pthread_t, der von pthread_self() oder die pid_t, die von gettid() zurückgegeben wurden, bei allen Anrufen einheitlich sind. Aus dem gleichen Grund sollten Sie keine TLS APIs (Local Storage) für Threads wie pthread_setspecific() und pthread_getspecific() von einem Rückruf.

Die Implementierung garantiert, dass gleichzeitige Rückrufe derselben Art für den zum gleichen Objekt nicht auftreten. Gleichzeitige Rückrufe unterschiedlicher Art für dasselbe Objekt sind bei unterschiedliche Threads.

Leistung

Da OpenSL ES eine native C API ist, haben Nicht-Laufzeit-Anwendungsthreads, die OpenSL ES aufrufen, laufzeitbezogenen Aufwand wie Pausen der automatischen Speicherbereinigung. Mit einer Ausnahme, die nachfolgend beschrieben ist, bietet OpenSL ES keine weiteren Leistungsvorteile. Insbesondere OpenSL ES garantiert keine Verbesserungen wie eine niedrigere Audiolatenz und höhere Priorität gegenüber den Plattformen, die die Plattform im Allgemeinen bietet. Da das Unternehmen Die Android-Plattform und spezifische Geräteimplementierungen entwickeln sich ständig weiter, eine OpenSL ES-Anwendung. wird von künftigen Verbesserungen der Systemleistung profitieren.

Eine dieser Entwicklungen ist die Unterstützung Latenz der Audioausgabe. Die Grundlage für reduzierte dass die Ausgabelatenz zuerst in Android 4.1 (API-Level 16) enthalten war, wurden in Android 4.2 (API-Level 17) fortgesetzt. Diese Verbesserungen sind verfügbar über OpenSL ES für Geräteimplementierungen, die android.hardware.audio.low_latency freischalten. Wenn das Gerät diese Funktion nicht beansprucht, aber Android 2.3 (API-Level 9) unterstützt können Sie zwar die OpenSL ES APIs verwenden, die Ausgabelatenz kann jedoch höher sein. Je niedriger Der Ausgabelatenzpfad wird nur verwendet, wenn die Anwendung eine Puffergröße und eine Abtastrate anfordert die ist mit der nativen Ausgabekonfiguration des Geräts kompatibel. Diese Parameter sind gerätespezifische und erhalten Sie wie unten beschrieben.

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-Level 18) wird ein Puffer ist die Anzahl von Eins ausreichend, um die Latenz zu verringern.

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

Die empfohlene Reihenfolge lautet:

  1. Prüfen Sie, ob API-Level 9 oder höher vorhanden ist, 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. Überprüfen Sie das API-Level 17 oder höher, um die Verwendung von zu bestätigen. android.media.AudioManager.getProperty()
  4. Rufen Sie die native oder optimale Ausgabeabtastrate und Puffergröße für die Primärausgabe mit folgendem Code streamen:

    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);
    
    sampleRate und framesPerBuffer sind Strings. Erste Überprüfung auf null und konvertieren Sie sie dann mithilfe von Integer.parseInt() in eine Ganzzahl.
  5. Erstellen Sie jetzt mit OpenSL ES einen AudioPlayer mit PCM-Pufferwarteschlangen-Data Locator.

Hinweis: Sie können die Größe der Audiozwischenspeicher Test-App zur Bestimmung der nativen Puffergröße und Abtastrate für OpenSL ES-Audio Apps auf Ihrem Audiogerät. Sie können auch GitHub aufrufen, um Audiopuffergröße.

Die Anzahl der Audioplayer mit niedrigerer Latenz ist begrenzt. Wenn für Ihre Anwendung mehr als ein paar Audioquellen können Sie Ihre Audioinhalte 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.

Um hörbare Störungen zu vermeiden, muss der Callback-Handler für die Pufferwarteschlange innerhalb eines vorhersehbaren Zeitfensters. Dies impliziert normalerweise keine unbegrenzte Blockierung von Mutexen, Bedingungen, oder E/A-Operationen. 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 einem nicht deterministischen Zeitraum ausgeführt werden oder in einem Burst- ihre Berechnungen durchführen. Eine Callback-Berechnung erfolgt stoßweise, 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 den Handler auf eine Varianz nahe null und legt ihn fest, dass er nicht für unbegrenzte Zeiträume blockiert wird.

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

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

Auf einigen Geräten ist die Sprecherlatenz höher als bei anderen Pfaden aufgrund der digitalen Signalverarbeitung für Korrektur und Schutz des Lautsprechers.

Ab Android 5.0 (API-Level 21): geringere Latenz Audioeingabe wird auf ausgewählten Geräten unterstützt. Um diese Funktion zu nutzen, müssen Sie zuerst bestätigen, dass eine Ausgabe mit geringerer Latenz wie oben beschrieben zur Verfügung steht. Die Möglichkeit einer Ausgabe mit geringerer Latenz ist eine Voraussetzung für die Eingabefunktion mit niedrigerer Latenz. Erstellen Sie dann einen Audiorekorder mit demselben Abtastrate und Puffergröße, wie sie für die Ausgabe verwendet werden. OpenSL-ES-Schnittstellen für Eingabeeffekte den Pfad mit niedrigerer Latenz ausschließen. 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 Datensätze finden Sie in der Android-Konfiguration oben im Abschnitt "Benutzeroberfläche".

Zur gleichzeitigen Eingabe und Ausgabe werden jeweils separate Handler für die Zwischenspeicherwarteschlange verwendet. zu verstehen. Es gibt keine Garantie für die relative Reihenfolge dieser Callbacks oder die Synchronisierung von auch wenn beide Seiten die gleiche Abtastrate verwenden. Ihre Anwendung sollte die Daten mit einer ordnungsgemäßen Puffersynchronisierung puffern.

Eine Folge potenziell unabhängiger Audiouhren ist die Notwendigkeit einer asynchronen Abtastrate. Conversion. Ein einfaches (aber nicht ideal für die Audioqualität) Verfahren für die asynchrone Abtastrate Conversion besteht darin, Stichproben nach Bedarf nahe einem Punkt zu duplizieren oder zu verwerfen. Komplexer Conversions möglich sind.

Leistungsmodi

Ab Android 7.1 (API-Ebene 25) bietet OpenSL ES die Möglichkeit, 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 hat die Latenz. Keine Hardware- oder Softwareeffekte. Dies ist der Standardmodus.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: Priorität wird Latenz gegeben und dennoch Hardware- und Softwareeffekte nutzen.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: Priorität der Energieeinsparung. Ermöglicht Hardware- und Softwareeffekte.

Hinweis: Wenn Sie keinen Pfad mit niedriger Latenz benötigen und der im Gerät integrierten Audioeffekte zu nutzen (z. B. zur Verbesserung der akustischen Qualität für Videowiedergabe) festgelegt ist, musst du den Leistungsmodus explizit auf SL_ANDROID_PERFORMANCE_NONE

Zum Festlegen des Leistungsmodus musst du SetConfiguration über die Android-App aufrufen Konfigurationsoberfläche 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-Programmierung Sprachcode kann nicht mehr als nativer Code und nativer Code auch nicht mehr als nativer Code. Code der Programmiersprache Java. Die einzigen Unterschiede zwischen ihnen sind die verfügbaren APIs.

Für Anwendungen, die OpenSL ES verwenden, müssen die Berechtigungen angefordert werden, die für ähnliche nicht native APIs. Wenn Ihre Anwendung beispielsweise Audio aufzeichnet, benötigt sie Berechtigung „android.permission.RECORD_AUDIO“. Für Anwendungen mit Audioeffekten müssen android.permission.MODIFY_AUDIO_SETTINGS Anwendungen, die Netzwerk-URI-Ressourcen wiedergeben android.permission.NETWORK erforderlich. Weitere Informationen finden Sie unter Arbeiten mit Systemen Berechtigungen:

Je nach 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 abstrakt, aber geräteabhängig. Fehlerhafte Inhalte, die darauf abzielen, den Parser und Codec auszunutzen Schwachstellen sind ein bekannter Angriffsvektor. Wir empfehlen, Medien nur von vertrauenswürdigen Quellen abzuspielen. oder Ihre Anwendung so partitionieren, dass Code Medien aus nicht vertrauenswürdige Quellen werden in einer relativ Sandbox-Umgebung ausgeführt. So können Sie beispielsweise 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.