使用 Android 播放任意音调

Posted

技术标签:

【中文标题】使用 Android 播放任意音调【英文标题】:Playing an arbitrary tone with Android 【发布时间】:2010-03-09 23:30:55 【问题描述】:

有什么方法可以让 android 发出任意频率的声音(意思是,我不想有预先录制的声音文件)?

我环顾四周,发现ToneGenerator 是我唯一能找到的更接近的东西,但它似乎只能输出标准的 DTMF 音调。

有什么想法吗?

【问题讨论】:

你找到真正的解决方案了吗? 不,但我最终没有做这个项目。 @JeremyLogan 你得到了积极的负面反馈。大声笑。 【参考方案1】:

我最初在博客上找到了this example code,但其中有一些错误,会产生一些可怕的声音。我已经修复了错误并在此处发布了生成的代码。似乎对我很有效!

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

【讨论】:

这条线正确吗? audioTrack.write(generatedSnd, 0, numSamples);或者应该是 numSamples * 2 因为每个样本有 2 个字节。另外 write 方法也需要一个 short 数组,那么创建一个中间字节数组有什么好处呢? 这确实是一个很好的例子,非常感谢。但是我发现了另一个讨厌的错误(如果您扩展代码),即: audioTrack.write(generatedSnd, 0, numSamples) 应该是 audioTrack.write(generatedSnd, 0, 2*numSamples) 或更好的 audioTrack.write(generatedSnd, 0 , 生成Snd.length); 不应在 AudioTrack 构造函数中使用“numSamples”,而应使用 generatedSnd.length,因为第五个参数是“缓冲区大小(以字节为单位)”。该示例仅播放音调的前半部分。 @Black27 样本以浮点形式创建,幅度范围从0.01.0。乘以32767 会将其转换为 16 位定点范围。 AudioTrack 期望缓冲区很小 endian 格式。因此接下来的两行只是将字节顺序从大端转换为小端。 使用私有静态最终 int sampleRate = 192000;我可以玩超声波【参考方案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                                                              // 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.

【讨论】:

主要变化是幅度的上升和下降。原始代码以最大幅度开始和结束。这会在音调的开头和结尾产生咔嗒声。此代码在前 20% 的样本中将幅度从 0 上升到全幅度。然后,它在最后 20% 的样本中从全幅度下降到零。音调更平滑,更令人愉悦。另一个变化是监视音调的播放,直到音调播放完毕才继续。 我不能让它运行..我能够运行第一个..但不能真正理解如何将它修改为你所做的......这对我来说真的很有帮助摆脱点击声.. +1,但这个答案中的代码并没有接近编译。我在这里正确地实现了它:gist.github.com/SuspendedPhan/7596139 只需用我的替换史蒂夫的 genTone() 方法,你就会得到斜坡效果。 由于 MODE_STATIC 存在内存泄漏,我将代码修改为使用下面的 MODE_STREAM 从 API 开始,可以使用 setVolume() 进行斜坡。这使得只循环一个非常小的样本,甚至播放一个动态长度的声音(例如,当用户拿着一个按钮时)。代码示例:github.com/stefanhaustein/android-tone-generator/blob/master/…【参考方案3】:

我将上述出色的解决方案打包成一个简洁的小包,作为一个简单的可配置蜂鸣器开箱即用。它在后台线程中运行,并具有停止和播放方法以及您可以设置的一些选项。

它在 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);

Extended blog post here about it here GitHub here

【讨论】:

@Melchester 现在已修复。感谢您的提醒,对此感到抱歉【参考方案4】:

由于在使用 MODE_STATIC 时某些较旧的 android 版本中存在导致内存泄漏的错误,因此我修改了上面 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】:

这是另一个博客,演示了一个简单的合成器和一些 UI

http://audioprograming.wordpress.com/2012/10/18/a-simple-synth-in-android-step-by-step-guide-using-the-java-sdk/

您可能还对适用于 android 的 csound 或 pdlib(纯数据库)感兴趣。

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案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();

【讨论】:

【参考方案10】:

有几个程序可以做到这一点,但它们很糟糕。我测量了一些:

http://www.endolith.com/wordpress/2009/11/24/android-audio-applications/

所以不要做他们所做的任何事情。 :D

【讨论】:

以上是关于使用 Android 播放任意音调的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 上播放非正弦音符?迷笛?

使用音轨 android studio 改变音高

Android 默认音调选择器问题(带有通知和警报的默认音调)

如何在 Android 中识别音调? [关闭]

在 Android 中运行音调分析器时出现未知模式字符 X

Android使用TextureView播放视频