![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Передача видео и звука с помощью Indy [D7]
3asys © (2011-11-26 15:48:00)
Страницы: < 1 2 3 4 5 6 7 8 9 10 >
На клиенте получаю видео и звук с 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, но мог пропустить что-то.