python - Python OpenCV 多个 UDP 流。巨大的延迟和掉线率
问题描述
我正在使用来自一个设备的 ffmpeg 流式传输多个 UDP 流,每个流还有一个并发进程,它计算每个帧的分数并将分数发送到另一个端口。在接收端,客户端正在读取每个流的帧和分数,并显示流中得分最高的帧。
问题是,如果有多个流,接收器开始变得非常滞后并丢掉很多帧,甚至更奇怪的是,在较低的比特率下情况更糟(在 200kpbs 和 4 个视频流下,大约 40% 的丢帧和约 10 秒的延迟) .
为了排除网络问题,我尝试在不同进程中显示客户端的所有流(每个进程读取并显示一个流)。这样,延迟和丢包最小。似乎在同一进程中有多个 VideoCapture 会以某种方式减慢它的速度。
这是客户端代码的相关部分。
# thread per stream, reads frames and queues them up
def rx_thread(ip, vport, tport, q):
global die
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((ip, tport))
reader = s.makefile('r')
cap = cv2.VideoCapture('udp://'+ip+':'+str(vport))
while not die:
score = float(reader.readline())
ret, frame = cap.read()
if not ret:
die = True
break
q.put((frame, score))
cap.release()
reader.close()
s.close()
output = open(argv[1], 'w', buffering=1)
# arg[3] .. are pairs of video port and score port
queues = []
for i in range(3, len(argv), 2):
q = queue.Queue(30)
t = threading.Thread(target=rx_thread,
args=(argv[2], int(argv[i]),int(argv[i+1]), q))
t.start()
queues.append(q)
frames_count = 0
start = None
while True:
try:
frames, info = zip(*map(lambda q: q.get(True, 5), queues))
except queue.Empty:
die = True
break
if start is None:
start = time()
output.write('start: %.3f\n' % start)
frames_count += 1
# get stream with max score and show
index = info.index(max(info))
cv2.imshow('Video', frames[index])
if cv2.waitKey(1) & 0xFF == ord('q'):
die = True
break
output.write('frames_count: %d\n' % frames_count)
编辑:
一些人认为这可能与分数的发送方式有关。我尝试完全忽略分数(我没有打开分数套接字,并且我在排队时为每一帧设置了一个 1.0 的虚拟分数)。所以基本上,客户端只是在读取一堆视频流。问题仍然存在。一开始,总是有一堆这样的错误:
[mpeg4 @ 0x7ff838016fc0] Error, header damaged or not MPEG-4 header (f_code=0)
[mpeg4 @ 0x7ff838016fc0] header damaged
[mpeg4 @ 0x7ff838046c40] warning: first frame is no keyframe
[mpeg4 @ 0x7ff840016f80] Error, header damaged or not MPEG-4 header (qscale=0)
当流数为 n 时,该first frame is no keyframe
错误似乎恰好发生 n-1 次。
解决方案
猜测这与您发送分数的方式有关。UDP 不是一个可靠的流协议,告诉 Python 假装它是 vias.makefile()
是一个非常容易 泄漏的抽象。我可以通过让客户端和服务器来复制类似的东西:
def receiver():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.bind(('', 8888))
with sock.makefile('r') as reader:
print('receiver running')
while True:
print('received', repr(reader.readline()))
def sender():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.connect(('127.0.0.1', 8888))
with sock.makefile('w') as writer:
for i in range(1000):
print('sending', i)
print('value =', i, 'stuff to fill the buffers up', file=writer)
time.sleep(0.02)
并同时运行它们。您会看到发送者定期“发送”值,但接收者会在它们碰巧被刷新时以块的形式接收它们
我猜当事情变慢时行为变得更糟的原因是因为缓冲区填充得更慢,所以分数的推送频率会降低
一般来说,你最好直接在套接字上使用send
和recv
直接使用,这样你就可以更好地控制缓冲和数据包边界。另请注意,UDP 不可靠,因此可以随意丢弃和重新排序数据包。因此,您需要一些东西来跟踪所有内容,如果没有及时收到一些分数,可能会要求评分服务重试发送分数
推荐阅读
- python - 具有相同名称的复选框数组,未选中的复选框在 python 和烧瓶中返回空值
- python - Beautifulsoup 不会在桌子上刮头
- javascript - 当我切换到移动视图时,我的按钮发生了什么?
- javascript - 如何更新此递归函数以检查对象的属性名称?
- git - git pull 真的和 git fetch+merge 一样吗?
- javascript - 防止 Vuetify 选项卡的默认点击事件动作
- css - 是否有任何纯 CSS 方法可以仅隐藏 [input=text] 中的文本,而不是整个元素?
- c# - 使用重复的子节点更新 XML
- apache-spark - 在 Java8 Spark 中将 Row[] 转换为二维数组
- javascript - intl-tel-input 未使用电话号码添加国家/地区代码