Воспроизведение произвольного тона с Android
есть ли способ заставить Android излучать звук произвольной частоты (то есть, я не хочу иметь предварительно записанные звуковые файлы)?
Я огляделся и ToneGenerator было единственное, что я смог найти, что было даже близко, но, похоже, он способен выводить только стандартные тона DTMF.
какие идеи?
10 ответов:
Я изначально нашел этот пример кода в блоге, но в нем были некоторые ошибки, которые создавали некоторые ужасные звуки. Я исправил ошибки и разместил полученный код здесь. Кажется, хорошо работает для меня!
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 <steve@staticfree.info> 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(); } }
улучшение кода выше:
добавить амплитуду рампы вверх и вниз, чтобы избежать щелчков.
добавить код, чтобы определить, когда курс закончила играть.
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.
я завернул вышеуказанные замечательные решения в аккуратный маленький пакет, который более пригоден для использования из коробки в качестве простого настраиваемого зуммера. Он запускает его в фоновом потоке и имеет методы остановки и воспроизведения и несколько вариантов, которые вы можете установить.
Это на JCenter, так что вы можете добавить его в список зависимостей, как это
compile 'net.mabboud:android-tone-player:0.2'
и вы используете его вот так для непрерывного зуммера
ContinuousBuzzer tonePlayer = new ContinuousBuzzer(); tonePlayer.play(); // just an example don't actually use Thread.sleep in your app Thread.sleep(1000); tonePlayer.stop();
или звонка играет только один раз, и вы можете установить частоты и такой объем
OneTimeBuzzer buzzer = new OneTimeBuzzer(); buzzer.setDuration(5); // volume values are from 0-100 buzzer.setVolume(50); buzzer.setToneFreqInHz(110);
вот еще один блог, демонстрирующий простой синтезатор плюс некоторый пользовательский интерфейс
вы также можете быть заинтересованы в csound или pdlib (pure data lib) для android.
поскольку в некоторых старых версиях android есть ошибка, которая вызывает утечку памяти при использовании MODE_STATIC, я изменил ответ Xarph выше, чтобы использовать MODE_STREAM. Надеюсь, это поможет некоторым.
public void playTone(double freqOfTone, double duration) { //double duration = 1000; // 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 { int bufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM); audioTrack.play(); // Play the track audioTrack.write(generatedSnd, 0, generatedSnd.length); // Load the track } catch (Exception e){ } if (audioTrack != null) audioTrack.release(); // Track play done. Release track. }
модифицированный код, основанный на ответе Сингхака
public class MainActivity extends Activity { private final int duration = 30; // 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(); private AudioTrack audioTrack; private boolean play = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, numSamples, AudioTrack.MODE_STREAM); } @Override protected void onResume() { super.onResume(); // Use a new tread as this can take a while Thread thread = new Thread(new Runnable() { public void run() { handler.post(new Runnable() { public void run() { playSound(); genTone(); } }); } }); thread.start(); } void genTone(){ // fill out the array while(play){ for (int i = 0; i < numSamples; ++i) { // float angular_frequency = sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone)); } int idx = 0; // convert to 16 bit pcm sound array // assumes the sample buffer is normalised. for (double dVal : sample) { short val = (short) (dVal * 32767); generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } audioTrack.write(generatedSnd, 0, numSamples); } } void playSound(){ play = true; audioTrack.play(); } }
float synth_frequency = 440; int minSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minSize, AudioTrack.MODE_STREAM); audioTrack.play(); short[] buffer = new short[minSize]; float angle = 0; while (true) { if (play) { for (int i = 0; i < buffer.length; i++) { float angular_frequency = (float)(2*Math.PI) * synth_frequency / SAMPLE_RATE; buffer[i] = (short)(Short.MAX_VALUE * ((float) Math.sin(angle))); angle += angular_frequency; } audioTrack.write(buffer, 0, buffer.length); }
// вы можете добавить произвольное значение в synth_frequency, чтобы получить изменение звука например, вы можете добавить случайную величину, чтобы получить звук
Do major (16 нот)
public class MainActivity extends AppCompatActivity { private double mInterval = 0.125; private int mSampleRate = 8000; private byte[] generatedSnd; private final double mStandardFreq = 440; Handler handler = new Handler(); private AudioTrack audioTrack; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_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() { byte[] tempByte = new byte[0]; for (int i = 0; i < 16 ; i++ ){ double note = getNoteFrequencies(i); byte[] tonByteNote = getTone(mInterval, mSampleRate, note); tempByte = concat(tonByteNote, tempByte); } generatedSnd = tempByte; handler.post(new Runnable() { public void run() { playTrack(generatedSnd); } }); } }); thread.start(); } public byte[] concat(byte[] a, byte[] b) { int aLen = a.length; int bLen = b.length; byte[] c= new byte[aLen+bLen]; System.arraycopy(a, 0, c, 0, aLen); System.arraycopy(b, 0, c, aLen, bLen); return c; } private double getNoteFrequencies(int index){ return mStandardFreq * Math.pow(2, (double) index/12.0d); } private byte[] getTone(double duration, int rate, double frequencies){ int maxLength = (int)(duration * rate); byte generatedTone[] = new byte[2 * maxLength]; double[] sample = new double[maxLength]; int idx = 0; for (int x = 0; x < maxLength; x++){ sample[x] = sine(x, frequencies / rate); } for (final double dVal : sample) { final short val = (short) ((dVal * 32767)); // in 16 bit wav PCM, first byte is the low order byte generatedTone[idx++] = (byte) (val & 0x00ff); generatedTone[idx++] = (byte) ((val & 0xff00) >>> 8); } return generatedTone; } private AudioTrack getAudioTrack(int length){ if (audioTrack == null) audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, mSampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, length, AudioTrack.MODE_STATIC); return audioTrack; } private double sine(int x, double frequencies){ return Math.sin( 2*Math.PI * x * frequencies); } void playTrack(byte[] generatedSnd){ getAudioTrack(generatedSnd.length) .write(generatedSnd, 0, generatedSnd.length); audioTrack.play(); } }
смотрите эту полезную библиотеку
https://github.com/karlotoy/perfectTune
легко использовать
добавьте это в свои зависимости
compile 'com.github.karlotoy:perfectTune:1.0.2'
и вы используете его так:
PerfectTune perfectTune = new PerfectTune(); perfectTune.setTuneFreq(desire_freq); perfectTune.playTune();
чтобы остановить мелодию:
perfectTune.stopTune();
есть несколько программ для этого, но они сосут. Я измерил несколько:
http://www.endolith.com/wordpress/2009/11/24/android-audio-applications/
Так что не делайте то, что они делают. : D