实时/零延迟视频流:使用什么编解码器参数



我正在编写一个Android应用程序,它包含从桌面PC发送和接收视频流。为了使应用程序正常工作,我们需要尽可能少的延迟,必要时牺牲视频质量。我们在两端都使用gstreamer 1.45,但在目前的管道中,我们在Galaxy Note S2上至少有0.5s的延迟,这是在两个设备都在同一网络上的情况下(稍后应该通过VPN工作)。

发送方管道

appsrc name=vs_src format=time do-timestamp=true
    caps="video/x-raw, format=(string)RGB, width=(int)640, height=(int)480, framerate=(fraction)15/1000" 
    ! videoconvert 
    ! x264enc speed-preset=ultrafast tune=zerolatency byte-stream=true threads=1 key-int-max=15 intra-refresh=true ! h264parse ! rtph264pay pt=96
    ! queue ! udpsink name=vs_sink host=%s port=%d async=false

接收器管道

udpsrc name=vr_src 
    caps="application/x-rtp, media=(string)video, clock-rate=(int)90000, payload=(int)96, encoding-name=(string)H264"
    ! rtpjitterbuffer 
    ! rtph264depay ! h264parse ! avdec_h264 
    ! videorate ! videoconvert
    ! glimagesink name=vr_sink async=false

设置threads=2或更高的值会发出一个gstreamer警告,表示它在没有多线程支持的情况下编译。我知道有些设备提供硬件解码器,但可靠地访问它们的唯一方法似乎是通过encodebin/decodebin。我已经尝试过使用decodebin,但由于某种原因,它抱怨它找不到所需的插件(例如No decoder to handle media type 'video/x-h264')。

当涉及到流媒体和视频编码/解码时,我不是专家,并且让应用程序达到一个工作点已经是一场噩梦:/如果H264应该是不合适的,我们可以切换到gstreamer支持的任何其他编解码器。有人能帮帮我吗?

我们做从桌面pc到Raspberry Pis的实时视频流,我们花了大量的时间来调整我们系统的编码和解码部分。不幸的是,大多数库和工具都有针对转码或一般视频播放(不是实时的)的开箱即用设置。我们最终编写了自己的GStreamer元素来进行编码(使用vaapi),并编写了自己的Raspberry Pi程序来进行解码(使用OMX)。

我可以提供一些想法给你,但没有特定的Android解码场景,不幸的是。

  • 如果您在功能强大的桌面(如i3-i7)上进行编码,请确保为任何重要操作添加队列:颜色空间转换、缩放、编码等。因此,在你的管道中,确保在"videoconvert"one_answers"x264enc"之间有一个"队列",这样它们就可以在单独的线程上运行。

  • 正如Ralf提到的,您可能只想使用p帧,而不是B帧,并且您的x264enc设置可能已经这样做了。

  • 我们通常倾向于丢弃帧和显示垃圾,而不是使用一个大的抖动缓冲区。我们还调整了QP(编码质量),使其符合我们的网络手段。所以我建议在你的接收程序中添加sync=false。你想要渲染一个帧,只要你有它。这可能会使你的视频不那么流畅,但如果你有一个大的抖动缓冲,你总是会延迟。最好把流调整到网络上,去掉缓冲区。x264enc有"qp-min"one_answers"qp-max"属性,你可以试试。

  • 尝试调整rtpjitterbuffer的"latency"one_answers"drop-on-latency"属性,或者尝试完全摆脱它。

  • 我们发现的一件非常讨厌的事情是,在树莓派解码器中,无论我们的流如何实时优化,它似乎总是有某种内置延迟。原来在h264流中有一个叫做VUI包的东西,它可以用来告诉解码器期望什么类型的流,当我们提供这个包时,解码器的反应非常不同。

<>之前Bitstream_restriction_flag: 1Motion_vectors_over_pic_boundaries_flagmax_bytes_per_pic_dom: 0max_bits_per_mb_dom: 0Log2_max_mv_length_horizontal: 10Log2_max_mv_length_vertical: 10Num_reorder_frames: 0Max_dec_frame_buffering: 1——这有很大的区别之前

参考:https://www.raspberrypi.org/forums/viewtopic.php?t=41053

所以在上面的VUI设置中,我告诉解码器我们最多需要缓冲一个p帧。这对我的帮助太大了。当然,我们还必须确保我们的编码器只发送一个P帧。我不确定这对x264enc是否可行。

这种东西可能会变得相当可怕。希望其他有Android视频的人能给你一个更简单的答案!

编辑:关于队列,我根本不参数化它们,在直播的情况下,如果你的队列填满了,你需要缩减(分辨率,质量,无论如何)。在GStreamer中,queue元素导致GStreamer启动一个新线程来处理管道的以下部分。你只需要确保你的编码/缩放/颜色空间转换元素是独立工作的。

gst-launch-1.0 [GET RAW VIDEO DATA] queue [SCALE] queue[COLORSPACE CONVERT] queue [ENCODE] queue [SEND anywhere]

上面的代码会给你5个线程。

如果你在这里什么也没得到,我的建议是点击Android视频API子论坛或邮件列表,看看是否有人有直播视频,如果有的话,他们对他们的流和解码器做了什么调整。

—附录1-5-18

我们还注意到一些流可能会填满内核套接字缓冲区并导致数据包丢失——特别是在大关键帧时。因此,如果您有较大的流,我建议使用sysctl:

检查内核缓冲区大小。
sysctl net.core.rmem_max;  sysctl net.core.rmem_default
net.core.rmem_max = 212992
net.core.rmem_default = 212992

在接收设备的/etc/sysctl.conf中添加net.core.rmem_max = whatever,并在udpsrc上设置buffer-size为这个新的最大值。您可以通过运行以下命令来判断是否仍然看到掉落:

watch -d 'cat /proc/net/snmp | grep Udp: '

…或者在你的接收管道上这样写:

出口GST_DEBUG = 2, rtpjitterbuffer: 5

gst-launch-1.0 udpsrc port=5100缓冲区大小= 825984 !应用程序/x-rtp编码名称= H264载荷= 96 !Rtpjitterbuffer延迟=200 !rtph264depay !h264parsedisable-passthrough = true !队列!Avdec_h264 output-corrupt=true !队列!videoconvert !Ximagesink 2>&1 | grep -i "buffer discon

—附录1-11-19

如果您有办法浏览专利情况,Cisco的open264库工作得非常好。它非常适合直播。

https://github.com/cisco/openh264

https://www.openh264.org/BINARY_LICENSE.txt

gst-plugins-bad下有GStreamer插件

当涉及到流媒体和视频编码/解码时,我不是专家,并且让应用程序达到一个工作点已经是一场噩梦:/如果H264应该是不合适的,我们可以切换到gstreamer支持的任何其他编解码器

这很可能不是编解码器相关的问题:编解码器引入的延迟发生在使用b帧时,IIRC x264在零延迟模式下不使用b帧。

实时流应用程序中的其他延迟是您的

  • 编码延迟(例如,如果你的手机编码数据不够快,你可以尝试降低分辨率/质量/比特率)
  • 网络(这在局域网中应该不是问题)
  • 抖动/播放缓冲区

我建议看看播放缓冲区,也许gstreamer有办法设置持续时间?此外,播放缓冲区的实现在实时程度上起着很大的作用,例如在旧版本的VLC中,可以将网络缓存参数设置得非常低,例如在100ms的量级。然而,在当前版本的VLC中,这会导致视频无法播放,因为数据"到达"晚了。另一方面,ffmpeg更适合低延迟的实时数据回放。我不确定gstreamer如何比较。

您可以尝试~100ms,然后根据它的表现进行调整。当然,这是假设您可以在gstreamer中设置此参数。

最新更新