Python OpenCV несколько потоков UDP. Огромная задержка и шанс выпадения - PullRequest
0 голосов
/ 06 ноября 2019

Я транслирую несколько потоков UDP с помощью ffmpeg с одного устройства, также есть параллельный процесс для каждого потока, который вычисляет оценку для каждого кадра и отправляет оценку на другой порт. На принимающей стороне клиент читает кадр и оценку для каждого потока и отображает кадр из потока, который имеет наилучшую оценку.

Проблема заключается в том, что при наличии нескольких потоков приемник начинаетстановятся очень запаздывающими и пропускают так много кадров, даже страннее, хуже при низких битрейтах (при 200 кбит / с и 4 видеопотоках, около 40% пропущенных кадров и задержка ~ 10 с).

Чтобы исключить проблему с сетью,Я попытался просто отобразить все потоки на стороне клиента в разных процессах (каждый процесс читает и отображает один поток). Таким образом, было минимальное время ожидания и падение. Похоже, что несколько VideoCaptures в одном процессе как-то замедляют его.

Вот соответствующая часть кода клиента.

# 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)

Ошибка first frame is no keyframe, кажется, возникает ровно n-1 раз, когда число потоков равно n.

1 Ответ

0 голосов
/ 07 ноября 2019

в предположении, это связано с тем, как вы отправляете результаты. UDP не является надежным потоковым протоколом и говорит Python, притворяясь, что он через s.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 не является надежным, и, следовательно, он может свободно отбрасывать и переупорядочивать пакеты. поэтому вам нужно что-то там, чтобы отслеживать все, возможно, попросить службу скоринга повторить отправку результатов, если некоторые не были получены вовремя

...