Juhara.com
Language : English Indonesia

Multimedia Player dengan DirectShow Part 1

Zamrony P Juhara
15 September 2006 16:28:00
 (12124 views)
Tutorial bagian pertama yang menjelaskan bagaimana memanfaatkan DirectShow, salah satu komponen DirectX, untuk membuat aplikasi multimedia player menggunakan Delphi.

Pendahuluan

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 di milis Delphindo 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.

Apa yang diperlukan?

Anda membutuhkan perangkat lunak berikut ini:

  • Otak.
  • DirectX versi 8 ke atas.
  • Kompiler Delphi. Saya menggunakan Delphi 7.
  • Konversi header DirectX. Anda bisa mendownload konversi DirectX 9 atau DirectX 8.1. Saya menggunakan konversi header DirectX 8.1.

Inisialisasi COM

Untuk menginisialisasi COM, kita panggil CoInitialize(). Contoh:

CoInitialize(nil);

CoInitialize() dideklarasikan di unit Activex.pas.

Menciptakan Filter Graph Manager.

DirectShow menggunakan istilah filter graph (biasa disebut filter) 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 untuk filter graph manager (dideklarasikan di unit Directshow.pas), CLSCTX_INPROC_SERVER mengindikasikan kita akan menggunakan in-process COM server (DirectX adalah in-process COM server). IID_IGraphBuilder adalah interface ID untuk IGraphBuilder. Interface IGraphiBuilder adalah interface filter graph manager. Jika sukses FFilterGraph akan diisi pointer ke interface IGraphBuilder, jika gagal isinya nil.

Menciptakan filter graph dan menambahkannya ke Filter graph manager.

Filter DirectShow cukup banyak, salah satu filter 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 untuk mengkonstruksi filter-filter 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, jika inputnya bertipe string harus di typecast 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.

Membuang filter graph dari filter graph manager.

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

FFilterGraph.RemoveFilter(aFilter);

Di mana aFilter adalah filter graph yang akan dihapus bertipe IBaseFilter. Untuk mendapatkan filter apa saja yang ada di filter graph manager, kita menggunakan enumFilters().

FFilterGraph.EnumFilters(enum);

enum bertipe IEnumFilters. Interface ini memiliki metode bernama Next yang digunakan untuk mengakses filter berikut. 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. Dicontoh di atas, kita mengambil filter satu-satu. afilter adalah filter bertipe IBaseFilter, totRead adalah pointer ke variable yang akan menampung jumlah aktual fiter yang dibaca. Menurut Micosoft, 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);
  enum.Reset();
end;

Setelah dihapus, enum menjadi tidak sinkron lagi (out of sync). Agar enum isinya sinkron, perlu kita reset.

Mendapatkan interface-interface lain dari filter 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 ekstensi dari IMediaEvent dan harus diquery dari IMediaEvent.

Mengirimkan permintaan notifikasi event DirectShow.

Agar kita dapat mengetahui event-event yang 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. Di mana 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 di atas kita mengirimkan address instance kelas.

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" di atas).

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 pakai adalah EC_COMPLETE, yang berarti proses streaming data telah selesai.

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;

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 menggunakan dua istilah posisi untuk seeking, yakni current position dan stop position. Current position adalah posisi streaming saat ini, sedangkan stop position adalah posisi di mana 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 memberitahukan bahwa curentPos dan stopPos adalah posisi absolut dihitung dari awal stream.
  • AMSEEKING_RelativePositioning yang berarti currentPos dan stopPos relatif 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.

Membebaskan memori dan uninitialize COM

//membuang semua filter graph 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. Format file yang sudah dites adalah 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);
     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 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('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);
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.

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

Download source code di sini.

Ringkasan

Kita telah mendiskusikan langkah-langkah dasar menggunakan DirectShow, meliputi menciptakan filter graph manager, mendapatkan interface kontrol media, mendapatkan interface media seeking dan bagaimana memainkan file multimedia seperti file audio dan movie menggunakan filter graph manager.

Artikel Terkait

Anda suka artikel ini? Bantu website ini berkembang dengan menyumbang. Berapapun jumlahnya akan sangat dihargai.

Atau Anda dapat membantu dengan membuat bookmark. Delicious Bookmark this on Delicious