У меня странная проблема, я работаю над камерой Bluetooth, мы хотим предоставить миру интерфейс mjpeg.
Mjpeg - это просто http-сервер, отвечающий один jpeg за другим с помощью keept соединенияоткрыть.Мой сервер сейчас дает мне:
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-клиент, чтобы мы могли использовать один и тот же код на любом устройстве., сервер реализован на 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
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
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
Поэтому я использую размер содержимого, чтобы узнать, сколько байтов осталосьвперед.То же самое происходит в IE8 (даже глючный IE совместим!)
В Safari это работает немного по-другому (возможно, это делает webkit). Я не получаю кусок http, только бинарный контент, который вынуждает меня искатьиз-за буфера для начала и конца кадра.
Проблема в Chrome, верите или нет, он не работает.Что-то странное происходит, по-видимому, я получаю первый пакет tcp / ip, а затем по какой-то причине Chrome решает закрыть соединение, вывод журнала таков:
*1024*
Я не должен получатьhttpStatus до тех пор, пока сервер не закроет соединение, что здесь не так.
Пожалуйста, не говорите мне использовать HTML5 Canvas или Video. Я все так готов, проблема в том, что мы хотим, чтобы это приложение работало во многих ОСи компиляция видеокодера для всех них (например, ffmpeg) не облегчит работу.Также мы хотим предоставить аудио SCO, который является просто потоком PCM, поэтому я не могу использовать обычный mjpeg.Холст слишком медленный, я проверял это, особенно на Android.