









DirectShow is one of DirectX component useful for multimedia streaming. This tutorial is first part of creating TV tuner application tutorial (inspired by question posted by mikroplus in Delphindo mailing list). Because creating TV Tuner with DirectShow needs knowledge about Directshow, so for first part, this tutorial discusses basic steps to create DirectShow application. To create simple application with DirectShow is not so hard.
You need following softwares:
To initialize COM, we callCoInitialize(). For example:
CoInitialize(nil);
CoInitialize() is declared in Activex.pas unit.
DirectShow uses filter graph (or simply filter) to refer to software components that operate on multimedia data. Filter graph manager is manager of filters.
To create Filter Graph Manager, we can use CoCreateInstance() function, declared in activex.pas
For example:
var FFilterGraph:IGraphBuilder;
CoCreateInstance(CLSID_FilterGraph,nil,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,
FFilterGraph);
CLSID_FilterGraph is classID for filter graph manager (declared in Directshow.pas unit), CLSCTX_INPROC_SERVER indicates that we use in-process COM server (DirectX is in-process COM server). IID_IGraphBuilder is interface ID for IGraphBuilder. IGraphiBuilder interface is interface of filter graph manager. If succeed, FFilterGraph will be filled with address of interface IGraphBuilder instance, otherwise it will be filled with nil.
DirectShow has many built-in filters, on of it is filter reader to read multimedia file. To read multimedia file and play it, IGraphBuilder has RenderFile() method useful for constructing filter graph and add filter graph to filter graph manager automatically.
var FFilename:string;
wFilename:widestring;
begin
wFilename:=WideString(FFilename);
FFilterGraph.RenderFile(PWideChar(wFilename),nil);
end;
RenderFile() expects filename to be rendered. It should be type of PWideChar (2 byte per 1 character), therefore, if we use string, it must be typecasted to widestring. Second parameter is PlayList. This parameter is reserved and must be set to nil.
RenderFile doesn't clear existing filter graph. If we call RenderFile twice then all filter will be played simultaneous. This behaviour may not what we expected.
IGraphBuilder has RemoveFilter method to remove a filter from filter graph manager. For example:
FFilterGraph.RemoveFilter(aFilter);
Where aFilter is filter graph type of IBaseFilter that we want to delete. To get list of filters in filter graph manager, we use enumFilters().
FFilterGraph.EnumFilters(enum);
enum is type of IEnumFilters. This interface has method called Next to access next filter. For example
while (enum.Next(1,afilter,@totRead)=S_OK) do
begin
//do something with filter
end;
First parameter is number of filter we want to retrieve. In example above, we take filter one by one. afilter is filter type of IBaseFilter, totRead is address of variable that will filled with actual filter retrieved. According to Microsoft, afilter is array of IBaseFilter and its total element is equal to number of filter requested, but ub its declaration in DirectShow.pas, its type is IBaseFilter.
To remove all filter:
while (enum.Next(1,afilter,@totRead)=S_OK) do
begin
FFilterGraph.RemoveFilter(aFilter);
enum.Reset;
end;
After a filter is deleted, enum is no longer consistent with content of filter graph (out of sync). To make it consistent, we need to reset it.
Other interfaces we need is IMediaControl useful for controlling multimedia streaming process. IMediaSeeking is useful for seeking position in multimedia stream. IMediaEvent and IMediaEventEx.is used for event notification. To get those interfaces (except IMediaEventEx), we query from IGraphBuilder interface. For example:
var FMediaControl:IMediaControl;
aEvent:IMediaEvent;
FMediaEvent:IMediaEventEx;
FMediaSeek:IMediaSeek;
FFilterGraph.QueryInterface(IID_IMediaControl,FMediaControl);
FFilterGraph.QueryInterface(IID_IMediaEvent,aEvent);
aEvent.QueryInterface(IID_IMediaEventEx,FMediaEvent);
FFilterGraph.QueryInterface(IID_IMediaSeeking,FMediaSeek);
For IMediaEventEx, we don't quiery it from IGraphBuilder. IMediaEventEx is an extension of IMediaEvent and must be queried from IMediaEvent.
To let us know what is currently going on, we need to tell DirectShow that we want to get notified. We use SetNotifyWindow() method of IMediaEventEx. For example:
FMediaEvent.SetNotifyWindow(aHandle,WM_MMNOTIFY,integer(self));
aHandle is window window that will receive WM_MMNOTIFY message. WM_MMNOTIFY is our own custom message we define. Custom message should be between WM_APP-WM_APP+$BFFF.
const WM_MMNOTIFY=WM_APP+$1234;
Don't forget to add messages.pas unit. Third parameter is our own data. This parameter will get passed to message handler via lParam of WM_MMNOTIFY. In above example, we send address of class instance.
Create and event handler for WM_MMNOTIFY, for example:
procedure WM_MMNotify(var msg:TMessage);message WM_MMNOTIFY;
and in its implementation
procedure TfrmMediaPlayer.WM_MMNotify(var msg: TMessage);
var aplayer:TBasicPlayer;
evCode,param1,param2:integer;
begin
aplayer:=TBasicPlayer(msg.LParam);
aplayer.EventObj.GetEvent(evCode,param1,param2,0);
case evCode of
EC_COMPLETE:begin
//lakukan sesuatu
end;
end;
aplayer.EventObj.FreeEventParams(evCode,param1,param2);
end;
msg.lParam will hold address of class instance (see "Sending DirectShow Event Notification Request" above).
IMediaEventEx has GetEvent() useful to get code of event occured and its parameters. Because this function allocates memory for parameters of event, we must call FreeEventParams() to avoid memory leak. For complete event code, you can look at DirectX documentation. I use EC_COMPLETE very often. It tells us when data streaming is complete.
IMediaControl is used to control flow of multimedia data.
To start streaming, we use Run.
FMediaControl.Run;
To stop streaming, we use Stop, for example
FMediaControl.Stop;
To pause,
FMediaControl.Pause;
We use IMediaSeeking to seek out position in multimedia stream. With IMediaSeeking, we can play stream from any position. From beginning of stream, from middle or else. Directshow uses two position terms for seeking, i.e, current position and stop position. Current position is current position of streaming, while stop position is position where streaming will be stopped. To get current position and stop position, we use GetPositions().
FMediaSeek.GetPositions(currentPos,
StopPos);
To set current position and stop position, we use SetPositions().
FMediaSeek.SetPositions(CurrentPos,
AM_SEEKING_AbsolutePositioning,
StopPos,
AM_SEEKING_AbsolutePositioning);
To get total duration of stream, we use GetDuration().
FMediaSeek.GetDuration(durasi);
CurrentPos, StopPos and durasi are type of int64.
//remove all filter from filter graph manager
RemoveAllFilters;
FMediaControl:=nil;
FMediaEvent:=nil;
FMediaSeeking:=nil;
FFilterGraph:=nil;
CoUninitialize;
Below is an eaxample of multimedia player implementation, it still not complete and have not yet been tried for all media file types. File formats that have been tested are WAV, MP3, MIDI, MPEG, AVI, WMV,WMA, ASF.
unit uDirectShowPlayer;
interface
uses classes,windows,messages,directShow,controls;
const WM_MMNOTIFY=WM_APP+$1234;
type TPlayPosition=record
Current:int64;
Stop:int64;
end;
TBasicPlayer=class(TObject)
private
FFilterGraph:IGraphBuilder;
FMediaControl:IMediaControl;
FMediaEvent:IMediaEventEx;
FMediaSeek:IMediaSeeking;
FHandle: HWND;
procedure SetHandle(const Value: HWND);
function GetDuration: int64;
function GetPosition: TPlayPosition;
procedure SetPosition(const Value: TPlayPosition);
procedure SetControl(const Value: TWinControl);
protected
procedure SetWindow(const ahandle:HWND);
procedure SetNotifyWindow(const ahandle:HWND);
public
constructor Create;
destructor Destroy;override;
procedure BuildFilterGraph;virtual;abstract;
procedure RemoveAllFilters;
procedure Run;
procedure Stop;
procedure Pause;
procedure Rewind;
published
property Handle:HWND read FHandle write SetHandle;
property GraphObj:IGraphBuilder read FFilterGraph;
property ControlObj:IMediaControl read FMediaControl;
property EventObj:IMediaEventEx read FMediaEvent;
property SeekObj:IMediaSeeking read FMediaSeek;
property Position:TPlayPosition read GetPosition write SetPosition;
property Duration:int64 read GetDuration;
end;
TMMPlayer=class(TBasicPlayer)
private
FFilename: string;
procedure SetFilename(const Value: string);
public
procedure BuildFilterGraph;override;
published
property Filename:string read FFilename write SetFilename;
end;
function SetPlayPosition(const curr,stop:int64):TPlayPosition;
implementation
uses sysutils,activeX;
function SetPlayPosition(const curr,stop:int64):TPlayPosition;
begin
result.Current:=curr;
result.Stop:=stop;
end;
{ TBasicPlayer }
constructor TBasicPlayer.Create;
var aEvent:IMediaEvent;
begin
CoCreateInstance(CLSID_FilterGraph,nil,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,FFilterGraph);
if FFilterGraph=nil then
raise Exception.Create('Filter graph manager initialization failed');
FFilterGraph.QueryInterface(IID_IMediaControl,FMediaControl);
FFilterGraph.QueryInterface(IID_IMediaEvent,aEvent);
aEvent.QueryInterface(IID_IMediaEventEx,FMediaEvent);
FFilterGraph.QueryInterface(IID_IMediaSeeking,FMediaSeek);
end;
destructor TBasicPlayer.Destroy;
begin
RemoveAllFilters;
FFilterGraph:=nil;
FMediaControl:=nil;
FMediaEvent:=nil;
FMediaSeek:=nil;
inherited;
end;
procedure TBasicPlayer.Run;
begin
FMediaControl.Run;
end;
procedure TBasicPlayer.Stop;
begin
FMediaControl.Stop;
end;
procedure TBasicPlayer.Pause;
begin
FMediaControl.Pause;
end;
procedure TBasicPlayer.SetHandle(const Value: HWND);
begin
if FHandle<>Value then
begin
FHandle := Value;
SetWindow(FHandle);
end;
end;
procedure TBasicPlayer.SetWindow(const aHAndle: HWND);
begin
SetNotifyWindow(AHandle);
end;
procedure TBasicPlayer.SetNotifyWindow(const ahandle: HWND);
begin
FMediaEvent.SetNotifyWindow(aHandle,WM_MMNOTIFY,integer(self));
end;
function TBasicPlayer.GetDuration: int64;
begin
FMediaSeek.GetDuration(result);
end;
function TBasicPlayer.GetPosition: TPlayPosition;
begin
FMediaSeek.GetPositions(result.current,
result.Stop);
end;
procedure TBasicPlayer.SetPosition(const Value: TPlayPosition);
var apos:TPlayPosition;
begin
apos:=value;
FMediaSeek.SetPositions(aPos.Current,
AM_SEEKING_AbsolutePositioning,
aPos.Stop,
AM_SEEKING_AbsolutePositioning);
end;
procedure TBasicPlayer.Rewind;
begin
SetPosition(SetPlayPosition(0,GetDuration));
end;
procedure TBasicPlayer.RemoveAllFilters;
var enum:IEnumFilters;
aFilter:IBaseFilter;
totread:cardinal;
begin
totRead:=0;
Stop;
FFilterGraph.EnumFilters(enum);
while (enum.Next(1,afilter,@totRead)=S_OK) do
begin
FFilterGraph.RemoveFilter(aFilter);
enum.Reset;
end;
enum:=nil;
end;
{TMMPlayer}
procedure TMMPlayer.BuildFilterGraph;
var wFilename:widestring;
begin
wFilename:=WideString(FFilename);
FFilterGraph.RenderFile(PWideChar(wFilename),nil);
end;
procedure TMMPlayer.SetFilename(const Value: string);
begin
FFilename := Value;
end;
initialization
CoInitialize(nil);
finalization
CoUnInitialize;
end.
Following code is example application built using TMMPlayer class.
unit ufrmMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,DirectShow, StdCtrls, ExtCtrls,
uDirectShowPlayer, ComCtrls;
type
TfrmMediaPlayer = class(TForm)
btnOpen: TButton;
OpenDialog1: TOpenDialog;
btnPlay: TButton;
btnStop: TButton;
Timer1: TTimer;
ProgressBar1: TProgressBar;
lblProgress: TLabel;
btnPause: TButton;
procedure btnOpenClick(Sender: TObject);
procedure btnPlayClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure btnPauseClick(Sender: TObject);
private
FDuration:int64;
FMMPlayer:TMMPlayer;
procedure WM_MMNotify(var msg:TMessage);message WM_MMNOTIFY;
{ Private declarations }
public
constructor Create(AOwner:TComponent);override;
destructor Destroy;override;
{ Public declarations }
end;
var
frmMediaPlayer: TfrmMediaPlayer;
implementation
{$R *.dfm}
{ TForm1 }
constructor TfrmMediaPlayer.Create(AOwner: TComponent);
begin
inherited;
FMMPlayer:=TMMPlayer.Create;
FMMPlayer.Handle:=Handle;
end;
destructor TfrmMediaPlayer.Destroy;
begin
FMMPlayer.Free;
inherited;
end;
procedure TfrmMediaPlayer.WM_MMNotify(var msg: TMessage);
var aplayer:TBasicPlayer;
evCode,param1,param2:integer;
begin
aplayer:=TBasicPlayer(msg.LParam);
aplayer.EventObj.GetEvent(evCode,param1,param2,0);
case evCode of
EC_COMPLETE:begin
Timer1.Enabled:=false;
aplayer.Stop;
aplayer.Rewind;
ProgressBar1.Position:=0;
lblProgress.Caption:='0%';
end;
end;
aplayer.EventObj.FreeEventParams(evCode,param1,param2);
end;
procedure TfrmMediaPlayer.btnOpenClick(Sender: TObject);
begin
if opendialog1.Execute then
begin
FMMPlayer.Stop;
FMMPlayer.Rewind;
FMMPlayer.RemoveAllFilters;
ProgressBar1.Position:=0;
lblProgress.Caption:='0%';
FMMPlayer.Filename:=opendialog1.FileName;
FMMPlayer.BuildFilterGraph;
FDuration:=FMMPlayer.Duration;
end;
end;
procedure TfrmMediaPlayer.btnPlayClick(Sender: TObject);
begin
Timer1.Enabled:=true;
FMMPlayer.Run;
end;
procedure TfrmMediaPlayer.btnStopClick(Sender: TObject);
begin
Timer1.Enabled:=false;
FMMPlayer.Stop;
FMMPlayer.Rewind;
ProgressBar1.Position:=0;
lblProgress.Caption:='0%';
end;
procedure TfrmMediaPlayer.Timer1Timer(Sender: TObject);
begin
ProgressBar1.Position:=round(FMMPlayer.Position.Current/FDuration*ProgressBar1.Max);
lblProgress.Caption:=inttostr(ProgressBar1.Position)+'%';
end;
procedure TfrmMediaPlayer.btnPauseClick(Sender: TObject);
begin
FMMPlayer.Pause;
end;
end.
Below is DFM of form above
object frmMediaPlayer: TfrmMediaPlayer
Left = 192
Top = 127
BorderStyle = bsDialog
Caption = 'Simple MediaPlayer DirectShow'
ClientHeight = 84
ClientWidth = 681
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object lblProgress: TLabel
Left = 408
Top = 48
Width = 51
Height = 13
Caption = 'lblProgress'
end
object btnOpen: TButton
Left = 16
Top = 16
Width = 75
Height = 25
Caption = 'Open'
TabOrder = 0
OnClick = btnOpenClick
end
object btnPlay: TButton
Left = 96
Top = 16
Width = 75
Height = 25
Caption = 'Play'
TabOrder = 1
OnClick = btnPlayClick
end
object btnStop: TButton
Left = 16
Top = 48
Width = 75
Height = 25
Caption = 'Stop'
TabOrder = 2
OnClick = btnStopClick
end
object ProgressBar1: TProgressBar
Left = 200
Top = 16
Width = 441
Height = 19
Smooth = True
TabOrder = 3
end
object btnPause: TButton
Left = 96
Top = 48
Width = 75
Height = 25
Caption = 'Pause'
TabOrder = 4
OnClick = btnPauseClick
end
object OpenDialog1: TOpenDialog
Left = 496
Top = 48
end
object Timer1: TTimer
Enabled = False
Interval = 1
OnTimer = Timer1Timer
Left = 544
Top = 48
end
end
Download source code here.
We have discussed basic steps to utilize DirectShow, including creating filter graph manager, querying media control interface and media seeking interface and how to play multimedia multimedia such as audio file and movie with filter graph manager.
Do you like this article? Help this website improve by donating. Any amounts is appreciated.
Or you can help by bookmarking this page.
Bookmark this on Delicious