Android:AudioTrack在开始时发出咔嗒声

时间:2014-07-16 20:46:15

标签: android audio

我正在开发Android应用程序,我想播放一些短音(~2s)。我试过Soundpool但它并不适合我,因为它无法检查声音是否已经播放。所以我决定使用AudioTrack。

它工作得很好但是大部分时间,当它开始播放声音时会发出“咔哒”声。 我检查了我的音响文件,它们很干净。

我在流模式下使用audiotrack。我看到静态模式对于短音更好,但经过多次搜索后我仍然不明白如何使其工作。 我还读到点击噪声可能是由wav文件的标题引起的,所以如果我用setPlaybackHeadPosition(int positionInFrames)函数跳过这个标题(这应该只能在静态模式下工作),声音可能会消失

这是我的代码(所以问题是开头的滴答声)

    int minBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, 
            AudioFormat.ENCODING_PCM_16BIT);
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, 
         AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM); 
audioTrack.play();
            int i = 0;
            int bufferSize = 2048; //don't really know which value to put
            audioTrack.setPlaybackRate(88200);

            byte [] buffer = new byte[bufferSize];
//there we open the wav file >
            InputStream inputStream = getResources().openRawResource(R.raw.abordage);
            try {
                while((i = inputStream.read(buffer)) != -1)
                    audioTrack.write(buffer, 0, i);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            try {
                inputStream.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

有没有人有解决方案来避免这种噪音?我尝试了this,它有时会起作用但不是每次都起作用。有人可以告诉我如何在MODE_STATIC中实现audiotrack吗? 谢谢

5 个答案:

答案 0 :(得分:1)

音频的常见原因" pop"是由于渲染过程没有在零交叉点开始/停止声音(假设最小/最大-1到+1交叉将是0)。扬声器或耳塞等传感器处于静止状态(无声输入),映射到此零交叉。如果音频渲染过程无法启动/停止此零点,则要求传感器执行不可能的操作,即瞬间从其静止状态转到其最小/最大移动范围内的某个非零位置(或签证)反之亦然,如果你在最后得到一个" pop"。

答案 1 :(得分:1)

最后,经过大量的实验,我让它在没有咔嗒声的情况下工作。这是我的代码(不幸的是,我无法读取inputStream的大小,因为getChannel().size()方法只适用于FileInputStream类型)

try{
    long totalAudioLen = 0;  
    InputStream inputStream = getResources().openRawResource(R.raw.abordage); // open the file
    totalAudioLen = inputStream.available();
    byte[] rawBytes = new byte[(int)totalAudioLen];
    AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC,
                                      44100,
                                      AudioFormat.CHANNEL_CONFIGURATION_MONO,
                                      AudioFormat.ENCODING_PCM_16BIT,
                                     (int)totalAudioLen,
                                     AudioTrack.MODE_STATIC);
    int offset = 0;
    int numRead = 0;

    track.setPlaybackHeadPosition(100); // IMPORTANT to skip the click
    while (offset < rawBytes.length
               && (numRead=inputStream.read(rawBytes, offset, rawBytes.length-offset)) >= 0) {
            offset += numRead;
    } //don't really know why it works, it reads the file
    track.write(rawBytes, 0, (int)totalAudioLen); //write it in the buffer?
    track.play();  // launch the play
    track.setPlaybackRate(88200);
    inputStream.close();
    }
    catch (FileNotFoundException e) {

        Log.e(TAG, "Error loading audio to bytes", e);
    } catch (IOException e) {
        Log.e(TAG, "Error loading audio to bytes", e);
    } catch (IllegalArgumentException e) {
        Log.e(TAG, "Error loading audio to bytes", e);
    }

因此,跳过点击噪音的解决方案是使用MODE_STATICsetPlaybackHeadPosition功能跳过音频文件的开头(可能是标题,或者我不知道是什么) 。 我希望这部分代码可以帮助某些人,我花了太多时间试图找到静态模式代码示例而没有找到加载原始资源的方法。

编辑:在各种设备上测试此解决方案后,无论如何它们似乎都有点击噪音。

答案 2 :(得分:1)

要使“setPlaybackHeadPosition”起作用,您必须先播放并暂停。如果您的曲目已停止或未启动,则无效。相信我。这是愚蠢的。但它的确有效:

track.play();
track.pause();
track.setPlaybackHeadPosition(100);   
// then continue with track.write, track.play, etc. 

答案 3 :(得分:1)

就我而言。这是WAV标头。 还有...

byte[] buf44 = new byte[44];
int read = inputStream.read(buf44, 0, 44);

...解决了。

答案 4 :(得分:0)

我发现Scott Stensland的推理符合我的问题(谢谢!)。

我通过在样本数组的开头运行一个死的简单线性淡入滤波器来消除pop。滤波器使采样值从0开始,并将振幅缓慢增加到其原始值。通过始终在零交叉点处从值0开始,弹出不会发生。

在样本数组的末尾应用了类似的淡出过滤器。过滤器的持续时间很容易调整。

import android.util.Log;

public class FadeInFadeOutFilter
{
    private static final String TAG = FadeInFadeOutFilter.class.getSimpleName();

    private final int filterDurationInSamples;

    public FadeInFadeOutFilter ( int filterDurationInSamples )
    {
        this.filterDurationInSamples = filterDurationInSamples;
    }

    public void filter ( short[] audioShortArray )
    {
        filter(audioShortArray, audioShortArray.length);
    }

    public void filter ( short[] audioShortArray, int audioShortArraySize )
    {
        if ( audioShortArraySize/2 <= filterDurationInSamples ) {
            Log.w(TAG, "filtering audioShortArray with less samples filterDurationInSamples; untested, pops or even crashes may occur.  audioShortArraySize="+audioShortArraySize+", filterDurationInSamples="+filterDurationInSamples);
        }
        final int I = Math.min(filterDurationInSamples, audioShortArraySize/2);

        // Perform fade-in and fade-out simultaneously in one loop.
        final int fadeOutOffset = audioShortArraySize - filterDurationInSamples;
        for ( int i = 0 ; i < I ; i++ ) {
            // Fade-in beginning.
            final double fadeInAmplification = (double)i/I; // Linear ramp-up 0..1.
            audioShortArray[i] = (short)(fadeInAmplification * audioShortArray[i]);

            // Fade-out end.
            final double fadeOutAmplification = 1 - fadeInAmplification; // Linear ramp-down 1..0.
            final int j = i + fadeOutOffset;
            audioShortArray[j] = (short)(fadeOutAmplification * audioShortArray[j]);
        }
    }
}