2012-04-15 21 views
8

primer póster aquí. Por lo general, me gusta encontrar la respuesta yo mismo (ya sea a través de investigación o ensayo y error), pero estoy perplejo aquí.Android Audio - Streaming generador de tono sinusoidal comportamiento impar

Lo que estoy tratando de hacer: Estoy construyendo un sintetizador de audio android simple. En este momento, estoy reproduciendo un tono sinusoidal en tiempo real, con un control deslizante en la interfaz de usuario que cambia la frecuencia del tono a medida que el usuario lo ajusta.

Cómo he construido es: Básicamente, tengo dos hilos - un subproceso de trabajo y una rosca de salida. El hilo de trabajo simplemente llena un búfer con los datos de onda sinusoidal cada vez que se llama a su método tick(). Una vez que se llena el búfer, alerta al hilo de salida que los datos están listos para escribirse en la pista de audio. La razón por la que estoy usando dos subprocesos es porque audiotrack.write() bloquea y quiero que el subproceso de trabajo pueda comenzar a procesar sus datos lo antes posible (en lugar de esperar a que la pista de audio termine de escribir). El control deslizante en la interfaz de usuario simplemente cambia una variable en el hilo de trabajo, de modo que cualquier cambio en la frecuencia (a través del control deslizante) será leído por el método tick() del hilo del trabajador.

Lo que funciona: Casi todo; Los hilos se comunican bien, no parece haber espacios o clics en la reproducción. A pesar del gran tamaño del búfer (gracias a Android), la capacidad de respuesta está bien. La variable de frecuencia cambia, al igual que los valores intermedios utilizados durante los cálculos del búfer en el método tick() (verificado por Log.i()).

lo que no funciona: Por alguna razón, me parece que no puede conseguir un cambio continuo en la frecuencia audible. Cuando ajusto el control deslizante, la frecuencia cambia en pasos, a menudo tan amplios como cuartos o quintos. Teóricamente, debería escuchar cambios tan pequeños como 1Hz, pero no lo estoy. Por extraño que parezca, parece como si los cambios en el control deslizante están causando que la onda sinusoidal se reproduzca a través de intervalos en la serie de armónicos; Sin embargo, puedo verificar que la variable de frecuencia NO está ajustando a múltiplos enteros de la frecuencia predeterminada.

Mi pista de audio está configurado como tal: tampón

_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 subproceso de trabajo se está poblada (a través de la garrapata()) como tal:

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

Los datos de audio está siendo escrita como esto:

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

Cualquier ayuda sería muy apreciada. ¿Cómo puedo obtener cambios más graduales en la frecuencia? Estoy bastante seguro de que mi lógica en tick() es sólida, ya que Log.i() verifica que las variables angleIncrement y currentAngle se estén actualizando correctamente.

¡Gracias!

Actualización:

me encontré con un problema similar aquí: Android AudioTrack buffering problems La solución propone que uno debe ser capaz de producir muestras lo suficientemente rápido para la pista de audio, lo cual tiene sentido. Bajé mi frecuencia de muestreo a 22050 Hz y realicé algunas pruebas empíricas: puedo llenar mi memoria intermedia (mediante tick()) en aproximadamente 6 ms en el peor de los casos. Esto es más que adecuado. A 22050Hz, el audioTrack me da un tamaño de buffer de 2048 muestras (o 4096 Bytes). Por lo tanto, cada memoria intermedia llena dura ~ 0.0928 segundos de audio, que es mucho más largo de lo que se necesita para crear los datos (1 ~ 6 ms). ASÍ, sé que no tengo ningún problema para producir muestras lo suficientemente rápido.

También debo tener en cuenta que durante los primeros 3 segundos del ciclo de vida de las aplicaciones, funciona bien: un barrido suave del deslizador produce un barrido suave en la salida de audio. Después de esto, comienza a ponerse muy picado (el sonido solo cambia cada 100Mhz), y después de eso, deja de responder a la entrada del control deslizante.

También solucioné un error, pero no creo que tenga ningún efecto. AudioTrack.getMinBufferSize() devuelve el tamaño de búfer permitido más pequeño en BYTES, y estaba usando este número como la longitud del búfer en tick(): ahora uso la mitad de este número (2 bytes por muestra).

+0

¿Qué evento usas para '_freq' changing? ¿Realmente cambia en 1Hz cada vez? Lo siento, no entiendo claramente tu sección ** Lo que no funciona **. ¿Podría proporcionar una muestra de código de trabajo completo o un fragmento de sonido sintetizado para que sea posible ** escuchar ** su problema. Para el fragmento de sonido: el formato de datos de sonido en bruto está bien; simplemente escriba su búfer en el archivo, no en la tarjeta de audio. Por ejemplo, 16 bit/signed/mono/44100 Hz. –

+0

Para cambiar _freq, estoy usando un OnSeekBarChangeListener que escribe un nuevo valor int en _freq vía onProgressChanged(). Sin embargo, también he intentado usar botones (arriba/abajo) que incrementan la frecuencia en 1 Hz, y solo hay una diferencia audible en el tono después de muchos, muchos incrementos (de modo que el sonido salta en tono aproximadamente un 3 o 4 , en lugar de 1Hz). Aquí hay un ejemplo de lo que está sucediendo; Esto es lo que escucho cuando deslizo lentamente el control deslizante hacia arriba y hacia abajo, incrementando la frecuencia en incrementos de 1Hz. (desde ~ 200 - ~ 1000Hz): http://www.sendspace.com/file/tpxuwr – Tsherr

+0

Encontré un problema similar en línea, pero su solución propuesta no ayudó - vea la actualización anterior. – Tsherr

Respuesta

4

¡Lo he encontrado!

Resulta que el problema no tiene nada que ver con los búferes o el enhebrado.

Suena bien en los primeros segundos, porque el ángulo del cálculo es relativamente pequeño. A medida que el programa se ejecuta y el ángulo crece, Math.sin (_currentAngle) comienza a producir valores no confiables.

Por lo tanto, reemplacé Math.sin() con FloatMath.sin().

I también sustituye _currentAngle = _currentAngle + _angleIncrement;

con

_currentAngle = ((_currentAngle + _angleIncrement) % (2.0f * (float) Math.PI));, por lo que el ángulo es siempre < 2 * pi.

¡Funciona como un encanto! Muchas gracias por su ayuda, ¡robot pretoriano!

+0

¡Maldición, me olvidé de la normalización de 2 * Pi! Lo he visto en mi propio código anterior, pero he perdido su ausencia en el tuyo. Pero pensé que el desbordamiento de la variable de ángulo debería dar efecto de los clics y su muestra sonaba diferente. De todos modos, me alegra que hayas resuelto tu problema. –