在进行一项实时应用程序的开发中,该应用程序需要对实时音频进行录制,随后将其发送给语音识别器,并接收识别结果。其中,需要使用 Socket 进行数据发送和接收,并在调试过程中使用了一个 wave 文件。在进行音频读取时,需要将音频发送出去并模拟成正在实时播放的效果。最初,使用 time.sleep
来尝试实现发送的同步,但发现这样的方法会导致发送速度低于实时速度。因此,考虑是否应该使用两个线程,分别用于发送和接收。
while True:
readable, writable, exceptional = select.select([s], [s], [])
if readable:
try:
data = s.recv(1024)
except:
break
if data == "":
break
else:
for line in data.split("\n"):
if not line: continue
t, w = line.split()
if w == "<EOL>":
w = ""
addResult(t, w)
if writable:
now = time.time()
if (now - start_time) / 0.01 >= frame_count - 5:
frame_count += 1
data = wav.readframes(80)
if data is None:
s.shutdown(s.SHUT_WR)
continue
s.send(struct.pack('I', len(data)) + data)
else:
time.sleep(0.005)
if exceptional:
print "Hangup"
break
- 解决方案
对于发送实时数据,有两种方法可供选择:
- 发送者以特定速率发送数据: 在这种情况下,发送者会针对特定时间帧发送数据,之后再发送下一帧数据,以此类推。由于每个数据包在传输过程中可能会有不同的延迟,因此需要添加某种时间戳,以便接收者知道何时播放该数据包。TCP 不是一个好的选择,因为它会在数据包丢失时进行重传,而重传的数据包会到达得太晚。因此,在这些情况下通常使用 UDP,并且编解码器需要能够处理数据包丢失的情况。这种协议的一个典型例子是 RTP,它用于 VoIP 和其他场合。
- 接收者以特定速率接收数据: 这只适用于软实时,例如可以接受几秒或更长时间的延迟的情况。在这种情况下,发送者只需使用 TCP 发送数据,并且流中的每个“帧”都包含一些时间戳,以便接收者知道何时播放它。接收者会根据需要以快或慢的速度读取数据包。如果数据包到达速度慢于所需速度,则需要暂停播放并等待更多数据(如在 YouTube 等网站上看到的那样);如果数据包到达速度快于所需速度,则只会以所需的慢速度读取这些数据包,这将自动导致发送者降低发送速度(TCP 的固有行为)。
根据程序的原始方法,发送者以特定速度传输数据。若要继续使用这种方法,应进行以下操作:
- 使用 UDP 代替 TCP,这样可以避免因数据包丢失而导致的重传引起的延迟。
- 使用能够处理数据包丢失的编解码器,例如 G.711 或类似编解码器。
- 向每个数据包添加时间戳,以便发送者知道何时播放该数据包。
- 添加精确的计时:使用
sleep
本身并不能计入发送数据包之前所需的时间,因此应该存储开始时间并计算每个数据包需要发送的时间(例如next_time = last_time+time_span_in_packet
)。然后,仅针对next_time - current_time
进行睡眠。