Reproducir un tono arbitrario con Android
¿Hay alguna forma de hacer que Android emita un sonido de frecuencia arbitraria (es decir, no quiero tener archivos de sonido pregrabados)?
Miré a mi alrededor y ToneGenerator fue lo único que pude encontrar que estaba siquiera cerca, pero parece que solo es capaz de emitir los tonos DTMF estándar.
¿Algunas ideas?
Originalmente encontré este código de ejemplo en un blog, pero tenía algunos errores que generaban algunos sonidos horrendos. Solucioné los errores y publiqué el código resultante aquí. ¡Parece que funciona bien para mí!
public class PlaySound extends Activity {
// originally from http://marblemice.blogspot.com/2010/04/generate-and-play-tone-in-android.html
// and modified by Steve Pomeroy <[email protected]>
private final int duration = 3; // seconds
private final int sampleRate = 8000;
private final int numSamples = duration * sampleRate;
private final double sample[] = new double[numSamples];
private final double freqOfTone = 440; // hz
private final byte generatedSnd[] = new byte[2 * numSamples];
Handler handler = new Handler();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onResume() {
super.onResume();
// Use a new tread as this can take a while
final Thread thread = new Thread(new Runnable() {
public void run() {
genTone();
handler.post(new Runnable() {
public void run() {
playSound();
}
});
}
});
thread.start();
}
void genTone(){
// fill out the array
for (int i = 0; i < numSamples; ++i) {
sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone));
}
// convert to 16 bit pcm sound array
// assumes the sample buffer is normalised.
int idx = 0;
for (final double dVal : sample) {
// scale to maximum amplitude
final short val = (short) ((dVal * 32767));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
}
void playSound(){
final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate, AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length,
AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length);
audioTrack.play();
}
}
Mejorando el código anterior:
Agregue una rampa de amplitud hacia arriba y hacia abajo para evitar los clics.
Agregue código para determinar cuándo terminó de jugar la tachuela.
double duration = 1; // seconds
double freqOfTone = 1000; // hz
int sampleRate = 8000; // a number
double dnumSamples = duration * sampleRate;
dnumSamples = Math.ceil(dnumSamples);
int numSamples = (int) dnumSamples;
double sample[] = new double[numSamples];
byte generatedSnd[] = new byte[2 * numSamples];
for (int i = 0; i < numSamples; ++i) { // Fill the sample array
sample[i] = Math.sin(freqOfTone * 2 * Math.PI * i / (sampleRate));
}
// convert to 16 bit pcm sound array
// assumes the sample buffer is normalized.
// convert to 16 bit pcm sound array
// assumes the sample buffer is normalised.
int idx = 0;
int i = 0 ;
int ramp = numSamples / 20 ; // Amplitude ramp as a percent of sample count
for (i = 0; i< ramp; ++i) { // Ramp amplitude up (to avoid clicks)
double dVal = sample[i];
// Ramp up to maximum
final short val = (short) ((dVal * 32767 * i/ramp));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
for (i = i; i< numSamples - ramp; ++i) { // Max amplitude for most of the samples
double dVal = sample[i];
// scale to maximum amplitude
final short val = (short) ((dVal * 32767));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
for (i = i; i< numSamples; ++i) { // Ramp amplitude down
double dVal = sample[i];
// Ramp down to zero
final short val = (short) ((dVal * 32767 * (numSamples-i)/ramp ));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
AudioTrack audioTrack = null; // Get audio track
try {
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, (int)numSamples*2,
AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length); // Load the track
audioTrack.play(); // Play the track
}
catch (Exception e){
RunTimeError("Error: " + e);
return false;
}
int x =0;
do{ // Monitor playback to find when done
if (audioTrack != null)
x = audioTrack.getPlaybackHeadPosition();
else
x = numSamples;
} while (x<numSamples);
if (audioTrack != null) audioTrack.release(); // Track play done. Release track.
Envolví las maravillosas soluciones anteriores en un paquete pequeño y elegante que es más utilizable desde el primer momento como un simple timbre configurable. Lo ejecuta en un hilo en segundo plano y tiene métodos de detener y reproducir y un puñado de opciones que puede configurar.
Está en JCenter así que puedes agregarlo a tu lista de dependencias de esta manera
compile 'net.mabboud:android-tone-player:0.2'
y lo usas así para un timbre continuo
ContinuousBuzzer tonePlayer = new ContinuousBuzzer();
tonePlayer.play();
// just an example don't actually use Thread.sleep in your app
Thread.sleep(1000);
tonePlayer.stop();
o un timbre sonado solo una vez y puedes configurar la frecuencia y el volumen de esta manera
OneTimeBuzzer buzzer = new OneTimeBuzzer();
buzzer.setDuration(5);
// volume values are from 0-100
buzzer.setVolume(50);
buzzer.setToneFreqInHz(110);
Publicación de blog extendida aquí sobre esto aquí GitHub aquí