【问题标题】:Android: Heart Rate BPM sound playingAndroid:心率 BPM 声音播放
【发布时间】:2014-09-02 21:42:18
【问题描述】:

对于我的应用程序,我需要复制心率监测器的蜂鸣声,也就是说,它每分钟播放 N 次声音。问题是它似乎滞后了大约 5ms。我知道由于操作系统上的上下文切换和其他开销,我无法期望实时性能。 BPM 为 80 时,我得到以下日志输出:

MainActivity: Beeping every 946ms

MainActivity: java.lang.InterruptedException

MainActivity: Beep 0: 951
MainActivity: Beep 1: 951
MainActivity: Beep 2: 951
MainActivity: Beep 3: 951
MainActivity: Beep 4: 951
MainActivity: Beep 5: 951
MainActivity: Beep 6: 952
MainActivity: Beep 7: 951
MainActivity: Beep 8: 954
MainActivity: Beep 9: 951
MainActivity: Beep 10: 953
MainActivity: Beep 11: 952
MainActivity: Beep 12: 951
MainActivity: Beep 13: 951

这是我用来播放哔声的Thread

mBeepThread = new Thread(new Runnable() {

        @Override
        public void run() {
            ArrayList<Long> beepTimes = new ArrayList<Long>(5000);
            try {
                AssetFileDescriptor afd;
                afd = getAssets().openFd("audio/heart_beep.ogg");
                SoundPool sp = new SoundPool(10,AudioManager.STREAM_MUSIC,0);

                int soundId = sp.load(afd, 1);
                MediaPlayer mp = new MediaPlayer();
                mp.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength());
                long duration = mp.getDuration();
                long waitTime = (60000/bpmVal) - duration;
                mp.release();
                Log.i(TAG,"Beeping every "+waitTime+"ms");
                while(true) {
                    beepTimes.add(SystemClock.elapsedRealtime());
                    sp.play(soundId,1,1,1,0,1); 
                    Thread.sleep(waitTime);
                }
            } catch (IllegalStateException | IOException | InterruptedException e) {
                Log.e(TAG,e.getMessage(),e);
                Iterator<Long> it = beepTimes.iterator();
                int count = 0;
                Long oldTime = it.next();
                while(it.hasNext()) {
                    long newTime = it.next();
                    long diff =  newTime - oldTime;
                    oldTime = newTime;
                    Log.i(TAG,String.format("Beep %d: %d",count++,diff));
                }
                return;
            }
        }
    });
    mBeepThread.start();

我能做些什么来让它按照设定的 BPM 播放,还是实际上每 946 毫秒播放一次,而其他 5 个只是播放声音和记录的开销?

我知道我可以从waitTime 缩短 5 毫秒,但这感觉像是在作弊,并没有解决实际问题。

【问题讨论】:

  • 调用 play 可能会有一些开销,但更大的问题是 Thread.sleep 没有任何实时保证。它受调度程序的突发奇想。
  • @jaket 那么你如何建议我每n ms 实现一次哔声呢?
  • 如果是我,我会同步到音频时钟而不是系统时钟。我的意思是,你能想象一个带有 Thread.sleeps 的鼓机或 DAW 吗?不幸的是,唯一正确的方法是将一堆零输出到输出,直到您需要播放哔声时的确切样本,然后输出。
  • 听起来你仍然在忙着等待,即:while(true)if(getTime() - lastPlay &gt;= waitTime) {audioTrack.write(soundBuffer);lastPlay = getTime();} else audioTrack.write(0);
  • 不一定。大多数 API 要么使用回调,要么具有阻塞调用。忙碌的等待不是问题。如果您想准确地为音频事件计时,正如您在实验中看到的那样,睡眠不会做到这一点。

标签: android audio soundpool


【解决方案1】:

这个问题本质上与之前关于节拍器实现的许多问题相似。

准确实现这一点的唯一方法是渲染您自己的 PCM 流。由于系统(墙壁)时钟与音频时钟不同步,因此您无法可靠地使用它来插入周期性事件 - 没有一种可用的方法(例如睡眠专用线程,使用计时器)足够准确,因为它们都依靠准确的调度和/或运行循环不被拥塞。

而是在每 N 个样本渲染到 PCM 流后插入事件,其中 N 是周期和采样率的函数。

【讨论】:

  • 你能否给出一个代码示例来说明这是如何实现的,我阅读了其他问题,没有人提供任何代码或链接到如何做到这一点。
  • 抱歉 - 没有 Android 设备。即使对于我做的 iOS,它的代码也相当多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多