Воспроизведение произвольного тона с Android

Есть ли способ заставить Android испускать звук произвольной частоты (что означает, что я не хочу иметь предварительно записанные звуковые файлы)?

Я огляделся, и ToneGenerator было единственное, что мне удалось найти, что было даже близко, но похоже быть способным выводить стандартные тональные сигналы DTMF.

Любые идеи?

Ответ 1

Я изначально нашел этот примерный код в блоге, но в нем были некоторые ошибки, которые породили ужасные звуки. Я исправил ошибки и разместил здесь полученный код. Кажется, хорошо работает для меня!

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

Ответ 2

Улучшение приведенного выше кода:

Добавьте амплитуду и уменьшите амплитуду, чтобы избежать кликов.

Добавить код, чтобы определить, когда игра завершилась.

        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{                                                     // Montior 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.

Ответ 4

Так как в некоторых старых версиях 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.
}

Ответ 5

Измененный код на основе ответа Singhaks

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

Ответ 6

Я завернул вышеупомянутые замечательные решения в аккуратный маленький пакет, который можно использовать из коробки как простой настраиваемый зуммер. Он запускает его в фоновом потоке и имеет методы остановки и воспроизведения и несколько параметров, которые вы можете установить.

Это на 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);

Расширенная запись в блоге здесь об этом

Ответ 7

    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 для получения звука изменения, например, вы можете добавить случайную переменную, чтобы получить звук

Ответ 8

Сделайте майор (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();
  }

}

Ответ 9

см. эту полезную библиотеку

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