Multimediální a hypermediální systémy Přehrávání videa v Delphi
Martin Mojžíš (
[email protected]) 14.5.2006
1 ÚVOD DO DIRECTSHOW
1
Úvod do DirectShow
DirectShow je součást celku DirectX umožňující aplikacím řadu multimediálních služeb. Lze jej využít pro záznam, přehrávání, či editaci videa a zvuku a ovládání multimediálních zařízení jako jsou kamery, digitální fotoaparáty nebo zařízení zvukového výstupu. DirectShow je postaven jako systém komponent využívající technologii COM. Ta kromě jiného zajišťuje nezávislé použití v různých vývojových nástrojích. Přístup k DirectShow je tedy stejný v případě použití libovolného nástroje z balíku Borland Development Studio (Delphi, C++ Builder) či Microsoft Visual Studia.
1.1
Objektový model COM
COM představuje model komponentní technologie vyvinutý firmou Microsoft. Komponenty obsahují kromě svojí nezávislé implementace také jedno nebo více rozhraní, které je zpřístupňuje aplikaci a zajišťuje jejich vnější ovládání. Komponenty lze tak snadno nahradit např. novější verzí, avšak díky použití rozhraní není třeba aplikaci modifikovat. Každý objekt COM je označen unikátním identifikátorem neboli CLSID. Tyto identifikátory jsou uloženy v systémových registrech. Tento identifikátor je použít při vytváření instance objektu. Má podobu 128 bitů dlouhého čísla, které je obvykle zapisováno jako sekvence osmi hexadecimálních číslic následovaná dvěma čtveřicemi a jednou dvanáctičlennou skupinou taktéž šestnáctkových číslic oddělených mezerami. Programovací jazyky obvykle již obsahují předdefinované konstanty, aby nebylo nutné v programech používat tato nepřehledná čísla. Konstanty identifikátorů tříd mají prefix CLSID, jako např. CLSID_FilterGraph. V Delphi je COM zpřístupněno v programových jednotkách ActiveX a ComObj. Identifikátor objektů je reprezentován datovým typem TGuid. Samotná inicializace COM se provádí voláním: CoInitialize(nil); Instanci třídy specifikovanou jejím identifikátorem lze vytvořit použitím procedury CoCreateInstance. Např. objekt pro graf filtrů (bude popsán dál) lze vytvořit následujícím příkazem. Result:=CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, Builder);
—1—
1.2 Rozhraní
1 ÚVOD DO DIRECTSHOW
Význam parametrů této funkce je po řadě: • identifikátor třídy • identifikátor nadřazené třídy, která je vlastníkem vytvářené třídy (doposud nebylo použito) • kontext, ve kterém vzniká nový objekt. Hodnota CLSCTX_INPROC_SERVER znamená vznik v procesu aplikace. • identifikátor rozhraní • rozhraní, které umožní přístup k instanci třídy Funkce COM včetně této vrací celočíselný chybový kód jako datový typ HResult. Jeho hodnoty jsou popsány v MSDN. Bezporuchový stav je možné ověřit funkcí SUCCEEDED.
1.2
Rozhraní
Rozhraní slouží k ovládání instancí tříd COM. Z hlediska programovacího jazyka rozhraní tvoří předpis metod, které bezpodmínečné musí třída implementovat. Za této podmínky lze rozhraní použít k zastoupení konkrétní třídy. Při návrhu rozhraní lze podobně jako u tříd využít dědičnosti. Každé rozhraní je podobně jako třída označeno unikátním číslem, které vypadá podobně jako identifikátor tříd. Narozdíl od něj má však prefix IID, tedy např. IID_IPropertyBag. Datové typy rozhraní začínají písmenem I, tedy např. IPropertyBag. Přístup k rozhraní lze získat dvěma způsoby. První z nich byl ukázán v předchozí sekci při vytváření instancí tříd. Druhý slouží k získání dalšího typu rozhraní pro přístup k již existující instanci. Nejméně jedno rozhraní instance již bylo vytvořeno, další získáme voláním: Result:= Builder.QueryInterface(IID_IMediaControl, MediaCtrl); Výše uvedený příkaz použije instanci, která vlastní rozhraní Builder, vytvoří nové rozhraní typu IID_IMediaControl a přiřadí jej MediaCtrl. Tato proměnná je datového typu IMediaControl. Význam těchto konkrétních rozhraní bude popsán v další kapitole.
—2—
1.3 Filtry a grafy
1.3
1 ÚVOD DO DIRECTSHOW
Filtry a grafy
Samostatná komponenta systému DirectShow se nazývá filtr. Filtr si lze představit jako např. elektronickou součástku. Je to samostatně operující jednotka, která má různé vstupy a výstupy. Tyto vstupy a výstupy se nazývají piny. Filtr může mít pouze výstupní pin (filtr pro souborový vstup), pouze vstupní pin (filtr pro renderování videa), případně různý počet vstupních a výstupních pinů (rozklad AVI na obrazovou a zvukovou složku). Je zřejmé, že filtry je možné právě díky pinům propojovat. Stejně tak je zřejmé, že lze propojit pouze vstupní pin s výstupním pinem. Avšak i to není možné za všech okolností. Vstupní pin musí být propojen s takovým výstupním pinem, který je mu schopen poskytnout data tak, aby je dokázal zpracovat. Je například nesmyslné propojovat pin výstup audiokodeku s filtrem pro renderování obrazu videa. Pokus propojit vstupní a výstupní pin filtrů může vést do různých situací. • Piny lze bezprostředně spojit. V takovém případě je spojení pinů provedeno a je vše v pořádku. • Existuje sekvence filtrů, které lze vložit mezi piny tak, aby sousední bylo možné přímo spojit. V tom případě DirectShow vloží požadované filtry mezi spojované piny a provede jejich propojení • Neexistuje žádná sekvence filtrů, aby bylo možné požadované piny propojit. V tomto případě pokus skončí chybovým kódem. Spojení filtrů se nazývá graf. O sestavení grafu se stará instance třídy FilterGraph. Ta poskytuje rozhraní IGraphBuilder, které obsahuje důležité funkce pro konstrukci grafu. Těmi jsou: • AddFilter - vloží filtr do sestavovaného grafu. Prvním parametrem je instance třídy implementující rozhraní IBaseFilter, druhým je textový název filtru. • Render - aktivuje graf a připraví jej ke spuštění. Jediným parametrem funkce je výstupní pin, který bude poskytovat data. Jedná se tedy o výstupní pin prvního filtru v řadě. • Connect - funkce propojí dva piny. Dva parametry funkce slouží pro předání jejich rozhraní.
—3—
1.4 Propojení filtrů
1.4
1 ÚVOD DO DIRECTSHOW
Propojení filtrů
Výše uvedená metoda Render sice připraví graf ke spuštění a pokusí se propojit piny, ale nenabízí funkci, která by filtry přímo propojila. Součástí některých knihoven je samostatná funkce Connect, která tuto činnost provádí. V Delphi se však nenachází, proto je nutné funkci pro propojení filtrů samostatně implementovat. Algoritmus, který propojení realizuje, lze charakterizovat těmito kroky: • nalezení prvního volného výstupního pinu vstupního filtru. • nalezení prvního volného vstupního pinu výstupního fiĺtru. • propojení těchto pinů. Hledání pinů se provádí využitím rozhraní pro jejich výčet IEnumPins. To provede příkaz Filter1.EnumPins(Enum); Rozhraní Enum nabízí sekvenční procházení pinů filtru. U každého pinu lze zjistit jeho orientaci a zda je již k některému pinu připojen. Procházení je provedeno tímto kusem kódu: Enum.Reset(); while Enum.Next(1, Src, @Fetched)=S_OK do begin Src.QueryPinInfo(PI); if (PI.dir=PINDIR_INPUT) then continue; if (Src.ConnectedTo(Tmp)=S_OK) then continue; break; end; Nejprve je inicializován výčet pinů. Pak je zpřístupněn vždy následující pin a prověřen, zda je vstupní nebo výstupní a zda je již připojen k některému pinu. Načtení informací o pinu provádí metoda QueryPinInfo. Ta vrací strukturu Pin Info, ve které se mimo jiné nachází údaj o směru pinu. Funkce ConnectedTo zjišťuje, zda je pin již propojen. Proměnná Tmp pak obsahuje rozhraní IPin pinu, se kterým je Src propojen. Jelikož je hledán volný výstupní pin, nalezení vstupního nebo úspěšně propojeného pinu znamená další iteraci cyklu. —4—
1.4 Propojení filtrů
1 ÚVOD DO DIRECTSHOW
Filter2.EnumPins(Enum); Enum.Reset(); while Enum.Next(1, Dst, @Fetched)=S_OK do begin Dst.QueryPinInfo(PI); if (PI.dir=PINDIR_OUTPUT) then continue; if (Dst.ConnectedTo(Tmp)=S_OK) then continue; break; end; Výše uvedený vzorek kódu provádí totéž s tím rozdílem, že vyhledává volný vstupní pin filtru Filter2. Konečné propojení už provede příkaz: Result:=Graph.Connect(Src, Dst); Podobným způsobem může fungovat funkce pro zjištění prvního výstupního pinu filtru. Tato funkce bude užitečná při volání metody Render rozhraní IGraphBuilder. Její zdrojový kód je jistě zřejmý. function TDSGraph.GetFirstPin(Filter: IBaseFilter): IPin; var Tmp: IPin; Enum: IEnumPins; Fetched: integer; PI: Pin_Info; begin Filter.EnumPins(Enum); Enum.Reset(); while Enum.Next(1, Tmp, @Fetched)=S_OK do begin Tmp.QueryPinInfo(PI); if (PI.dir=PINDIR_INPUT) then continue; break; end; GetFirstPin:=Tmp; end;
—5—
2 PŘEHRÁVÁNÍ VIDEA
2
Přehrávání videa
Přehrávání videa spočívá ve vytvoření velmi jednoduchého grafu, rozhraní potřebných pro jeho řízení a konečně příprava renderování. Tento úkol je možné provést několika způsoby, zde je popsán ten univerzální.
2.1
Základní graf
Graf pro přehrávání videa ukazuje obrázek 1. Ten ukazuje, že stačí pouze vytvořit konstruktor grafu (GraphBuilder), filtr pro zdrojový soubor, separátor zvukové a obrazové složky a filtr pro renderování videa a zvuku. DirectSound device
File source
AVI splitter
Video renderer
Obrázek 1: Graf přehrávání videa
Result:=CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, Builder); Result:=CoCreateInstance(CLSID_VideoRenderer, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, Renderer); Result:=CoCreateInstance(CLSID_DSoundRender, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, Audio); Result:=CoCreateInstance(CLSID_AviSplitter, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, Splitter); Builder.AddSourceFilter( PWideChar(WideString(FileName)), ’src’, Src); Builder.AddFilter(Splitter, ’splitter’); Builder.AddFilter(Renderer, ’video render’); Builder.AddFilter(Audio, ’audio renderer’); Proměnná Builder je typu IGraphFilter, proměnné Renderer, Audio i Splitter jsou deklarovány jako IBaseFilter, jak již samozřejmě identifikátor rozhraní napovídá. —6—
2.2 Řízení přehrávání
2 PŘEHRÁVÁNÍ VIDEA
Dalším krokem bude propojení vstupu s výstupem a přípravu grafu k přehrávání. Connect(Src, Splitter); Connect(Splitter, Renderer); Connect(Splitter, Audio); Builder.Render(GetFirstPin(Src)); Přehrávání je tedy téměř připraveno, zbývá jen jej spustit.
2.2
Řízení přehrávání
Řízením přehrávání se rozumí spouštění a zastavení videa. Metody pro spuštění a zastavení poskytuje rozhraní IMediaControl konstruktoru grafu. Přístup k rozhraní poskytne kód: Result:=Builder.QueryInterface(IID_IMediaControl, MediaControl); MediaControl.Run(); MediaControl.Stop(); MediaControl.Pause(); Poslední tři řádky slouží ke spuštění, zastavení a pozastavení přehrávání. Samozřejmě je nevhodné použít je v uvedeném pořadí.
2.3
Změny pozice a rychlosti
K těmto účelům slouží rozhraní IMediaSeeking. Získání instance a použití demonstruje následující ukázka zdrojového kódu. Builder.QueryInterface(IID_IMediaSeeking, Seeking); Result:=Seeking.SetTimeFormat(TIME_FORMAT_MEDIA_TIME); Seeking.GetDuration(Len); Seeking.SetPositions(Position, AM_SEEKING_AbsolutePositioning, Len, AM_SEEKING_AbsolutePositioning); Seeking.GetCurrentPosition(CurrentPosition); Seeking.SetRate(rate);
—7—
// 1. // 2. // 3.
// 4. // 5.
2.4 Přehrávání v okně
2 PŘEHRÁVÁNÍ VIDEA
Očíslované příkazy po řadě provádí: 1. nastavuje čas jako měřítko pro určení pozice. Časovou jednotkou je zde desetina mikrosekundy. Tato jednotka není příliš praktická, proto je vhodné zajistit přepočet na milisekundy nebo sekundy. Další možnosti zadávání pozice (např. pořadí snímku) jsou uvedeny v MSDN. 2. zjišťuje celkovou délku v zvolených jednotkách. 3. nastavuje počátek a konec přehrávání. Pro realizaci posunu na určité místo (seek) je třeba nastavit požadovanou pozici jako začátek a celkovou délku jako konec. 4. zjišťuje aktuální pozici. 5. nastavuje rychlost přehrávání. Platné hodnoty jsou nenulová kladná čísla menší než dvě.
2.4
Přehrávání v okně
Přehrávání videa v okně spočívá ve vytvoření několika instancí rozhraní a použití funkcí pro jejich nastavení. Celý postup provádí tato procedura: procedure TVideoPlayer.UseWindow (Handle: HWND; x1: Integer; y1: Integer; x2: Integer; y2: Integer); var MixRecorder: IBaseFilter; FilterConfig: IVMRFilterConfig; begin Result:=CoCreateInstance( CLSID_VideoMixingRenderer, nil, CLSCTX_INPROC, IID_IBaseFilter, MixRecorder); Result:=Builder.AddFilter(MixRecorder, ’Video Mixing Recorder’); Result:=MixRecorder.QueryInterface(IID_IVMRFilterConfig, FilterConfig); MixRecorder.QueryInterface(IID_IVMRWindowlessControl, WindowControl); Result:=FilterConfig.SetRenderingMode(VMRMode_Windowless); UpdateWindow(Handle, x1, y1, x2, y2); end; —8—
2.5 Vzorky videa
2 PŘEHRÁVÁNÍ VIDEA
Účelem této sekvence kódu je vložení filtru VideoMixingRenderer, který se stará o zobrazení videa v prostředí (okno aplikace, samostatné okno, které si vytvoří). Tento filtr je nutné nakonfigurovat použitím rozhraní VMRFilterConfig. Režim VMRMode Windowless znamená zobrazení uvnitř existujícího okna. Windowcontrol je rozhraní typu IVMRWindowlessControl. To zajišťuje, aby se video přehrálo v požadovaném okně na požadovaném umístění. Nastavení rozměrů a určení okna provede metoda UpdateWindow. Její tělo následuje: procedure TVideoPlayer.UpdateWindow (Handle: HWND; x1, y1, x2, y2: integer); var W, H, AW, AH: integer; Src, Dst: TRect; begin WindowControl.SetVideoClippingWindow(Handle); WindowControl.GetNativeVideoSize(W, H, AW, AH); SetRect(Src, 0, 0, W, H); SetRect(Dst, X1, Y1, X2, Y2); WindowControl.SetVideoPosition(@Src, @Dst); end; Procedura SetVideoClippingWindow nastaví pro zobrazení okno určené parametrem Handle. Poté metoda GetNativeVideoSize zjistí výšku a šířku videa (další dva parametry jsou nepodstatné). Dále metoda připraví pro video i zobrazovací okno výřezy, které předá rozhraní WindowControl voláním metody SetVideoPosition. Metoda UpdateWindow slouží nejen k počátečnímu nastavení okna pro přehrávání, ale také vynutí překreslení okna podle zadaných parametrů. Je vhodné ji volat, kdykoli se změní velikost přehrávacího okna.
2.5
Vzorky videa
Někdy je zapotřebí změnit či jinak použít samostatný snímek videa. Pokus každého přesvědčí, že přístup k oknu videa s použitím kontextu zařízení selže. Avšak DirectShow nabízí filtr, který vede k samostatnému snímku. Tím filtrem je SampleGrabber. Jeho použití se na první pohled může zdát složitější, neboť je nutné použít rozhraní pro zpětné volání a založit třídu, které snímek zpracuje a implementuje toto rozhraní.
—9—
2.5 Vzorky videa
2 PŘEHRÁVÁNÍ VIDEA
Samotný SampleGrabber a jeho rozhraní ISampleGrabber založí následující příkaz: Result:=CoCreateInstance(CLSID_SampleGrabber, nil, CLSCTX_ALL, IID_ISampleGrabber, Grabber); Grabber.QueryInterface(IID_IBaseFilter, GrabberBaseFilter); Pro použití v grafu je nutné i rozhraní IBaseFilter. Dále je nutné zvolit formát, ve kterém budou vzorky nabízeny. Bitmapový obrázek RGB je poskytnut následujícím kódem: New(MediaType); MediaType^.majortype := MEDIATYPE_Video; MediaType^.subtype:=MEDIASUBTYPE_RGB24; Grabber.SetMediaType(MediaType^); Proměnná MediaType je deklarována jako PAMMediaType. Další příkaz vytvoří novou instanci třídy, která snímek zpracuje, a předá jí filtru. Grabber.SetCallback(TGrabberCallback.Create(), 1); Jak tato třída vypadá? Její hlavičku ukazuje následující zdrojový kód: TGrabberCallback = class (TObject, ISampleGrabberCB) function BufferCB(SampleTime: Double; pBuffer: PByte; BufferLen: longint): HResult; stdcall; function SampleCB(SampleTime: Double; pSample: IMediaSample): HResult; stdcall; function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; end; Pokud je k dispozici nový snímek videa, je volána metoda BufferCB. Tělo této metody by mělo tedy obsahovat kód, který tento snímek použije a případně upraví. Parametr SampleTime obsahuje časovou vzdálenost od počátku, pBuffer je ukazatel na pole bytů obsahujících vzorek a v BufferLen je uložena velikost tohoto pole. Ukázku zpracování nabízí tento kód, který překlopí polovinu obrazu podle vertikální osy1 . 1
Ačkoli tato úprava nemá žádné praktické využití, srozumitelně demonstruje modifikaci snímku.
— 10 —
2.5 Vzorky videa
2 PŘEHRÁVÁNÍ VIDEA
type PSampleBuffer = ^TSampleBuffer; TSampleBuffer = array[0..239, 0..351, 0..2] of byte; function TGrabberCallback.BufferCB (SampleTime: Double; pBuffer: PByte; BufferLen: Integer): HResult; const W = 352; H = 240; var P: PSampleBuffer; i,j,k: integer; begin P:=PSampleBuffer(pBuffer); for i := 0 to H-1 do for j := 0 to (W div 2)-1 do for k := 0 to 2 do P^[i, j, k]:=P^[i, (W-1)-j, k]; end; Ostatní metody třídy nejsou použity, takže jejich kód může být prázdný. Je však nutné tyto metody deklarovat, neboť implementace rozhraní znamená povinnost implementovat všechny jeho funkce.
— 11 —
3 KÓDOVÁNÍ VIDEA
3
Kódování videa
Cílem této sekce je objasněni postupu, jakým ze zdrojového videosouboru vybrat určitý úsek, ten zakódovat s použitím zvoleného kodeku a uložit jej do samostatného souboru. K tomu účelu bude sloužit graf, který se postará o dekódování zvukové a obrazové složky, jejich zpětné zakódování a sloučení do výsledného souboru.
3.1
Základní graf
Strukturu grafu ukazuje obrázek 2. Oproti grafu přehrávání videa neobsahuje renderování obrazu a zvuku. Na místo toho jsou zde navíc kodeky, multiplexor pro spojení obrazové a zvukové složky a konečně výstupní soubor. Video Codec File source
AVI splitter
AVI Mux
File output
Audio Codec
Obrázek 2: Graf kódování videa Nyní následuje zdrojový kód, který vytvoří takový graf. CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, Builder); CoCreateInstance(CLSID_AviSplitter, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, Splitter); CoCreateInstance(CLSID_AviMux, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, Mux); CoCreateInstance(CLSID_FileWriter, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, OFile); OFile.QueryInterface(IID_IFileSinkFilter, OutFile); OutFile.SetFileName(PWideChar(WideString(Dst)), nil); Builder.AddSourceFilter( PWideChar(WideString(Src)), ’’, SourceFile); Builder.AddFilter(ACodec, ’audio codec’); Builder.AddFilter(VCodec, ’video codec’); Builder.AddFilter(Mux, ’mux’); Builder.AddFilter(Splitter, ’Spliter’); — 12 —
3.2 Výběr kodeků
3 KÓDOVÁNÍ VIDEA
Builder.AddFilter(OFile, ’Output file’); Connect(SourceFile, Splitter); Connect(Splitter, VCodec); Connect(Splitter, ACodec); Connect(VCodec, Mux); Connect(ACodec, Mux); Connect(Mux, OFile); Builder.Render(GetFirstPin(Self.Src)); Nastavení jména výstupního souboru provádí rozhraní IFileSinkFilter, které poskytuje třída CLSID FileWriter. Význam ostatních filtrů je zřejmý, tedy až na doposud neinicializované kodeky videa a zvuku. Jejich výběru a inicializaci je věnována následující část.
3.2
Výběr kodeků
Jelikož obvykle nebývají identifikační čísla zvukového a videokodeku pevně uvedeny ve zdrojovém kódu (např. proto, že kodeky vybírá uživatel aplikace), je nutné je nějakým způsobem zjistit. Nejprve je třeba získat informace o tom, které kodeky má aplikace k dispozici. K tomu poslouží třída SystemDeviceEnum, která implementuje rozhraní ICreateDevEnum. Třídu vytvoří a rozhraní poskytne známý příkaz: CoCreateInstance(CLSID_SystemDeviceEnum, nil, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, Enum); Aktivaci výčtu kodeků nebo zařízení provede: Enum.CreateClassEnumerator(DevType, EnumMoniker, 0); Hodnotou proměnné DevType je jedna z konstant určujících kategorii kodeků. Na výběr jsou následující předdefinované konstanty: • CLSID VideoCompressorCategory zpřistupňuje kodeky videa. • CLSID AudioCompressorCategory slouží k výčtu audio kodeků. • CLSID VideoInputDeviceCategory zpřístupňuje seznam hardwarových zařízení, které jsou schopné produkovat videozáznam (např. kamery). — 13 —
3.3 Řízení kódování
3 KÓDOVÁNÍ VIDEA
• CLSID AudioInputDeviceCategory dle očekávání poskytne seznam zvukových vstupních zařízení. Nyní již nic nebrání provedení výčtu, jehož položky zpřístupňuje rozhraní IMoniker. Rozhraní nabízí dvě důležité metody, které dovolí kodek využít. První z nich je BindToStorage. Ta svazuje původní objekt, ke kterému IMoniker náleží, s úložištěm. Jako úložiště je použit objekt s rozhraním IPropertyBag. Takový objekt nabízí možnost přečíst „srozumitelné jméno kodeku. Jména kodeků je možné nabídnout uživateli. Další důležitou metodou je BindToObject. Tato metoda přímo vytvoří instanci objektu, který je s rozhraním IMoniker spojen, a zpřístupní jej pomocí zvoleného rozhraní. Konečně zdrojový kód, který provede výčet kodeků, je vidět v následující ukázce, jež navazuje na výše uvedené příkazy. Je jistě zřejmé, že proměnná Bag je deklarována jako IPropertyBag a CodecFilter jako IBaseFilter, ale CodecName není očekávaný pascalovský řetězec. Datovým typem této proměnné je OleVariant, který lze ovšem přímo přetypovat na požadovaný řetězcový typ. while (S_OK = EnumMoniker.Next(1, Moniker, @Fetched)) do begin Moniker.BindToStorage(nil, nil, IID_IPropertyBag, Bag); Moniker.BindToObject(nil, nil, IID_IBaseFilter, CodecFilter); Bag.Read(’FriendlyName’, CodecName, nil); end;
3.3
Řízení kódování
Odlišnosti řízení kódování videa od řízení jeho přehrávání jsou minimální. Opět je třeba použít rozhraní IMediaSeeking, avšak tentokrát jej neposkytuje graf, nýbrž jej zpřístupní pin filtru rozdělujícího zvukovou a obrazovou složku. Zpřístupnění tohoto rozhraní je třeba provést až po sestavení grafu metodou Render. Ovládání filtru je ovšem stejné jako při přehrávání videa. Výběr konkrétního úseku a jeho překódování do samostatného souboru spočívá v nastavení počáteční a koncového časového okamžiku běhu grafu a jeho spuštění. Nastavení počátku a konce provádí metoda SetPositions popsaná v kapitole 2.3. Je vhodné uložit si délku trvání celého zaznamenaného úseku jako rozdíl těchto časových okamžiků. Ta se hodí pro zobrazení průběhu zpracování v procentech, který je dán jako podíl aktuálního časového okamžiku zpracování a délky požadovaného úseku. — 14 —
3.3 Řízení kódování
3 KÓDOVÁNÍ VIDEA
Situaci poněkud komplikuje to, že existující rozhraní IMediaSeeking neposkytuje správný údaj o zpracovávaném časovém okamžiku. Pro jeho zjištění je třeba stejné rozhraní, avšak navázané na Mux neboli filtr pro spojení obrazu a zvuku. Použití nového rozhraní a výpočet poměrné doby průběhu ukazuje následující kód. function TVideoConvertor.GetProgress: integer; var Ratio: real; P: Int64; begin Mux.QueryInterface(IID_IMediaSeeking, Seeking2); Seeking2.GetCurrentPosition(P); Ratio:=100*(P/VideoLength); if (Round(Ratio)>=100) then begin MediaControl.Stop(); end; GetProgress:=Round(Ratio); end; Je důležité pro korektní uzavření souboru, aby byla činnost grafu zastavena po zpracování požadovaného úseku. To provádí metoda Stop rozhraní IMediaControl, které bylo osvětleno v sekci 2.2. Jelikož se dá předpokládat, že metoda pro zjištění průběhu bude volána v pravidelných intervalech, je vhodné umístit toto ukončení činnosti grafu právě sem.
— 15 —
4 LFMT VIDEO
4
LFMT Video
LFMT Video je programová jednotka, která vznikla jako zapouzdření přehrávače a kodéru videa pro usnadnění těchto operací. Jednotka obsahuje tři hlavní třídy, které podrobněji popisuje tato sekce.
4.1
TVideoPlayer
Třída zajišťuje přehrávání videa v samostatném okně nebo v okně aplikace. Přehrávání videa spouští následující sekvence příkazů: VideoPlayer:=TVideoPlayer.Create(); VideoPlayer.Load(’video.avi’); VideoPlayer.Play(); Kompletní výpis metod se nachází v následujícím výčtu: • Load(FileName,IsWindow) načte zadaný soubor a připraví jej pro přehrávání. Druhý parametr je boolovská proměnná, která je true v případě, že má být video přehráváno v okně aplikace. • Play spustí přehrávání. • Stop ukončí přehrávání. • Pause pozastaví přehrávání. • Seek(Ms) přesune přehrávání na zadaný časový okamžik. • SetRate nastaví rychlost přehrávání. • GetPos() vrátí aktuální pozici přehrávání. • GetDuration() vrátí celkovou délku videa. • UpdateWindow(Handle, Left, Top, Right, Bottom) aktualizuje okno aplikace přehrávající video. • UseGrabber(Grabber) použije předanou instanci třídy pro zpracování snímku. Instance třídy musí implementovat rozhraní ISampleGrabberCB.
— 16 —
4.2 TVideoCoder
4.2
4 LFMT VIDEO
TVideoCoder
Tato třída zajišťuje překódování úseku zdrojového souboru do samostatného nového souboru. Opět následuje výčet metod, které třída poskytuje. • CreateCodec(Audio,Video) vytvoří instance tříd pro kodeky daného názvu. • FindCodec(Name,CodecType) vrací záznam o kodeku identifikovaném názvem. Tento záznam obsahuje název kodeku a odkaz na jeho IMoniker.Typ kodeku může být ctAudio nebo ctVideo. • LoadCodecs(Target,CodecType) načte seznam dostupných kodeků zadaného typu. Kodeky jsou načteny do seznamu řetězců odvozeného od TStrings. Seznam je prvním parametrem této funkce. • Convert(Source, Target, StartMS, EndMS, Grabber) zakóduje úsek souboru Sourcezačínají v čase StartMS a končící v čase EndMS do souboru Target. Poslední parametr je nepovinný. Je jím odkaz na objekt, který má zpracovat snímky. • GetProgress vrací velikost již překódovaného úseku v procentech.
4.3
TGrabberCallback
Tato třída je určena pro zpracování samostatných snímků. Její použití demonstruje následující příklad2 Efekt kódu je stejný jako v příkladu v sekci 2.5. type PSampleBuffer = ^TSampleBuffer; TSampleBuffer = array[0..239, 0..351, 0..2] of byte; TMyGrabber = class (TGrabberCallback) procedure Frame(Tm: double; P: Pointer; Len: integer); override; end; procedure TMyGrabber.Frame(Tm: double; P: Pointer; Len: integer); const 2
Příklad předpokládá video o šířce 352 a výšce 240 bodů.
— 17 —
4.3 TGrabberCallback
4 LFMT VIDEO
W = 352; H = 240; var Ptr: PSampleBuffer; i,j,k: integer; begin Ptr:=PSampleBuffer(P); for i := 0 to H-1 do for j := 0 to (W div 2)-1 do for k := 0 to 2 do Ptr^[i, j, k]:=Ptr^[i, (W-1)-j, k]; end; Třída neobsahuje další metody a její samostatné použití není vhodné. Doporučený postup je ukázán ve výše uvedeném příkladu.
— 18 —
5 ZÁVĚR
5
Závěr
Tento návod měl demonstrovat použití služeb DirectShow pro účely zpracování videa. Věřím, že objasnil postupy, kterými lze těchto účelů dosáhnout. Zaujaly-li čtenáře možnosti tohoto rozsáhlého systému, mohu doporučit jako informační zdroj stránky MSDN. Ty obsahují kompletní popis všech rozhraní a tříd, které DirectX obsahuje. Martin Mojžíš
— 19 —
OBSAH
OBSAH
Obsah 1 Úvod do DirectShow 1.1 Objektový model COM 1.2 Rozhraní . . . . . . . . 1.3 Filtry a grafy . . . . . 1.4 Propojení filtrů . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
1 1 2 3 4
2 Přehrávání videa 2.1 Základní graf . . . . . . 2.2 Řízení přehrávání . . . . 2.3 Změny pozice a rychlosti 2.4 Přehrávání v okně . . . . 2.5 Vzorky videa . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
6 6 7 7 8 9
3 Kódování videa 12 3.1 Základní graf . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 3.2 Výběr kodeků . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.3 Řízení kódování . . . . . . . . . . . . . . . . . . . . . . . . . . 14 4 LFMT Video 4.1 TVideoPlayer . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 TVideoCoder . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 TGrabberCallback . . . . . . . . . . . . . . . . . . . . . . . .
16 16 17 17
5 Závěr
19
— 20 —