2010-07-22 7 views
15

Ok, tengo un generador de frecuencia que usa AudioTrack para enviar datos de PCM al hardware. Aquí está el código que estoy usando para ello:Problemas de almacenamiento en el búfer de AudioTrack de Android

private class playSoundTask extends AsyncTask<Void, Void, Void> { 
    float frequency; 
    float increment; 
    float angle = 0; 
    short samples[] = new short[1024]; 

    @Override 
    protected void onPreExecute() { 
    int minSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);   
    track = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, 
    AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, 
    minSize, AudioTrack.MODE_STREAM); 
    track.play(); 
    } 

    @Override 
    protected Void doInBackground(Void... params) { 
    while(Main.this.isPlaying) 
    { 
    for(int i = 0; i < samples.length; i++) 
    { 
    frequency = (float)Main.this.slider.getProgress(); 
    increment = (float)(2*Math.PI) * frequency/44100; 
    samples[i] = (short)((float)Math.sin(angle)*Short.MAX_VALUE); 
    angle += increment; 
    } 

    track.write(samples, 0, samples.length); 
    } 
    return null; 
    } 
} 

La frecuencia está ligada a una barra deslizante, y se notifica el valor correcto en el circuito de generación de muestra. Todo está bien y elegante cuando inicio la aplicación. Cuando arrastras el dedo por la barra deslizante obtienes un buen sonido de barrido. Pero después de unos 10 segundos de jugar con él, el audio comienza a ponerse nervioso. En lugar de un barrido suave, está escalonado, y solo cambia el tono alrededor de cada 1000 Hz más o menos. ¿Alguna idea de qué puede estar causando esto?

Aquí es todo el código en caso de que el problema radica en otro lugar:

public class Main extends Activity implements OnClickListener, OnSeekBarChangeListener { 
AudioTrack track; 
SeekBar slider; 
ImageButton playButton; 
TextView display; 

boolean isPlaying=false; 

/** Called when the activity is first created. */ 
@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 

    display = (TextView) findViewById(R.id.display); 
    display.setText("5000 Hz"); 

    slider = (SeekBar) findViewById(R.id.slider); 
    slider.setMax(20000); 
    slider.setProgress(5000); 
    slider.setOnSeekBarChangeListener(this); 


    playButton = (ImageButton) findViewById(R.id.play); 
    playButton.setOnClickListener(this); 

} 

private class playSoundTask extends AsyncTask<Void, Void, Void> { 
    float frequency; 
    float increment; 
    float angle = 0; 
    short samples[] = new short[1024]; 

    @Override 
    protected void onPreExecute() { 
    int minSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);   
    track = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, 
    AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, 
    minSize, AudioTrack.MODE_STREAM); 
    track.play(); 
    } 

    @Override 
    protected Void doInBackground(Void... params) { 
    while(Main.this.isPlaying) 
    { 
    for(int i = 0; i < samples.length; i++) 
    { 
    frequency = (float)Main.this.slider.getProgress(); 
    increment = (float)(2*Math.PI) * frequency/44100; 
    samples[i] = (short)((float)Math.sin(angle)*Short.MAX_VALUE); 
    angle += increment; 
    } 

    track.write(samples, 0, samples.length); 
    } 
    return null; 
    } 
} 



@Override 
public void onProgressChanged(SeekBar seekBar, int progress, 
    boolean fromUser) { 
    display.setText(""+progress+" Hz"); 
} 

public void onClick(View v) { 
    if (isPlaying) { 
    stop(); 
    } else { 
    start(); 
    } 
} 

public void stop() { 
    isPlaying=false; 
    playButton.setImageResource(R.drawable.play); 
} 

public void start() { 
    isPlaying=true; 
    playButton.setImageResource(R.drawable.stop); 
    new playSoundTask().execute(); 
} 

@Override 
protected void onResume() { 
    super.onResume(); 

} 

@Override 
protected void onStop() { 
    super.onStop(); 
    //Store state 
    stop(); 

} 


@Override 
public void onStartTrackingTouch(SeekBar seekBar) { 
    // TODO Auto-generated method stub 

} 


@Override 
public void onStopTrackingTouch(SeekBar seekBar) { 
    // TODO Auto-generated method stub 

} 
} 
+0

no respondiendo directamente a su pregunta, pero como nota al margen, tenga en cuenta que existe un problema conocido con la barra de progreso y la transmisión: http://code.google.com/p/android/issues/detail?id=4124 Aunque su problema no está en la barra de progreso sino en la transmisión de audio. Por lo tanto, no es una solución a su problema, pero dado que trabaja con transmisión de audio, debe tener en cuenta que hay uno o más errores abiertos. –

+0

Tengo exactamente el mismo problema. Cuanto más grande es el buffer, más tarde comienza el salto. En el registro veo muchos GC muy largos (GC limpió 180 objetos en 4200ms). No estoy haciendo ninguna asignación. En el rastreador de asignación DDMS parece que el AudioTrack mismo está haciendo asignaciones. No estoy seguro si los GC tienen que ver con el salto. ¿Has resuelto esto? –

+0

¿alguna vez limpias tu "audiotrack"? puede ser un problema relacionado con la memoria –

Respuesta

11

he encontrado la causa!

El problema es el tamaño del búfer de AudioTrack. Si no puede generar muestras lo suficientemente rápido, las muestras se agotan, la reproducción se detiene y continúa cuando hay suficientes muestras nuevamente.

La única solución es asegurarse de que puede generar muestras lo suficientemente rápido (44100/s). Como solución rápida, intente reducir la frecuencia de muestreo a 22000 (o aumentar el tamaño del búfer).

Al menos así es como se comporta conmigo: cuando optimicé mi rutina de generación de muestras, el salto desapareció.

(Aumentar el tamaño del búfer hace que el sonido comience a reproducirse más tarde, ya que se espera cierta reserva hasta que se llene el búfer. Pero aún así, si no genera las muestras lo suficientemente rápido, las muestras se agotan)


¡Oh, y debe poner variables invariables fuera del ciclo! Y tal vez haga que las muestras sean más grandes, de modo que el ciclo se ejecute más tiempo.

short samples[] = new short[4*1024]; 
... 

frequency = (float)Main.this.slider.getProgress(); 
increment = (float)(2 * Math.PI) * frequency/44100; 
for(int i = 0; i < samples.length; i++) 
{ 
    samples[i] = (short)((float)Math.sin(angle) * Short.MAX_VALUE); 
    angle += increment; 
} 

También se puede calcular previamente los valores de todos los senos de las frecuencias de 200Hz-8000Hz, con un paso de 10 Hz. O haga esto bajo demanda: cuando el usuario seleccione una frecuencia, genere muestras utilizando su método por un tiempo y también guárdelas en una matriz. Cuando generas suficientes muestras para tener una onda sinusoidal completa, puedes seguir dando vueltas sobre la matriz (ya que sin es una función periódica). De hecho, puede haber algunas pequeñas inconsistencias en el sonido, ya que nunca toca un punto exactamente (porque está aumentando el ángulo en 1 y la longitud de una onda sinusoidal completa es de sampleRate/(double)frequency muestras). Pero tomar un múltiplo correcto del período hace que las inconsistencias no sean perceptibles.

También, ver http://developer.android.com/guide/practices/design/performance.html

4

En caso de que alguien se tropieza con esto con el mismo problema (como yo), la solución en realidad no tiene nada que ver con tampones, que tiene que ver con la función seno. Intente reemplazar angle += increment con angle += (increment % (2.0f * (float) Math.PI));. Además, para mayor eficiencia, intente usar FloatMath.sin() en lugar de Math.sin().

1

Creo que he podido resolver el problema.

... 
samples[i] = (short)((float)Math.sin(angle)*Short.MAX_VALUE); 
angle += increment; 
angle = angle % (2.0f * (float) Math.PI); //added statement 
... 

Como se dijo antes, no se trata de almacenamientos intermedios. Se trata de la variable de ángulo, aumenta continuamente. Después de un tiempo, la variable se vuelve demasiado grande y no admite pequeños pasos.

Como el seno se repite después de 2 * PI, necesitamos tomar el módulo de ángulo.

Espero que esto ayude.

editado: ángulo + = incremento es suficiente para el trabajo.

Cuestiones relacionadas