音频延迟问题

and*_*dev 8 audio android latency microphone

在我想要创建的应用程序中,我面临一些技术障碍.我在应用程序中有两个音乐曲目.例如,用户将音乐背景导入为第一曲目.第二路径是由用户记录到由扬声器设备(或耳机)播放的第一轨道的节奏的语音.此刻我们面临延迟.在应用程序中录制和回放之后,用户听到轨道之间失去同步,这是由于麦克风和扬声器延迟而发生的.

首先,我尝试通过过滤输入声音来检测延迟.我使用android的AudioRecord类和方法read().这个方法用音频数据填充我的短数组.我发现这个数组的初始值是零,所以在我开始将它们写入输出流之前我决定将它们删除.因此我将这些零视为麦克风的"预热"延迟.这种方法是否正确?这个操作给出了一些结果,但它没有解决问题,在这个阶段,我远离那个.

但最糟糕的情况是启动扬声器和播放音乐之间的延迟.这种延迟我无法过滤或检测.我试图创建一些计算延迟的校准功能.我通过扬声器发出"嘟嘟"的声音,当我开始播放时,我也开始测量时间.然后,我开始录制并听取麦克风检测到的声音.当我在应用程序中识别出这个声音时,我会停止测量时间.我重复这个过程几次,最终值是这些结果的平均值.这就是我尝试测量设备延迟的方法.现在,当我有这个值时,我可以简单地向后移动第二个轨道以实现两个记录的同步(我将丢失一些初始毫秒的记录,但我跳过这种情况,现在,有一些可能来解决它​​) .我认为这种方法可以解决问题,但事实证明这并不像我想象的那么简单.我在这里发现了两个问题:1.同时播放两首曲目时延迟2.设备音频延迟随机.

第一个:我使用AudioTrack类播放两首曲目,我运行如下方法play():

val firstTrack = //creating a track
val secondTrack = //creating a track

firstTrack.play()
secondTrack.play()
Run Code Online (Sandbox Code Playgroud)

此代码导致播放曲目阶段的延迟.现在,我甚至不必考虑录制时的延迟; 我不能同时播放两首曲目而没有延迟.我用一些外部音频文件测试了这个(没有记录在我的应用程序中) - 我使用上面的代码启动相同的音频文件,我可以看到延迟.我也尝试使用MediaPlayer类,我也有相同的结果.在这种情况下,我甚至尝试在回调OnPreparedListener调用时播放曲目:

val firstTrack = //AudioPlayer
val secondTrack = //AudioPlayer

second.setOnPreparedListener {
  first.start()
  second.start()
}
Run Code Online (Sandbox Code Playgroud)

它没有帮助.我知道Android还提供了一个名为SoundPool的类.根据文档,它可以更好地同时播放曲目,但我不能使用它,因为它只支持小音频文件,这不能限制我.我该如何解决这个问题?如何在同一时间精确地开始播放两首曲目?

第二个:音频延迟不是确定性的 - 有时它更小,有时它很大,而且它不在我的手中.因此,测量设备延迟可以再次帮助 - 它无法解决问题.

总结一下:有没有任何解决方案可以为我提供每个设备(或应用会话?)或其他检测实际延迟的触发器的确切延迟,以便在同时回放两个轨道时提供最佳同步?

先感谢您!

don*_*ner 7

同步卡拉OK应用的音频很难.您似乎面临的主要问题是输出流中的可变延迟.

这几乎可以肯定是由于"热身"延迟造成的:从背景音轨上的"播放"到音频设备(例如耳机)呈现的音频数据的第一帧所花费的时间.这可能有很大的差异,很难衡量.

第一个(也是最简单的)尝试是MODE_STREAM在构建你的时候使用,AudioTrackbufferSizeInBytes在调用play之前用数据填充它(更多这里).这应该导致更低,更一致的"预热"延迟.

更好的方法是使用Android NDK来连续运行音频流,直到您点击播放时输出静音,然后立即开始发送音频帧.这里唯一的延迟是连续输出延迟.

如果您决定沿着这条路走下去,我建议您看看Oboe图书馆(完全披露:我是其中一位作者).

回答你的一个具体问题......

有没有办法以编程方式计算音频输出流的延迟?

是.最简单的解释方法是使用代码示例(这是用于AAudio API的C++,但使用Java AudioTrack的原理是相同的):

// Get the index and time that a known audio frame was presented for playing
int64_t existingFrameIndex;
int64_t existingFramePresentationTime;
AAudioStream_getTimestamp(stream, CLOCK_MONOTONIC, &existingFrameIndex, &existingFramePresentationTime);

// Get the write index for the next audio frame
int64_t writeIndex = AAudioStream_getFramesWritten(stream);

// Calculate the number of frames between our known frame and the write index
int64_t frameIndexDelta = writeIndex - existingFrameIndex;

// Calculate the time which the next frame will be presented
int64_t frameTimeDelta = (frameIndexDelta * NANOS_PER_SECOND) / sampleRate_;
int64_t nextFramePresentationTime = existingFramePresentationTime + frameTimeDelta;

// Assume that the next frame will be written into the stream at the current time
int64_t nextFrameWriteTime = get_time_nanoseconds(CLOCK_MONOTONIC);

// Calculate the latency
*latencyMillis = (double) (nextFramePresentationTime - nextFrameWriteTime) / NANOS_PER_MILLISECOND;
Run Code Online (Sandbox Code Playgroud)

警告:此方法依赖于音频硬件报告的准确时间戳.我知道这可以在Google Pixel设备上运行,但是听说过它在其他设备上不那么准确,所以YMMV.