Android Audio – Streaming sinus tone générateur de comportement étrange

première affiche ici. J’aime généralement trouver la réponse moi-même (que ce soit par la recherche ou par essais-erreurs), mais je suis perplexe ici.

Ce que j’essaie de faire: Je construis un synthétiseur audio Android simple. Pour le moment, je joue simplement une tonalité sinusoïdale en temps réel, avec un curseur dans l’interface utilisateur qui modifie la fréquence de la tonalité à mesure que l’utilisateur la règle.

Comment je l’ai construit: En gros, j’ai deux threads – un thread de travail et un thread de sortie. Le thread de travail remplit simplement un tampon avec les données d’onde sinusoïdale chaque fois que sa méthode tick () est appelée. Une fois le tampon rempli, il avertit le thread de sortie que les données sont prêtes à être écrites sur la piste audio. J’utilise deux threads parce qu’audiotrack.write () se bloque et que je veux que le thread de travail puisse commencer à traiter ses données dès que possible (plutôt que d’attendre la fin de l’écriture de la piste audio). Le curseur de l’interface utilisateur modifie simplement une variable du thread de travail, de sorte que toute modification de la fréquence (via le curseur) sera lue par la méthode tick () du thread de travail.

Ce qui fonctionne: presque tout Les threads communiquent bien, il ne semble pas y avoir de lacunes ni de clics dans la lecture. Malgré la grande taille de la mémoire tampon (merci à Android), la réactivité est satisfaisante. La variable de fréquence change, de même que les valeurs intermédiaires utilisées lors des calculs de tampon dans la méthode tick () (vérifiée par Log.i ()).

Ce qui ne fonctionne pas: Pour une raison quelconque, je n’arrive pas à obtenir un changement continu de la fréquence audible. Lorsque j’ajuste le curseur, la fréquence change par paliers, souvent jusqu’à la quasortingème ou cinquième place. Théoriquement, j’entends des changements à la minute près, mais ce n’est pas le cas. Curieusement, il semble que des modifications apscopes au curseur entraînent la lecture de la sinusoïde à travers des intervalles de la série harmonique; Cependant, je peux vérifier que la variable de fréquence N’accroche PAS à des multiples intégraux de la fréquence par défaut.

Ma piste audio est configurée comme telle:

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

Le tampon du thread de travail est en cours de remplissage (via tick ()) en tant que tel:

 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; } 

Les données audio sont écrites comme ceci:

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

Toute aide serait grandement appréciée. Comment puis-je obtenir des changements plus graduels de fréquence? Je suis assez confiant que ma logique dans tick () est correcte, car Log.i () vérifie que les variables angleIncrement et currentAngle sont correctement mises à jour.

Je vous remercie!

Mettre à jour:

J’ai trouvé un problème similaire ici: Problèmes de mise en mémoire tampon dans Audio AudioTrack La solution proposée consiste à être en mesure de produire des échantillons assez rapidement pour l’audio, ce qui est logique. J’ai abaissé ma fréquence d’échantillonnage à 22050Hz et effectué des tests empiriques – je peux remplir mon tampon (via tick ()) en 6 ms environ dans le pire des cas. C’est plus que suffisant. À 22050Hz, audioTrack me donne une taille de tampon de 2048 échantillons (ou 4096 octets). Ainsi, chaque tampon rempli dure environ 0,0928 seconde d’audio, ce qui est beaucoup plus long que la création des données (1 à 6 ms). SO, je sais que je n’ai aucun problème à produire des échantillons assez rapidement.

Je dois également noter que cela fonctionne très bien pendant les trois premières secondes environ du cycle de vie des applications: un balayage en douceur du curseur produit un balayage en douceur de la sortie audio. Après cela, il commence à être vraiment saccadé (le son ne change que tous les 100 MHz environ), puis cesse de répondre à l’entrée du curseur.

J’ai aussi corrigé un bug, mais je ne pense pas que cela ait un effet. AudioTrack.getMinBufferSize () renvoie la plus petite taille de tampon autorisée dans BYTES et j’utilisais ce nombre comme longueur du tampon dans tick (). J’utilise maintenant la moitié de ce nombre (2 octets par échantillon).

J’ai trouvé ça!

Il s’avère que le problème n’a rien à voir avec les tampons ou les threads.

Cela semble bien dans les premières secondes, car l’angle de calcul est relativement petit. Au fur et à mesure que le programme s’exécute et que l’angle augmente, Math.sin (_currentAngle) commence à générer des valeurs non fiables.

Donc, j’ai remplacé Math.sin() par FloatMath.sin() .

J’ai également remplacé _currentAngle = _currentAngle + _angleIncrement;

avec

_currentAngle = ((_currentAngle + _angleIncrement) % (2.0f * (float) Math.PI)); donc l’angle est toujours <2 * PI.

Fonctionne comme un charme! Merci beaucoup pour votre aide, droïde prétorien!