Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Передача видео и звука с помощью Indy [D7]


Страницы:   1 2 3 4 5 6 7 8 9 10

3asys ©   (2011-11-26 15:48:00) 

На клиенте получаю видео и звук с web-камеры (с встроенным микрофоном) с помощью DirectShow.
Нужно передать их серверу для рассылки другим клиентам.
Видео и звук с Web-камеры получаю стандартным способом, описанным в примерах к DSPack, - примерно так:

var
 multiplexer: IBaseFilter;
 Writer: IFileSinkFilter;
 PinList: TPinList;
 i: integer;
begin
with MainForm do
begin
 CaptureGraph.Active := true;
 if AudioSourceFilter.FilterGraph <> nil then
 begin
   PinList := TPinList.Create(AudioSourceFilter as IBaseFilter);
   i := 0;
   while i < PinList.Count do
     if PinList.PinInfo[i].dir = PINDIR_OUTPUT then
       begin
         if AudioFormats.ItemIndex <> -1 then
           with (PinList.Items[i] as IAMStreamConfig) do
             SetFormat(AudioMediaTypes.Items[AudioFormats.ItemIndex].AMMediaType^);
         PinList.Delete(i);
       end else inc(i);
   if InputLines.ItemIndex <> -1 then
     with (PinList.Items[InputLines.ItemIndex] as IAMAudioInputMixer) do
       put_Enable(true);
   PinList.Free;
 end;

 if VideoSourceFilter.FilterGraph <> nil then
 begin
   PinList := TPinList.Create(VideoSourceFilter as IBaseFilter);
   if VideoFormats.ItemIndex <> -1 then
     with (PinList.First as IAMStreamConfig) do
       SetFormat(VideoMediaTypes.Items[VideoFormats.ItemIndex].AMMediaType^);
   PinList.Free;
 end;

 with CaptureGraph as IcaptureGraphBuilder2 do
 begin
   // set the output filename
   SetOutputFileName(MEDIASUBTYPE_Avi, PWideChar(CapFile), multiplexer, Writer);

   if VideoSourceFilter.BaseFilter.DataLength > 0 then
     RenderStream(@PIN_CATEGORY_PREVIEW, nil, VideoSourceFilter as IBaseFilter,
       nil , form1.VideoWindow as IBaseFilter);

   if VideoSourceFilter.FilterGraph <> nil then
     RenderStream(@PIN_CATEGORY_CAPTURE, nil, VideoSourceFilter as IBaseFilter,
       nil, multiplexer as IBaseFilter);

   if AudioSourceFilter.FilterGraph <> nil then
   begin

     RenderStream(nil, nil, AudioSourceFilter as IBaseFilter,
       nil, multiplexer as IBaseFilter);
   end;
 end;
 CaptureGraph.Play;

В RenderStream получаем потоки, но как их передать через IdTCPClient, IdTCPServer я сообразить не могу.
Как это сделать?


DVM   (2011-12-11 22:07:00) [61]

И еще VideoStream очищай каждый раз перед записью в него


DVM   (2011-12-11 22:03:00) [62]


> 3asys ©   (11.12.11 21:53) [125]

А FSendNewFrameMessage у тебя чему равен?


3asys   (2011-12-11 21:53:00) [63]

Создаю сообщение в соответствии с [102]:

SendMessage(frmClient.Handle, FSendNewFrameMessage, 0, Longint(FrameData));

Принимаю сообщение в модуле:

unit uclnt;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, ExtCtrls, ComCtrls, ToolWin, StdCtrls, jpeg,

 IdHTTP, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,

 uClientFunctions, uBuffer;

type
 TfrmClient = class(TForm)
   ToolBar1: TToolBar;
   tbtnStart: TToolButton;
   Splitter1: TSplitter;
   Image: TImage;
   Memo1: TMemo;
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
   procedure tbtnStartClick(Sender: TObject);
//    procedure GetVideoMsg(var Msg: TMsg; var Handled: Boolean);
   procedure WndProc(var Msg: TMessage); override;
 private
   { Private declarations }
 public
   { Public declarations }
 end;

 TFrameData = record
   FrameData: PAnsiChar;
   FrameDataLen: integer;
 end;
 PFrameData = ^TFrameData;

var
 frmClient: TfrmClient;
 Host: string;
 Port: integer;
 Path: string;
 UserName: string;
 Password: string;
 VideoThread : THTTPInputThread;
 VideoStream : TMemoryStream;

implementation

{$R *.dfm}

procedure TfrmClient.FormCreate(Sender: TObject);
begin
 Host := '127.0.0.1';
 Port := 8081;
 Path := '/';
 UserName := '';
 Password := '';
 VideoStream:=TMemoryStream.Create;
end;

procedure TfrmClient.tbtnStartClick(Sender: TObject);
begin
 VideoThread := THTTPInputThread.Create(Host, Port, Path, UserName, Password);
end;

procedure TfrmClient.WndProc(var Msg: TMessage);
var
 FrameData: PFrameData;
 jpeg : TJPEGImage;
 b : TBitmap;
begin
 if Msg.Msg = FSendNewFrameMessage then
 begin
   FrameData := PFrameData(Msg.Lparam);
   VideoStream.WriteBuffer(FrameData^.FrameData, FrameData^.FrameDataLen);

   jpeg:=TJPEGImage.Create;
   VideoStream.Position:=0;
   jpeg.LoadFromStream(VideoStream);
   b:=TBitmap.Create;
   b.Assign(jpeg);
   Image.Picture.Assign(b);
   jpeg.Free;
   b.Free;
 end
 else
   inherited;
end;

procedure TfrmClient.FormDestroy(Sender: TObject);
begin
 VideoThread.Terminate;
 VideoThread.WaitFor;
 FreeAndNil(VideoThread);
end;

end.

При выполнении строки

VideoStream.WriteBuffer(FrameData^.FrameData, FrameData^.FrameDataLen);

выдается сообщение "Access Violation". Иногда эту строку проходит благополучно, но тогда при выполнении строки

jpeg.LoadFromStream(VideoStream);

выдается сообщение "Exception class EJPEG with message 'JPEG error #53' ".
Точного значения этой ошибки найти не удалось (но, насколько я понял - общий смысл в несоответствии данных формату jpeg).
На входе (от сервера) во время тестирования - видео с web-камеры.
Что тут теперь может быть?


DVM   (2011-12-11 19:19:00) [64]


> Что я делаю неправильно?

с какого перепуга ты приводишь FBuffer типа TBuffer к PFrameData ???

в [102] же все написано было. Не торопись, сделай сначала так как я написал, потом будешь пытаться улучшать.

Это раз.

type

TFrameData = record
  FrameData: PAnsiChar;
  FrameDataLen: integer;
end;
PFrameData = ^TFrameData;

  FrameData := PFrameData(Msg.Lparam);
  VideoStream.WriteBuffer(FrameData ^.FrameData,FrameData^.FrameDataLen);



Это два.


3asys   (2011-12-11 18:39:00) [65]

Пробую создавать сообщение:
SendMessage(frmClient.Handle, FSendNewFrameMessage, 0, LParam(FBuffer));
В WndProc принимаю таким образом:

   FrameData := PFrameData(Msg.Lparam);
   VideoStream.WriteBuffer(FrameData,FrameData^.FrameDataLen);

расчитывая в дальнейшем сделать что-то вроде:

   jpg:=TJPEGImage.Create;
   VideoStream.Position:=0;
   jpg.LoadFromStream(VideoStream);
   b:=TBitmap.Create;
   b.Assign(jpg);
   Image1.Picture.Assign(b);

При обработки строки
VideoStream.WriteBuffer(FrameData,FrameData^.FrameDataLen);
выдается ошибка "Access violation".
Что я делаю неправильно?


DVM   (2011-12-10 21:29:00) [66]


> Как загрузить принятые данные в поток (передается Longint)
> и преобразовать их в jpg ?

Наверное лучше (на будущее) будет не LongInt там использовать, а LParam. Но пока суть не в этом. Ты неправильно написал немного. Ты передаешь с сообщением указатель на сам кадр, но не передаешь размер кадра. Такое в поток не загрузить. Нужно и размер передавать LParam(FrameData). Я подразумеваю, что FrameData у тебя это указатель на запись в которой есть поле FrameData - указатель на данные, и есть там еще одно поле - размер.

В WndProc ты получаешь код сообщения, LPARAM и WPARAM. Твой LPARAM это на самом деле указатель на структуру с данными. Просто приведи его обратно к типу структуры:

var
 FrameData: PFrameData; // считаем, что PFrameData = ^TFrameData

...

FrameData := PFrameData(Msg.Lparam);

Вуаля. В твоем распоряжении указатель на структуру с данными. Тебе остается скопировать данные в TMemoryStream. Смотри методы TMemoryStream которые могу загрузить данные из буфера.

Потом можешь грузить данные обратно в JPEG. Не забудь предварительно только позицию в TMemoryStream выставить на начало его.

Все описанное выше десяток строк.

Вообще я бы не стал туда сюда гонять данные между буфером и стримом потом TJpegImage - можно сразу передать с сообщением буфер и прямо из него декодировать, например с помощью Intel Jpeg Library. Юудет быстрее. Но это потом. Сначала разберись по простому.


3asys   (2011-12-10 15:43:00) [67]

Добрый день )

> DVM ©
Реализовал получение окном формы пользовательского сообщения (procedure TfrmClient.WndProc(var Msg: TMessage)):

unit uclnt;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, ExtCtrls, ComCtrls, ToolWin, StdCtrls, jpeg,

 IdHTTP, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,

 uClientFunctions, uBuffer;

type
 TfrmClient = class(TForm)
   ToolBar1: TToolBar;
   tbtnStart: TToolButton;
   PaintBox1: TPaintBox;
   Splitter1: TSplitter;
   Image1: TImage;
   Memo1: TMemo;
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
   procedure tbtnStartClick(Sender: TObject);
//    procedure GetVideoMsg(var Msg: TMsg; var Handled: Boolean);
   procedure WndProc(var Msg: TMessage); override;
 private
   { Private declarations }
 public
   { Public declarations }

 end;

var
 frmClient: TfrmClient;
 Host: string;
 Port: integer;
 Path: string;
 UserName: string;
 Password: string;
 VideoThread : THTTPInputThread;
 VideoStream : TMemoryStream;

implementation

{$R *.dfm}

procedure TfrmClient.FormCreate(Sender: TObject);
begin
 Host := '127.0.0.1';
 Port := 8081;
 Path := '/';
 UserName := '';
 Password := '';
 VideoStream:=TMemoryStream.Create;
end;

procedure TfrmClient.tbtnStartClick(Sender: TObject);
begin
 VideoThread := THTTPInputThread.Create(Host, Port, Path, UserName, Password);
end;

procedure TfrmClient.WndProc(var Msg: TMessage);
begin
 if Msg.Msg = FSendNewFrameMessage then
 begin
   // Здесь загрузка данных в поток, выгрузка оттуда их в Jpeg
   // декодирование и отрисовка
 end
 else
   inherited;

end;

procedure TfrmClient.FormDestroy(Sender: TObject);
begin
 VideoThread.Terminate;
 VideoThread.WaitFor;
 FreeAndNil(VideoThread);
end;

end.

Для упрощения обработки, с сообщением передаю сам кадр:

SendMessage(frmClient.Handle, FSendNewFrameMessage, 0, Longint(FrameData^.FrameData));

Далее пробовал по разному, но не смог загрузить данные в поток и не понимаю, как преобразовать их в jpg/
Как загрузить принятые данные в поток (передается Longint) и преобразовать их в jpg ?


3asys   (2011-12-04 01:13:00) [68]

Спасибо, буду стараться :)


DVM   (2011-12-04 01:09:00) [69]


> 3asys ©   (04.12.11 01:00) [118]

Достукиваться лучше не до самого буфера:

 FrameData^.FrameData := FBuffer.Extract(FContentLength);
                  FrameData^.FrameDataLen := FContentLength;^.FrameData := FBuffer.Extract(FContentLength);
FrameData^.FrameDataLen := FContentLength;

Вот здесь в первой строке из буфера извлекается очередной кадр и помещается в структуру FrameData. Указатель на эту структуру можно с сообщением Windows передать в основной поток программы окну твоей формы например. Можно в принципе с сообщением передавать и сам буфер, так как SendMessage все равно синхронизирует доступ к нему, но наверное лучше вот так со структурой.
Как получать окном формы пользовательские сообщения давай сам думай, это не сложно и примеров интернет масса.

Когда получишь сообщение, тебе лишь надо будет декодировать из JPEG данных кадр и отрисовать его. Т.е придется сначала загнать данные в поток, загрузить оттуда их в JPEG потом декодировать и отрисовать на BMP.


3asys   (2011-12-04 01:00:00) [70]

Спасибо, создал поток, как было указано,
смотрю отладчиком - с сервером соединяется, данные получает,
а как вывести их на канву (для простоты использую TPaintBox) не могу сообразить.
Правильно ли я понимаю, что экземпляр буффера создается созданным экземпляром THTTPInputThread, так сказать находится внутри потока и мне его создавать самому не нужно?
Если это так, то каким образом до него (Buffer) достучаться? А если это не так, то создав VideoBuffer := TBuffer.Create, как связать его с потоком и канвой?


DVM   (2011-12-03 15:12:00) [71]

Также, я хотел заметить, что приведенный выше метод function THTTPInputThread.GetResponse(): integer; сильно упрощен. Это даже не чтение ответа именно HTTP сервера, это чтение и попытка выделить JPEG из любого потока TCP в любом формате. По-хорошему, надо принимать и анализировать заголовки сервера, тип контента, длину контента в подзаголовках и на основании этого читать строго заданное количество данных, учитывать разделители. Все это положительным образом скажется на производительности. Но код усложниться в десяток раз. Поэтому пока так.


DVM   (2011-12-03 15:05:00) [72]


> 3asys ©   (03.12.11 14:37) [114]


> Правильно ли я понял, что
> 1. При запуске клиента вызываем THTTPInputThread.Create
> 1.1. передаем ей
>  адрес сервера,
>  порт,
>  имя пользователяи
>  пароль пользователя

вобщем да, но лучше чуть по другому, смотри ниже.


> ЧТО ТАКОЕ APath ?

в том сервере, что я тебе писал выше это игнорируется, ну передай туда '/', это путь на сервере, ну например, он мог бы быть /mjpeg.cgi или /GetVideo.


> 2. При подключении к серверу вызываем THTTPInputThread.Execute

При подключении к серверу ты должен создать поток.
VideoThread := THTTPInputThread.Create(...)

Execute protected метод его ты не вызовешь из кода программы, да это и не надо, он сам вызовется. Читай справку про потоки.


> 3. При выключении клиента вызываем THTTPInputThread.Destroy

при отключении вызывай метод Free у экземпляра этого потока, предварительно остановив его (вообще это азы и это в справке есть).

VideoThread.Terminate;
VideoThread.WaitFor;
FreeAndNil(VideoThread)


> Сейчас модуль клиента выглядит так:

Касательно использования потока там написан бред. См. как надо выше.


Германн   (2011-12-03 14:50:00) [73]


> Что можно сделать?
>

Создать THTTPInputThread правильно.


3asys   (2011-12-03 14:37:00) [74]

Добрый день :)
> DVM ©
Правильно ли я понял, что
1. При запуске клиента вызываем THTTPInputThread.Create
1.1. передаем ей
 адрес сервера,
 порт,
 имя пользователяи
 пароль пользователя
 ЧТО ТАКОЕ APath ?
2. При подключении к серверу вызываем THTTPInputThread.Execute
3. При выключении клиента вызываем THTTPInputThread.Destroy

Включил в проект модуль uBuffer (буфер) и модуль uClientFunctions (работа клиента)
Сейчас модуль клиента выглядит так:

unit uclnt;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, ExtCtrls, ComCtrls, ToolWin,

 IdHTTP, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,

 uBuffer, uClientFunctions;

type
 TfrmClient = class(TForm)
   ToolBar1: TToolBar;
   tbtnStart: TToolButton;
   Panel1: TPanel; // на нее буду выводить видео
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
   procedure tbtnStartClick(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
 end;

var
 frmClient: TfrmClient;
 Host: string;
 Port: integer;
 Path: string;
 UserName: string;
 Password: string;

implementation

{$R *.dfm}

procedure TfrmClient.FormCreate(Sender: TObject);
begin
 Host := '127.0.0.1';
 Port := 8081;
 Path := '';
 UserName := '';
 Password := '';
 THTTPInputThread.Create(Host, Port, Path, UserName, Password);
end;

procedure TfrmClient.tbtnStartClick(Sender: TObject);
begin
 THTTPInputThread.Execute;
end;

procedure TfrmClient.FormDestroy(Sender: TObject);
begin
 THTTPInputThread.Destroy;
end;

end.

На вызов THTTPInputThread.Execute; сообщается "недекларированный идентификатор Execute",
На вызов THTTPInputThread.Destroy; сообщает "not enough actual parameters"
Что можно сделать?


DVM   (2011-12-03 01:34:00) [75]


> Германн ©   (03.12.11 01:19) [112]


> Не.

C ICS можно было бы отказаться от потока. Но, это бы усложнило логику. Тут желательно чтобы все последовательно было,так оно нагляднее, особенно для примера. Indy потребовала бы поток, но можно было бы избавиться от рутины типа ReadData(). Но у Indy блокирующий коннект. Иногда это сильно мешает. А на сокетах все прозрачно и на виду. Для понимания процесса полезно имхо.


Германн   (2011-12-03 01:19:00) [76]


> Да можно и с инди и с ICS.

Не. ICS рассчитана исторически на асинхронную работу. Ну это то (примерно) что вы привели в [99] - [102].


3asys   (2011-12-03 00:51:00) [77]

понял - спасибо :)


3asys   (2011-12-03 00:50:00) [78]

если InetAddr - это Inet_Addr, то все равно несовпадение типов в выражении String и PAnsiChar


DVM   (2011-12-03 00:46:00) [79]


> Что с этими идентификаторами делать?

с какими идентификаторами?


DVM   (2011-12-03 00:45:00) [80]


> 3asys ©   (03.12.11 00:39) [106]


function InetAddr(const AHost: AnsiString): DWORD;
var
 PHost: PAnsiChar;
 HostEnt: PHostEnt;
begin
 if AHost = '' then
   result := DWORD($FFFFFFFF)
 else
   begin
     PHost := PAnsiChar(AHost);
     Result := inet_addr(PHost);
     if Result = DWORD($FFFFFFFF) then
       begin
         HostEnt := GetHostByName(PHost);
         if HostEnt <> nil then
           Result := DWORD(pointer(HostEnt^.h_addr^)^);
       end;
   end;
end;


Ну а SendMessage пока просто убери. Или заведи поле FWindowHandle у класса потока и передавай в его конструктор хэндл окна которому будут приходить сообщения из потока. В конструкторе присваивай FWindowHandle переданное в конструктор значение.

Будь внимателен, код старый, пока постил сюда подправил кое-где на предмет PChar - > PAnsiChar, но мог пропустить что-то.


Страницы:   1 2 3 4 5 6 7 8 9 10

Наверх

Титульная страница Поиск, карта сайта Написать письмо