因项目需要在 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();
- }
- }
来源: http://www.jianshu.com/p/0b8b5bd65b42