![]() |
| ||
|
|
Новости |
Поиск |
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-01 00:05:00) [101]
unit uBuffer;
interface
const
MinAllocation = 1024;
type
TBuffer = class(TObject)
private
FStorage: PAnsiChar; // Указатель на начало буфера
FAllocation: integer; // Размер памяти, выделенной под буфер
FHead: PAnsiChar; // Указатель на начало данных в буфере
FTail: PAnsiChar; // Указатель на конец данных в буфере
FSize: integer; // Размер данных в буфере
function GetBytes(Index: Integer): PAnsiChar;
procedure SetSize(ASize: integer);
public
constructor Create; overload;
constructor Create(ASize: integer); overload;
constructor Create(AStorage: PAnsiChar; ASize: integer); overload;
constructor Create(ABuffer: TBuffer); overload;
destructor Destroy; override;
function Append(ABuffer: TBuffer): integer; overload;
function Append(AStorage: PAnsiChar; ASize: integer): integer; overload;
function Assign(AStorage: PAnsiChar; ASize: integer): integer; overload;
function Assign(ABuffer: TBuffer): integer; overload;
function Consume(ACount: integer): integer;
procedure Empty;
procedure Compact;
function IsEmpty: boolean;
function Expand(ACount: integer): integer;
function Extract(ACount: integer): PAnsiChar;
function Shrink(ACount: integer): integer;
procedure Tidy;
property Head: PAnsiChar read FHead;
property Size: integer read FSize write SetSize;
property Storage: PAnsiChar read FStorage;
property Tail: PAnsiChar read FTail;
property Allocation: integer read FAllocation;
property Bytes[Index: Integer]: PAnsiChar read GetBytes;
end;
DVM (2011-12-01 00:05:00) [102]
implementation
//------------------------------------------------------------------------------
constructor TBuffer.Create;
begin
Create(0);
end;
//------------------------------------------------------------------------------
constructor TBuffer.Create(ASize: integer);
begin
if ASize > 0 then
FAllocation := ASize
else
FAllocation := MinAllocation;
FSize := 0;
GetMem(FStorage, FAllocation);
FHead := FStorage;
FTail := FHead;
end;
//------------------------------------------------------------------------------
constructor TBuffer.Create(AStorage: PAnsiChar; ASize: integer);
begin
if (ASize > 0) and Assigned(AStorage) then
begin
FAllocation := ASize;
FSize := ASize;
GetMem(FStorage, FAllocation);
FHead := FStorage;
Move(AStorage^, FStorage^, ASize);
FTail := FHead + FSize;
end
else
Create;
end;
//------------------------------------------------------------------------------
constructor TBuffer.Create(ABuffer: TBuffer);
begin
if (Assigned(ABuffer)) and (ABuffer.Size > 0) then
begin
FAllocation := ABuffer.Size;
FSize := ABuffer.Size;
GetMem(FStorage, FAllocation);
FHead := FStorage;
Move(ABuffer.Storage^, FStorage^, ABuffer.Size);
FTail := FHead + FSize;
end
else
Create;
end;
//------------------------------------------------------------------------------
procedure TBuffer.SetSize(ASize: integer);
begin
if FSize <> ASize then
begin
if FSize < ASize then
Expand(ASize)
else
begin
FSize := ASize;
FTail := FHead + FSize;
end;
end;
end;
// Отсекает первые ACount символов ----------------------------------------------
function TBuffer.Consume(ACount: integer): integer;
begin
if ACount > FSize then ACount := FSize;
if ACount < 0 then ACount := 0;
FHead := FHead + ACount;
FSize := FSize - ACount;
Result := ACount;
end;
// Отсекает последние ACount символов -------------------------------------------
function TBuffer.Shrink(ACount: integer): integer;
begin
if ACount > FSize then ACount := FSize;
if ACount < 0 then ACount := 0;
FSize := FSize - ACount;
if FTail > FHead + FSize then FTail := FHead + FSize;
Result := ACount;
end;
// Расширение буфера -----------------------------------------------------------
function TBuffer.Expand(ACount: integer): integer;
var
Spare, HeadSpace, TailSpace, Width, OldAllocation: integer;
NewStorage: PAnsiChar;
begin
result := FSize;
if ACount <= 0 then exit;
// Свободный (незанятый) объем буфера
Spare := FAllocation - FSize;
// Свободное место в начале буфера
HeadSpace := FHead - FStorage;
// Свободное место в конце буфера
TailSpace := Spare - HeadSpace;
// Размер (ширина) занятой части буфера
Width := Tail - Head;
// Если в буфере есть достаточно свободного места для добавления ACount байт
if Spare >= ACount then
begin
// Если хвост меньше чем надо добавить
if TailSpace < ACount then
begin
// Двигаем полезные данные в начало буфера
Move(FHead^, FStorage^, FSize);
// Начало данных совпадает с началом буфера
FHead := FStorage;
// Хвост данных на расстоянии width от головы
FTail := FHead + Width;
end;
end
else
// Если в буфере недостаточно места для добавления count символов
begin
OldAllocation := FAllocation;
// Общий объем буфера увеличиваем на count
FAllocation := FAllocation + ACount;
// Создаем временный буфер нужного размера
GetMem(NewStorage, FAllocation);
FillChar(NewStorage^, FAllocation, 0);
if FStorage <> nil then
begin
// Копируем в него данные из старого буфера
Move(FHead^, NewStorage^, FSize);
// Старый буфер удаляем
FreeMem(FStorage, OldAllocation);
end;
// Новый буфер заменяет старый
FStorage := NewStorage;
// Данные в начале буфера
FHead := FStorage;
// Хвоcт на расстоянии width от головы буфера
FTail := FHead + Width;
end;
// Устанавливаем новый размер буфера
FSize := FSize + ACount;
// Возвращаем новый размер
result := FSize;
end;
3asys (2011-11-30 23:58:00) [103]Спасибо :)
DVM (2011-11-30 23:57:00) [104]
> если все будут по одному порту вещать, то не будут ли они
> мешать друг другу?
Сервера же на разных компьютерах, мешать друг другу они не будут. А у клиентов у всех порт разный, он системой автоматически выбирается.
DVM (2011-11-30 23:55:00) [105]
> 3asys © (30.11.11 23:45) [81]
> Как можно сделать клиента?
Из TIdTCPClient или TIDHTTPClient (хотя насчет последнего не уверен). Можно и чистые Windows Sockets использовать.
Для начала поищи тут на форуме класс TBuffer я когда то выкладывал для Германн - это будет твой приемный буфер.
Подумай, как сделать TCP клиента, который бы принимал данные с твоего сервера и клал в этот буфер.
Остальное завтра.
3asys (2011-11-30 23:49:00) [106]Попробую :)
Я тогда имел в виду, что каждый участник конференции будет не только смотреть но и сам транслировать и вопрос был - если все будут по одному порту вещать, то не будут ли они мешать друг другу?
DVM (2011-11-30 23:45:00) [107]Ну таймер думаю отсюда надо убрать. Перенести получение очередного кадра в отдельный поток. И вообще все оформить в виде отдельного класса со своими методами, отделить от формы, веб сервер создавать в рантайм. Короче там есть что прикрутить.
Ты вот как то спрашивал про несколько клиентов. Открой пять вкладок в файерфоксе и во всех можешь наблюдать видео. Можешь открыть и 50.
3asys (2011-11-30 23:45:00) [108]> DVM ©
Спасибо, без Вас бы это не получилось :)
Как можно сделать клиента?
3asys (2011-11-30 23:41:00) [109]Для тех кому это интересно, - это код http-сервера транслирующего видео с web-камеры. В качестве клиента используется FireFox.
3asys (2011-11-30 23:35:00) [110]вот полный код того, что получилось:
unit uMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, SyncObjs, jpeg,
ExtCtrls,
IdBaseComponent, IdComponent, IdTCPServer, IdCustomHTTPServer, IdGlobal,
IdHTTPServer, IdCustomTCPServer, IdContext, IdSchedulerOfThread, IdGlobalProtocols,
StdCtrls,
DSUtil, DirectShow9, DSPack;
type
TSafeBuffer = class(TMemoryStream)
private
FLock: TCriticalSection;
public
constructor Create;
destructor Destroy; override;
procedure Lock;
procedure Unlock;
end;
TfrmMin = class(TForm)
tmrUpdateFrame: TTimer;
idhtpsrvrMain: TIdHTTPServer;
Button1: TButton;
VideoWindow2: TVideoWindow;
VideoSourceFilter: TFilter;
CaptureGraph: TFilterGraph;
SampleGrabber: TSampleGrabber;
procedure tmrUpdateFrameTimer(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure idhtpsrvrMainCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
Buffer: TSafeBuffer;
end;
var
frmMin: TfrmMin;
CompFilter : TFilterList;
CapFilters : TSysDevEnum;
CapEnum: TSysDevEnum;
VideoMediaTypes: TEnumMediaType;
implementation
{$R *.dfm}
constructor TSafeBuffer.Create;
begin
FLock := TCriticalSection.Create;
inherited Create;
end;
destructor TSafeBuffer.Destroy;
begin
inherited Destroy;
FLock.Free;
end;
// VIDEO
function SetVideoParams(CB_B2: ICaptureGraphBuilder2; Category: TGUID;
fSource: IBaseFilter): HResult;
var
StreamConf: IAMStreamConfig;
PAMT: PAMMediaType;
begin
Result:= E_FAIL;
StreamConf:= nil;
PAMT:= nil;
try
Result:= CB_B2.FindInterface(@Category, @MEDIATYPE_Video, fSource,
IID_IAMStreamConfig, StreamConf);
if Assigned(StreamConf) then
begin
StreamConf.GetFormat(PAMT);
if Assigned(PAMT) then
begin
if PAMT.cbFormat= sizeOf(TVideoInfoHeader) then
begin
PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader.biWidth:= 640;
PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader.biHeight:= 480;
PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader.biBitCount:= 24;
PVIDEOINFOHEADER(PAMT^.pbFormat)^.AvgTimePerFrame:= 10000000 div 25;
//fps
with PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader do
PAMT^.lSampleSize := ((biWidth + 3) and (not (3))) * biHeight * biBitCount
shr 3;
PVIDEOINFOHEADER(PAMT^.pbFormat)^.bmiHeader.biSizeImage:=PAMT^.lSampleSize;
end;
Result:= StreamConf.SetFormat(PAMT^)
end;
end;
result:= S_OK;
except
on E: Exception do
MessageBox(0, PChar(E.Message), '', MB_OK or MB_ICONERROR);
end;
StreamConf:= nil;
if Assigned(PAMT) then
DeleteMediaType(PAMT);
end;
procedure TfrmMin.FormCreate(Sender: TObject);
begin
Buffer := TSafeBuffer.Create;
CompFilter := TFilterList.Create;
CapFilters := TSysDevEnum.create(CLSID_VideoCompressorCategory);
CapEnum := TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);
CapEnum.SelectGUIDCategory(CLSID_AudioInputDeviceCategory);
VideoMediaTypes := TEnumMediaType.Create;
end;
procedure TSafeBuffer.Lock;
begin
FLock.Enter;
end;
procedure TSafeBuffer.Unlock;
begin
FLock.Leave;
end;
procedure TfrmMin.FormDestroy(Sender: TObject);
begin
Buffer.Free;
end;
procedure TfrmMin.idhtpsrvrMainCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
const
Boundary = '--myboundary';
CRLF = #13#10;
var
Stream: TMemoryStream;
SubHeader: AnsiString;
begin
Stream := TMemoryStream.Create;
try
AResponseInfo.FreeContentStream := false;
AResponseInfo.Server := 'StreamServer';
AResponseInfo.CacheControl := 'no-cache';
AResponseInfo.Pragma := 'no-cache';
AResponseInfo.Expires := Now;
AResponseInfo.CharSet :='';
AResponseInfo.Connection := 'close';
AResponseInfo.ContentType := 'multipart/x-mixed-replace; boundary=' + Boundary;
AResponseInfo.ContentLength := -1;
AResponseInfo.WriteHeader;
while ((not (AContext.Yarn as TIdYarnOfThread).Thread.Terminated) and (AContext.Connection.Connected)) do
begin
Buffer.Lock;
try
AResponseInfo.ContentLength := Buffer.Size;
SubHeader := AnsiString(Boundary + CRLF +
'Content-Type: image/jpeg' + CRLF +
'Content-Length: ' + IntToStr(AResponseInfo.ContentLength) + CRLF + CRLF);
Stream.Size := 0;
Stream.Write(SubHeader[1], length(SubHeader));
Stream.Write(Buffer.Memory^, Buffer.Size);
finally
Buffer.Unlock;
end;
Stream.Position := 0;
AResponseInfo.ContentStream := Stream;
AResponseInfo.WriteContent;
Sleep(100);
end;
finally
Stream.Free;
end;
end;
procedure TfrmMin.tmrUpdateFrameTimer(Sender: TObject);
var
Bmp: TBitmap;
JPG: TJPEGImage;
begin
Bmp := TBitmap.Create;
try
SampleGrabber.GetBitmap(Bmp);
{
Bmp.Width := 320;
Bmp.Height := 240;
Bmp.PixelFormat :=pf24bit;
Bmp.Canvas.TextOut(50, 50, FormatDateTime('hh:nn:ss.zzz', Now));
}
JPG := TJPEGImage.Create;
try
JPG.Assign(Bmp);
Buffer.Lock;
try
Buffer.Size := 0;
JPG.SaveToStream(Buffer);
Buffer.Position :=0;
finally
Buffer.Unlock;
end;
finally
JPG.Free;
end;
finally
Bmp.Free;
end;
end;
procedure TfrmMin.Button1Click(Sender: TObject);
begin
idhtpsrvrMain.Active:=True;
tmrUpdateFrame.Enabled:=True;
VideoWindow2.FilterGraph:=CaptureGraph;
CapEnum:= TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);
CaptureGraph.ClearGraph;
CaptureGraph.Active := false;
VideoSourceFilter.BaseFilter.Moniker := CapEnum.GetMoniker(0);
VideoSourceFilter.FilterGraph := CaptureGraph;
CaptureGraph.Active := true;
SetVideoParams(CaptureGraph as ICaptureGraphBuilder2,
PIN_CATEGORY_CAPTURE, VideoSourceFilter as IBaseFilter);
with CaptureGraph as ICaptureGraphBuilder2 do
RenderStream(@PIN_CATEGORY_PREVIEW, nil, VideoSourceFilter as IBaseFilter, SampleGrabber as IBaseFilter, VideoWindow2 as IbaseFilter);
CaptureGraph.Play;
end;
end.
написано на Delphi 7, использовалась библиотека DSPack
3asys (2011-11-30 23:31:00) [111]Запустил. Работает. В FireFox транслируется видео :)
поставил
AResponseInfo.ContentLength := -1;
работает :)
DVM (2011-11-30 22:56:00) [112]А, и это надо придумать как сделать так, чтобы в ответе сервера не был указан Content-Length иначе тот же файерфокс после приема указанного количества байт остановится. Поэкспериментируй короче. Самодельный клиент конечно это поле может игнорировать.
DVM (2011-11-30 22:54:00) [113]
> 3asys © (30.11.11 22:48) [72]
> Теперь часы появились :)
Ну вот теперь попробуй прикрутить свою камеру туда.
3asys (2011-11-30 22:52:00) [114]:) да все так - не был назначен.
DVM (2011-11-30 22:50:00) [115]
> я вообще-то не предполагал, что OnCreate может не отрабатывать.
> С чем это связано?
Ты туда точку останова ставил? С копипастом не туда это может быть связано. ОБРАБОТЧИК НЕ НАЗНАЧЕН ФОРМЕ.
3asys (2011-11-30 22:48:00) [116]> DVM ©
Заработало!
Действительно не отрабатывало. Стал создавать по кнопке, вместе с включением сервера:
procedure TfrmMin.Button1Click(Sender: TObject);
begin
Buffer := TSafeBuffer.Create;
idhtpsrvrMain.Active:=True;
tmrUpdateFrame.Enabled:=True;
end;
Теперь часы появились :)
Спасибо большое - я вообще-то не предполагал, что OnCreate может не отрабатывать. С чем это связано?
DVM (2011-11-30 22:41:00) [117]
> 3asys © (30.11.11 22:27) [69]
оно отрабатывает?
3asys (2011-11-30 22:39:00) [118]это и есть создание буфера, насколько я понимаю... .
3asys (2011-11-30 22:27:00) [119]> DVM
procedure TfrmMin.FormCreate(Sender: TObject);
begin
Buffer := TSafeBuffer.Create;
end;
DVM (2011-11-30 22:18:00) [120]
> 3asys © (30.11.11 21:54) [67]
Ты буфер то создал где нибудь? Обращаешься к несуществующему объекту ведь.