U ovom dokumentu opisani su osnovni početci programiranja DirectShow aplikacija. Za programiranje DirectShow aplikacija potrebno je imati Microsoft DirectX SDK. U ovom dokumentu kao programski alat koristi se C++ / Visual C++, iako je programiranje DirectShow aplikacija moguće i u Visual Basicu.
Rad s multimedijom predstavlja niz velikih izazova:
Multimedijalni streamovi sadrže veliku količinu podataka, koji moraju biti obrađeni vrlo brzo
Audio i video moraju biti sinkronizirani tako da reprodukcija počne i završi u istom trenutku te se prikazuju jednakom brzinom
Podaci dolaze iz raznih izvora, uključujući lokalne datoteke, računalne mreže, televizijske signale i video kamere
Podaci dolaze u raznim formatima
Programeri ne znaju unaprijed kakav hardware ima krajnji korisnik
Microsoft DirectShow je arhitektura namijenjena za prikazivanje multimedijskih sadržaja na Microsoft Windows platformi. Dizajniran je da efikasno rješava navedeni niz izazova. Podržava više formata uključujući Advanced System Format (ASF), Digital Video (DV), Motion Picture Experts Group (MPEG), Audio-Video Interleaved (AVI) , MPEG Audio Layer 3 (MP3), WAV zvučne datoteke te mnoge druge. Podržava snimanje (capture) sa TV kartice putem Windows Driver Model (WDM) ili starijih Video for Windows (VfW) drivera. DirectShow je integriran zajedno sa ostalim DirectX tehnologijama te automatski pronalazi i koristi video i audio akceleraciju (DirectDraw i DirectSound) ukoliko je moguće, ali podržava i sustave koji nemaju hardwaresku akceleraciju. Koristi vremenski označene uzorke te tako rješava problem sinkronizacije. DirectShow je vrlo moćan alat koji znatno pojednostavljuje pisanje aplikacija kao što su DVD playeri, programi za obradu video zapisa, MP3 playeri, programi za snimanje sa TV kartica. DirectShow je zasnovan na Component Object Modelu (COM-u). Za pisanje DirectShow aplikacija potrebno je osnovno znanje COM objektnog programiranja.
Component Object Model (COM) je model objektno-orijentiranog programiranja korišten pri izradi svih DirectX aplikacija pa ga tako koriste i DirectShow programeri. DirectShow programeri uglavnom ne moraju znati kako napraviti svoje COM objekte što može biti izrazito kompleksno već moraju imati osnove korištenja već postojećih COM objekata što je znatno jednostavnije. Za potrebe DirectShow programiranja, u ovom poglavlju biti će opisano što je COM objekt, kako ga stvoriti te kako dohvatiti njegova sučelja.
COM objekti su jednostavno rečeno crne kutije koje programi učitavaju i koriste. Uglavnom su implementirani kao DLL datoteke. Kao i normalni objekti COM objekti izlažu metode koje se pozivaju i obavljaju zadatke. Ono što bitno razlikuje COM objekte od normalnih je njihovo stvaranje te to da se DLL datoteke ne moraju eksplicitno učitavati već se COM brine o tome i automatski učitava potreban DLL. Svaki objekt izlaže određen broj sučelja (interfaces). Sučelje je zapravo grupa metoda (funkcijskih članova objekta) koje obavljaju razne zadatke. Dakle, ukoliko se želi obaviti neka operacija nad COM objektom potrebno je prvo zatražiti određeno sučelje, a nakon toga pozvati metodu preko tog sučelja.
Svaki objekt mora imati GUID (Globally Unique Identifier) kako bi se objekti razlikovali. GUID je 128-bitna struktura koja je stvorena na takav način da ne postoji još jedna ista takva struktura. COM koristi GUID-e radi jedinstvenog označavanja objekata (CLSID) i radi jedinstvenog označavanja sučelja (IID).
Svaka COM metoda vraća 32-bitnu strukturu nazvanu HRESULT. Kod većine metoda HRESULT donosi dvije informacije; da li je metoda uspješno obavila svoj zadatak te malo detaljniji opis o ishodu operacija unutar metode. Prema dogovoru, ukoliko je metoda uspješno obavila operaciju prefiks HRESULT vrijednosi je S_, a ukoliko je došlo do greške prefiks je E_. Npr. dva najviše korištena koda su S_OK i E_FAIL. Potrebno je biti oprezan prilikom provjere vraćenih vrijednosti što može ilustrirati slijedeći primjer:
if(hr == E_FAIL) { //U slučaju greške } else { //U slučaju da je sve OK }
Moglo se dogoditi da je metoda vratila npr. vrijednost E_INVALIDARG (poslani krivi argumenti), a uz ovakvo provjeravanje ispalo bi da je sve ok te bi vrlo vjerojatno došlo do rušenja programa. Zbog toga se koriste makro funkcije SUCCEDED i FAILED. Makro SUCCEDED vraća TRUE za sve vrijednosti s prefiksom S_, dok makro FAILED vraća TRUE za sve vrijednosti s prefiksom E_. Ispravno napisano provjeravanje bilo bi:
if(FAILED(hr)) { //U slučaju greške } else { //U slučaju da je sve OK }
Dva najviše korištena načina za stvaranje COM objekta kod DirectShow programiranja su:
Direktno, slanjem CLSID vrijednosti CoCreateInstance funckiji. Funkcija će stvoriti objekt te vratiti pokazivač na sučelje koje se navede.
Indirektno, pozivanjem DirectShow metode ili funkcije koja stvara objekt. Ovako se uglavnom ne može odrediti koje sučelje želimo da objekt izlaže
Prije nego se bilo koji objekt može stvoriti potrebno je inicijalizirati COM pozivanjem funkcije CoInitialize. Na kraju programa kada se više neće koristiti COM programiranje potrebno je pozvati CoUninitialize. Ovdje će biti navedeno stvaranje objekta direktno budući da se indirektno stvaranje objekta koristi rjeđe u DirectShow-u.
Da bi stvorili objekt putem CoCreateInstance funkcije potrebno je znati CLSID objekta koji želimo stvoriti. Funkcija prima pet parametara:
rclsid. Podesiti ovaj parametar na CLSID objekta koji želite stvoriti
pUnkOuter. Podesiti ovaj parametar na NULL.
dwClsContext. Podesiti ovaj parametar na CLSCTX_INPROC_SERVER. Ovaj parametar govori da je objekt implementiran unutar DLL-a.
riid. Podesiti ovaj parametar na IID sučelja koje želite da objekt izloži nakon svog stvaranja. Funkcija će stvoriti objekt te vratiti pokazivač na sučelje putem ppv parametra.
ppv. Podesiti ovaj parametar na adresu pokazivača sučelja. Ova varijabla treba biti deklarirana kao pokazivač na traženo sučelje.
Slijedeći primjer pokazuje kako stvoriti Filter Graph Manager objekt te dohvatiti njegovo IGraphBuilder sučelje putem CoCreateInstance funkcije.
IGraphBuilder* pGraph;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph);
Ponekad su nam potrebna još neka sučelja koje objekt može izložiti. Svaki COM objekt ima IUnknown sučelje koje sadrži metodu QueryInterface. Zbog toga nije potrebno stvarati novi objekt kako bi zatražili drugo sučelje već je moguće na postojećem objektu zatražiti dodatno sučelje koje trebamo putem QueryInterface metode. Naravno, potrebno je znati IDD sučelja koje želimo. Primjer:
IMediaControl *pControl;
IMediaEvent *pEvent;
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
Navedeni primjer dohvaća pokazivače na IMediaControl i IMediaEvent sučelja koja izlaže Filter Graph Manager.
U ovom poglavlju navedena je osnovna terminologija i koncept DirectShow programiranja. Osnovna jedinica DirectShow-a naziva se filtar. Filtar je softwareska komponenta koja obavlja neku radnju na multimedijalnim podacima. DirectShow filtri mogu:
čitati multimedijalne podatke iz datoteka
dohvatiti video sa npr. TV kartice
dekodirati raznovrsne formate
proslijediti multimedijalne podatke grafičkim i zvučnim karticama
Filtri dobivaju ulazne podatke i stvaraju izlazne. Npr. ako filtar dekodira MPEG video, njegov ulaz je MPEG komprimiran signal, a izlaz je niz nekomprimiranih video frameova. U DirectShow-u aplikacija obavlja zadatak povezivanjem više filtara u lance tako da je izlaz jednog filtra ulaz drugome. Ovako povezani filtri čine graf filtara (filter graph). Aplikacija ne mora voditi računa o toku podataka kroz graf već je za to zadužena komponenta Filter Graph Manager. Aplikacija poziva API funkcije kao što su "Run" (puštanje streama podataka kroz graf) i "Stop" (zaustavljanje streama kroz graf). Ukoliko je potrebno više kontrole, nad pojedinim filtrima moguće je zatražiti njihova dodatna sučelja te preko njih pristupati direktno filtrima. Filter Graph Manager pruža metode koje aplikacija poziva radi stvaranja grafa povezivanjem filtara u lance. Dakle, programer piše DirectShow aplikaciju tako što pomoću Filter Graph Managera gradi graf, tj. povezuje filtre. Strogo gledajući postoje tri osnovna koraka koje aplikacija mora napraviti. Koraci su ilustrirani slikom:
Aplikacija mora stvoriti objekt Filter Graph Manager
Aplikacija koristi Filter Graph Manager da sagradi graf
Aplikacija koristi Filter Graph Manager radi kontroliranja grafa te protoka podataka kroz graf. Aplikacija također reagira na događaje koje joj šalje Filter Graph Manager.
DirectShow filtri sadrže COM objekte koji se zovu pinovi (pins). Pinovi su spojna mjesta filtara te ih filtri koriste za prebacivanje podataka iz jednog filtra u drugi. Filtri se mogu svrstati u nekoliko osnovnih kategorija:
Source filtar ubacuje podatke u graf. Source filtar može sam generirati podatke, može ih učitavati iz datoteke, dohvaćati s video kamere, itd.
Transform filtar dobiva na ulaz podatke, obrađuje ih te stvara izlazni stream. Enkoderi i dekoderi su primjer takvih filtara.
Renderer filtar nalazi se na kraju grafa. On dobiva podatke i prikazuje ih korisniku. Npr. video renderer prikazuje video uzorke na zaslon, audio renderer šalje uzorke zvučnoj kartici.
Splitter filtar podjeli ulazni stream na dva ili više izlazna streama. Npr. odjeljuje video i audio streamove.
Mux filtar dobiva više ulaznih streamova te ih sastavlja u jedan izlazni.
Već je prije rečeno da se graf stvara pomoću objekta zvanog Filter Graph Manager. Filter Graph Manager podržava sljedeće metode za stvaranje grafa. Metode su navedene tako da je napisano sučelje preko kojeg se pristupa određenoj metodi.
IFilterGraph::ConnectDirect pokušava direktno povezati dva pina. Ukoliko je pinove nemoguće povezati, metoda javlja grešku.
IGraphBuilder::Connect spaja dva pina. Ukoliko je moguće metoda pokušava povezati pinove direktno, ukoliko nije, pokušava povezati pinove posredstvom odgovarajućih među filtara
IGraphBuilder::Render počinje od izlaznog pina te stvara ostatak grafa do kraja. Ova metoda dodaje potrebne filtre te na kraju spaja renderer filtar
IGraphBuilder::RenderFile stvara kompletan graf za produkciju datoteke.
IFilterGraph::AddFilter dodaje filtar u graf, ali ga ne spaja. Prije poziva ove metode potrebno je stvoriti filtar npr. pomoću CoCreateInstance funkcije.
Ove metode nude tri osnovna pristupa stvaranju grafa:
Filter Graph Manager stvara cijeli graf
Ovaj način stvaranja grafa koristi se pozivom RenderFile metode ukoliko
programer želi producirati npr. avi, mpeg, wav ili mp3 datoteku. RenderFile
metoda automatski dodaje potrebne filtre u graf te ih spaja.
Filter Graph Manager stvara dio grafa
Ukoliko želimo da naša aplikacija radi bilo što drugo osim samo produciranja
datoteke, potrebno je da aplikacija stvori barem dio grafa. Npr. ukoliko
radimo aplikaciju za prikaz TV signala sa TV kartice potrebno je "ručno"
dodati source filtar, a nakon toga prepustiti Filter Graph Manageru da dovrši
posao pozivanjem Render metode.
Aplikacija stvara cijeli graf
Ponekad je potrebno da aplikacija dodaje u graf svaki filtar posebno pomoću
AddFilter metode. Nakon toga potrebno je dohvatiti pokazivače na svaki pin
pojedinog filtra te ih međusobno povezati pomoću Connect ili ConnectDirect
metoda. Iako je ovakvo stvaranje najkompliciranije, omogućuje nam najveću
moguću specifikaciju prilikom stvaranja grafa, a ponekad je i jedino moguće.
Prilikom povezivanja grafa pomoću metoda Render, RenderFile i Connect koristi se tzv. inteligentno spajanje (Intelligent Connect). Inteligentno spajanje sastoji se od nekoliko algoritama pomoću kojih Filter Graph Manager određuje koje filtre će ubaciti u graf radi mogućeg spajanja. Ukoliko npr. želimo spojiti izlaz filtra koji podržava RGB24 tip video podataka sa filtrom kojem ulaz zahtjeva tip RGB32, spajanje nećemo moći obaviti metodom ConnectDirect budući da ova metoda ne podržava inteligentno spajanje već pokušava direktno spojiti navedene filtre. Ukoliko ova dva filtra spajamo metodom Connect, ova metoda će pokušati naći odgovarajući filtar koji će ubaciti između ova dva filtra radi prilagođavanja tipa podataka. Tako, za navedeni primjer, metoda Connect će ubaciti filtar zvan Color Space Converter koji će ulazni tip RGB24 pretvoriti u tip RGB32 na svom izlazu, te će se filtri uspješno spojiti. Inteligentno spajanje omogućuje nam, dakle, da ne moramo točno znati koji filtar je moguće spojiti s kojim već prepuštamo DirectShow-u da se pobrine o tome.
GraphEdit je pomoćni alat za stvaranje i simuliranje grafa koji dolazi zajedno sa Microsoft DirectX SDK. Pomoću GraphEdit-a možemo stvoriti graf, provjeriti njegovu funkcionalnost, eksperimentirati prije nego što uopće počnemo pisati aplikaciju.
Pomoću GraphEdit-a, moguće je:
Također je moguće učitati graf koji se izrađuje u aplikaciji te vidjeti da li aplikacija stvara točan graf. Ukoliko proizvedete svoj filtar moguće unutar GraphEdit-a moguće ga je testirati. Moguće je i stvoriti graf, spremiti ga u datoteku i kasnije učitati u aplikaciji i koristiti kao da je u aplikaciji i sastavljen. Međutim, ovakav način pisanja aplikacije ipak se ne preporučuje.
Da bi se vidjelo kako se sve ovo napisano može iskoristiti, naveden je jedan primjer. Primjer predstavlja jednostavno napisan player koji između ostalog može reproducirati wav, mp3, mpeg i avi. Primjer nema veliku funkcionalnost, te je naveden radi pokazivanja kako koristiti navedene objekte i sučelja.
Program počinje pozivanjem CoInitialize funkcije potrebne za inicijalizaciju COM programiranja.
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
// Ispisati poruku ukoliko je došlo do greške
}
Radi jednostavnosti u daljnjem kodu neće biti provjeravane greške, ali u pravom programu uvijek bi trebalo provjeravati povratne vrijednosti funkcija.
Koristeći CoCreateInstance funkciju stvaramo objekt Filter Graph Manager:
IGraphBuilder *pGraph;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph);
Vidljivo je da je CLSID Filter Graph Managera CLSID_FilterGraph. Pozivom funkcije CoCreateInstance stvoren je objekt, te je vraćeno sučelje IGraphBuilder potrebno za gradnju grafa. Također će nam u programu zatrebati još dva sučelja IMediaControl i IMediaEvent.
Preko IMediaControl sučelja kontrolira se graf. U ovom primjeru služi za
pokretanje grafa.
Preko IMediaEvent sučelja dobivaju se događaji koje šalje aplikacija ili Filter
Graph Manager. U ovom primjeru koristi se radi čekanja da reprodukcija završi.
Sučelja dohvaćamo pozivom QueryInterface metode preko IGraphBuilder sučelja.
IMediaControl *pControl;
IMediaEvent *pEvent;
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
Sada gradimo graf. Ovdje korstimo prvi od tri navedena načina stvaranja grafa. Pozivom RenderFile metode, Filter Graph Manager automatski dodaje potrebne filtre za prikaz datoteke te ih spaja. Kao prvi parametar navodimo ime datoteke koju želimo pustiti, dok je drugi parametar rezerviran i mora biti NULL.
hr = pGraph->RenderFile(L"C:\\Primjer.avi", NULL);
Ova metoda neće se uspješno izvršiti ukoliko navedena datoteka ne postoji ili format datoteke nije podržan. (Ne može se sagraditi graf). Svakako ovdje upisati stvarno ime postojeće datoteke na disku. U pravom programu ovo bi svakako trebalo provjeriti.
Graf se pokreće pozivom Run metode preko IMediaControl sučelja.
hr = pControl->Run();
Nakon poziva ove metode podaci kreću kroz graf, dolaze do audio i video renderer filtara i prikazuju se. Međutim, protok podataka se odvija u posebnoj niti, a program se izvršava dalje. Zbog toga je potrebno pozivom funkcije WaitForCompletion preko IMediaEvent sučelja blokirati program i pričekati kraj prikazivanja.
long evCode = 0;
pEvent->WaitForCompletion(INFINITE, &evCode);
Ova metoda blokira dok nije došao kraj datoteke ili dok se, u prvom parametru, navedeni vremenski trenutak ne dostigne (izrazen u milisekundama). Kako je ovdje prvi parametar INFINITE, metoda blokira sve dok se ne dostigne kraj datoteke.
Kada je prikazivanje završilo, potrebno je otpustiti pokazivače na sučelja te zatvoriti COM.
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();
Ovdje je naveden cijeli kod programa. Prilikom prevođenja potrebno je dodati Strmiids.lib datoteku unutar link opcije.
include <dshow.h>
void main(void)
{
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent *pEvent = NULL;
// Inicijalizacija COM-a
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
printf("Greska, nemoguce inicijalizirati COM.\n");
return;
}
// Stvaranje Filter Graph Managera i trazenje dodatnih sucelja
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr))
{
printf("Greska, nemoguce stvoriti Filter Graph Manager.\n");
return;
}
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
// Gradjenje grafa. VAZNO: Podesiti prvi parametar na postojecu datoteku.
hr = pGraph->RenderFile(L"C:\\Example.avi", NULL);
if (SUCCEEDED(hr))
{
// Pokrece graf
hr = pControl->Run();
if (SUCCEEDED(hr))
{
// Cekanje kraja prikazivanja.
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);
// Ne koristiti INFINITE u pravom programu jer moze blokirati beskonacno dugo
}
} else {
printf("Greska, ne mogu pronaci datoteku ili format nije podrzan.\n");
return;
}
// Otpustiti sucelja
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();
}
Izvorni kod napisan kao Visual C++ projekt: Primjer.zip
1. Microsoft DirectX 9.0 SDK C++ Documentation
2. Mark D.
Pesce, Programming Microsoft DirectShow for Digital Video and Television