我有一个奇怪的问题,我正在开发蓝牙相机,我们希望为世界提供一个mjpeg接口。
Mjpeg只是一个http服务器,在连接保持打开的情况下回复一个jpeg。我的服务器现在正在给我:
HTTP/1.1 200 OK Transfer-Encoding: chunked Cache-Directive: no-cache Expires: 0 Pragma-Directive: no-cache Server: TwistedWeb/10.0.0 Connection: Keep-Alive Pragma: no-cache Cache-Control: no-cache, no-store, must-revalidate; Date: Sat, 26 Feb 2011 20:29:56 GMT Content-Type: multipart/x-mixed-replace; boundary=myBOUNDARY HTTP/1.1 200 OK Transfer-Encoding: chunked Cache-Directive: no-cache Expires: 0 Pragma-Directive: no-cache Server: TwistedWeb/10.0.0 Connection: Keep-Alive Pragma: no-cache Cache-Control: no-cache, no-store, must-revalidate; Cate: Sat, 26 Feb 2011 20:29:56 GMT Content-Type: multipart/x-mixed-replace; boundary=myBOUNDARY
然后为每一帧:
--myBOUNDARY Content-Type: image/jpeg Content-Size: 25992 BINARY JPEG CONTENT..... (new line)
我为它制作了一个Flash客户端,因此我们可以在任何设备上使用相同的代码,服务器使用twisted实现Python,并且针对Android等等,Android中的问题是Google 忘记了包括mjpeg支持....这个客户端正在使用URLStream。
代码是这样的:
package net.aircable { import flash.errors.*; import flash.events.*; import flash.net.URLRequest; import flash.net.URLRequestMethod; import flash.net.URLRequestHeader; import flash.net.URLStream; import flash.utils.ByteArray; import flash.utils.Dictionary; import flash.system.Security; import mx.utils.Base64Encoder; import flash.external.ExternalInterface; import net.aircable.XHRMultipartEvent; public class XHRMultipart extends EventDispatcher{ private function trc(what: String): void{ //ExternalInterface.call("console.log", what); //for android trace(what); } private var uri: String; private var username: String; private var password: String; private var stream: URLStream; private var buffer: ByteArray; private var pending: int; private var flag: Boolean; private var type: String; private var browser: String; private function connect(): void { stream = new URLStream(); trc("connect") var request:URLRequest = new URLRequest(uri); request.method = URLRequestMethod.POST; request.contentType = "multipart/x-mixed-replace"; trc(request.contentType) /* request.requestHeaders = new Array( new URLRequestHeader("Content-type", "multipart/x-mixed-replace"), new URLRequestHeader("connection", "keep-alive"), new URLRequestHeader("keep-alive", "115")); */ trace(request.requestHeaders); trc("request.requestHeaders") configureListeners(); try { trc("connecting"); stream.load(request); trc("connected") } catch (error:Error){ trc("Unable to load requested resource"); } this.pending = 0; this.flag = false; this.buffer = new ByteArray(); } public function XHRMultipart(uri: String = null, username: String = null, password: String = null){ trc("XHRMultipart()"); var v : String = ExternalInterface.call("function(){return navigator.appVersion+'-'+navigator.appName;}"); trc(v); v=v.toLowerCase(); if (v.indexOf("chrome") > -1){ browser="chrome"; } else if (v.indexOf("safari") > -1){ browser="safari"; } else { browser=null; } trc(browser); if (uri == null) uri = "../stream?ohhworldIhatethecrap.mjpeg"; this.uri = uri; connect(); } private function configureListeners(): void{ stream.addEventListener(Event.COMPLETE, completeHandler, false, 0, true); stream.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler, false, 0, true); stream.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler, false, 0, true); stream.addEventListener(Event.OPEN, openHandler, false, 0, true); stream.addEventListener(ProgressEvent.PROGRESS, progressHandler, false, 0, true); stream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler, false, 0, true); } private function propagatePart(out: ByteArray, type: String): void{ trc("found " + out.length + " mime: " + type); dispatchEvent(new XHRMultipartEvent(XHRMultipartEvent.GOT_DATA, true, false, out)); } private function readLine(): String { var out: String = ""; var temp: String; while (true){ if (stream.bytesAvailable == 0) break; temp = stream.readUTFBytes(1); if (temp == "\n") break; out+=temp; } return out; } private function extractHeader(): void { var line: String; var headers: Object = {}; var head: Array; while ( (line=readLine()) != "" ){ if ( stream.bytesAvailable == 0) return; if (line.indexOf('--') > -1){ continue; } head = line.split(":"); if (head.length==2){ headers[head[0].toLowerCase()]=head[1]; } } pending=int(headers["content-size"]); type = headers["content-type"]; if ( pending > 0 && type != null) flag = true; trc("pending: " + pending + " type: " + type); } private function firefoxExtract(): void { trc("firefoxPrepareToExtract"); if (stream.bytesAvailable == 0){ trc("No more bytes, aborting") return; } while ( flag == false ) { if (stream.bytesAvailable == 0){ trc("No more bytes, aborting - can't extract headers"); return; } extractHeader() } trc("so far have: " + stream.bytesAvailable); trc("we need: " + pending); if (stream.bytesAvailable =0; x-=1){ buffer.position=x; buffer.readBytes(temp, 0, 2); // check if we found end marker if (temp[0]==0xff && temp[1]==0xd9){ end=x; break; } } trc("findImageInBuffer, start: " + start + " end: " + end); if (start >-1 && end > -1){ var output: ByteArray = new ByteArray(); buffer.position=start; buffer.readBytes(output, 0 , end-start); propagatePart(output, type); buffer.position=0; // drop everything buffer.length=0; } } private function safariExtract(): void { trc("safariExtract()"); stream.readBytes(buffer, buffer.length); findImageInBuffer(); } private function chromeExtract(): void { trc("chromeExtract()"); stream.readBytes(buffer, buffer.length); findImageInBuffer(); } private function extractImage(): void { trc("extractImage"); if (browser == null){ firefoxExtract(); } else if (browser == "safari"){ safariExtract(); } else if (browser == "chrome"){ chromeExtract(); } } private function isCompressed():Boolean { return (stream.readUTFBytes(3) == ZLIB_CODE); } private function completeHandler(event:Event):void { trc("completeHandler: " + event); //extractImage(); //connect(); } private function openHandler(event:Event):void { trc("openHandler: " + event); } private function progressHandler(event:ProgressEvent):void { trc("progressHandler: " + event) trc("available: " + stream.bytesAvailable); extractImage(); if (event.type == ProgressEvent.PROGRESS) if (event.bytesLoaded > 1048576) { //1*1024*1024 bytes = 1MB trc("transfered " + event.bytesLoaded +" closing") stream.close(); connect(); } } private function securityErrorHandler(event:SecurityErrorEvent):void { trc("securityErrorHandler: " + event); } private function httpStatusHandler(event:HTTPStatusEvent):void { trc("httpStatusHandler: " + event); trc("available: " + stream.bytesAvailable); extractImage(); //connect(); } private function ioErrorHandler(event:IOErrorEvent):void { trc("ioErrorHandler: " + event); } } };
客户端在Firefox上工作得很好,我得到了所有的http标题:
--myBOUNDARY Content-Type: image/jpeg Content-Size: 25992
所以我使用content-size来知道要前进多少字节。在IE8中也是如此(即使有错误的IE兼容!)
在Safari上它的工作方式有点不同(也许是webkit这样做)我没有得到http片段只是二进制内容,这迫使我在缓冲区中搜索帧的开始和结束。
问题是Chrome,无论信不信,它都无法正常工作。有些奇怪的事情发生了,显然我得到了第一个tcp / ip包然后由于某种原因Chrome决定关闭连接,日志的输出是这样的:
XHRMultipart() 5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.114 Safari/534.16-Netscape chrome connect multipart/x-mixed-replace request.requestHeaders connecting connected openHandler: [Event type="open" bubbles=false cancelable=false eventPhase=2] openHandler: [Event type="open" bubbles=false cancelable=false eventPhase=2] progressHandler: [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=3680 bytesTotal=0] available: 3680 extractImage chromeExtract() findImageInBuffer, start: 0 end: -1 httpStatusHandler: [HTTPStatusEvent type="httpStatus" bubbles=false cancelable=false eventPhase=2 status=200 responseURL=null] available: 0 extractImage chromeExtract() findImageInBuffer, start: 0 end: -1
我不应该在服务器关闭连接之前获取httpStatus,而不是这里的情况。
请不要告诉我使用HTML5 Canvas或Video我都是这样做的,问题是我们希望这个应用程序在许多操作系统中运行并为所有它们编译视频编码器(例如ffmpeg)将不会工作更轻松。另外我们想提供SCO音频,它只是一个PCM流,所以我不能使用普通的mjpeg。 Canvas太慢了,我测试过,特别是在Android上。
答案 0 :(得分:3)
最后我发现了问题!
根据Chrome的Flash插件,内容类型有误,正确的是:
Content-Type: multipart/x-mixed-replace
而不是
Content-Type: multipart/x-mixed-replace; boundary=myBOUNDARY
所以我的服务器现在根据请求参数发送或不发送边界。
答案 1 :(得分:1)
这对我来说也不起作用 - 最终我使用chrome进行了工作:
Content-Type:text / html; boundary = - myboundary
我生命中有6个小时:(