nginx - 在Nginx中以低客户端带宽流式传输Mjpeg

标签 nginx stream buffer live mjpeg

我正在使用Nginx流式传输MJPEG。只要客户端的带宽足够,此方法就可以正常工作。当带宽不足时,它似乎落后了约2分钟,然后跳到当前帧并再次开始回落。

有什么方法可以控制缓冲区永远不要存储超过2帧? -这样,如果客户端无法跟上,它将不会落后一到两秒?

编辑:基本上,服务器(当前在nginx反向代理后面的python龙卷风)正在以5mbit的速率发送流,客户端具有1mbit的带宽(出于争论的目的)-服务器(nginx或python)需要能够检测到这一点,并且丢帧。问题是如何?

最佳答案

这在很大程度上取决于您实际部署M-JPEG帧的方式以及是否必须使用内置的浏览器支持,或者是否可以编写自己的JavaScript。

背景

请记住,从服务器流式传输M-JPEG时,它实际上只是发送一系列JPEG文件,但作为对单个Web请求的响应。也就是说,正常的Web请求看起来像

Client             Server
   | --- Request ---> |
   |                  |
   | <-- JPEG File -- |


请求M-JPEG看起来更像

Client             Server
   | --- Request ---> |
   |                  |
   | <- JPEG part 1 - |
   | <- JPEG part 2 - |
   | <- JPEG part 3 - |


因此,问题不在于客户端缓冲,而是一个事实,即一旦启动M-JPEG,服务器将发送每个帧,即使下载每个帧所需的时间比指定的显示时间更长。

纯JS解决方案

如果您可以在应用程序中编写javascript,请考虑使应用程序的请求/响应部分明确。也就是说,对于每个所需的帧,从您的JavaScript向服务器发送对所需帧的显式请求(作为单个JPEG)。如果JavaScript开始落后,那么您有两种选择


丢帧。以所需带宽的50%运行?请求每隔一帧。
请求较小的文件。以25%的带宽运行?从服务器以50%的宽度和高度请求文件版本。


很久以前,从javascript发出额外的请求会为每个请求都需要一个新的TCP连接引入额外的开销。如果您通过Nginx在服务器上使用Keep-Alive或更好的SpdyHTTP/2,那么使用javascript发出这些请求几乎没有开销。最后,使用javascript将允许您实际对一些帧进行显式缓冲,并控制缓冲超时。

对于一个非常基本的示例(为便于说明,使用jQuery imgload plugin):

var timeout = 250; // 4 frames per second, adjust as necessary
var image = // A reference to the <img> tag for display
var accumulatedError = 0; // How late we are

var doFrame = function(frameId) {
    var loaded = false, timedOut = false, startTime = (new Date()).getTime();
    $(image).bind("load", function(e) {
        var tardiness = (new Date()).getTime() - startTime - timeout;
        accumulatedError += tardiness; // Add or subtract tardiness
        accumulatedError = Math.max(accumulatedError, 0); // but never negative
        if (!timedOut) {
            loaded = true;
        } else {
            doFrame(frameId + 1);
        }
    }
    var timeCallback = function() {
        if (loaded) {
            doFrame(frameId + 1); // Just do the next frame, we're on time
        } else {
            timedOut = true;
        }
    }
    while(accumulatedError > timeout) {
        // If we've accumulated more than 1 frame or error
        // skip a frame
        frameId += 1;
        accumulatedError -= timeout;
    }
    // Load the image
    $(image).src = "http://example.com/images/frame-" + frameId + ".jpg";
    // Start the display timer
    setTimeout(timeCallback, timeout);
}

doFrame(1); // Start the process


为了使此代码真正无缝,您可能需要使用两个图像标签,并在加载完成后交换它们,以便没有可见的加载工件(例如Double Buffering)。

Websocket解决方案

如果您无法在应用程序中编写javascript,或者需要较高的帧速率,则需要修改服务器以检测其发送帧的速率。例如,假设帧速率为4 fps,则写出每个帧花费的时间超过250毫秒,则丢弃下一帧并将250毫秒添加到帧偏移缓冲区中。不幸的是,这仅修改了发送帧的速率。从长远来看,虽然服务器发送的速率和客户端接收的速率相似,但由于TCP缓冲等原因,它们在短期内可能会有很大差异。

但是,如果您可以限制自己使用大多数浏览器的最新版本(请参见support here),则Websockets应该提供一种很好的机制,用于将服务器上的帧发送到客户端通道,以及将客户端上的性能信息发送回服务器通道。另外,Nginx能够proxying Websockets

在客户端,建立一个Websocket。开始从服务器发送jpeg帧的速度稍快于所需的显示速率(例如,对于每秒30帧,每20-25毫秒发送一个帧,可能是一个不错的开始位置,如果服务器上有一些缓冲区-没有缓冲区,以最大可用帧速率发送)。在客户端上完全接收到每个帧之后,将消息发送回服务器,其中包含帧ID以及客户端在帧之间经过了多少时间。

使用从客户端收到的帧之间的时间,开始使用与先前示例相同的方法在服务器上累积accumulatedError变量(从实际帧间时间中减去所需的帧间时间)。 accumulatedError到达一帧(或什至接近一帧)时,跳过发送一帧并重置accumulatedError

但是请注意,此解决方案可能会在视频播放中造成一些麻烦,因为仅在绝对必要时才跳过一帧,这意味着不会以常规节奏跳过帧。理想的解决方案是将帧发送计时器视为PID控制变量,并使用实际帧接收时间作为PID loop的反馈。从长远来看,PID循环可能会提供最稳定的视频演示,但是accumulatedErrror方法仍应提供令人满意的(且相对简单)的解决方案。

关于nginx - 在Nginx中以低客户端带宽流式传输Mjpeg,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38414047/

相关文章:

node.js - Nginx 反向代理 + ExpressJS + Angular + SSL 配置问题

nginx - 需要来自 Nginx 的证书链(在传入接口(interface)上)

tomcat/Liferay 没有重定向到 https 主题和 Hook

c# - 直到文件完全下载后,使用HttpClient请求的音频流才会播放

nginx - 如何在nginx中为多个网站创建反向代理

Android 在 BitmapFactory.decodeStream() 中重用流

php - 使用 PHP SSH2 打开流时使用 ~/.ssh/config

java - 同步 FIFO 缓冲区使用

database - 内存高效的大型数据集流式传输到 S3

c - 如何正确读取标准输入的任意长度输入?