Note di programmazione di OpenSL ES

Le note in questa sezione integrano la specifica di OpenSL ES 1.0.1.

Inizializzazione dell'interfaccia e degli oggetti

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 nei linguaggi di programmazione come Java e C++, ad eccezione del fatto che un oggetto OpenSL ES è visibile solo tramite le interfacce associate. Include l'interfaccia iniziale per tutti gli oggetti, denominata SLObjectItf. Non esiste un handle per un oggetto stesso, ma solo un handle per l'interfaccia SLObjectItf dell'oggetto.

Un oggetto OpenSL ES viene prima creato, che restituisce SLObjectItf, quindi realizzato. È simile al modello di programmazione comune durante la creazione iniziale di un oggetto (che non dovrebbe mai avere esito negativo se non per mancanza di memoria o parametri non validi) e quindi del completamento dell'inizializzazione (che potrebbe non riuscire a causa della mancanza di risorse). Il passaggio di creazione offre all'implementazione un luogo logico in cui allocare risorse aggiuntive, se necessario.

Nell'ambito dell'API per la creazione di un oggetto, un'applicazione specifica un array di interfacce desiderate che prevede di acquisire in un secondo momento. Tieni presente che questo array non acquisisce automaticamente le interfacce, ma indica semplicemente un'intenzione futura di acquisirle. Le interfacce si distinguono come implicite o esplicite. Nell'array deve essere elencata un'interfaccia esplicita se verrà acquisita in un secondo momento. Non è necessario elencare un'interfaccia implicita nell'array di creazione dell'oggetto, ma elencarla non comporta alcun danno. OpenSL ES dispone di un altro tipo di interfaccia, denominata dinamica, che non deve essere specificata nell'array di creazione dell'oggetto e può essere aggiunta in un secondo momento dopo la creazione dell'oggetto. L'implementazione di Android offre una funzionalità utile per evitare questa complessità, descritta nella sezione Interfacce dinamiche nella creazione di oggetti.

Dopo che l'oggetto è stato creato e realizzato, l'applicazione dovrebbe acquisire le interfacce per ogni funzionalità di cui ha bisogno, utilizzando GetInterface nella SLObjectItf iniziale.

Infine, l'oggetto è disponibile per l'uso tramite le sue interfacce, ma tieni presente che alcuni oggetti richiedono una configurazione aggiuntiva. In particolare, un lettore audio con origine dati URI richiede un po' più di preparazione per rilevare errori di connessione. Consulta la sezione Precaricamento del lettore audio per i dettagli.

Una volta che l'oggetto ha terminato l'applicazione, devi eliminarlo esplicitamente; consulta la sezione Elimina di seguito.

Precaricamento del lettore audio

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

Alcune informazioni potrebbero rimanere sconosciute fino a un momento relativamente tardi di questa sequenza. In particolare, inizialmente Player::GetDuration restituisce SL_TIME_UNKNOWN e MuteSolo::GetChannelCount restituisce correttamente il numero di canali con zero o restituisce il risultato di errore SL_RESULT_PRECONDITIONS_VIOLATED. Una volta noti, queste API restituiscono i valori corretti.

Altre proprietà inizialmente sconosciute includono la frequenza di campionamento e il tipo di contenuto multimediale effettivo in base all'esame dell'intestazione del contenuto (anziché il tipo MIME e il tipo di contenitore specificati dall'applicazione). Questi vengono inoltre determinati in un secondo momento durante la preparazione/il precaricamento, ma non ci sono API per recuperarle.

L'interfaccia dello stato di precaricamento è utile per rilevare quando tutte le informazioni sono disponibili o quando la tua applicazione può eseguire il polling periodicamente. Tieni presente che alcune informazioni, come la durata di un file MP3 in streaming, potrebbero non essere note.

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

Si prevede che la prossima versione di OpenSL ES aggiungerà un supporto più esplicito per la gestione degli errori nell'origine dati. Tuttavia, per la futura compatibilità dei programmi binari, intendiamo continuare a supportare il metodo attuale per segnalare un errore non recuperabile.

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 il callback viene chiamato con aggiornamenti periodici dello stato.

Elimina

Assicurati di eliminare tutti gli oggetti quando esci dall'applicazione. Gli oggetti dovrebbero essere distrutti nell'ordine inverso della loro creazione, poiché non è sicuro distruggere un oggetto con oggetti dipendenti. Ad esempio, elimina in questo ordine: lettori e registratori audio, mix di output e infine il motore.

OpenSL ES non supporta la garbage collection automatica o il conteggio dei riferimenti delle interfacce. Dopo aver chiamato Object::Destroy, tutte le interfacce esistenti derivate dall'oggetto associato diventano non definite.

L'implementazione di Android OpenSL ES non rileva l'utilizzo non corretto di queste interfacce. Continuare a utilizzare queste interfacce dopo l'eliminazione dell'oggetto può causare arresti anomali dell'applicazione o comportamenti imprevedibili.

Ti consigliamo di impostare esplicitamente sia l'interfaccia dell'oggetto principale che tutte le interfacce associate su NULL nell'ambito della sequenza di eliminazione degli oggetti, per impedire l'uso improprio di un handle dell'interfaccia inattivo.

Panoramica stereo

Quando si utilizza Volume::EnableStereoPosition per abilitare il panning stereo di una sorgente mono, si applica una riduzione di 3 dB del livello di potenza sonora totale. Ciò è necessario per far sì che il livello di potenza sonora totale rimanga costante mentre la sorgente viene eseguita da un canale all'altro. Pertanto, attiva il posizionamento stereo solo se necessario. Per ulteriori informazioni, consulta l'articolo di Wikipedia sul panning audio.

Callback e thread

I gestori di callback vengono generalmente chiamati in modo sincrono quando l'implementazione rileva un evento. Questo punto è asincrono rispetto all'applicazione, pertanto devi utilizzare un meccanismo di sincronizzazione non bloccante per controllare l'accesso a qualsiasi variabile condivisa tra l'applicazione e il gestore di callback. Nel codice di esempio, ad esempio per le code di buffer, abbiamo omesso questa sincronizzazione oppure abbiamo utilizzato la sincronizzazione di blocco per motivi di semplicità. Tuttavia, una sincronizzazione che non blocchi correttamente è fondamentale per qualsiasi codice di produzione.

I gestori di callback vengono chiamati da thread interni non di applicazioni che non sono collegati ad Android Runtime, pertanto non sono idonei all'utilizzo di JNI. Poiché questi thread interni sono fondamentali per l'integrità dell'implementazione di OpenSL ES, un gestore di callback non deve bloccare o eseguire un lavoro eccessivo.

Se il gestore di callback deve utilizzare JNI o eseguire operazioni non proporzionali al callback, il gestore dovrebbe pubblicare un evento per l'elaborazione di un altro thread. Esempi di carico di lavoro di callback accettabile includono il rendering e l'accodamento del buffer di output successivo (per un AudioPlayer), l'elaborazione del buffer di input appena compilato e l'accodamento del buffer vuoto successivo (per un AudioRecorder) o semplici API come la maggior parte della famiglia Get. Consulta la sezione Prestazioni di seguito relativa al carico di lavoro.

Tieni presente che il contrario è sicuro: un thread di un'applicazione Android che è entrato in JNI può chiamare direttamente le API OpenSL ES, incluse quelle che bloccano. Tuttavia, non è consigliabile bloccare le chiamate dal thread principale perché potrebbero causare il messaggio L'applicazione non risponde (ANR).

La decisione relativa al thread che chiama un gestore di callback è in gran parte lasciata all'implementazione. Il motivo di questa flessibilità è consentire ottimizzazioni future, in particolare sui dispositivi multi-core.

Non è garantito che il thread su cui viene eseguito il gestore di callback abbia la stessa identità in chiamate diverse. Pertanto, non fare affidamento sul valore pthread_t restituito da pthread_self() o sul valore pid_t restituito da gettid() affinché sia 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 un callback.

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 in thread diversi.

Esibizione

Poiché OpenSL ES è un'API C nativa, i thread di applicazioni non di runtime che chiamano OpenSL ES non hanno overhead relativo al runtime, come le pause della garbage collection. Con un'eccezione descritta di seguito, l'utilizzo di OpenSL ES non presenta ulteriori vantaggi in termini di prestazioni oltre a questo. In particolare, l'uso di OpenSL ES non garantisce miglioramenti come una minore latenza audio e una priorità di pianificazione superiore rispetto a quella fornita in genere dalla piattaforma. D'altra parte, man mano che la piattaforma Android e le implementazioni specifiche dei dispositivi continuano a evolversi, un'applicazione OpenSL ES può aspettarsi di trarre vantaggio da eventuali miglioramenti futuri delle prestazioni del sistema.

Una di queste evoluzione è il supporto per la riduzione della latenza dell'output audio. I concetti alla base della riduzione della latenza di output sono stati inclusi per la prima volta in Android 4.1 (livello API 16) e poi i progressi continuativi si sono verificati in Android 4.2 (livello API 17). Questi miglioramenti sono disponibili tramite OpenSL ES per le implementazioni dei dispositivi che rivendicano la funzionalità android.hardware.audio.low_latency. Se il dispositivo non richiede questa funzionalità ma supporta Android 2.3 (livello API 9) o versioni successive, puoi comunque utilizzare le API OpenSL ES, ma la latenza di output potrebbe essere superiore. Il percorso di latenza di output inferiore viene utilizzato solo se l'applicazione richiede una dimensione del buffer e una frequenza di campionamento compatibili con la configurazione dell'output nativo del dispositivo. Questi parametri sono specifici per dispositivo e devono essere ottenuti come descritto di seguito.

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

Per Android 4.2 (livello API 17) e versioni precedenti, per ridurre la latenza è richiesto un numero di buffer pari a due o più. A partire da Android 4.3 (livello API 18), un numero di buffer pari a 1 è sufficiente per ridurre la latenza.

Tutte le interfacce OpenSL ES per gli effetti di output precludono il percorso a latenza inferiore.

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 simile al 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 successivo per confermare l'utilizzo di android.media.AudioManager.getProperty().
  4. Ottieni la frequenza di campionamento e la dimensione del buffer nativi o ottimali per il flusso di output principale 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. Innanzitutto verifica la presenza di null, quindi converti in int utilizzando Integer.parseInt().
  5. Ora usa OpenSL ES per creare un AudioPlayer con un localizzatore di dati della coda di buffer PCM.

Nota: puoi utilizzare l'app di test Dimensioni buffer audio per determinare la dimensione del buffer e la frequenza di campionamento native per le applicazioni audio OpenSL ES sul tuo dispositivo audio. Puoi anche visitare GitHub per visualizzare esempi audio-buffer-size.

Il numero di player audio a bassa latenza è limitato. Se la tua applicazione richiede più di alcune sorgenti audio, valuta la possibilità di mixare l'audio a livello di applicazione. Assicurati di distruggere i tuoi lettori audio quando l'attività è in pausa, dato che si tratta di una risorsa globale condivisa con altre app.

Per evitare problemi udibili, il gestore di callback della coda del buffer deve essere eseguito entro un periodo di tempo ridotto e prevedibile. In genere, ciò implica l'assenza di blocchi illimitati su mutex, condizioni o operazioni di I/O. Prendi in considerazione invece l'utilizzo di blocchi, blocchi e attese con timeout e algoritmi non di blocco.

Il calcolo necessario per eseguire il rendering del buffer successivo (per AudioPlayer) o per consumare il buffer precedente (per AudioRecord) dovrebbe richiedere approssimativamente la stessa quantità di tempo per ogni callback. Evita gli algoritmi che vengono eseguiti in un periodo di tempo non deterministico o che sono bursty nei calcoli. Il calcolo del callback non funziona se il tempo di CPU impiegato in un determinato callback è notevolmente superiore alla media. In sintesi, l'ideale è che il tempo di esecuzione della CPU del gestore abbia una varianza vicina a zero e che il gestore non blocchi per tempi illimitati.

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

Su alcuni dispositivi, la latenza degli altoparlanti è superiore rispetto ad 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), su alcuni dispositivi è supportato l'input audio con latenza inferiore. Per sfruttare questa funzionalità, verifica innanzitutto che sia disponibile un output con latenza inferiore come descritto sopra. La capacità di produrre output con latenza più bassa è un prerequisito per la funzionalità di input con latenza inferiore. Quindi, crea un AudioRecorder con la stessa frequenza di campionamento e la stessa dimensione di buffer che useresti per l'output. Le interfacce OpenSL ES per gli effetti di input precludono il percorso a bassa latenza. La preimpostazione del record SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION deve essere utilizzata per ridurre la latenza; questa preimpostazione disabilita l'elaborazione del segnale digitale specifico per dispositivo che potrebbe aggiungere latenza al percorso di input. Per ulteriori informazioni sulle preimpostazioni dei record, consulta la sezione Interfaccia di configurazione di Android in alto.

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

Una conseguenza di orologi audio potenzialmente indipendenti è la necessità di una conversione della frequenza di campionamento asincrona. Una tecnica semplice (anche se non ideale per la qualità audio) per la conversione della frequenza di campionamento asincrona è duplicare o eliminare i campioni secondo necessità vicino a un punto di passaggio zero. Sono possibili conversioni più sofisticate.

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: viene data priorità alla latenza. Nessun effetto hardware o software. Questa è la modalità predefinita.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: viene data priorità alla latenza, ma vengono comunque consentiti gli effetti su hardware e software.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: priorità assegnata alla conservazione dell'energia. Consente effetti hardware e software.

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

Per impostare la modalità Prestazioni, devi chiamare SetConfiguration utilizzando l'interfaccia di configurazione di Android, 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 di Android viene applicata a livello di processo. il codice del linguaggio di programmazione Java non può fare altro che il codice nativo e non può fare altro che il codice del linguaggio di programmazione Java. L'unica differenza tra i due 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, deve avere l'autorizzazione android.permission.RECORD_AUDIO. Le applicazioni che utilizzano effetti audio richiedono android.permission.MODIFY_AUDIO_SETTINGS. Le applicazioni che riproducono le risorse URI di rete richiedono android.permission.NETWORK. Per ulteriori informazioni, consulta Utilizzo delle autorizzazioni di sistema.

A seconda della versione della piattaforma e dell'implementazione, i parser di contenuti multimediali e i codec software possono essere eseguiti nel contesto dell'applicazione Android che chiama OpenSL ES (i codec hardware sono astratti ma dipendono dal dispositivo). I contenuti non corretti progettati per sfruttare le vulnerabilità dell'analizzatore sintattico e del codec sono un vettore di attacco noto. Ti consigliamo di riprodurre contenuti multimediali solo da fonti attendibili o di partizionare la tua applicazione in modo che il codice che gestisce i contenuti multimediali da fonti non attendibili venga eseguito in un ambiente con sandbox. Ad esempio, potresti elaborare contenuti multimediali da fonti non attendibili in un processo separato. Sebbene entrambi i processi vengano comunque eseguiti con lo stesso UID, questa separazione rende un attacco più difficile.