Android:AudioTrack 在开始时会发出咔哒声

Posted

技术标签:

【中文标题】Android:AudioTrack 在开始时会发出咔哒声【英文标题】:Android: AudioTrack makes a click sound at beginning 【发布时间】:2014-07-16 20:46:15 【问题描述】:

我正在开发一个 android 应用程序,我想播放一些短声音(~ 2 秒)。我尝试了 Soundpool,但它并不适合我,因为它无法检查声音是否已经在播放。所以我决定使用 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 中实现音轨吗? 谢谢

【问题讨论】:

【参考方案1】:

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

【讨论】:

所以我必须开始播放带有偏移量的曲目? 使用一些标准工具手动播放相同的音轨......你也听到流行音乐吗? 使用soundpool播放器我没有这个pop(它是更高级别的播放器,所以它必须更好地配置)。我找到了解决方案,并在答案中写了它:) 这个解释很有道理,并帮助我找到了我发布的解决方案。【参考方案2】:

最后,经过大量的实验,我让它在没有点击噪音的情况下工作。这是我的代码(不幸的是,我无法读取 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 函数跳过音频文件的开头(那可能是标题或者我不知道是什么)。 我希望这部分代码对某人有所帮助,我花了太多时间试图找到一个静态模式代码示例,却没有找到加载原始资源的方法。

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

【讨论】:

您是在播放 wav 文件吗?然后跳过前 44 个字节就足够了,因为标头(大部分)是那个大小。更好的是跳过流中已经存在的那些字节: inputStream.skip(44) 您应该会看到我的答案和解决方案,然后将已接受的答案打勾;)【参考方案3】:

要使“setPlaybackHeadPosition”起作用,您必须先播放和暂停。如果您的曲目停止或未开始,它就不起作用。相信我。这是愚蠢的。但它有效:

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

【讨论】:

【参考方案4】:

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

我通过在样本数组的开头运行一个简单的线性淡入过滤器来消除流行音乐。滤波器使样本值从 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]);
        
    

【讨论】:

【参考方案5】:

就我而言。它是 WAV 标头。 还有……

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

...解决了。

【讨论】:

有趣!我从来没想过。

以上是关于Android:AudioTrack 在开始时会发出咔哒声的主要内容,如果未能解决你的问题,请参考以下文章

Android使用AudioTrack发送红外信号

从 ByteStream 可视化 Android AudioTrack

Android使用AudioTrack播放WAV音频文件

Android 自定义音频播放器,用 libPd 或 OpenSL ES 或 AudioTrack 替换 MediaPlayer

安卓Android开发:使用AudioRecord录音将录音保存为wav文件使用AudioTrack保存录音

安卓Android开发:使用AudioRecord录音将录音保存为wav文件使用AudioTrack保存录音