2012-04-15 13 views
8

poster prima volta qui. Di solito mi piace trovare la risposta da solo (attraverso ricerche o tentativi ed errori), ma sono qui perplesso.Android Audio - Streaming generatore di sine-tone comportamento strano

Quello che sto cercando di fare: Sto costruendo un semplice sintetizzatore audio Android. In questo momento, sto solo suonando un sinusoidale in tempo reale, con un cursore nell'interfaccia utente che cambia la frequenza del tono mentre l'utente lo regola.

Come l'ho creato: Fondamentalmente, ho due thread: un thread di lavoro e un thread di output. Il thread worker semplicemente riempie un buffer con i dati dell'onda sinusoidale ogni volta che viene chiamato il suo metodo tick(). Una volta riempito il buffer, avvisa il thread di output che i dati sono pronti per essere scritti sulla traccia audio. La ragione per cui sto usando due thread è perché i blocchi audiotrack.write() e voglio che il thread worker sia in grado di iniziare ad elaborare i suoi dati il ​​prima possibile (piuttosto che aspettare che la traccia audio finisca di scrivere). Il cursore sull'interfaccia utente modifica semplicemente una variabile nel thread di lavoro, in modo che eventuali modifiche alla frequenza (tramite il cursore) vengano lette dal metodo tick() del thread di lavoro.

Cosa funziona: Quasi tutto; I thread comunicano bene, non sembrano esserci lacune o clic nella riproduzione. Nonostante la grande dimensione del buffer (grazie android), la reattività è OK. La variabile di frequenza cambia, così come i valori intermedi utilizzati durante i calcoli del buffer nel metodo tick() (verificato da Log.i()).

Che cosa non funziona: Per qualche motivo, non riesco a ottenere un cambiamento continuo nella frequenza udibile. Quando aggiusto il cursore, la frequenza cambia a passi, spesso ampi come quarti o quinti. In teoria, dovrei sentire i cambiamenti al minuto come 1Hz, ma non lo sono. Stranamente, sembra che le modifiche al cursore stiano facendo oscillare l'onda sinusoidale attraverso gli intervalli della serie armonica; Tuttavia, posso verificare che la variabile di frequenza NON stia scattando ai multipli interi della frequenza predefinita.

mia traccia audio è configurato come tale: tampone

_buffSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT); 
_audioTrackOut = new AudioTrack(AudioManager.STREAM_MUSIC, _sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, _buffSize, AudioTrack.MODE_STREAM); 

del thread di lavoro viene popolata (via tick()) in quanto tale:

public short[] tick() 
{ 
    short[] outBuff = new short[_outBuffSize/2]; // (buffer size in Bytes)/2 
    for (int i = 0; i < _outBuffSize/2; i++) 
    { 
     outBuff[i] = (short) (Short.MAX_VALUE * ((float) Math.sin(_currentAngle))); 

     //Update angleIncrement, as the frequency may have changed by now 
     _angleIncrement = (float) (2.0f * Math.PI) * _freq/_sampleRate; 
     _currentAngle = _currentAngle + _angleIncrement;  
    } 
    return outBuff;  
} 

I dati audio vengono scritti come this:

_audioTrackOut.write(fromWorker, 0, fromWorker.length); 

Qualsiasi aiuto sarebbe molto apprezzato. Come posso ottenere più graduali cambiamenti di frequenza? Sono abbastanza fiducioso che la mia logica in tick() sia corretta, poiché Log.i() verifica che le variabili angleIncrement e currentAngle vengano aggiornate correttamente.

Grazie!

Aggiornamento:

Ho trovato un problema simile qui: Android AudioTrack buffering problems La soluzione proposta che uno deve essere in grado di produrre campioni abbastanza veloce per la traccia audio, che ha un senso. Ho abbassato la mia frequenza di campionamento a 22050 Hz e ho eseguito alcuni test empirici: posso riempire il mio buffer (tramite tick()) in circa 6ms nel peggiore dei casi. Questo è più che adeguato. A 22050Hz, l'audioTrack mi dà una dimensione del buffer di 2048 campioni (o 4096 byte). Quindi, ogni buffer riempito dura ~ 0.0928 secondi di audio, che è molto più lungo di quanto necessario per creare i dati (1 ~ 6 ms). SO, so che non ho problemi a produrre campioni abbastanza velocemente.

Devo anche notare che per circa i primi 3 secondi del ciclo di vita delle applicazioni, funziona bene - una scansione uniforme del cursore produce una scansione uniforme nell'uscita audio. Dopo questo, inizia a diventare veramente instabile (il suono cambia solo ogni 100 Mhz), dopodiché smette di rispondere all'input del cursore.

Ho anche corretto un errore, ma non penso che abbia un effetto. AudioTrack.getMinBufferSize() restituisce la dimensione del buffer più piccola consentita in BYTES, e stavo usando questo numero come la lunghezza del buffer in tick() - Ora uso metà di questo numero (2 byte per campione).

+0

Che evento usi per cambiare '_freq'? Cambia davvero di 1Hz ogni volta? Spiacente, non capisco chiaramente la sezione ** Cosa non funziona **. Potresti fornire un esempio di codice completo funzionante o un frammento sonoro sintetizzato per rendere possibile ** sentire ** il tuo problema. Per il frammento del suono: il formato dei dati audio grezzi è ok - basta scrivere il buffer sul file, non sulla scheda audio. Ad esempio, 16 bit/firmato/mono/44100 Hz. –

+0

Per modificare _freq, sto utilizzando un OnSeekBarChangeListener che scrive un nuovo valore int in _freq tramite onProgressChanged(). Tuttavia, ho anche provato a usare i pulsanti (su/giù) che aumentano la frequenza di 1 Hz, e c'è solo una differenza udibile nell'intonazione dopo molti, molti incrementi (tale che il suono salta di circa 3 o 4 , piuttosto che 1Hz). Ecco un esempio di cosa sta succedendo; Questo è quello che sento quando faccio scorrere lentamente il cursore su e giù, incrementando la frequenza con incrementi di 1Hz. (da ~ 200 - ~ 1000Hz): http://www.sendspace.com/file/tpxuwr – Tsherr

+0

Ho trovato un problema simile online, ma la sua soluzione proposta non ha aiutato - vedi l'aggiornamento sopra. – Tsherr

risposta

4

L'ho trovato!

Si scopre che il problema non ha nulla a che fare con i buffer o il threading.

Suona bene nel primo paio di secondi, perché l'angolo del calcolo è relativamente piccolo. Mentre il programma gira e l'angolo cresce, Math.sin (_currentAngle) inizia a produrre valori non affidabili.

Quindi, ho sostituito Math.sin() con FloatMath.sin().

Ho anche sostituito _currentAngle = _currentAngle + _angleIncrement;

con

_currentAngle = ((_currentAngle + _angleIncrement) % (2.0f * (float) Math.PI));, per cui l'angolo è sempre < 2 * PI.

Funziona come un fascino! Grazie mille per il tuo aiuto, droide del pretorio!

+0

Dannazione, ho dimenticato la normalizzazione 2 * Pi! L'ho visto nel mio vecchio codice, ma ho perso la sua assenza nel tuo. Ma ho pensato che il trabocco della variabile dell'angolo avrebbe dovuto dare l'effetto dei clic e il suono del tuo campione sarebbe diverso. Ad ogni modo, sono felice che tu abbia risolto il tuo problema. –