首页 > 解决方案 > 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 次。

标签: pythonopencvudpvideo-streaming

解决方案


猜测这与您发送分数的方式有关。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)

并同时运行它们。您会看到发送者定期“发送”值,但接收者会在它们碰巧被刷新时以块的形式接收它们

我猜当事情变慢时行为变得更糟的原因是因为缓冲区填充得更慢,所以分数的推送频率会降低

一般来说,你最好直接在套接字上使用sendrecv直接使用,这样你就可以更好地控制缓冲和数据包边界。另请注意,UDP 不可靠,因此可以随意丢弃和重新排序数据包。因此,您需要一些东西来跟踪所有内容,如果没有及时收到一些分数,可能会要求评分服务重试发送分数


推荐阅读