我正在使用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
或更好的Spdy
或HTTP/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/