Multimedia Player Dengan DirectShow (1)

March 7, 2006 at 10:06 am | Posted in Code Samples, Tutorials | 5 Comments

by: Zamrony P. Juhara

DirectShow adalah salah satu komponen DirectX yang berguna untuk streaming multimedia. Tutorial ini sebenarnya adalah bagian pertama dari tutorial menciptakan aplikasi TV Tuner (terinspirasi pertanyaan yang diposting mikroplus dimilis ini tentang bagaimana membuat aplikasi TV Tuner). Karena untuk membuat aplikasi TV Tuner memanfaatkan DirectShow butuh pengetahuan tentang Directshow, maka untuk bagian pertama, tutorial ini membahas langkah-langkah dasar membuat aplikasi DirectShow. Untuk membuat aplikasi sederhana dengan DirectShow tidak terlalu susah.

Berikut ini adalah step dasar membuat aplikasi DirectShow dengan Delphi. Untuk menggunakan DirectX dengan Delphi kita butuh unit-unit konversi header DirectX yg bisa didownload dari project-jedi.org.

1.Menginisialisasi COM dengan memanggil CoInitialize().

Contoh:

CoInitialize(nil);

CoInitialize() dideklarasikan di unit activex.pas

2.Menciptakan Filter Graph Manager.

DirectShow menggunakan istilah filter graph untuk mengacu pada komponen software yang memproses data multimedia. Filter graph manager tentunya adalah yang mengatur filter-filter tesebut.

Untuk menciptakan Filter Graph Manager kita bisa menggunakan fungsi CoCreateInstance(), juga dideklarasi di activex.pas

Contohnya:

Var FFilterGraph:IGraphBuilder;

 CoCreateInstance(CLSID_FilterGraph,nil,
                   CLSCTX_INPROC_SERVER,
                   IID_IGraphBuilder,FFilterGraph);

CLSID_FilterGraph adalah classID utk filter graph manager (didkelarasikan di unit directshow.pas),CLSCTX_INPROC_SERVER mengindikasikasn kita kan menggunakan in-process COM server (DirectX adalah in-process COM server). IID_IGraphBuilder adalah interface ID untuk IGraphBuilder. Interface IGraphBuilder adalah interface filter graph manager. Jika sukses FFilterGraph akan diisi pointer ke interface IGraphBuilder, jika gagal isinya nil.

3.Menciptakan filter graph dan menambahkannya ke Filter graph manager.

Filter graph DirectShow cukup banyak, salah satu filter graph adalah filter reader untuk membaca dan menciptakan filter untuk memainkan suatu file multimedia. Untuk membaca file multimedia dan memainkannya IGraphBuilder dilengkapi fungsi RenderFile yang berfungsi untk mengkonstruksi filter graph dan menambahkan filter graph ke filter graph manager. Contoh

var  FFilename:string
     wFilename:widestring;
begin
  wFilename:=WideString(FFilename);
  FFilterGraph.RenderFile(PWideChar(wFilename),nil);
end;

RenderFile mengharapkan nama file yang akan di render Namanya bertipe PWideChar (2 byte per 1 karakter) oleh karena itu kalo inputnya bertipe string harus di cast ke widestring. Parameter kedua adalah PlayList parameter ini reserved dan harus diisi nil.

RenderFile tidak menghapus filter graph yang sebelumnya sudah ada. Jika sebelumnya kita memanggil RenderFile dua kali maka ketika dimainkan kedua filter dimainkan bersama-sama.

4. Membuang filter graph dari filter graph manager

IGraphBuilder memiliki metode RemoveFilter digunakan untuk menghapus sebuah filter dari filter graph.
Contoh:

FFilterGraph.RemoveFilter(aFilter);

Dimana aFilter adalah filter graph yang akan dihapus bertipe IBaseFilter. Untuk mendapatkan filter apa saja yang ada di filer graph manager kita menggunakan fungsi enumFilters.

FFilterGraph.EnumFilters(enum);

enum bertipe IEnumFilters. Interface ini memiliki metode bernama Next yang digunakanan untuk mengakses maing-masing filter. Contoh

while (enum.Next(1,afilter,@totRead)=S_OK) do
begin
//lakukan sesuatu dengan filter
end;

Parameter pertama adalah jumlah total filter yang ingin diambil. Di contoh di atas kita mengambil filter satu-satu.. afilter adalah filter bertipe IBaseFilter, totRead adalah pointer ke variable yang akan menampung jumlah actual fiter yang dibaca. Menurut M$, afilter adalah array IBaseFilter sebanyak total filter yang direquest, tapi dari deklarasinya di DirectShow.pas, tipenya adalah IBaseFilter??

Untuk meremove semua filter contohnya:

  while (enum.Next(1,afilter,@totRead)=S_OK) do
  begin
    FFilterGraph.RemoveFilter(aFilter);
  end;

4. Mendapatkan interface-interface lain dari filer graph manager.

Interface-interface lain yang kita perlukan adalah IMediaControl berguna untuk mengontrol proses streaming multimedia data. IMediaSeeking untuk mengubah-ubah/mencari posisi dalam stream multimedia. IMediaEvent dan IMediaEventEx.digunakan untuk proses notifikasi event.
Untuk mendapatkan interface-interface tersebut (kecuali IMediaEventEx) kita meng-query dari interface IGraphBuilder. Contoh:

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);

Khusus untuk IMediaEventEx, tidak langsung diquery dari IGraphBuilder. IMediaEventEx adalah ekstensifikasi dari IMediaEvent dan harus diquery interface dari IMediaEvent.

5.Mengirimkan permintaan notifikasi event DirectShow.

Agar kita dapat mengetahui event-event yag terjadi kita perlu memberitahu DirectShow bahwa kita ingin diberi notifikasi.
Caranya adalah dengan dengan menggunakan fungsi setNotifyWindow milik IMediaEventEx. Contoh

FMediaEvent.SetNotifyWindow(aHandle,WM_MMNOTIFY,integer(self));

Parameter aHAndle adalah handle window yang akan menerima pesan WM_MMNOTIFY. Dimana WM_MMNOTIFY adalah message yang kita definisikan sendiri. Syaratnya message ini harus berada antara WM_APP-WM_APP+$bfff
Contoh:

const WM_MMNOTIFY=WM_APP+$1234;

jangan lupa tambahkan unit messages.pas.
Parameter ketiga adalah data milik kita sendiri, isinya bebas. Nantinya data ini dipass ke message handler melalui parameter lParam pesan WM_MMNOTIFY. Pada contoh diatas kita mengirimkan address instance kelas.

6.Memproses event

Buat sebuah event handler untuk WM_MMNOTIFY, contohnya

procedure WM_MMNotify(var msg:TMessage);message WM_MMNOTIFY;

dan pada implementasinya

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 akan berisi alamat instance kelas (lihat Mengirim permintaan notifikasi DirectShow diatas).

IMediaEventEx memiliki metode GetEvent() yang berguna untuk mendapatkan kode event yang sedang terjadi beserta parameter-parameternya. Berhubung fungsi ini mengalokasikan memori, untuk parameter-parameter event, kita wajib memanggil
FreeEventParams() untuk mencegah memory leak.
Mengenai daftar lengkap kode-kode event, bisa dilihat di dokumentasi DirectX. Yang paling sering saya pake adalah EC_COMPLETE, yang berarti proses streaming data telah selesai.

7. Mengontrol streaming data

IMediaControl digunakan untuk kontrol aliran data multimedia.

Untuk memulai streaming kita menggunakan Run. Contoh

FMediaControl.Run;

Untuk menghentikan streaming kita menggunakan Stop, contoh

FMediaControl.Stop;

Untuk pause, contoh

FMediaControl.Pause;

8.Mencari Posisi dalam Stream

IMediaSeeking kita pergunakan untuk mencari posisi dalam stream multimedia. Dengan IMediaSeeking, kita bisa memainkan stream dari mana saja dari awal stream, dari tengah atau lainnya. Directshow menggunkan dua istilah posisi untukseeking yakni current positiondan stop position. Current position adalah posisi streaming saat ini, sedangkan stop position adalah posisi dimana streaming akan dihentikan.
Untuk mendapatkan current Position dan stop position kita menggunakan GetPositions

FMediaSeek.GetPositions(currentPos,
                                   StopPos);

Untuk mengeset current position dan stop position menggunakan SetPositions


  FMediaSeek.SetPositions(CurrentPos,
                 AM_SEEKING_AbsolutePositioning,
                 StopPos,
                 AM_SEEKING_AbsolutePositioning,                 );

AMSEEKING_AbsolutePositioning adalah flag yang membertahukan bahwa curentPos dan stopPos adalalah posisi absolute dihitung dari awal stream.
Flag lain adalah
AMSEEKING_RelativePositioning yang berarti currentPos dan stopPos relative terhadap posisi current dan stop sebelumnya.
AMSEEKING_NoPositioning yang berarti posisi tidak diubah, dipakai bila kita ingin mengubah salah satu (current atau stop) dan yang lainnya tidak diubah.

Untuk mendapatkan total durasi stream, kita menggunakan GetDuration

FMediaSeek.GetDuration(durasi);

Current pos, stop pos dan durasi semuanya bertipe int64.

9. Membebaskan memori dan uninitialize COM


//membuang semua filter raph dari filter graph manager
RemoveAllFilters; 
FMediaControl:=nil;
FMediaEvent:=nil;
FMediaSeeking:=nil;
FFilterGraph:=nil;

CoUninitialize;

Berikut ini contoh implementasi kelas player multimedia, implementasinya belum lengkap dan belum dicoba pada semua tipe file. Formt file yang sudah dites adalah wav, mp3, midi, mpg,avi,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;
       FVideoWindow:IVideoWindow;

       FHandle: HWND;
    FControl: TWinControl;

       procedure SetHandle(const Value: HWND);

       function GetDuration: int64;
       function GetPosition: TPlayPosition;
       procedure SetPosition(const Value: TPlayPosition);
    procedure SetControl(const Value: TWinControl);
     protected
       procedure SetNotifyWindow(const ahandle:HWND);
       procedure SetWindow(const aHandle:HWND);virtual;
     public
       constructor Create;
       destructor Destroy;override;

       procedure BuildFilterGraph;virtual;abstract;
       procedure RemoveAllFilters;

       procedure Run;
       procedure Stop;
       procedure Pause;
       procedure Rewind;

     published
       property Control:TWinControl read FControl write SetControl;
       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 VideoWindow: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('Inisialisasi filter graph manager gagal');

  FFilterGraph.QueryInterface(IID_IMediaControl,FMediaControl);
  FFilterGraph.QueryInterface(IID_IMediaEvent,aEvent);
  aEvent.QueryInterface(IID_IMediaEventEx,FMediaEvent);
  FFilterGraph.QueryInterface(IID_IMediaSeeking,FMediaSeek);
  FFilterGraph.QueryInterface(IID_IVideoWindow,FVideoWindow);
end;

destructor TBasicPlayer.Destroy;
begin
  RemoveAllFilters;

  FVideoWindow.put_Visible(FALSE);
  FVideoWindow.put_Owner(0);

  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);
  end;
  enum:=nil;
end;

procedure TBasicPlayer.SetControl(const Value: TWinControl);
begin
  FControl:= Value;
  if FControl<>nil then
  begin
    FVideoWindow.put_Owner(OAHWND(FControl.handle));
    FVideoWindow.put_WindowStyle(WS_CHILD or WS_CLIPSIBLINGS);
  end;
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.


Kode berikut ini adalah contoh aplikasi yang memanfaatkan kelas TMMPlayer.

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.

Berikut ini adalah file DFM form diatas

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

5 Comments »

RSS feed for comments on this post. TrackBack URI

  1. Ass..
    Mohon Maaf ya mas,,sy mw nanya nih,kemrin itu q dah sedot delphi 8,tp pas mw q instal g bisa,kayaknya ada yang kurang deh..
    Mohon Bantuannya ya mas…

  2. Agak kurang jelas. Yang gak jalan itu delphi 8 nya atau demo ini gak jalan di delphi 8? Tolong lebih spesifik lagi

  3. Seru tutorialnya…

    Kalau kita pingin membuat kamera pengintai dari web cam terus hasilnya bisa di lihat dari komputer lain gimana teroy nya mas. kalo bisa langsung contoh streaming dari web cam nya ya..
    thk’s banget ya..

  4. Saya belum membuat tutorial bagaimana menampilkan aplikasi yang menampilkan isi web camera di komputer lain. Tapi tutorial bagaimana menampilkan isi web camera ke dalam aplikasi dapat Anda baca di
    sini

  5. terimakasih atas penjelasan dan link nya.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.
Entries and comments feeds.