PyAudio - 同步播放和录音
我现在正在使用PyAudio开发一个轻量级的录音工具,这个工具是为了满足我计划中的一个应用需求。我正在使用ASIO音频接口。我写这个程序的目的是通过这个接口播放一个wav文件,同时录制从接口输出的音频。这个接口会实时处理信号并改变音频。因为我打算把这个处理后的音频导入到数字音频工作站(DAW)中,所以我需要输出和输入的音频完全同步。在DAW中,我可以同时将音频播放到接口并录制输出,这样做时在DAW中是完全同步的。我的工具的目的是能够通过一个Python脚本来触发这个过程。
通过一种蛮力的方法,我找到了一个可行的解决方案,但现在我遇到了一个“魔法数字”,我不确定这是否是某种常数,或者是我可以计算出来的。如果这是一个可以计算的数字,那就太好了,但我还是想搞清楚它的来源。
我的回调函数如下:
def testCallback(in_data, frame_count, time_info, status):
#read data from wave file
data = wave_file.readframes(frame_count)
#calculate number of latency frames for playback and recording
#1060 is my magic number
latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060
#no more data in playback file
if data == "":
#this is the number of times we must keep the loop alive to capture all playback
recordEndBuffer = latencyCalc / frame_count
if lastCt < recordEndBuffer:
#return 0-byte data to keep callback alive
data = b"0"*wave_file.getsampwidth()*frame_count
lastCt += 1
#we start recording before playback, so this accounts for the initial "pre-playback" data in the output file
if firstCt > (latencyCalc/frame_count):
wave_out.writeframes(in_data)
else:
firstCt += 1
return (data, pyaudio.paContinue)
我关注的函数是:
latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060
我通过观察输出文件与原始播放文件的偏移量来得出这个计算。出现了两个问题:我的输出文件在同时播放时比原始文件晚开始,并且也会提前结束。经过反复试验,我发现开始时多了特定数量的帧,而结束时则缺少了一些帧。这个计算就是为了找出这些帧的数量。我理解第一个部分,它是输入/输出延迟(以秒或亚秒为单位)转换为帧数,使用的是采样率。但我不太确定1060这个值是怎么来的。
我发现通过调整ASIO驱动的延迟设置,我的应用程序仍然能够正确同步录制的文件,即使上述的输出/输入延迟因调整而变化(输入/输出延迟的值始终是相同的),所以1060在我的机器上似乎是稳定的。然而,我不知道这个值是否可以计算出来。或者如果它是一个特定的常数,我也不确定它具体代表什么。
任何帮助我更好理解这些值的建议都非常感谢。我很高兴我的工具现在能正常工作,但我想完全理解这里发生了什么,因为我怀疑如果使用不同的接口,可能就无法正常工作了(出于几个原因,我希望将来能支持这一点)。
编辑 2014年4月8日 回复Roberto:我得到的值是 latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060 是8576,额外的1060使总延迟达到9636帧。你对我为什么加1060帧的假设是正确的。我是通过外部ASIO接口播放文件,而我希望在录制的文件中捕捉到的处理结果是接口上发生的处理(不是我编写的代码)。为了比较输出,我只是播放测试文件并录制接口的输出,而没有在接口上启用任何处理效果。然后我在Audacity中检查了这两条轨道,通过反复试验确定1060是我能让两者对齐的最接近的值。我后来意识到这仍然不是完全完美,但在同时播放时几乎无法察觉(如果去掉1060的偏移,就会有明显的延迟)。添加或移除一个额外的帧相比1060来说补偿过多。
我确实认为你是对的,额外的延迟来自外部接口。我最初在想这是否是我可以用手头的数值信息计算出来的,但我得出的结论是这只是接口中的一个常数。我觉得这是对的,因为我发现如果去掉1060,文件的偏移量与在Reaper中手动执行相同测试时完全相同(这正是我在自动化的过程)。我在我的新蛮力偏移下获得的延迟比在Reaper中要好得多,所以我认为这是一个胜利。在我的应用中,目标是用新处理的文件完全替换原始文件,因此希望两者之间的延迟尽可能小。
关于PyAudio中的ASIO,你问的问题,答案是肯定的。你必须使用ASIO SDK编译PortAudio,才能让PortAudio与ASIO一起工作,然后更新PyAudio的设置以这样编译。幸运的是,我在Windows上工作,http://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio已经内置了ASIO支持,这样设备就可以通过ASIO访问。
1 个回答
因为我不能评论,所以我在这里问你:stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()
这个值是多少?你是怎么得到1060这个数字的?
在你标记的那行代码中:
latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060
,你只是把额外的1060帧加到总延迟上。根据你的描述,我不太明白你为什么这么做,但我猜测你是测量了结果文件的总延迟,并且总是有一个固定的额外帧数,除了输入延迟和输出延迟的总和。所以,你有没有考虑过这个额外的延迟可能是因为处理造成的?你说你对输入音频信号进行了处理,而处理肯定需要一些时间。试着用未修改的输入信号做同样的事情,看看额外的延迟是否减少或消失。即使是你应用的其他部分,比如如果应用有图形界面,所有这些东西都可能会减慢录音速度。你没有完全描述你的应用,但我猜测额外的延迟是由你的代码和代码执行的操作造成的。为什么这个“魔法数字”总是一样?因为你的代码总是一样的。
总结:
这个“魔法数字”代表什么?
显然,它代表了一些额外的延迟,除了你的总往返延迟之外。
是什么导致了这个额外的延迟?
原因很可能在你的代码中。你的应用正在做一些需要额外时间的事情,因此造成了一些额外的延迟。另一个可能的原因是,你在设置中添加了一些额外的“静音时间”,所以你也可以检查一下这个。