Note di programmazione di OpenSL ES

ATTENZIONE: OpenSL ES è deprecato. Gli sviluppatori dovrebbero usare l'open source Libreria di oboe disponibile su GitHub. Oboe è un wrapper C++ che fornisce un'API molto simile Audio. Oboe chiama AAudio quando AAudio disponibile e torna a OpenSL ES se AAudio non è disponibile.

Le note in questa sezione integrano le OpenSL ES 1.0.1 la specifica del prodotto.

Inizializzazione degli oggetti e dell'interfaccia

Due aspetti del modello di programmazione OpenSL ES che potrebbero non essere familiari ai nuovi sviluppatori sono: la distinzione tra oggetti e interfacce e la sequenza di inizializzazione.

In breve, un oggetto OpenSL ES è simile al concetto di oggetto in linguaggi di programmazione come Java e C++, ad eccezione del fatto che un oggetto OpenSL ES è visibile solo tramite le interfacce associate. È inclusa l'interfaccia iniziale per tutti gli oggetti, chiamata SLObjectItf. Non esiste un handle per un oggetto stesso, solo un handle all'interfaccia SLObjectItf dell'oggetto.

Un oggetto OpenSL ES viene prima creato, che restituisce un SLObjectItf, quindi capito. È simile al comune pattern di programmazione della prima costruzione di un (che non dovrebbe mai generare un errore se non per mancanza di memoria o parametri non validi), e quindi completare l'inizializzazione (che potrebbe non riuscire per mancanza di risorse). Il passaggio relativo alla realizzazione fornisce un luogo logico in cui allocare risorse aggiuntive, se necessario.

Come parte dell'API per creare un oggetto, un'applicazione specifica un array di interfacce desiderate che ha in programma di acquisire in seguito. Tieni presente che questo array non esegue automaticamente acquisire le interfacce; indica solo l'intenzione futura di acquisirli. Le interfacce si distinguono come implicito o esplicito. Un'interfaccia esplicita deve essere elencata nell'array se verranno acquisiti in un secondo momento. Non è necessario elencare un'interfaccia implicita nel alla creazione di un array di oggetti, non c'è pericolo di mostrarli. OpenSL ES ha un altro tipo di interfaccia chiamata Dynamic, che non deve essere specificato nell'oggetto crea un array e può essere aggiunto in un secondo momento dopo la creazione dell'oggetto. L'implementazione di Android fornisce una funzionalità di evitare questa complessità, descritta in Interfacce dinamiche alla creazione degli oggetti.

Una volta creato e realizzato l'oggetto, l'applicazione deve acquisire le interfacce per ogni funzionalità di cui ha bisogno, utilizzando GetInterface nel primo SLObjectItf.

Infine, l'oggetto è disponibile per l'utilizzo tramite le sue interfacce, anche se tieni presente che alcuni oggetti richiedono ulteriore configurazione. In particolare, un player audio con origine dati URI richiede un po' di più di preparazione per rilevare gli errori di connessione. Per ulteriori dettagli, consulta la sezione Prefetch del player audio.

Al termine dell'applicazione, devi distruggere esplicitamente l'oggetto; vedi il Elimina di seguito.

Precaricamento del lettore audio

Per un player audio con origine dati URI, Object::Realize alloca le risorse, ma non si connette all'origine dati (prepara) né inizia il pre-recupero dei dati. Si verificano quando lo stato del player è impostato su SL_PLAYSTATE_PAUSED o SL_PLAYSTATE_PLAYING.

Alcune informazioni potrebbero essere ancora sconosciute fino a un momento relativamente avanzato di questa sequenza. Nel particolare, inizialmente Player::GetDuration restituisce SL_TIME_UNKNOWN e MuteSolo::GetChannelCount viene restituito correttamente con un conteggio dei canali pari a zero o risultato dell'errore SL_RESULT_PRECONDITIONS_VIOLATED. Queste API restituiscono i valori corretti. una volta note.

Altre proprietà che inizialmente sono sconosciute includono la frequenza di campionamento e tipo di contenuti multimediali effettivo sulla base dell'esame dell'intestazione dei contenuti (piuttosto che di tipo MIME specificato dall'applicazione container). Anche questi vengono stabiliti in un secondo momento, durante per la preparazione/precaricamento, ma non vi sono API recuperarle.

L'interfaccia dello stato di precaricamento è utile per rilevare quando tutte le informazioni o la tua l'applicazione può eseguire sondaggi periodici. Tieni presente che alcune informazioni, come durata di un flusso di dati MP3, potrebbe non essere noto.

L'interfaccia dello stato del precaricamento è utile anche per rilevare gli errori. Registra una richiamata e attivare almeno SL_PREFETCHEVENT_FILLLEVELCHANGE e SL_PREFETCHEVENT_STATUSCHANGE eventi. Se entrambi gli eventi vengono pubblicati contemporaneamente PrefetchStatus::GetFillLevel segnala un livello pari a zero e PrefetchStatus::GetPrefetchStatus riporta SL_PREFETCHSTATUS_UNDERFLOW, questo indica un errore irreversibile nell'origine dati. Ciò include l'impossibilità di connettiti al origine dati perché il nome file locale non esiste o l'URI di rete non è valido.

La prossima versione di OpenSL ES dovrebbe aggiungere un supporto più esplicito per per gestire gli errori nel origine dati. Tuttavia, per la futura compatibilità binaria, intendiamo continuare per supportare le attuali per segnalare un errore irreversibile.

Per riassumere, una sequenza di codice consigliata è:

  1. Engine::CreateAudioPlayer
  2. Object:Realize
  3. Object::GetInterface per SL_IID_PREFETCHSTATUS
  4. PrefetchStatus::SetCallbackEventsMask
  5. PrefetchStatus::SetFillUpdatePeriod
  6. PrefetchStatus::RegisterCallback
  7. Object::GetInterface per SL_IID_PLAY
  8. Da Play::SetPlayState a SL_PLAYSTATE_PAUSED o SL_PLAYSTATE_PLAYING

Nota: La preparazione e il precaricamento avvengono qui; durante questo periodo la chiamata viene chiamata con aggiornamenti periodici dello stato.

Elimina

Assicurati di eliminare tutti gli oggetti quando esci dall'applicazione. Gli oggetti devono essere eliminati l'ordine inverso di creazione, in quanto non è sicuro distruggere un oggetto che ha di oggetti strutturati. Ad esempio, eliminali in questo ordine: lettori e registratori audio, mix di output e poi infine il motore.

OpenSL ES non supporta la garbage collection automatica o riferimento il conteggio delle interfacce. Dopo aver chiamato Object::Destroy, tutti quelli esistenti che sono derivati dall'oggetto associato diventano indefinito.

L'implementazione di OpenSL ES per Android non rileva l'uso errato di queste interfacce. Se continui a utilizzare queste interfacce dopo l'eliminazione dell'oggetto, l'applicazione potrebbe si verificano in modo anomalo o si comportano in modo imprevedibile.

Ti consigliamo di impostare in modo esplicito sia l'interfaccia dell'oggetto principale sia tutte le istanze associate si interfaccia con NULL come parte della sequenza di eliminazione degli oggetti, il che impedisce l'errore uso improprio di un handle di interfaccia inattivo.

Panning stereo

Quando viene usato Volume::EnableStereoPosition per attivare il panning stereo di una sorgente mono, c'è una riduzione totale di 3 dB potenza sonora a livello di progetto. Ciò è necessario per mantenere costante il livello di potenza sonora totale l'origine è una volta eseguita la panoramica da un canale all'altro. Abilita il posizionamento stereo solo se necessario li annotino. Per ulteriori informazioni, vedi l'articolo di Wikipedia su panning audio.

Callback e thread

I gestori di callback vengono generalmente chiamati in modo sincrono quando l'implementazione rileva un . Questo punto è asincrono rispetto all'applicazione, quindi devi utilizzare un meccanismo di sincronizzazione non bloccante per controllare l'accesso a eventuali variabili condivise tra l'applicazione e il gestore del callback. Nel codice di esempio, come per le code del buffer, abbiamo omesso questo o aver utilizzato il blocco della sincronizzazione per semplicità. Tuttavia, i contenuti appropriati non bloccare la sincronizzazione è fondamentale per qualsiasi codice di produzione.

I gestori di callback vengono chiamati da thread interni non dell'applicazione che non sono collegati Il runtime Android, quindi non è idoneo all'utilizzo di JNI. Poiché questi thread interni vengono fondamentale per l'integrità dell'implementazione di OpenSL ES, nemmeno un gestore di callback deve bloccare o eseguire di lavoro eccessivo.

Se il tuo gestore del callback deve utilizzare JNI o eseguire un lavoro non proporzionale al il gestore deve pubblicare un evento per l'elaborazione di un altro thread. Alcuni esempi di carica utile di callback accettabili sono il rendering e l'inserimento in coda del buffer di output successivo (per un AudioPlayer), l'elaborazione del buffer di input appena compilato e l'inserimento in coda del buffer vuoto successivo (per un AudioRecorder) oppure API semplici come la maggior parte della famiglia Get. Consulta le la sezione Prestazioni riportata di seguito relativa al carico di lavoro.

Tieni presente che il contrario è sicuro: il thread di un'applicazione Android che è entrato in JNI può direttamente le API OpenSL ES, incluse quelle che bloccano. Tuttavia, il blocco delle chiamate consigliati dal thread principale, in quanto possono comportare L'applicazione non risponde (ANR).

La determinazione riguardo al thread che chiama un gestore di callback viene lasciata in gran parte al implementazione. Il motivo di questa flessibilità è consentire ottimizzazioni future, soprattutto sulle dispositivi multi-core.

Non è garantito che il thread su cui viene eseguito il gestore di callback abbia la stessa identità tra diverse. Pertanto, non fare affidamento sul valore pthread_t restituito pthread_self() o pid_t restituiti da gettid() da coerente tra le chiamate. Per lo stesso motivo, non utilizzare le API di archiviazione locale (TLS) dei thread come pthread_setspecific() e pthread_getspecific() da una richiamata.

L'implementazione garantisce che non si verifichino callback simultanei dello stesso tipo per lo stesso oggetto. Tuttavia, sono possibili callback simultanei di tipi diversi per lo stesso oggetto thread diversi.

Prestazioni

Poiché OpenSL ES è un'API C nativa, i thread delle applicazioni non di runtime che chiamano OpenSL ES non hanno l'overhead correlato al runtime, ad esempio le pause della garbage collection. Con un'eccezione descritta di seguito, l'uso di OpenSL ES non presenta ulteriori vantaggi in termini di prestazioni. In particolare, l'utilizzo di OpenSL ES non garantisce miglioramenti quali latenza audio inferiore e la priorità della pianificazione rispetto a quella generalmente offerta dalla piattaforma. D'altra parte, poiché La piattaforma Android e le implementazioni specifiche dei dispositivi continuano a evolversi, un'applicazione OpenSL ES di poter trarre vantaggio da eventuali miglioramenti futuri delle prestazioni del sistema.

Una di queste evoluzioni è il supporto di modelli latenza dell'output audio. Gli elementi fondamentali per una la latenza di output è stata inclusa per la prima volta in Android 4.1 (livello API 16) e poi continui progressi si sono verificati in Android 4.2 (livello API 17). Questi miglioramenti sono disponibili tramite OpenSL ES per implementazioni di dispositivi che funzionalità di rivendicazione android.hardware.audio.low_latency. Se il dispositivo non supporta questa funzionalità, ma supporta Android 2.3 (livello API 9) o versioni successive, puoi comunque utilizzare le API OpenSL ES, ma la latenza in uscita potrebbe essere superiore. Più basso è il percorso di latenza dell'output viene utilizzato solo se l'applicazione richiede dimensioni del buffer e frequenza di campionamento che sono compatibile con la configurazione di output nativa del dispositivo. Questi parametri sono specifici per dispositivo come descritto di seguito.

A partire da Android 4.2 (livello API 17), un'applicazione può eseguire query frequenza di campionamento di output nativa o ottimale della piattaforma e dimensione del buffer per l'output principale del dispositivo flusso di dati. Se combinata con il test delle funzionalità appena menzionato, ora un'app può configurarsi da sola in modo appropriato per ottenere una latenza minore sui dispositivi che richiedono assistenza.

Per Android 4.2 (livello API 17) e versioni precedenti, il conteggio del buffer è pari o superiore a due richiesta per una latenza minore. A partire da Android 4.3 (livello API 18), un valore di buffer di 1 è sufficiente per una latenza inferiore.

Tutte le interfacce OpenSL ES per gli effetti di output precludono il percorso di latenza più bassa.

La sequenza consigliata è la seguente:

  1. Controlla il livello API 9 o superiore per confermare l'utilizzo di OpenSL ES.
  2. Controlla la funzionalità android.hardware.audio.low_latency utilizzando un codice come il seguente:

    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. Controlla il livello API 17 o superiore per confermare l'utilizzo delle android.media.AudioManager.getProperty().
  4. Ottieni la frequenza di campionamento e la dimensione del buffer di output nativa o ottimale per lo stream di output primario di questo dispositivo utilizzando un codice come questo:

    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);
    
    Tieni presente che sampleRate e framesPerBuffer sono stringhe. Primo controllo null, quindi converti in int utilizzando Integer.parseInt().
  5. Ora utilizza OpenSL ES per creare un AudioPlayer con un localizzatore di dati di coda con buffer PCM.

Nota: Puoi utilizzare lo Dimensione del buffer audio app di test per determinare la dimensione del buffer nativo e la frequenza di campionamento per l'audio OpenSL ES applicazioni sul tuo dispositivo audio. Puoi anche visitare GitHub per visualizzare campioni a dimensione del buffer audio.

Il numero di lettori audio a latenza più bassa è limitato. Se la tua applicazione richiede più di alcune sorgenti audio, ti consigliamo di mixare l'audio a livello di applicazione. Assicurati di eliminare l'audio quando la tua attività viene sospesa, poiché sono una risorsa globale condivisa con altre app.

Per evitare glitch udibili, il gestore del callback della coda del buffer deve essere eseguito in un breve periodo di tempo prevedibile. Questo in genere non implica alcun blocco illimitato su mutex, condizioni o operazioni di I/O. Valuta invece la possibilità di utilizzare blocchi di prova, blocchi e attese con timeout e algoritmi non bloccanti.

Il calcolo necessario per eseguire il rendering del buffer successivo (per AudioPlayer) o utilizzare il precedente buffer (per AudioRecord) dovrebbe richiedere circa la stessa quantità di tempo per ogni callback. Evita gli algoritmi che vengono eseguiti a intervalli di tempo non deterministici o che sono bursosi in i loro calcoli. Il calcolo di un callback è intensivo se il tempo di CPU impiegato in un determinato callback è notevolmente superiore alla media. Riassumendo, l'ideale è il tempo di esecuzione della CPU il gestore abbia una varianza vicina allo zero e non blocchi per orari illimitati.

L'audio a latenza più bassa è possibile solo per queste uscite:

  • Altoparlanti sul dispositivo.
  • Cuffie con cavo.
  • Cuffie con cavo.
  • Allineati.
  • Audio digitale USB.

Su alcuni dispositivi, la latenza degli altoparlanti è superiore a quella di altri percorsi a causa dell'elaborazione del segnale digitale per la correzione e la protezione degli altoparlanti.

A partire da Android 5.0 (livello API 21), l'input audio con latenza inferiore è supportato su alcuni dispositivi. Per sfruttare questa funzionalità, verifica innanzitutto sia disponibile un output a latenza più bassa, come descritto sopra. La funzionalità di output a latenza più bassa è un prerequisito per la funzionalità di input a bassa latenza. Quindi, crea un AudioRecorder con la stessa frequenza di campionamento e le stesse dimensioni del buffer che verranno utilizzate per l'output. Interfacce OpenSL ES per effetti di input precludono il percorso a latenza più bassa. La preimpostazione di registrazione SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION deve essere utilizzato per una latenza minore. questo La preimpostazione disattiva l'elaborazione del segnale digitale specifica del dispositivo, che potrebbe aggiungere latenza al percorso di input. Per ulteriori informazioni sulle preimpostazioni di registrazione, consulta la sezione Interfaccia di configurazione Android sopra.

Per l'input e l'output simultanei, vengono utilizzati gestori di completamento della coda del buffer separati per ciascun lato. Non vi è alcuna garanzia dell'ordine relativo di questi callback o della sincronizzazione gli orologi audio, anche quando entrambi i lati utilizzano la stessa frequenza di campionamento. L'applicazione deve mettere in buffer i dati con una sincronizzazione del buffer adeguata.

Una delle conseguenze di un orologio audio potenzialmente indipendente è la necessità di una frequenza di campionamento asincrona e conversione in blocco. Una tecnica semplice (anche se non ideale per la qualità audio) per la conversione della frequenza di campionamento asincrona è duplicare o eliminare i campioni in base alle esigenze vicino a un punto di passaggio a zero. Più sofisticato sono possibili.

Modalità di rendimento

A partire da Android 7.1 (livello API 25), OpenSL ES ha introdotto un modo per specificare una modalità di rendimento per il percorso audio. Le opzioni sono:

  • SL_ANDROID_PERFORMANCE_NONE: nessun requisito di rendimento specifico. Consente effetti hardware e software.
  • SL_ANDROID_PERFORMANCE_LATENCY: la priorità viene assegnata alla latenza. Nessun hardware o degli effetti software. Questa è la modalità predefinita.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: la priorità viene assegnata alla latenza consentendo l'uso di effetti hardware e software.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: priorità assegnata al risparmio energetico. Consente effetti hardware e software.

Nota : se non hai bisogno di un percorso a bassa latenza e vuoi adottare sfruttare gli effetti audio integrati nel dispositivo (ad esempio per migliorare l'acustica) per la riproduzione video), devi impostare esplicitamente la modalità prestazioni su SL_ANDROID_PERFORMANCE_NONE.

Per impostare la modalità prestazioni, devi chiamare SetConfiguration usando il Android di configurazione del deployment, come mostrato di seguito:

  // 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));

Sicurezza e autorizzazioni

Per quanto riguarda chi può fare cosa, la sicurezza in Android avviene a livello di processo. Programmazione Java il codice del linguaggio non può fare nulla di più del codice nativo Codice del linguaggio di programmazione Java. Le uniche differenze sono le API disponibili.

Le applicazioni che utilizzano OpenSL ES devono richiedere le autorizzazioni necessarie per API non native simili. Ad esempio, se la tua applicazione registra audio, è necessaria la Autorizzazione android.permission.RECORD_AUDIO. Le applicazioni che utilizzano effetti audio richiedonoandroid.permission.MODIFY_AUDIO_SETTINGS. Applicazioni che riproducono risorse URI di rete hanno bisogno di android.permission.NETWORK. Per ulteriori informazioni, consulta Utilizzo delle autorizzazioni di sistema.

A seconda della versione e dell'implementazione della piattaforma, i parser dei contenuti multimediali e codec software possono vengono eseguiti nel contesto dell'applicazione Android che chiama OpenSL ES (i codec hardware sono sono astratti, ma dipendono dal dispositivo). Contenuti di formato non corretto progettati per sfruttare parser e codec è un vettore di attacco noto. Ti consigliamo di riprodurre contenuti multimediali solo da contenuti affidabili o partizionare l'applicazione in modo tale che il codice gestisca i contenuti multimediali non attendibili viene eseguito in un ambiente relativamente sandbox. Ad esempio, potresti elaborano i contenuti multimediali provenienti da fonti inaffidabili in un processo separato. Sebbene entrambi i processi eseguito con lo stesso UID, questa separazione rende più difficile l'attacco.