播放短暂的声音

时间:2017-06-04 14:09:03

标签: android audio

我想播放短于1秒的生成声音。但是,AudioTrack的minBufferSize似乎总是1秒或更长。在某些设备上,我可以将bufferSize设置为小于使用AudioTrack.getMinBufferSize评估的值,但这在所有设备上都不可能。我想知道是否可以为AudioTrack生成更短的声音。我目前正在使用此代码(它包含一些平滑功能,因为我不断获得新的频率):

int buffSize = AudioTrack.getMinBufferSize(sampleRate,
                                           AudioFormat.CHANNEL_OUT_MONO,
                                           AudioFormat.ENCODING_PCM_16BIT);
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
                                       AudioFormat.CHANNEL_OUT_MONO,
                                       AudioFormat.ENCODING_PCM_16BIT, buffSize,
                                       AudioTrack.MODE_STREAM);
short samples[] = new short[buffSize];
int amp = 10000;
double twopi = 8. * Math.atan(1.);
double phase = 0.0;
audioTrack.play();
double currentFrequency = getFrequency();
double smoothing = 300;
double deltaTime = buffSize / 500;
while (playing && PreferenceManager.getDefaultSharedPreferences(
        MainActivity.this).getBoolean("effect_switch", true))
{
    double newFrequency = getFrequency();
    for (int i = 0; i < buffSize; i++)
    {
        currentFrequency += deltaTime * (newFrequency - currentFrequency) / smoothing;
        samples[i] = (short) (amp * Math.sin(phase));
        phase += twopi * currentFrequency / sampleRate;
    }
    audioTrack.write(samples, 0, buffSize);
}
audioTrack.stop();
audioTrack.release();

事实上,我希望更频繁地更新声音,这就是我需要更短样本的原因。

1 个答案:

答案 0 :(得分:1)

我想我有一个解决方案。由于我的最小缓冲区似乎远小于1秒,我通过加载一个5秒数据的缓冲区模拟你的问题,但只播放0.5秒,然后是另一个频率。这个音调我也创造了5秒的数据,但只播放了0.5秒&amp;重复这几个音调。这一切都适合我。

另外,由于我把它塞进了我正在处理的当前项目中,因此我很难剪切和粘贴代码。虽然我已经测试了我的解决方案,但我在此处发布的内容并未完全按照书面形式进行测试。其中一些是切割&amp;粘贴,一些伪代码。

关键功能是使用OnPlaybackPositionUpdateListener。

private AudioTrack.OnPlaybackPositionUpdateListener audioTrackListener = new AudioTrack.OnPlaybackPositionUpdateListener() {
    @Override
    public void onMarkerReached(AudioTrack audioTrack) {
        int marker = audioTrack.getNotificationMarkerPosition();
        // I just used 8 tones of 0.5 sec each to determine when to stop but you could make 
        //      the condition based on a button click or whatever is best for you
        if(marker < MAX_FRAME_POSITION) {
            audioTrack.pause();
            newSamples();
            audioTrack.play();
        } else {
            audioTrack.stop();
        }
        audioTrack.setNotificationMarkerPosition(marker + FRAME_MARKER);
        Log.d(TAG, "MarkerReached");
    }

    @Override
    public void onPeriodicNotification(AudioTrack audioTrack) {
        int position = audioTrack.getPlaybackHeadPosition();
        if(position < MAX_FRAME_POSITION) {
            audioTrack.pause();
            newSamples();
            audioTrack.play();
        } else {
            audioTrack.stop();
        }
        Log.d(TAG, "PeriodNotification");
    }
};

然后

    audioTrack.setPlaybackPositionUpdateListener(AudioTrackListener);

我使用了标记(必须重复重新初始化)才能进行测试......

    audioTrack.setNotificationMarkerPosition(MARKER_FRAMES);    

但您也应该能够使用定期通知。

    audioTrack.setPositionNotificationPeriod(PERIODIC_FRAMES);

从侦听器调用newSamples()方法

public void newSamples() {
    /*
    * generate buffer, I'm doing similar to you, without the smoothing
    */

    // AudioTrack write is a blocking operation so I've moved it off to it's own Thread.
    //      Could also be done with an AsyncTask.
    Thread thread = new Thread(writeSamples);
    thread.start();
}

private Runnable writeSamples = new Runnable() {
    @Override
    public void run() {
        audioTrack.write(samples, 0, buffSize);
    }
};