1

我正在尝试编写一个 Android 应用程序,该应用程序将用作节拍器等。这需要能够定期以准确的时间播放各种声音,但现在我正在努力让一个工作。超过 2-3ms 的错误很容易被训练有素的听众检测到。这是我的播放线程:

protected void onCreate(Bundle savedInstanceState) {
    // stuff
    int minBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
    mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);
}

public void startThread(View view){
    mThread = new Thread (new Runnable() {
        public void run(){
            Log.d(TAG, "Starting met at tempo: " + mTempo + " Interval: " + (60000 / mTempo) );

            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);

            InputStream in = getResources().openRawResource(R.raw.chirp);
            byte [] output = getBytesFromStream(in);

            Log.d(TAG, "Output length: " + output.length);

            mAudioTrack.play();
            while (mRunning) {
                mAudioTrack.write(output, 0, output.length);

                try {
                    Thread.sleep(60000 / mTempo);
                } catch (InterruptedException e) {
                }
            }

            mAudioTrack.release();
        }
    });

    mThread.start();
}

R.raw.chirp 是 Audacity 中生成的正弦波。1000hz 以 44100hz 采样 100ms,所以应该是 4410 个样本左右。在 Nexus 4 上进行测试,速度设置为 200bpm(即,啁啾声应该每 300 毫秒播放一次),声音似乎不错,但为了更好地衡量,我通过线路输入将输出记录到 Audacity。我注意到两个奇怪的地方:

1) 唧唧声似乎在 298-299 毫秒之间的某个时间段播放。这在很大程度上是一致的,但是 1-2 毫秒将速度更改为 201bpm,这足以让它与参考节拍器不同步。

2)第二个奇点不是始终可重复的,但是在看似随机数量的啁啾声之后,会有一个啁啾声延迟整整 20 毫秒。

第二个问题我可以想象得到解释;也许是线程/优先级问题?然而,第一个问题让我感到困惑。我想如果有什么叫声应该迟到,而不是早。我相信这些问题是相关的,而且我也开始认为我没有正确理解正在发生的事情并且正在做一些非常基本的错误(这是我第一次使用任何类型的音频代码。)

任何帮助将非常感激。

4

2 回答 2

5

除了目前使用较差的计时源之外,还有一个更大的问题,即 Android 仅在请求播放的时间和实际发生的时间之间提供相对松散且相当可变的耦合。

要解决这个问题,请连续播放声音 - 主要是静音 - 并在所需时间混合您想要的点击。不要使用任何普通的软件可访问的系统时钟来确定时间,而是使用先前写入的样本计数,这样您的时序将与数模转换器采样时钟具有固定的关系。

于 2013-07-23T04:10:24.057 回答
0

所以你依赖于睡眠是否准确。它不是。它不是这样设计的。它至少会睡那个时间,但可能会睡得更久。操作系统不会在睡眠结束后立即将任务切换回该线程,但只要它成为其调度队列中的最高优先级线程。你需要一种更可靠的等待方式——基于 RTC 的东西。

基于 RTC 警报类型的警报可能会执行此操作。我不确定它是否真的是一个 RTC 中断,它会立即向您发送警报,或者它是否只是它使用的相对时钟。

于 2013-07-23T03:09:40.173 回答