Phát một giai điệu tùy ý với Android


92

Có cách nào để khiến Android phát ra âm thanh có tần số tùy ý (nghĩa là, tôi không muốn có các tệp âm thanh được ghi sẵn)?

Tôi đã nhìn xung quanh và ToneGenerator là thứ duy nhất tôi có thể tìm thấy nó thậm chí còn gần, nhưng nó dường như chỉ có khả năng xuất ra âm DTMF tiêu chuẩn.

Bất kỳ ý tưởng?


2
Bạn đã tìm thấy giải pháp thực sự nào chưa?
o0 '.

20
Không, nhưng tôi đã không thực hiện dự án.
Jeremy Logan

1
@JeremyLogan Và bạn đã nhận được phản hồi tiêu cực tích cực. cười lớn.
TheRealChx101

Câu trả lời:


109

Ban đầu tôi đã tìm thấy mã ví dụ này trên một blog, nhưng nó có một số lỗi trong đó tạo ra một số âm thanh khủng khiếp. Tôi đã sửa các lỗi và đăng mã kết quả ở đây. Có vẻ làm việc tốt cho tôi!

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
Dòng này có đúng không? audioTrack.write (createdSnd, 0, numSamples); hoặc phải là numSamples * 2 vì có 2 byte cho mỗi mẫu. Ngoài ra phương thức write cũng lấy mảng short vậy việc tạo mảng byte trung gian có lợi gì?
Damian Kennedy

2
Đây thực sự là một ví dụ tuyệt vời, cảm ơn rất nhiều. Tuy nhiên, tôi đã tìm thấy một lỗi khó chịu khác (nếu bạn mở rộng mã), đó là: audioTrack.write (createdSnd, 0, numSamples) phải là audioTrack.write (createdSnd, 0, 2 * numSamples) hoặc audioTrack.write (createdSnd, 0) tốt hơn , createSnd.length);
AudioDroid

6
Thay vì sử dụng "numSamples" trong phương thức khởi tạo AudioTrack, bạn nên sử dụng createdSnd.length vì Tham số thứ năm là "kích thước bộ đệm tính bằng byte". Ví dụ chỉ phát nửa đầu của giai điệu.
Torben,

5
@ Black27 Các mẫu được tạo dưới dạng dấu chấm động với biên độ từ 0.0đến 1.0. Nhân với 32767sẽ chuyển đổi nó thành phạm vi điểm cố định 16 bit. Các AudioTrack hy vọng bộ đệm để có ít endian format. Do đó, hai dòng tiếp theo chỉ chuyển đổi thứ tự byte từ endian lớn thành endian nhỏ.
AINS

2
sử dụng private static final int sampleRate = 192000; Tôi đã có thể chơi siêu âm
user3505444

26

Cải thiện đoạn mã trên:

Thêm biên độ tăng và dốc xuống để tránh nhấp chuột

Thêm mã để xác định thời điểm chơi xong.

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
Sự thay đổi chính là sự tăng lên và xuống của biên độ. Mã gốc bắt đầu và kết thúc với biên độ cực đại. Điều này tạo ra các nhấp chuột ở đầu và cuối của giai điệu. Mã này tăng biên độ lên từ 0 đến hết biên độ trong 20% ​​mẫu đầu tiên. Sau đó, nó giảm từ biên độ đầy đủ xuống 0 trong 20% ​​mẫu cuối cùng. Các tông màu mượt mà và dễ chịu hơn nhiều. Sự thay đổi khác là theo dõi việc phát âm báo và không tiếp tục cho đến khi giai điệu phát xong.
Xarph

Tôi không thể làm cho nó chạy được..Tôi có thể chạy cái đầu tiên..nhưng tôi không thể thực sự hiểu cách sửa đổi nó theo những gì bạn đã làm..nó thực sự hữu ích khi tôi đang tìm cách loại bỏ tiếng nhấp chuột. .
Coder

3
+1, nhưng mã trong câu trả lời này không gần được biên dịch. Tôi đã triển khai nó một cách chính xác ở đây: gist.github.com/SuspendedPhan/7596139 Chỉ cần thay thế phương thức genTone () của Steve bằng phương thức của tôi và bạn sẽ nhận được hiệu ứng tăng tốc.
Dylan P

Vì có sự cố rò rỉ bộ nhớ trên MODE_STATIC, tôi đã sửa đổi mã để sử dụng MODE_STREAM bên dưới
cực

Bắt đầu với API, bạn có thể thực hiện đoạn đường nối bằng setVolume (). Điều này cho phép chỉ lặp lại một mẫu rất nhỏ và thậm chí phát âm thanh trong một độ dài động (ví dụ: trong khi người dùng cầm một cái nút). Mã ví dụ: github.com/stefanhaustein/android-tone-generator/blob/master/…
Stefan Haustein

8

Tôi đã gói các giải pháp tuyệt vời ở trên vào một gói nhỏ gọn gàng có thể sử dụng nhiều hơn khi rời khỏi hộp như một bộ rung có thể cấu hình đơn giản. Nó chạy nó trong một chuỗi nền và có các phương thức dừng và phát và một số tùy chọn bạn có thể đặt.

Nó có trên JCenter nên bạn có thể thêm nó vào danh sách phụ thuộc của mình như thế này

compile 'net.mabboud:android-tone-player:0.2'

và bạn sử dụng nó như thế này để có tiếng rung liên tục

ContinuousBuzzer tonePlayer = new ContinuousBuzzer();
tonePlayer.play();

// just an example don't actually use Thread.sleep in your app
Thread.sleep(1000); 
tonePlayer.stop();

hoặc một bộ rung chỉ phát một lần và bạn có thể đặt tần số và âm lượng như thế này

OneTimeBuzzer buzzer = new OneTimeBuzzer();
buzzer.setDuration(5);

// volume values are from 0-100
buzzer.setVolume(50);
buzzer.setToneFreqInHz(110);

Bài đăng trên blog mở rộng ở đây về nó ở đây GitHub ở đây


@Melchester nó đã được sửa ngay bây giờ. Cảm ơn vì sự quan tâm và xin lỗi về điều đó
meese

4

Vì có lỗi trong một số phiên bản Android cũ hơn gây rò rỉ bộ nhớ khi sử dụng MODE_STATIC, tôi đã sửa đổi câu trả lời của Xarph ở trên để sử dụng MODE_STREAM. Hy vọng rằng nó sẽ giúp một số.

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

Mã sửa đổi dựa trên câu trả lời của 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();
    }
}

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

// Bạn có thể thêm giá trị tùy ý trong synth_frequency để nhận thay đổi âm thanh, ví dụ bạn có thể thêm biến ngẫu nhiên để nhận âm thanh


Cuối cùng, bạn đang chuyển đổi tất cả thành một đoạn ngắn. Không có lý do gì để làm một góc như một chiếc phao. toán kép có cùng tốc độ và không yêu cầu nhiều lần truyền.
Tatarize

2

Làm chính (16 nốt)

 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

xem thư viện hữu ích này

https://github.com/karlotoy/perfectTune

nó dễ sử dụng

thêm điều này vào phụ thuộc của bạn

 compile 'com.github.karlotoy:perfectTune:1.0.2'

Và bạn sử dụng nó như thế này:

PerfectTune perfectTune = new PerfectTune();
perfectTune.setTuneFreq(desire_freq);
perfectTune.playTune();

để dừng giai điệu:

perfectTune.stopTune();

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.