AVVISO: OpenSL ES è deprecato. Gli sviluppatori devono utilizzare la libreria Oboe open source disponibile su GitHub. Oboe è un wrapper C++ che fornisce un'API molto simile ad AAudio. Oboe chiama AAudio se AAudio è disponibile e passa a OpenSL ES se AAudio non è disponibile.
Le note in questa sezione integrano la specifica OpenSL ES 1.0.1.
Inizializzazione degli oggetti e dell'interfaccia
Due aspetti del modello di programmazione OpenSL ES che potrebbero non essere familiari ai nuovi sviluppatori sono la distinta 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++, tranne per il fatto che un oggetto OpenSL ES è visibile solo tramite le relative interfacce associate.
Questa include
l'interfaccia iniziale per tutti gli oggetti, denominata SLObjectItf
.
Non esiste un handle per un oggetto stesso, ma solo per l'interfaccia SLObjectItf
dell'oggetto.
Viene prima creato un oggetto OpenSL ES, che restituisce un SLObjectItf
, quindi viene realizzato. È simile al pattern di programmazione comune che prevede prima di costruire un oggetto (che non dovrebbe mai fallire se non per mancanza di memoria o parametri non validi) e poi di completare l'inizializzazione (che potrebbe non riuscire a causa della mancanza di risorse). Il passaggio Realizza fornisce all'implementazione un luogo logico per 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 seguito. Tieni presente che questo array non acquisisce automaticamente le interfacce, ma indica solo l'intenzione futura di acquisirle. Le interfacce sono distinte come implicite o esplicite. Un'interfaccia esplicita deve essere elencata nell'array se verrà acquisita in un secondo momento. Un'interfaccia implicita non deve essere elencata nell'array di creazione dell'oggetto, ma non c'è nulla di male a farlo. OpenSL ES ha 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 fornisce una funzionalità di praticità per evitare questa complessità, descritta in Interfacce dinamiche alla creazione dell'oggetto.
Dopo aver creato e realizzato l'oggetto, l'applicazione deve acquisire le interfacce per ogni
funzionalità di cui ha bisogno, utilizzando GetInterface
sull'SLObjectItf
iniziale.
Infine, l'oggetto è disponibile per l'utilizzo tramite le sue interfacce, anche se tieni presente che alcuni oggetti richiedono un'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 informazioni dettagliate, consulta la sezione Precaricamento del lettore audio.
Quando l'applicazione non ha più bisogno dell'oggetto, devi distruggerlo esplicitamente. Consulta la sezione Distruggere 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 una fase relativamente avanzata di questa sequenza. In particolare, inizialmente Player::GetDuration
restituisce SL_TIME_UNKNOWN
e MuteSolo::GetChannelCount
restituisce correttamente il conteggio dei canali pari a zero o il risultato di errore SL_RESULT_PRECONDITIONS_VIOLATED
. Queste API restituiscono i valori corretti
quando sono noti.
Altre proprietà inizialmente sconosciute includono la frequenza di campionamento e il tipo di contenuti multimediali effettivi in base all'esame dell'intestazione dei contenuti (a differenza del tipo MIME e del tipo di contenitore specificato dall'applicazione). Anche questi vengono determinati in un secondo momento durante la preparazione/il precaricamento, ma non esistono API per recuperarli.
L'interfaccia dello stato del precaricamento è utile per rilevare quando sono disponibili tutte le informazioni o la tua applicazione può eseguire periodicamente sondaggi. Tieni presente che alcune informazioni, ad esempio la durata di un file MP3 in streaming, potrebbero non essere mai note.
L'interfaccia dello stato del 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 inviati contemporaneamente,
PrefetchStatus::GetFillLevel
registra un livello zero e
PrefetchStatus::GetPrefetchStatus
registra SL_PREFETCHSTATUS_UNDERFLOW
,
indica un errore non recuperabile nell'origine dati. Sono inclusi l'impossibilità di collegarsi all'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 la gestione degli errori nell'origine dati. Tuttavia, per la futura compatibilità binaria, intendiamo continuare a supportare il metodo attuale per segnalare un errore non recuperabile.
Per riassumere, una sequenza di codice consigliata è:
Engine::CreateAudioPlayer
Object:Realize
Object::GetInterface
perSL_IID_PREFETCHSTATUS
PrefetchStatus::SetCallbackEventsMask
PrefetchStatus::SetFillUpdatePeriod
PrefetchStatus::RegisterCallback
Object::GetInterface
perSL_IID_PLAY
- Da
Play::SetPlayState
aSL_PLAYSTATE_PAUSED
oSL_PLAYSTATE_PLAYING
Nota: la preparazione e il pre-caricamento avvengono qui; durante questo periodo, il tuo callback viene chiamato con aggiornamenti periodici dello stato.
Distruggi
Assicurati di distruggere tutti gli oggetti quando esci dall'applicazione. Gli oggetti devono essere distrutti nell'ordine opposto rispetto a quello della loro creazione, in quanto non è sicuro distruggere un oggetto che ha oggetti dipendenti. Ad esempio, elimina i contenuti in questo ordine: lettori e registratori audio, mix di output e infine il motore.
OpenSL ES non supporta la raccolta automatica dei rifiuti o il
conteggio
delle referenze delle interfacce. Dopo aver chiamato Object::Destroy
, tutte le interfacce esistenti derivanti dall'oggetto associato diventano non definite.
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 subire un arresto anomalo o comportarsi in modo imprevedibile.
Ti consigliamo di impostare esplicitamente sia l'interfaccia dell'oggetto principale sia tutte le interfacce associate su NULL
nell'ambito della sequenza di distruzione dell'oggetto, in modo da evitare l'uso improprio accidentale di un handle dell'interfaccia obsoleto.
Panning stereo
Quando viene utilizzato Volume::EnableStereoPosition
per attivare il panning stereo di una sorgente mono, si verifica una riduzione di 3 dB del livello di potenza sonora totale. Questo è necessario per consentire al livello di potenza audio totale di rimanere costante quando la sorgente viene spostata da un canale all'altro. Pertanto, attiva il posizionamento stereo solo se necessario. Per ulteriori informazioni, consulta l'articolo di Wikipedia sul
pan 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, 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, ad esempio per le code del buffer, abbiamo omesso questa sincronizzazione o utilizzato il blocco della sincronizzazione per semplicità. Tuttavia, una sincronizzazione non bloccante corretta è fondamentale per qualsiasi codice di produzione.
I gestori dei rilanci vengono chiamati da thread interni non di applicazione non associati al runtime Android, pertanto non sono idonei all'utilizzo di JNI. Poiché questi thread interni sono fondamentali per l'integrità dell'implementazione di OpenSL ES, anche un gestore dei callback non deve bloccarsi o eseguire un lavoro eccessivo.
Se il gestore del callback deve utilizzare JNI o eseguire un'operazione non proporzionale al callback, deve invece pubblicare un evento da elaborare da un altro thread. Alcuni esempi di workload 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 la sezione Prestazioni di seguito per informazioni sul carico di lavoro.
Tieni presente che il contrario è sicuro: un thread dell'applicazione Android che ha eseguito l'accesso a JNI è autorizzato a chiamare direttamente le API OpenSL ES, incluse quelle che bloccano. Tuttavia, le chiamate di blocco non sono consigliate dal thread principale, in quanto potrebbero causare un errore ANR (L'applicazione non risponde).
La determinazione del thread che chiama un gestore di callback è in gran parte lasciata all'implementazione. Il motivo di questa flessibilità è consentire ottimizzazioni future, soprattutto su dispositivi multi-core.
Non è garantito che il thread su cui viene eseguito il gestore del callback abbia la stessa identità in chiamate diverse. Pertanto, non fare affidamento su pthread_t
restituito da
pthread_self()
o pid_t
restituito da gettid()
per essere
coerente tra le chiamate. Per lo stesso motivo, non utilizzare le API di archiviazione locale dei thread (TLS) 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, su thread diversi sono possibili callback simultanei di tipi diversi per lo stesso oggetto.
Prestazioni
Poiché OpenSL ES è un'API C nativa, i thread dell'applicazione non di runtime che chiamano OpenSL ES non hanno overhead correlato al runtime, come le interruzioni della raccolta dei rifiuti. A parte un'eccezione descritta di seguito, non sono previsti altri vantaggi in termini di prestazioni per l'utilizzo di OpenSL ES. In particolare, l'utilizzo di OpenSL ES non garantisce miglioramenti come una latenza audio inferiore e una prioritaria pianificazione superiore rispetto a quella fornita in genere dalla piattaforma. D'altra parte, poiché la piattaforma Android e le implementazioni di dispositivi specifici continuano a evolversi, un'applicazione OpenSL ES può beneficiare di eventuali miglioramenti futuri delle prestazioni del sistema.
Una di queste evoluzioni è il supporto per la riduzione della
latenza dell'uscita audio.
I fondamenti per la riduzione della latenza di output sono stati inclusi per la prima volta in Android 4.1 (livello API 16), poi i progressi continui 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 dichiara 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 con la latenza di output inferiore viene utilizzato solo se l'applicazione richiede una dimensione del buffer e una frequenza di campionamento compatibili con la configurazione di output nativa del dispositivo. Questi parametri sono specifici del 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 in uscita nativa o ottimale della piattaforma e la dimensione del buffer per lo stream di output principale del dispositivo. Se combinato con il test di funzionalità appena menzionato, ora un'app può configurarsi in modo appropriato per un output con una latenza inferiore sui dispositivi che dichiarano di supportarla.
Per Android 4.2 (livello API 17) e versioni precedenti, è obbligatorio un conteggio del buffer di almeno due per una latenza inferiore. A partire da Android 4.3 (livello API 18), un valore di conto buffer pari a 1 è sufficiente per una latenza inferiore.
Tutte le interfacce OpenSL ES per gli effetti di output precludono il percorso con latenza inferiore.
La sequenza consigliata è la seguente:
- Verifica che il livello API sia 9 o superiore per confermare l'utilizzo di OpenSL ES.
- Verifica la presenza della funzionalità
android.hardware.audio.low_latency
utilizzando un codice come questo: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);
- Verifica che il livello API sia 17 o superiore per confermare l'utilizzo di
android.media.AudioManager.getProperty()
. - Ottieni la frequenza di campionamento e la dimensione del buffer di output principale o nativa o ottimale per lo stream di output principale di questo dispositivo utilizzando 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);
sampleRate
eframesPerBuffer
sono stringhe. Per prima cosa, controlla se il valore è nullo e poi convertilo in int utilizzandoInteger.parseInt()
. - Ora utilizza OpenSL ES per creare un AudioPlayer con locator dei dati della coda del buffer PCM.
Nota: puoi utilizzare l'app di test Dimensione del 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 gli esempi di audio-buffer-size.
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 distruggere i tuoi lettori 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. In genere, ciò implica che non vi siano blocchi illimitati 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 buffer precedente (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 nei calcoli. Un calcolo del callback è a intermittenza se il tempo della CPU speso in un determinato callback è notevolmente superiore alla media. Riassumendo, 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 latenza più bassa è possibile solo per queste uscite:
- Altoparlanti sul dispositivo.
- Cuffie con cavo.
- Cuffie con cavo.
- Uscita linea.
- Audio digitale USB.
Su alcuni dispositivi, la latenza dello speaker è superiore rispetto ad altri percorsi a causa dell'elaborazione del segnale digitale per la correzione e la protezione dello speaker.
A partire da Android 5.0 (livello API 21), l'input audio con latenza inferiore è supportato su alcuni dispositivi. Per usufruire di questa funzionalità, verifica innanzitutto che sia disponibile un'uscita con una latenza inferiore come descritto sopra. La possibilità di ottenere un'uscita con latenza inferiore è 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 verrebbero utilizzati per l'output. Le interfacce OpenSL ES per gli effetti di input preclude il percorso a latenza inferiore. Il valore di registrazione preimpostato
SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
deve essere utilizzato per una latenza più bassa; questo
preset disabilita l'elaborazione del segnale digitale specifico del dispositivo, che potrebbe aggiungere latenza al percorso di input.
Per ulteriori informazioni sulle preimpostazioni di registrazione, consulta la sezione Interfaccia di configurazione Android qui sopra.
Per l'input e l'output simultanei, vengono utilizzati gestori di completamento della coda del buffer separati per ciascun lato. Non c'è alcuna garanzia dell'ordine relativo di questi callback o della sincronizzazione degli orologi audio, anche quando entrambe le parti utilizzano la stessa frequenza di campionamento. L'applicazione deve mettere in buffer i dati con una sincronizzazione del buffer adeguata.
Una conseguenza di orologi audio potenzialmente indipendenti è la necessità di una conversione asincrona della frequenza di campionamento. 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. Sono possibili conversioni più sofisticate.
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
: viene data la priorità alla latenza. Nessun effetto hardware o software. Questa è la modalità predefinita.SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS
: viene data la priorità alla latenza consentendo comunque gli 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 usufruire degli effetti audio integrati del dispositivo (ad esempio per migliorare la qualità acustica della riproduzione video), devi impostare esplicitamente la modalità di prestazioni su SL_ANDROID_PERFORMANCE_NONE
.
Per impostare la modalità di rendimento, 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 in Android viene eseguita a livello di processo. Il codice del linguaggio di programmazione Java non può fare di più del codice nativo, né il codice nativo può fare di più del codice del linguaggio di programmazione Java. Le uniche differenze tra loro 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 l'audio, ha bisogno dell'autorizzazioneandroid.permission.RECORD_AUDIO
. Le applicazioni che utilizzano effetti audio richiedonoandroid.permission.MODIFY_AUDIO_SETTINGS
. Le applicazioni che riproducono risorse URI di rete necessitano di android.permission.NETWORK
. Per ulteriori informazioni, consulta Utilizzo delle autorizzazioni di sistema.
A seconda della versione e dell'implementazione della piattaforma, gli analizzatori dei 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 con formato non valido progettati per sfruttare le vulnerabilità di parser e codec sono un vettore di attacco noto. Ti consigliamo di riprodurre contenuti multimediali solo da fonti affidabili o di eseguire il partizionamento dell'applicazione in modo che il codice che gestisce i contenuti multimediali di fonti inaffidabili venga eseguito in un ambiente relativamente sandbox. Ad esempio, potresti elaborare i contenuti multimediali provenienti da fonti non attendibili in un processo separato. Sebbene entrambi i processi continueranno a essere eseguiti con lo stesso UID, questa separazione rende più difficile un attacco.