Programmierhinweise zu OpenSL ES

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. Dies ähnelt dem üblichen Programmiermuster bei der ersten Konstruktion eines (der niemals fehlschlagen sollte, außer aufgrund von zu wenig Arbeitsspeicher oder ungültigen Parametern), und Die Initialisierung wird abgeschlossen (was aufgrund fehlender 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. Beachten Sie, dass dieses Array nicht automatisch die Schnittstellen erwerben; deutet dies lediglich auf die zukünftige Absicht hin, sie zu akquirieren. 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.

Schließlich steht das Objekt zur Verwendung über seine Schnittstellen zur Verfügung. Beachten Sie jedoch, dass erfordern einige Objekte weitere Einstellungen vornehmen. 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. In insbesondere gibt Player::GetDuration anfänglich SL_TIME_UNKNOWN zurück und MuteSolo::GetChannelCount wird zurückgegeben, wobei die Kanalanzahl null ist oder die 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 Statusoberfläche des Prefetches ist auch hilfreich bei der Fehlererkennung. 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 in Bezug auf die Anwendung asynchron, daher sollten Sie einen nicht blockierenden Synchronisierungsmechanismus, um den Zugriff auf Variablen zu steuern, die zwischen der Anwendung und dem Callback-Handler. 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-Arbeitslast ist das Rendern und das Einreihen des nächsten Ausgabepuffers in einer Warteschlange. (für einen AudioPlayer), verarbeitet den gerade gefüllten Eingabepuffer und stellt den nächsten Zwischenspeicher leer (für einen Audiorekorder) oder einfache APIs wie den Großteil der Get-Familie verwenden. Weitere Informationen finden Sie in der Leistung unten bezüglich der Arbeitslast.

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, auf dem der Callback-Handler ausgeführt wird, hat möglicherweise nicht dieselbe Identität auf unterschiedliche Aufrufe. 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-Level 17) kann eine App den plattformnative oder optimale Ausgabe-Abtastrate und Puffergröße für die primäre Ausgabe des Geräts . In Kombination mit dem gerade erwähnten Funktionstest kann eine App nun selbst konfigurieren. für eine Ausgabe mit geringerer Latenz auf Geräten, die Support anbieten.

Unter Android 4.2 (API-Level 17) und niedriger beträgt die Anzahl der Puffer mindestens zwei die für eine niedrigere Latenz erforderlich sind. 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. Suchen Sie mit folgendem Code 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. Überprüfen Sie das API-Level 17 oder höher, um die Verwendung von zu bestätigen. android.media.AudioManager.getProperty()
  4. Holen Sie sich 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-Zwischenspeicherwarteschlangen-Data Locator.

Hinweis: Sie können die <ph type="x-smartling-placeholder"></ph> 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. Audio zerstören , wenn deine Aktivitäten pausiert werden, da diese globale Ressource 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. Stattdessen sollten Sie Sperren, Sperren und Warten mit Zeitüberschreitungen verwenden. <ph type="x-smartling-placeholder"></ph> nicht blockierende Algorithmen.

Die Berechnung, die zum Rendern des nächsten Zwischenspeichers (für AudioPlayer) oder zum Verarbeiten des vorherigen Zwischenspeichers erforderlich ist buffer (für AudioRecord) sollte für jeden Callback ungefähr gleich lang dauern. Vermeiden Sie Algorithmen, die in einem nicht deterministischen Zeitraum ausgeführt werden oder in einem Burst- ihre Berechnungen an. 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
  • <ph type="x-smartling-placeholder"></ph> Digital (USB) 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. Aufnahmevoreinstellung Für eine geringere Latenz muss SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION verwendet werden. dies Durch die Voreinstellung wird die gerätespezifische digitale Signalverarbeitung deaktiviert, die im Eingabepfad eine Latenz verursachen 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 die Puffer mit ordnungsgemäßer Zwischenspeichersynchronisierung.

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-Level 25) wurde in OpenSL ES eine Möglichkeit eingeführt, einen Leistungsmodus anzugeben für den Audiopfad. Folgende Optionen sind verfügbar:

  • SL_ANDROID_PERFORMANCE_NONE: Keine spezifische 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 der 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 und können Software-Codecs werden im Kontext der Android-Anwendung ausgeführt, die OpenSL ES aufruft (Hardware-Codecs sind abstrahiert, aber geräteabhängig sind). 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. Zum Beispiel könnten Sie werden Medien aus nicht vertrauenswürdigen Quellen in einem separaten Prozess verarbeitet. Obwohl beide Prozesse immer noch unter derselben UID ausgeführt wird, erschwert diese Trennung einen Angriff.