-
Господа, подскажите, какой компонент использовать для получения MJPEG over HTTP и каким образом лучше этот поток обработать? Для получения отдельного кадра я использовал TidHTTP (инди). memorystream:=TMemoryStream.Create;
Jpeg:=TJpegImage.create;
IdHTTP1.Get('http://192.168.1.99/snapshot.cgi',memorystream);
jpeg.LoadFromStream(memorystream);
С этим проблем нет. Когда отправляешь запрос: http://192.168.1.99/videostream.cgi, в ответ приходит поток данных: --myboundary (разделитель)
Content-Type: image/jpeg
Content-Length: (размер кадра)
<JPEG image data>
--myboundary
Content-Type: image/jpeg
Content-Length:
<JPEG image data>
и т.д...
Мне нужно вытаскивать из этого потока кадры. Но у меня не получается используя TidHTTP очищать memorystream, чтобы избежать переполнения памяти уже обработанными кадрами, да и по правде сказать, ума не преложу, как их считать из него... В идеале, хотелось бы определять в потоке начало изображения (FFD8), читать далее во времнную Tmemorystream до конца (FFD9) и кидать его в массив TJpegImage для дальнейшего вывода на форму и записи. После, освобождать (free) временную переменную. Мастера, подскажите, как реализовать сей механизм или предложите более правильный путь.
-
1. Создаешь мемстрим
2. Читаешь 4 строки заголовка ответа
--myboundary (разделитель) Content-Type: image/jpeg Content-Length: (размер кадра) <JPEG image data>
3. имея (размер кадра) читаешь в мемстрим не более (размер кадра) байт 4. обрабатываешь меместрим 5. чистишь мем стрим 6. гоуту 1
-
При очистке мем стрима возникает исключение, по всей видимости потому, что он уже используется TidHTTP. IdHTTP1.Get(' http://192.168.1.99/videostream.cgi',memorystream); Может мне подключаться к камере через другой компонент, где будет возможность более детального управления потоком данных? Или просто по достижению чтения определённого числа элементов вызывать TIdHTTP1.EndWork(); а потом заново отправлять запрос? - во втором случае будут провалы в кадрах...
-
-
Ну видимо на такой, что я несколько быдлокодер. ))) Не подскажете, какой использовать правильно?
-
Интересно
-
Ау, Сергей, куда вы пропали?
-
Ответ на свой вопрос нашёл. Использовал TClientSocket. Всё стало просто и понятно. Вот, для интересующихся набросал быдлокод пример. Это только для ознакомления с механизмом. Здесь конечно стоит работать в отдельном потоке и читать данные не в строку->мемстрим.
procedure TForm1.Button1Click(Sender: TObject); begin
ClientSocket1.Host:=Edit1.Text; ClientSocket1.Open; mem:=tmemorystream.Create; jpeg:=tjpegimage.create; f:=false; end;
procedure TForm1.Button2Click(Sender: TObject); begin
ClientSocket1.Socket.SendText('GET /videostream.cgi HTTP/1.1'+#13+#10+
'Authorization: Basic ' + encodestring('admin:admin')+#13+#10+#13+#10);
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket); //событие приёма данных
var s:string;
begin
Application.ProcessMessages; //Обработка сообщений программы (для исключения зависания)
s:=socket.ReceiveText; //принятая порция данных
start:=pos('яШяа',s); if start>0 then
begin
if mem.Size>0 then
Begin
mem.Position := 0;
jpeg.LoadFromStream(mem);
mem.Clear;
Image.Canvas.Lock;
try
Image.Picture.Bitmap.assign(JPEG); finally
Image.Canvas.Unlock;
end;
end;
delete(s,1,start-1); mem.Write(s[1],length(s)); f:=true;
end else
if f then
mem.Write(s[1],length(s));
end;
-
Добрый день, парюсь с ip-камерами axis, но через ВинСок АПИ, так вот какая засада... не могу понять где... JPEG Error #51... может поможет кто? <procedure TForm1.Button1Click(Sender: TObject);
Const
C_WORD:Cardinal = 65536;
var
sock: TSocket;
buf: array [0..65535] of Char;
tmp,S: String;
RcvLen: Integer;
host: PHostEnt;
addr: sockaddr_in;
ip: pInteger;
d: WSAData;
Start,Finish:Cardinal;
Jpg:TJpegImage;
f:boolean;
mem:tmemorystream;
z:cardinal;
sX:Cardinal;
begin
mem:=tmemorystream.Create;
jpg:=tjpegimage.create;
f:=false;
z:=100; WSAStartup($0101, d);
sock := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
host := gethostbyname('192.168.0.90');
ip := pInteger(host.h_addr_list^);
addr.sin_family := AF_INET;
addr.sin_port := htons(80);
addr.sin_addr.S_addr := ip^;
connect(sock, addr, sizeof(addr));
tmp := 'GET /mjpg/video.mjpg HTTP/1.1'+#13+#10+'Authorization: Basic ' + encodestring('admin:12345')+#13+#10+#13+#10;
send(sock, tmp[1], length(tmp), 0);
tmp := '';
ZeroMemory(@buf, C_WORD);
RcvLen := recv(Sock, buf, C_WORD, 0);
while RcvLen > 0 do begin
Tmp := Tmp + Copy(buf, 0, RcvLen);
Memo1.Lines.Add(tmp);
RcvLen := recv(sock, buf, C_WORD, 0);
sX:=Pos('Content-Length:',tmp);
if sX>0 then
begin
sX:=StrToInt(Copy(tmp,sX+16,5));
end;
s:='';
start:=pos('яШяа',tmp);
if start>0 then
begin
if start>1 then delete(tmp,1,start-1);
finish:=pos('--myboundary',tmp);
if finish>0 then
begin
s:=Copy(tmp,1,finish-1);
delete(tmp,1,finish+5);
end;
end;
if s<>'' then
begin
mem.Clear;
mem.Write(s[1],length(s));
mem.Position := 0;
jpg.LoadFromStream(mem); Image.Canvas.Lock;
try
application.processmessages;
Image.Picture.Bitmap.assign(JPG);
finally
Image.Canvas.Unlock;
end;
end;
dec(z);
if z=0 then break;
end;
WSACleanup();
mem.Free;
jpg.Free;
end;/CODE>
А данные которые я получаю выглядят так:
HTTP/1.0 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: Thu, 01 Dec 1994 16:00:00 GMT
Connection: close
Content-Type: multipart/x-mixed-replace; boundary=--myboundary
--myboundary
Content-Type: image/jpeg
Content-Length: 36441
яШяа%lЫДГ™Ы±ЗчGo^NЁкsI©Я,г8UПъТ?„І;љ«Щя±Ґу®SqG<ЉtnсЙ”8'·cMЗзUµKСaҐЬЭqѕ4щоЗ…™Q Ѕф%«и#л±jqgс[Д„Зq,њ°§?©ПzЩЌH;’PСАфЇ.?4*Ў•Ђ#p<њх®ѓIсoцeјknVm‘NМэ±ЬQMєќКЇ=йЊ?–d1їњЈ¶zЉ09q[§ФИ¬Гv</кETo} jъ &» ·pOјHLwЙЛБКsъњчЁФѓ№%
тШѓBЄЩX7ЙПZи4џяТsкЂэ‰[6с0жvмqэСЫЧЇNЁк“I©Я,г8UПъТ?„І;љ«ЩядэiшзҐ%+Дs<GЎеЇйЉ”цвЈёR J –Z—!Ђ+ИnAх¤mлЫ>”фҐЗQF3юzРqљ1ЗшРМ‰чЋ)ћao»v<Rю¤SZEC‚A>ѓ“MШнчЯЏAАҐ
Ђ1Ћ‡†&й@O~х‰?‰ЬIОy9©ї1HFTЊuўБqиКPA!ЬЉЭыэiq@ Љ)qF(ґbќъRPH¤§bЊPw=iЁrѕгIЉЏоИGf SH§љm--myboundary
Content-Type: image/jpeg
Content-Length: 36525
и т.д.
-
CL CR, ESC надо обрабатывать должным образом.
-
unit uMultipartHTTPStream;
interface
uses
System.SysUtils, System.Classes,
SysUtils, Classes,
uFlexibleMemoryStream;
const
CR: AnsiChar = #13;
LF: AnsiChar = #10;
ContentLengthMatch: AnsiString = 'content-length:';
ContentTypeMatch: AnsiString = 'content-type:';
MAX_BUFFER_SIZE = 1024 * 1024;
type
TMultipartHTTPStreamState = (mhssSubHeaders, mhssSubHeadersCont, mhssContent);
TOnDataPartEvent = procedure(ASender: TObject; const AContentType: string; AData: PAnsiChar; ADataLen: LongInt) of object;
TOnSubHeadersAvailable = procedure(ASender: TObject; ASubHeaders: TStrings) of object;
TMultipartHTTPStream = class(TFlexibleMemoryStream)
strict private
FBoundary: AnsiString;
FStreamState: TMultipartHTTPStreamState;
FOnDataPart: TOnDataPartEvent;
FOnSubHeadersAvailable: TOnSubHeadersAvailable;
FSubHeaders: TStrings;
FContentLength: Integer;
FContentType: string;
procedure ProcessResponse;
function FindCRLF(AOffset: NativeInt): NativeInt;
function CRLFLen(AOffset: NativeInt): NativeInt;
procedure ParseSubHeaders;
protected
procedure DoDataPart(const AContentType: string; AData: PAnsiChar; ADataLen: LongInt); virtual;
procedure DoSubHeadersAvailable(ASubHeaders: TStrings); virtual;
public
constructor Create(const ABoundary: string; AOnDataPartEvent: TOnDataPartEvent = nil);
destructor Destroy; override;
procedure Reset;
function Write(const ABuffer; ACount: Longint): Longint; override;
property Boundary: AnsiString read FBoundary write FBoundary;
property SubHeaders: TStrings read FSubHeaders;
property OnDataPartEvent: TOnDataPartEvent read FOnDataPart write FOnDataPart;
property OnSubHeadersAvailable: TOnSubHeadersAvailable read FOnSubHeadersAvailable write FOnSubHeadersAvailable;
end;
implementation
resourcestring
rsTooLargeBufferSize = 'Too large a buffer: %d';
constructor TMultipartHTTPStream.Create(const ABoundary: string; AOnDataPartEvent: TOnDataPartEvent = nil);
begin
inherited Create;
FSubHeaders := TStringList.Create;
FBoundary := AnsiString(ABoundary);
FOnDataPart := AOnDataPartEvent;
Reset;
end;
destructor TMultipartHTTPStream.Destroy;
begin
FSubHeaders.Free;
inherited Destroy;
end;
procedure TMultipartHTTPStream.DoDataPart(const AContentType: string; AData: PAnsiChar; ADataLen: Integer);
begin
if Assigned(FOnDataPart) then
FOnDataPart(Self, AContentType, AData, ADataLen);
end;
procedure TMultipartHTTPStream.DoSubHeadersAvailable(ASubHeaders: TStrings);
begin
if Assigned(FOnSubHeadersAvailable) then
FOnSubHeadersAvailable(Self, ASubHeaders);
end;
function TMultipartHTTPStream.FindCRLF(AOffset: NativeInt): NativeInt;
var
P, K: PAnsiChar;
begin
Result := -1;
P := Head + AOffset;
K := Tail - 2;
while P <= K do
begin
if ((P[0] = CR) or (P[0] = LF)) then
begin
Result := P - Head;
Break;
end;
Inc(P);
end;
end;
function TMultipartHTTPStream.CRLFLen(AOffset: NativeInt): NativeInt;
var
P, K: PAnsiChar;
begin
Result := 0;
P := Head + AOffset;
K := Tail - 2;
while P <= K do
begin
if (P[0] = CR) or (P[0] = LF) then
Inc(Result)
else
Break;
Inc(P);
end;
end;
-
procedure TMultipartHTTPStream.ProcessResponse;
var
LineEnd, CRLFCount: Integer;
Header: AnsiString;
BoundaryPos: Integer;
CRLFPtr: PAnsiChar;
begin
while True do
case FStreamState of
mhssSubHeaders:
begin
FSubHeaders.Clear;
FContentLength := 0;
FContentType := '';
FStreamState := mhssSubHeadersCont;
end;
mhssSubHeadersCont:
begin
CRLFCount := CRLFLen(0);
if CRLFCount > 0 then
begin
CRLFPtr := Head;
Consume(CRLFCount);
if (CRLFCount >= 4) or
((CRLFCount >= 2) and (CRLFPtr[0] = LF) and (CRLFPtr[1] = LF)) and (FSubHeaders.Count > 0) then
begin
FStreamState := mhssContent;
ParseSubHeaders;
DoSubHeadersAvailable(FSubHeaders);
Continue;
end;
end;
LineEnd := FindCRLF(0);
if LineEnd > 0 then
begin
SetLength(Header, Integer(LineEnd));
Move(Head^, Header[1], LineEnd);
Consume(LineEnd);
FSubHeaders.Add(AnsiLowerCase(string(Header)));
end
else
Break;
end;
mhssContent:
if FContentLength > 0 then
begin
if Size >= FContentLength then
begin
DoDataPart(FContentType, Head, FContentLength);
Consume(FContentLength);
FStreamState := mhssSubHeaders;
end
else
Break;
end
else
begin
BoundaryPos := Pos(FBoundary[1], Length(FBoundary), 0);
if BoundaryPos < 0 then
Break
else
begin
DoDataPart(FContentType, Head, BoundaryPos);
Consume(BoundaryPos);
FStreamState := mhssSubHeaders;
end;
end;
end;
end;
procedure TMultipartHTTPStream.ParseSubHeaders;
function ExtractContentLength(const S: string): integer;
const
Digits: set of AnsiChar = ['1', '2', '3', '4', '5' ,'6' ,'7', '8', '9', '0'];
var
i: integer;
ContentLengthStr, TempStr: string;
begin
TempStr := Trim(Copy(S,
Length(ContentLengthMatch) + 1,
Length(S) - Length(ContentLengthMatch)));
ContentLengthStr := '';
for i := 1 to Length(TempStr) do
if AnsiChar(TempStr[i]) in Digits then
ContentLengthStr := ContentLengthStr + TempStr[i]
else
Break;
Result := StrToIntDef(ContentLengthStr, 0);
end;
function ExtractContentType(const S: string): string;
begin
Result := Trim(Copy(S,
Length(ContentTypeMatch) + 1,
Length(S) - Length(ContentTypeMatch)));
end;
var
i: integer;
begin
for I := 0 to FSubHeaders.Count - 1 do
begin
if System.Pos(string(ContentLengthMatch), FSubHeaders[i]) > 0 then
begin
FContentLength := ExtractContentLength(FSubHeaders[i]);
Break;
end;
end;
for I := 0 to FSubHeaders.Count - 1 do
begin
if System.Pos(string(ContentTypeMatch), FSubHeaders[i]) > 0 then
begin
FContentType := ExtractContentType(FSubHeaders[i]);
Break;
end;
end;
end;
procedure TMultipartHTTPStream.Reset;
begin
Clear;
FSubHeaders.Clear;
FStreamState := mhssSubHeaders;
FContentLength := 0;
FContentType := '';
end;
function TMultipartHTTPStream.Write(const ABuffer; ACount: Integer): Longint;
begin
Result := inherited Write(ABuffer, ACount);
if Size >= MAX_BUFFER_SIZE then
raise Exception.CreateFmt(rsTooLargeBufferSize, [Size]);
ProcessResponse;
end;
end.
-
-
> IdHTTP1.Get('http://192.168.1.99/videostream.cgi',memorystream); > > > > Накой же шиш ты именно ЭТОТ метод используешь ?
С правильным стримом - это метод, который Реми Лебо и рекомендует. Только вызывать это дело надо в отдельном потоке, а остановка приема - IdHTTP.Socket.Close; из другого потока
-
DVM, а не могли бы пример использования MultipartHTTPStream показать, а то я не очень разобрался с чем его едят...
-
> Goodle © (31.03.13 09:32) [14]
Тут все довольно просто дальше. Берете TIdHTTP и вызываете его метод Get, передав экземпляр этого MultipartHTTPStream в качестве последнего параметра. Предварительно для MultipartHTTPStream надо задать обработчик события OnDataPartEvent - оно будет вызываться каждый раз по приходу очередного кадра. Собственно все. Далее кадр надо декодировать из JPEG и отобразить,тут можно использовать и стандартный TJpegImage и Intel Jpeg Library и LibJpeg, но это уже другая история. Разумеется вызов TIdHTTP.Get надо делать из дополнительного потока, иначе он заблокирует навечно основной поток программы, т.к. видеопоток никогда не закончится. Для прерывания работы метода Get в доп потоке для остановки доп потока, следует закрыть сокет принадлежащий TidHTTP из основного потока. По ссылке полностью рабочий пример (тестировался на Delphi XE2, на остальных возможно надо подкорректировать). В качестве декодера в пример использован IJL. http://yadi.sk/d/LlvLIOKH3et10
-
Спасибо, огромное! Отличный пример! Только у меня еще один вопрос, а как будет выглядеть процедура декодирования JPEG для стандартного TJPEGImege?
-
> Только у меня еще один вопрос, а как будет выглядеть процедура > декодирования JPEG для стандартного TJPEGImege?
А имеет ли смысл его использовать? Как декодер для видео TJpegImage очень медленный. IJL раза в 4 быстрее.
Но если очень хочется, то можно и его использовать, создаешь TMemoryStream, копируешь в него буфер присланный из читающего потока, загружаешь TMemoryStream в TJpegImage, затем последний отображаешь.
-
Очень извиняюсь за назойливость, но теперь другая проблема, вообщем добавилась еще одна камера, и два потока работают какое-то время, а потом вылетает ошибка IJL (кстати действительно работает быстрее на порядок), затем что-то вроде Unaut..., а за тем может вернутся в прием потока, а может и нет... Ковыряю уже неделю, то ли лыжи не едут...
-
> Goodle © (13.04.13 07:55) [18]
Выводите изображение как на канву? Так как я в примере сделал, так нельзя в реальных приложениях, синхронизация нужна между доп потоком и основным.
|