Конференция "Media" » Обработка потока с ip камеры [D7, WinXP]
 
  • Eternal_Stranger © (01.12.11 01:54) [0]
    Господа, подскажите, какой компонент использовать для получения 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) временную переменную.
    Мастера, подскажите, как реализовать сей механизм или предложите более правильный путь.
  • Сергей М. © (01.12.11 11:20) [1]
    1. Создаешь мемстрим

    2. Читаешь 4 строки заголовка ответа

    --myboundary (разделитель)
    Content-Type: image/jpeg
    Content-Length: (размер кадра)
    <JPEG image data>

    3. имея (размер кадра) читаешь в мемстрим не более (размер кадра) байт
    4. обрабатываешь меместрим
    5. чистишь мем стрим
    6. гоуту 1
  • Eternal_Stranger © (01.12.11 20:36) [2]
    При очистке мем стрима возникает исключение, по всей видимости потому, что он уже используется TidHTTP.
    IdHTTP1.Get('http://192.168.1.99/videostream.cgi',memorystream);
    Может мне подключаться к камере через другой компонент, где будет возможность более детального управления потоком данных?
    Или просто по достижению чтения определённого числа элементов вызывать TIdHTTP1.EndWork(); а потом заново отправлять запрос? - во втором случае будут провалы в кадрах...
  • Сергей М. © (01.12.11 21:08) [3]

    > IdHTTP1.Get('http://192.168.1.99/videostream.cgi',memorystream);


    Накой же шиш ты именно ЭТОТ метод используешь ?
  • Eternal_Stranger © (01.12.11 22:30) [4]
    Ну видимо на такой, что я несколько быдлокодер. ))) Не подскажете, какой использовать правильно?
  • stroidomin © (02.12.11 09:48) [5]
    Интересно
  • Eternal_Stranger © (04.12.11 18:47) [6]
    Ау, Сергей, куда вы пропали?
  • Eternal_Stranger © (06.12.11 21:11) [7]
    Ответ на свой вопрос нашёл. Использовал 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);
    {/videostream.cgi - запрос потока для камеры (для своей камеры поищите в инете)
    #13+#10 - перевод каретки и новая строка
    Authorization: Basic ' + encodestring('admin:admin') - авторизация здесь кодируется base64 пользователь:пароль
    в конце запроса пустая строка
    }

    end;

    procedure TForm1.ClientSocket1Read(Sender: TObject;
     Socket: TCustomWinSocket); //событие приёма данных
     var s:string;
    begin
    Application.ProcessMessages; //Обработка сообщений программы (для исключения зависания)
    s:=socket.ReceiveText; //принятая порция данных
    start:=pos('
    яШяа',s); //Поиск начала кадра JPEG в строке (FFD8)
    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); //Запихиваем кадр на image для отображения
       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;

  • Константин (29.03.13 15:33) [8]
    Добрый день, парюсь с 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;//кол-во повторов принимающего while
     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);
               //if length(s)<5 then s:='';
             end;
         end;
      if s<>'' then
         begin
           mem.Clear;

           mem.Write(s[1],length(s));
           //Memo1.Lines.Add('@@@@@@@     '+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 Ѕф%«и#л±j qgс[Д„Зq,њ°§?©ПzЩЌH;’PСАфЇ.?4*Ў•Ђ#p<њх®ѓIсoцeјknVm‘NМэ±Ь{ЉнҐUIШВ ­µ‰ЭgљЋR[lЯ9?AюE2 ЁndЉDta•el‚=AҐ‹,L¤ї ?Эн]hгdщ<ћ(Ьr9¦дzPН%lЫДГ™Ы±ЗчGo^Ѕ: c©Н&§|qgВ®Ц‘ь#Шw5Wіюѕв-§хч‰бЛС¤Ю
    -њ=ҐЖdі|фхЏъЃх®БX0вё™­о5‹!4ТГo ѕm"6 9КсИнпф­ЯкЗQµhзщ/-Џ—qг
    к=Џ_ЖЉ±з\л~їЧжfџ+т6JХyЈЮ…{хРХћў?В№“іг}
    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
    и т.д.

  • Павиа (29.03.13 15:42) [9]
    CL CR, ESC надо обрабатывать должным образом.
  • DVM © (30.03.13 00:04) [10]

    ////////////////////////////////////////////////////////////////////////////////
    //
    //  ****************************************************************************
    //  * Модуль       : uMultipartHTTPStream.pas
    //  * Назначение   : Stream для разбора multipart/x-mixed-replace данных
    //  * Copyright    : 2012 © Дмитрий Муратов (dvmuratov@yandex.ru)
    //  ****************************************************************************
    //
    ////////////////////////////////////////////////////////////////////////////////

    {
     Данный стрим предназначен для разбора multipart/x-mixed-replace и
     multipart/mixed данных.
     Может быть использован совместно с TIdHTTP для разбора таких данных "на лету".

     1) Content-type: multipart/mixed;boundary="SomeRandomString"

     --SomeRandomString
     Content-type: text/plain

     Data for the first part
     --SomeRandomString
     Content-type: text/plain

     Data for the second part

     2) Content-type: multipart/x-mixed-replace;boundary=SomeRandomString

    }


    unit uMultipartHTTPStream;

    {$I DEFINES.INC}

    interface

    uses
    {$IFDEF HAS_UNITSCOPE}
     System.SysUtils, System.Classes,
    {$ELSE}
     SysUtils, Classes,
    {$ENDIF}
     uFlexibleMemoryStream;

    const
     CR: AnsiChar = #13;
     LF: AnsiChar = #10;

     ContentLengthMatch: AnsiString = 'content-length:';
     ContentTypeMatch: AnsiString = 'content-type:';

     MAX_BUFFER_SIZE = 1024 * 1024; // 1 Мб

    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)
     {$IFDEF SUPPORTS_STRICT}strict{$ENDIF} 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';

    { TMultipartHTTPStream }

    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;

    //------------------------------------------------------------------------------

  • DVM © (30.03.13 00:04) [11]

    //------------------------------------------------------------------------------

    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
             // Подзаголовки отделяются друг от друга либо символами CRLF либо LF
             // И до и после подзаголовков может следовать произвольное количество
             // символов CR и LF практически в любых компбинациях. Признаком того, что
             // блок подзаголовков завершился, служит, во-первых, отличное от нуля
             // количество прочитанных подзаголовков, во-вторых, либо более 4 символов CR или LF,
             // либо более 2 символов подряд LF.

             // Вычисляем сколько подряд идущих символов CR и LF в начале буфера
             CRLFCount := CRLFLen(0);
             if CRLFCount > 0 then
               begin
                 // Получаем указатель на символы CRLF
                 CRLFPtr := Head;
                 // Отбрасываем символы CR и LF
                 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:
           // Имеется заголовок с указанием размера ContentLength
           if FContentLength > 0 then
             begin
               // И буфере данных больше или равно, чем ContentLength
               if Size >= FContentLength then
                 begin
                   // Получена очередная порция данных
                   DoDataPart(FContentType, Head, FContentLength);
                   // Отбрасываем эту порцию
                   Consume(FContentLength);
                   // Переходим в состояние ожидания подзаголовков очередной порции
                   FStreamState := mhssSubHeaders;
                 end
               else
                // Данных в буфере пока еще меньше, чем указано в ContentLength выходим
                Break;
             end
           else
             begin
               // Если ContentLength неизвестен, то копим данные до появления
               // очередного разделителя
               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;

     // Content-Length: 051840 Stamp:07dc0c1c 000e1a1f 50 000004d9
     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;

     // Content-Type: image/jpeg
     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.


  • DVM © (30.03.13 00:09) [12]
  • DVM © (30.03.13 00:26) [13]

    >  IdHTTP1.Get('http://192.168.1.99/videostream.cgi',memorystream);
    >
    >
    >
    > Накой же шиш ты именно ЭТОТ метод используешь ?

    С правильным стримом - это метод, который Реми Лебо и рекомендует. Только вызывать это дело надо в отдельном потоке, а остановка приема -   IdHTTP.Socket.Close; из другого потока
  • Goodle © (31.03.13 09:32) [14]
    DVM, а не могли бы пример использования MultipartHTTPStream показать, а то я не очень разобрался с чем его едят...
  • DVM © (31.03.13 10:29) [15]

    > 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
  • Goodle © (01.04.13 10:31) [16]
    Спасибо, огромное! Отличный пример! Только у меня еще один вопрос, а как будет выглядеть процедура декодирования JPEG для стандартного TJPEGImege?
  • DVM © (01.04.13 11:40) [17]

    > Только у меня еще один вопрос, а как будет выглядеть процедура
    > декодирования JPEG для стандартного TJPEGImege?

    А имеет ли смысл его использовать? Как декодер для видео TJpegImage очень медленный. IJL раза в 4 быстрее.

    Но если очень хочется, то можно и его использовать, создаешь TMemoryStream, копируешь в него буфер присланный из читающего потока, загружаешь TMemoryStream в TJpegImage, затем последний отображаешь.
  • Goodle © (13.04.13 07:55) [18]
    Очень извиняюсь за назойливость, но теперь другая проблема, вообщем добавилась еще одна камера, и два потока работают какое-то время, а потом вылетает ошибка IJL (кстати действительно работает быстрее на порядок), затем что-то вроде Unaut..., а за тем может вернутся в прием потока, а может и нет... Ковыряю уже неделю, то ли лыжи не едут...
  • DVM © (13.04.13 14:08) [19]

    > Goodle ©   (13.04.13 07:55) [18]

    Выводите изображение как на канву? Так как я в примере сделал, так нельзя в реальных приложениях, синхронизация нужна между доп потоком и основным.
 
Конференция "Media" » Обработка потока с ip камеры [D7, WinXP]
Есть новые Нет новых   [118666   +35][b:0][p:0.013]