因项目需要在 APP 中播放纯音,耳机测听的需求,了解在 Android 系统中,纯音播放有两种方式,一种是在程序中模拟出一个波形满足正弦波的音频数据,另一种就是事先准备好多个音频文件然后直接播放。若使用事先准备的音频,效果是可以达到(且可能更好),但需要准备各种分贝、频率的音频文件,算下来,要制作上百个,所以这里讨论通过程序模拟生成纯音。
所有声音都是有正弦波组成,只不过纯音是固定频率的正弦波。主要思路就是,可以使用 sin 函数 实现想要的频率的正弦波,然后再用AudioTrack类来实现声音的播放。
一、简单纯音计算
首先正弦波的高度设置为 127,因为这里使用 8 位的采样率,2 的 8 次方就应该是 256,所以正弦波的波峰就应该是 127 了。
/** 正弦波的高度 **/
public static final int HEIGHT = 127;
/** 2PI **/
public static final double TWOPI = 2 * 3.1415;
/**
* 生成正弦波
* @param wave
* @param waveLen 每段正弦波的长度
* @param length 总长度
* @return
*/
public static byte[] sin(byte[] wave, int waveLen, int length) {
for (int i = 0; i < length; i++) {
wave[i] = (byte) (HEIGHT * (1 - Math.sin(TWOPI
* ((i % waveLen) * 1.00 / waveLen))));
}
return wave;
}
初始化 AudioTrack 首先传入一个指定频率,这里先将声音总频率设置为 44100,然后计算出单个正弦波(也就是 2PI)的长度,最后将单个正弦波的长度乘以频率取得声波的实际频率,这样的是为了避免声波最后出现一段空的声波。 然后将参数传入上述的 sin 方法中取得正弦波。
AudioFormat.ENCODING_PCM_8BIT 就是指定 AudioTrack 使用 8 位采样率。
/**
* 设置频率
* @param rate
*/
public void start(int rate) {
if (rate > 0) {
Hz = rate;
waveLen = 44100 / Hz;
length = waveLen * Hz;
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
AudioFormat.CHANNEL_CONFIGURATION_STEREO, // CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_8BIT, length, AudioTrack.MODE_STREAM);
//生成正弦波
wave = SinWave.sin(wave, waveLen, length);
if (audioTrack != null) {
audioTrack.play();
}
} else {
return;
}
}
AudioTrack.play() 是还不能播放声音的,因为这个时候 AudioTrack 里面还没有声波数据。下面这段代码喂数据才能真的播放声音:
audioTrack.write(wave, 0, length);
这种生产的方法,在频率高的时候,就会产生误差,频率越高,误差越大。
二、生成不同分贝和频率的纯音
根据不同分贝,不同频率的生成纯音。
- 初始化 AudioTrack
- 播放 audioTrackz.play()
- 喂数据 AudioTrackZThread
private int sampleRateInHz = 16000; // 采样率,MAX 44100
private int mChannel = AudioFormat.CHANNEL_CONFIGURATION_MONO;// 声道 :单声道
private int mSampBit = AudioFormat.ENCODING_PCM_16BIT;// 采样精度 :16bit
private AudioTrackZThread audioTrackZThread;
private boolean isRunning = false;
private AudioTrack audioTrackz;
public WaveOutZ() {
int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, mChannel, mSampBit);
audioTrackz = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, mChannel, mSampBit, bufferSize * 2, AudioTrack.MODE_STREAM);
audioTrackz.setStereoVolume(1.0f, 1.0f);
audioTrackz.play();
}
public void palyWaveZ(float rate, int db) {
if (audioTrackZThread==null) {
audioTrackZThread = new AudioTrackZThread();
audioTrackZThread.start();
}
audioTrackZThread.setDb(rate,db);
}
public void colseWaveZ() {
if (audioTrackz != null) {
if (!AudioTrackZThread.interrupted()) {
isRunning = false;
audioTrackZThread=null;
}
}
}
class AudioTrackZThread extends Thread {
private short m_iAmp = Short.MAX_VALUE;
private short m_bitDateZ[] = new short[44100];
private double x = 2.0 * Math.PI * 8250.0 / 44100.0;
private int mM_bitDateZSize;
public void setDb(float rate, int db) {
x = 2.0 * Math.PI * rate / 44100.0;
m_iAmp = (short) ((Math.pow(10.0, db / 20.0) * 1.414) * Short.MAX_VALUE);
for (int i = 0; i < 44100; i++) {
m_bitDateZ[i] = (short) (m_iAmp * Math.sin(x * i));
}
mM_bitDateZSize = m_bitDateZ.length;
}
@Override
public void run() {
isRunning = true;
do {
audioTrackz.write(m_bitDateZ, 0, mM_bitDateZSize);
} while (isRunning);
super.run();
}
}
网友评论