Відтворення довільного тону з Android


92

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

Я озирнувся навколо, і ToneGenerator було єдиним, що мені вдалося виявити, що було навіть близько, але, схоже, він здатний видавати лише стандартні тони DTMF.

Будь-які ідеї?


2
Ви знайшли якесь реальне рішення?
o0 '.

20
Ні, але в підсумку я не виконав проект.
Джеремі Логан

1
@JeremyLogan І ви отримали позитивні негативні відгуки. Лол.
TheRealChx101

Відповіді:


109

Спочатку я знайшов цей приклад коду в блозі, але в ньому були деякі помилки, які генерували якісь жахливі звуки. Я виправив помилки і розмістив отриманий код тут. Здається, це добре працює для мене!

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

2
Чи правильний цей рядок? audioTrack.write (generatedSnd, 0, numSamples); або це має бути numSamples * 2, оскільки на зразок припадає 2 байти. Також метод write також бере масив шортів, то яка перевага створення проміжного масиву байтів?
Даміан Кеннеді

2
Це справді чудовий приклад, велике спасибі. Однак я знайшов ще одну неприємну помилку (якщо ви розширюєте код), а саме: audioTrack.write (generatedSnd, 0, numSamples) повинен бути audioTrack.write (generatedSnd, 0, 2 * numSamples) або краще audioTrack.write (generatedSnd, 0 , generatedSnd.length);
AudioDroid

6
Замість того, щоб використовувати "numSamples" у конструкторі AudioTrack, ви повинні використовувати generatedSnd.length, оскільки п'ятим параметром є "розмір буфера в байтах". Приклад відтворює лише першу половину тону.
Торбен

5
@ Black27 Зразки створюються з плаваючою комою з діапазоном амплітуди від 0.0до 1.0. Помножте на, 32767щоб перетворити його в 16-розрядний діапазон фіксованих точок. AudioTrack очікує , що буфер буде трохи молодший форматом. Отже, наступні два рядки просто перетворюють порядок байт з великого ендіана в маленький ендіан.
айнс

2
використовуючи приватний статичний фінал int sampleRate = 192000; Я зміг зіграти ультразвуковий
user3505444

26

Вдосконалення наведеного вище коду:

Додайте збільшення амплітуди вгору і вниз, щоб уникнути клацань.

Додайте код, щоб визначити, коли закінчується відтворення.

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.

1
Основною зміною було збільшення амплітуди вгору і вниз. Вихідний код починався і закінчувався з максимальною амплітудою. Це викликає клацання на початку і в кінці звуку. Цей код збільшує амплітуду з 0 до повної амплітуди протягом перших 20% зразків. Потім він зменшується з повної амплітуди до нуля протягом останніх 20% зразків. Тони більш плавні та набагато приємніші. Інша зміна полягала в тому, щоб стежити за відтворенням тону і не продовжувати, поки тон не закінчиться.
Xarph

Я cudnt змушую це запустити .. Я можу запустити перший .. але не можу насправді зрозуміти, як змінити його до того, що ви зробили .. це було б дуже корисно, оскільки я прагну позбутися звуку клацання. .
Coder

3
+1, але код у цій відповіді не наближається до компіляції. Я правильно його реалізував тут: gist.github.com/SuspendedPhan/7596139 Просто замініть метод genTone () Стіва на мій, і ви отримаєте ефект нарощування.
Ділан П,

Оскільки на MODE_STATIC спостерігається витік пам'яті, я змінив код, використовуючи MODE_STREAM нижче
екстремально

Починаючи з API, можна здійснити скачок за допомогою setVolume (). Це дозволяє просто прокрутити дуже малий зразок і навіть відтворити звук на динамічну довжину (наприклад, поки користувач тримає заголовок). Приклад коду: github.com/stefanhaustein/android-tone-generator/blob/master/…
Stefan

8

Я обернув вищезазначені чудові рішення в акуратний маленький пакет, який є більш корисним нестандартно як простий настроюваний звуковий сигнал. Він запускає його у фоновому потоці та має методи зупинки та відтворення та кілька варіантів, які ви можете встановити.

Це на 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);

Розширений допис у блозі тут про це тут GitHub тут


@Melchester це зараз виправлено. Дякую за голови і вибачте за це
meese

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


3

Модифікований код на основі відповіді Сінгхакса

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

2
    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, щоб отримати звук зміни, наприклад, ви можете додати випадкову величину, щоб отримати звук


Зрештою, ви перетворюєте це на коротке. Немає жодної причини робити кут як поплавок. подвійна математика має однакову швидкість і не вимагає купу кастингу.
Татарізувати

2

До мажор (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();
  }

}

2

подивіться цю корисну бібліотеку

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.