Note di programmazione di OpenSL ES

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. Sono inclusi 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 lettore audio con origine dati URI ha bisogno di un po' e maggiore preparazione per rilevare gli errori di connessione. Consulta le Sezione Precaricamento del lettore audio per informazioni dettagliate.

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

Precaricamento del lettore audio

Per un lettore audio con origine dati URI, Object::Realize alloca ma non collegati all'origine dati (preparati) o inizia a precaricare i dati. Questi si verificano una volta che lo stato del player è impostato su SL_PLAYSTATE_PAUSED o SL_PLAYSTATE_PLAYING.

Alcune informazioni potrebbero essere ancora sconosciute fino a una fase relativamente avanzata di questa sequenza. Nella 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 indefiniti.

L'implementazione di Android OpenSL ES non rileva l'utilizzo non corretto 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 tutti i componenti associati 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, pertanto dovresti utilizzare un'interfaccia meccanismo di sincronizzazione per controllare l'accesso alle variabili condivise tra l'applicazione e di callback di Google. 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 sono 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. Esempi di il carico di lavoro di callback accettabile include il rendering e l'accodamento del successivo buffer di output (per un AudioPlayer), elaborando il buffer di input appena riempito e accodando il successivo buffer vuoto (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 i callback simultanei dello stesso tipo, per il stesso oggetto, non avvengono. 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 ridurre 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 dichiara questa funzionalità, ma supporta Android 2.3 (livello API 9) o in un secondo momento, puoi continuare a utilizzare le API OpenSL ES ma la latenza dell'output potrebbe essere più elevata. 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 buffer uno è sufficiente per una latenza più bassa.

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 dimensione del buffer e la frequenza di campionamento di output nativa o ottimale per il output principale usa un codice simile al seguente:

    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ù risorse rispetto ad alcune sorgenti audio, valuta la possibilità 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 problemi udibili, il gestore di callback della coda di buffer deve eseguire una finestra temporale prevedibile. Questo in genere non implica alcun blocco illimitato su mutex, condizioni o operazioni di I/O. Prendi in considerazione invece i blocchi, i blocchi e i tempi di attesa con timeout. . algoritmi che non bloccano la migrazione.

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.
  • USB digitale audio.

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), minore latenza input audio è 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 lo stesso la frequenza di campionamento e la dimensione del buffer che verrebbero 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, vedi Configurazione Android dell'interfaccia utente qui sopra.

Per l'input e l'output simultanei, vengono utilizzati gestori di completamento della coda del buffer separati lato server. 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. La tua applicazione dovrebbe eseguire il buffering con una corretta sincronizzazione del buffer.

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 frequenza di campionamento asincrona la conversione consiste nel duplicare o rilasciare campioni secondo necessità in prossimità di un punto di zero crossing. Più sofisticato sono possibili.

Modalità prestazioni

A partire da Android 7.1 (livello API 25) OpenSL ES ha introdotto un modo per specificare una modalità prestazioni 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 le API non native. Ad esempio, se la tua applicazione registra audio, è necessaria la Autorizzazione android.permission.RECORD_AUDIO. Le applicazioni che utilizzano effetti audio devono android.permission.MODIFY_AUDIO_SETTINGS. Applicazioni che riproducono risorse URI di rete hanno bisogno di android.permission.NETWORK. Per ulteriori informazioni, vedi Utilizzo del sistema Autorizzazioni.

A seconda della versione e dell'implementazione della piattaforma, i parser dei contenuti multimediali e codec software può 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 su contenuti affidabili parti dell'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.