websocket 从IP Camera获取低延迟帧

6qqygrtg  于 2023-02-08  发布在  其他
关注(0)|答案(2)|浏览(261)

我目前使用这个命令从RTSP流中获取帧,并从stdout中读取帧:

ffmpeg -nostdin -rtsp_transport tcp -i <rtsp_stream> -pix_fmt bgr24 -an -vcodec rawvideo -f rawvideo -

但是,我希望获得与通过ffplay看到它时相同的延迟:

ffplay -fflags nobuffer -flags low_delay -tune zerolatency -framedrop -rtsp_transport tcp <rtsp_stream>

或者当我通过VLC媒体〉打开网络流播放时:network_caching = 300ms。
我想知道我可以在ffmpeg命令中使用哪些其他参数来获得与ffplay命令相当(或更好)的结果。
我参考了:How to dump raw RTSP stream to file?Open CV RTSP camera buffer lagHow to pipe output from ffmpeg using python?bad ffmpeg performace compared to ffplay and VLCHow to minimize the delay in a live streaming with ffmpeg
我目前的实施情况:

FFMPEG_CMD = "ffmpeg -nostdin -rtsp_transport tcp -i <rtsp_stream> -pix_fmt bgr24 -an -vcodec rawvideo -f rawvideo -".split(" ")
WIDTH = 2560
HEIGHT = 1440

process = subprocess.Popen(FFMPEG_CMD, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)

while True:
    raw_frame = process.stdout.read(WIDTH*HEIGHT*3)
    frame = np.frombuffer(raw_frame, np.uint8) 
    frame = frame.reshape((HEIGHT, WIDTH, 3))

    <do stuff with frame/ show frame etc.>

感谢阅读。
ffmpeg命令,我现在使用的延迟小于1s。

ffmpeg -nostdin -flags low_delay -rtsp_transport tcp -i <rtsp_stream> -pix_fmt bgr24 -an -vcodec rawvideo -f rawvideo -

根据答案中的建议实施:

import subprocess
import numpy as np

FFMPEG_CMD = "ffmpeg -nostdin -flags low_delay -rtsp_transport tcp -i <rtsp_stream> -pix_fmt bgr24 -an -vcodec rawvideo -f rawvideo -".split(" ")
WIDTH = 2560
HEIGHT = 1440

process = subprocess.Popen(FFMPEG_CMD, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)

raw_frame = np.empty((HEIGHT, WIDTH, 3), np.uint8) 
frame_bytes = memoryview(raw_frame).cast("B")

while process.poll() is None:
    process.stdout.readinto(frame_bytes)
    frame = raw_frame.reshape((HEIGHT, WIDTH, 3))

    <do stuff with frame/ show frame etc.>
ibrsph3r

ibrsph3r1#

我做了一些关于减少视频延迟的研究。
我的following answer演示了相关的FFmpeg标志是-probesize 32-flags low_delay
上述标志与视频解码器侧(接收器侧)有关。
视频编码参数"发送器/编码器侧"对于确定端到端延迟更重要。
添加参数-tune zerolatency可以将编码器延迟降至最低,但所需的带宽要高得多(可能与Internet上的流媒体无关)。
我将把我的答案限制在解码延迟上,因为它似乎与您的问题主题更相关。
关于"知道其他人如何获得低延迟的视频帧"的主题是一个单独问题的主题(我不知道答案)。
为了比较FFplay和FFmpeg(解码器)之间的延迟差异,我创建了一个"自包含"测试示例。
主要"原则":

  • 执行FFmpeg子进程,以并行流式传输两个RTSP输出流。

流视频是合成模式,帧计数器作为视频上的文本。
两个输出流应用相同的编码参数(仅端口不同)。
RTSP IP地址为127.0.0.1(本地主机)。
(Note:我们可能会使用tee muxer而不是编码两次,但我从来没有尝试过)。

  • 执行FFplay子进程,解码并显示一个视频流。
  • 执行FFmpeg子进程,以解码其他视频流。

OpenCV imshow用于显示视频。

  • 具有较大计数器的显示视频是具有较低延迟的视频。

代码示例(已更新):

import cv2
import numpy as np
import subprocess as sp
import shlex

rtsp_stream0 = 'rtsp://127.0.0.1:21415/live.stream'  # Use localhost for testing 
rtsp_stream1 = 'rtsp://127.0.0.1:31415/live.stream'
width = 256  # Use low resolution (for testing).
height = 144
fps = 30

# https://stackoverflow.com/questions/60462840/ffmpeg-delay-in-decoding-h264
ffmpeg_cmd = shlex.split(f'ffmpeg -nostdin -probesize 32 -flags low_delay -fflags nobuffer -rtsp_flags listen -rtsp_transport tcp -stimeout 1000000 -an -i {rtsp_stream0} -pix_fmt bgr24 -an -vcodec rawvideo -f rawvideo pipe:')

# FFplay command before updating the code (latency is still too high):  
# ffplay_cmd = shlex.split(f'ffplay -probesize 32 -analyzeduration 0 -sync ext -fflags nobuffer -flags low_delay -avioflags direct -rtsp_flags listen -strict experimental -framedrop -rtsp_transport tcp -listen_timeout 1000000 {rtsp_stream1}')

# Updated FFplay command - adding "-vf setpts=0" (fixing the latency issue):
# https://stackoverflow.com/questions/16658873/how-to-minimize-the-delay-in-a-live-streaming-with-ffmpeg
ffplay_cmd = shlex.split(f'ffplay -probesize 32 -analyzeduration 0 -sync ext -fflags nobuffer -flags low_delay -avioflags direct -rtsp_flags listen -strict experimental -framedrop -vf setpts=0 -rtsp_transport tcp -listen_timeout 1000000 {rtsp_stream1}')

# Execute FFplay to used as reference
ffplay_process = sp.Popen(ffplay_cmd)

# Open sub-process that gets in_stream as input and uses stdout as an output PIPE.
process = sp.Popen(ffmpeg_cmd, stdout=sp.PIPE) #,stderr=sp.DEVNULL

# The following FFmpeg sub-process stream RTSP video.
# The video is synthetic video with frame counter (that counts every frame) at 30fps.
# The arguments of the encoder are almost default arguments - not tuned for low latency.
# drawtext filter with the n or frame_num function https://stackoverflow.com/questions/15364861/frame-number-overlay-with-ffmpeg
rtsp_streaming_process = sp.Popen(shlex.split(f'ffmpeg -re -f lavfi -i testsrc=size={width}x{height}:rate={fps} '
                                               '-filter_complex "drawtext=fontfile=Arial.ttf: text=''%{frame_num}'': start_number=1: x=(w-tw)/2: y=h-(2*lh): fontcolor=black: fontsize=72: box=1: boxcolor=white: boxborderw=5",'
                                               'split[v0][v1] '  # Split the input into [v0] and [v1]
                                               '-vcodec libx264 -pix_fmt yuv420p -g 30 -rtsp_transport tcp -f rtsp -muxdelay 0.1 -bsf:v dump_extra '
                                              f'-map "[v0]" -an {rtsp_stream0} '
                                               '-vcodec libx264 -pix_fmt yuv420p -g 30 -rtsp_transport tcp -f rtsp -muxdelay 0.1 -bsf:v dump_extra '
                                              f'-map "[v1]" -an {rtsp_stream1}'))

while True:
    raw_frame = process.stdout.read(width*height*3)

    if len(raw_frame) != (width*height*3):
        print('Error reading frame!!!')  # Break the loop in case of an error (too few bytes were read).
        break

    # Transform the byte read into a numpy array, and reshape it to video frame dimensions
    frame = np.frombuffer(raw_frame, np.uint8)
    frame = frame.reshape((height, width, 3))

    # Show frame for testing
    cv2.imshow('frame', frame)
    key = cv2.waitKey(1)

    if key == 27:
        break
  
process.stdout.close()
process.wait()
ffplay_process.kill()
rtsp_streaming_process.kill()
cv2.destroyAllWindows()

添加-vf setpts=0之前的示例输出:
样本输出(左侧为OpenCV,右侧为FFplay):

在将-vf setpts=0添加到FFplay命令之前,FFmpeg-OpenCV延迟似乎降低了6帧
注意:我花了一些时间才找到解决方案,我决定保留原帖子的结果,以显示添加setpts过滤器的重要性。

更新:

添加-vf setpts=0解决了延迟问题。
following post的最新回答建议添加setpts视频过滤器,将所有视频时间戳重置为零。
这可能不是一个好主意与目前的音频流,但当最低的视频延迟是必需的,这是最好的解决方案,我可以找到。
添加-vf setpts=0后,FFplay和OpenCV的延迟大致相同:

使用mpv media player重复测试:
(Note:在我找到FFplay解决方案之前似乎更相关)。
当应用this page中的所有mpv "延迟破解"时,mpv和OpenCV的延迟大致相同:

FFplay一定有解决的办法,但我找不到......
代码示例(使用mpv而不是FFplay):

import cv2
import numpy as np
import subprocess as sp
import shlex

rtsp_stream0 = 'rtsp://127.0.0.1:21415/live.stream'  # Use localhost for testing 
rtsp_stream1 = 'rtsp://127.0.0.1:31415/live.stream'
width = 256  # Use low resolution (for testing).
height = 144
fps = 30

# https://stackoverflow.com/questions/60462840/ffmpeg-delay-in-decoding-h264
ffmpeg_cmd = shlex.split(f'ffmpeg -nostdin -probesize 32 -flags low_delay -fflags nobuffer -rtsp_flags listen -rtsp_transport tcp -stimeout 1000000 -an -i {rtsp_stream0} -pix_fmt bgr24 -an -vcodec rawvideo -f rawvideo pipe:')

# https://stackoverflow.com/questions/16658873/how-to-minimize-the-delay-in-a-live-streaming-with-ffmpeg
#ffplay_cmd = shlex.split(f'ffplay -probesize 32 -analyzeduration 0 -sync ext -fflags nobuffer -flags low_delay -avioflags direct -rtsp_flags listen -strict experimental -framedrop -rtsp_transport tcp -listen_timeout 1000000 {rtsp_stream1}')

# https://github.com/mpv-player/mpv/issues/4213
mpv_cmd = shlex.split(f'mpv --demuxer-lavf-o=rtsp_flags=listen --rtsp-transport=tcp --profile=low-latency --no-cache --untimed --no-demuxer-thread --vd-lavc-threads=1 {rtsp_stream1}')

# Execute FFplay to used as reference
#ffplay_process = sp.Popen(ffplay_cmd)

# Execute mpv media player (as reference)
mpv_process = sp.Popen(mpv_cmd)

# Open sub-process that gets in_stream as input and uses stdout as an output PIPE.
process = sp.Popen(ffmpeg_cmd, stdout=sp.PIPE) #,stderr=sp.DEVNULL

# The following FFmpeg sub-process stream RTSP video.
# The video is synthetic video with frame counter (that counts every frame) at 30fps.
# The arguments of the encoder are almost default arguments - not tuned for low latency.
# drawtext filter with the n or frame_num function https://stackoverflow.com/questions/15364861/frame-number-overlay-with-ffmpeg
rtsp_streaming_process = sp.Popen(shlex.split(f'ffmpeg -re -f lavfi -i testsrc=size={width}x{height}:rate={fps} '
                                               '-filter_complex "drawtext=fontfile=Arial.ttf: text=''%{frame_num}'': start_number=1: x=(w-tw)/2: y=h-(2*lh): fontcolor=black: fontsize=72: box=1: boxcolor=white: boxborderw=5",'
                                               'split[v0][v1] '  # Split the input into [v0] and [v1]
                                               '-vcodec libx264 -pix_fmt yuv420p -g 30 -rtsp_transport tcp -f rtsp -muxdelay 0.1 -bsf:v dump_extra '
                                              f'-map "[v0]" -an {rtsp_stream0} '
                                               '-vcodec libx264 -pix_fmt yuv420p -g 30 -rtsp_transport tcp -f rtsp -muxdelay 0.1 -bsf:v dump_extra '
                                              f'-map "[v1]" -an {rtsp_stream1}'))

while True:
    raw_frame = process.stdout.read(width*height*3)

    if len(raw_frame) != (width*height*3):
        print('Error reading frame!!!')  # Break the loop in case of an error (too few bytes were read).
        break

    # Transform the byte read into a numpy array, and reshape it to video frame dimensions
    frame = np.frombuffer(raw_frame, np.uint8)
    frame = frame.reshape((height, width, 3))

    # Show frame for testing
    cv2.imshow('frame', frame)
    key = cv2.waitKey(1)

    if key == 27:
        break
  
process.stdout.close()
process.wait()
#ffplay_process.kill()
mpv_process.kill()
rtsp_streaming_process.kill()
cv2.destroyAllWindows()
mzmfm0qo

mzmfm0qo2#

假设瓶颈确实在示例代码中的某个地方(而不是在<do stuff with frame/ show frame etc.>中),您可以尝试更新numpy array,而不是每次都创建一个:

frame = np.empty((HEIGHT, WIDTH, 3), np.uint8) 
frame_bytes = memoryview(frame).cast("b")
while True:
    process.stdout.readinto(frame_bytes) # fills the buffer of frame
    ...

相关问题