0

我正在尝试创建一个允许我输出 WAVE 文件的低级 Stream,同时在同一音频设备上记录输入。我的音频设备已设置,以便输出 WAVE 文件将通过输出播放,这将通过一个系统运行,然后转到设备上的输入。使用 python-sounddevice 中的便捷函数 playrec() 可以完整记录输入中看到的内容,但是使用较低级别的 Stream() 函数的代码,录制开始较晚,并且音频的最后一点是没有记录。我想使用较低级别的 Stream() 函数的原因是测试与 playrec() 相比,我是否可以减少该系统的整体延迟。我尝试更改块大小和缓冲区大小无济于事。

def callback(indata, outdata, frames, time, status):
  assert frames == args.blocksize
  qr.put(indata.copy())
  rec_file.write(qr.get())
  if status.output_underflow:
    print('Output underflow: increase blocksize?', file=sys.stderr)
    raise sd.CallbackAbort
  assert not status
  try:
    data = q.get_nowait()
  except queue.Empty:
    print('Buffer is empty: increase buffersize?', file=sys.stderr)
    raise sd.CallbackAbort
  if data.size < outdata.size:
    outdata[:len(data),0] = data
    outdata[len(data):] = 0
    raise sd.CallbackStop
  else:
    outdata[:,0] = data

try:
    with sf.SoundFile(args.filename) as f:
        #queue for recording input
        qr = queue.Queue(maxsize=args.buffersize)
        #queue for output WAVE file
        q = queue.Queue(maxsize=args.buffersize)
        event = threading.Event()
        for _ in range(args.buffersize):
            data = f.read(frames=args.blocksize, dtype='float32')
            if data.size == 0:
                break
            q.put_nowait(data)  # Pre-fill queue
        stream = sd.Stream(   
            samplerate=f.samplerate, blocksize=args.blocksize,
            dtype='float32', callback=callback, finished_callback=event.set,
            latency='low')
        with sf.SoundFile('output'+str(itr)+'.wav', mode='x', samplerate=f.samplerate,
                          channels=1) as rec_file:
            with stream:
                timeout = args.blocksize * args.buffersize / f.samplerate
                while data.size != 0:
                    data = f.read(args.blocksize, dtype='float32')
                    q.put(data, timeout=timeout)
                event.wait()  # Wait until playback is finished
4

1 回答 1

0

如果您不介意一次将整个输入和输出信号存储在内存中,您应该可以随意使用sd.playrec(). 您将无法使用自己的代码减少延迟sd.Streamsd.playrec()内部使用sd.Stream,它不会增加延迟。

如果您想减少延迟,您应该尝试为blocksize和/或latency参数使用较低的值。但是请注意,较低的值会更加不稳定,并可能导致播放/录制出现故障。


如果您不想一次将所有数据都保存在内存中,则无法使用sd.playrec(),可以尝试使用sd.Stream,就像上面的示例一样。

但是请注意,这两条相邻行中的队列充其量是无用的:

qr.put(indata.copy())
rec_file.write(qr.get())

你不妨写:

rec_file.write(indata)

但请不要!

写入文件可能会长时间阻止音频回调,从而导致音频丢失。

因此,使用队列是一个好主意(使用也是一个好主意indata.copy())。

但是您应该只在回调函数中写入您的内容。阅读应该发生在不同的地方qr

您应该在循环之前或之后进行非阻塞 ,并将数据写入那里的文件。qr.get_nowait()whileq.put(...)

在回调函数中,你不应该做一个阻塞 qr.put(indata.copy()),因为这可能会阻塞你的音频回调导致退出。相反,您应该使用qr.put_nowait(). 为了避免一个完整的队列,您应该从(但将其保留在另一个队列中!)中删除maxsize参数。qr

最后,在离开with stream上下文管理器之后,可能仍然存在qr尚未写入文件的数据。

所以流关闭后,你应该确保清空“录制队列”并将剩余的块写入文件。

于 2020-06-21T09:42:21.913 回答