使用 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.0
到1.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 播放任意音调的主要内容,如果未能解决你的问题,请参考以下文章