유돌이

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

2008. 12. 29. 23:36 델파이
이제 본격적으로 DShow어플을 제작하도록 하겠습니다. 여기까지 여러분은 DShow에 대한 기초적인 생각을 가지고 있어야 할 것입니다. 그것은 필터를 생성하고 연결하고 랜더링한다 입니다. 여러분이 이번장을 실습하기 위해서는 일단 델파이에 DSPack2.34버전이 설치되어 있어야 할 것입니다. 우리는 아주 간단하게 USB 카메라에서부터 영상을 가져와서 랜더링하는 데까지 실습할 것입니다.

[1] DShow개발의 여러가지 방식.

DShow 어플을 제작하는 방식에는 크게 2가지의 방법이 있습니다. 하나는 DSPack의 콤포넌트를 이용하는 방식이고 또다른 하나는 이런 컴포넌트를 이용하는 것이 아니라 DSPack에 있는 DirectShow.pas를 직접 사용하는 것을 의미합니다. 그리고 여기서 또다시 세분화 하자면 DirectShow.pas를 직접 사용하여 DShow 응용 어플을 개발하는 것에도 크게 두가지 방식이 있습니다. 하나는 필터들의 연결을 FitlerGraph에 의해 자동연결로서 작동하도록 개발하는 것이며, 다른 하나는 수동으로 직접 필터들을 하나하나 연결해 나가는 방식입니다.

일반적으로 여러분이 델파이에서 DirectShow용 응용프로그램을 개발한다면 DSPack의 컴포넌트를 사용해야 하는것으로 알고 있을 것입니다. 하지만 이 방식은 절대로 사용하지 말라고 충고하고 싶습니다. 왜냐하면 나중에 가면 전혀 쓸데없는 시간 낭비였다는 것을 후회하게 됩니다. 이 컴포넌트 의존적인 DShow의 개발은 나중에 가면 갈수록 배기량 1000cc도 안되는 티코에 수백만원(혹은 그 이상) 짜리 옵션을 다는 것과 마찬가지로 계속적인 추가 부담을 엄청나게 요구하기 때문입니다. 
그 부담은 로직상의 까다로운 구현이 될수도 있겠고, 혹은 시스템상의 리소스 부담이 될수도 있을 것입니다. (이것을 풀어서 이야기하면, 컴포넌트에서 제공하는 것 이상의 기능을 구현하기 위해서는 상당히 복잡한  둘러치기 코딩이 필요하기 때문이고, 또는 필터 내부에서 동작하여 어플의 인터페이스를 통해 해결되어야 할 문제를 컴포넌트 자체에서 해결하기 위해 불필요한 버퍼링을 하기가 쉽기 때문입니다. 동영상 어플에서 버퍼링은 곧 죽음임을 명심하셔야 합니다.)  

(참고 : 오해의 소지가 있을 것 같아서 설명합니다. DSPack의 컴포넌트를 사용하지 않고 개발해야 한다고 주장한다해서 DSPack의 가치를 폄하하는 것은 절대로 아닙니다. DSPack은 그 자체가 델파이에서 DShow의 선생님이라고 생각하셔야 합니다. 다양한 방식으로 컴포넌트를 구조화 하였고, 그 방대한 소스는 엄청난 참고가치가 있기 때문입니다. 물론 소스 중간중간에 약간의 버그가 있기는 합니다만, 그것은 오픈소스로 개발하면서 그정도까지의 세심한 배려를 바라는 것은 유저들의 과다한 욕심에 불과한 것이라고 봅니다. )  
  
자, 이제 우리는 DSPack 컴포넌트를 사용하여 DShow 어플을 개발하는 것에 대한 막대한 추가부담에 대하여 이야기 하였습니다. 그러므로 우리가 목표로 하는 것은 DirectShow.pas파일을 직접 사용하여 제작하는 것이라고 봅니다. 그런데 앞서도 이야기 하였듯이 이런 직접 개발에도 두가지의 방식이 존재한다고 하였습니다. 즉 FilterGraph의 자동연결 기능을 사용하는 방식과 수동으로 직접 하나하나 연결하는 방식이 있다고 하였지요. 이제 FitlerGraph라는 것에 대하여 이야기 하지 않으면 안되겟네요.    
 
한마디로 이야기 하자면 이것은 DShow에서 거의 아버지와 같은 역할을 하는 존재입니다. 전체 필터들을 관리하고  제어하는 역할을 하는 놈입니다. 이 FitlerGraph를 설명하기 위해서 저는 다음과 같은 클래스를 만들어 보았습니다. 
물론 설명을 하기 위한 임시용이며, 이해를 돕기위한 가상의 클래스입니다. 

TManager = Class(TObject)
  FilterList : TList;
  procedure Add(...);
  procedure Delete(...);
  procedure Connect(...);
  procedure Service1;  
  procedure Service2;  
  procedure Service3;
end;


위와같은 클래스가 있다고 해봅시다. 이 클래스를 사용하기 위해서 우리는 Filter라는 또다른 객체를 생성하여 FilterList에 Add하거나, 혹은 기존에 존재하는 Fitler를 Delete라는 멤버함수로 삭제할 수가 있을 것입니다. 그리고  그밖에 여러가지 Service 함수가 존재할 수가 있겠죠. 위의 클래스는 전형적인 클래스 사용방식의 하나임을 아실 것입니다.

이제 저는 FilterGraph가 위와같은 메인(중심) 클래스를 의미한다고 말씀드릴 수가 있겟습니다.  여러분은 앞으로 직 접 FilterGraph를 생성시켜야 하고요, 그 안에다 또다시 각각의 필터를 생성하여 Add 시켜야 하는 것입니다. 자, FilterGraph가 이러한 존재라는 것만을 어느정도 염두에 두셨다면 이제 다음으로 넘어가 봅니다.  


[2] DShow 클래스를 만들어 보자. 

우선 응용어플을 만들기 위해서 기본이 되는 베이스 클래스를 제작하도록 합시다. 그 이름을 TBaseDShow라고 이름붙이고 다음과 같이 선언합니다. 
Uses에 DirectShow9, DsUtil 추가합니다.

type 
  TBaseDShow = class(TObject)
   private
   public
     constructor Create;
     destructor Destroy; override;     
   end;

위와같이 기본 클래스가 만들어 졌습니다. 그 안에 생성자와 소멸자가 있군요. 여기에 우리는 가장 처음으로 준비할 사항을 코딩해 넣어야 합니다. DShow는 COM이라고 말씀드렸습니다. 이 COM을 사용하기 위해서는 먼저 초기화 작업이 필요하며 다 사용한 후에는 해제해 주어야 합니다.  COM을 초기화 하기 위해서는 CoInitialize(nil) 라고 하시면되고요, 이것을 해제하기 위해서는 CoUninitialize 을 사용하시면 됩니다. 즉 위 객체의 생성자와 소멸자에 각각 초기화와 해제코드를 넣어주시면 되겠지요.  

COM에대한 준비가 설정되었으면 우리는 그 다음으로 앞서 말한 FitlerGraph를 생성하여야 합니다. FitlerGraph는 프로그램이 시작되었을 때 가장먼저 준비되어야 하는 존재이기 때문에 마찬가지로 TBaseDShow객체의 생성자에서 만들어 놓도록 하는데요, 이것을 만드는게 조금 양이 많기 때문에 별도의 함수를 사용할 것입니다. 그 함수의 내용은 아래와 같습니다. 

function TBaseDShow .CreateFilterGraph(var Graph: IGraphBuilder): Boolean;
var
  ID : Integer;
begin
  Result := False;
  if Failed(CoCreateInstance(CLSID_FilterGraph,
                              nil,
                              CLSCTX_INPROC_SERVER,
                              IID_IFilterGraph,
                              Graph))
  then Exit;
  Result := True;
end;


여기까지를 한꺼번에 표현하면 다음과 같습니다.

type 
  TBaseDShow = class(TObject)
   private 
   public 
     FilterGraph: IGraphBuilder;   //필터그래프의 인터페이스 중의 하나.
     constructor Create;
     destructor Destroy; override;      
     function CreateFilterGraph(var Graph: IGraphBuilder): Boolean;
   end; 

implementation

constructor TBaseDShow .Create;
begin
  inherited Create;
  CoInitialize(nil);        //COM을 초기화한다.
  CreateFilterGraph(FilterGraph);   //필터그래프를 생성한다.
end;

 
destructor TBaseDShow .Destroy;
begin
  FilterGraph := nil;    //필터 그래프를 소멸시킨다. 
  CoUninitialize;         //COM을 셧다운시킨다.
  inherited Destroy;
end;

function TBaseDShow .CreateFilterGraph(var Graph: IGraphBuilder): Boolean; 
begin
  //생략. 위에 있으므로... 
end;     


 
[3] COM의 인터페이스에 대하여.

위의 소스코드에서 보시면 아시겠듯이, 이상한 놈의 타입이 나옵니다.   FilterGraph: IGraphBuilder 라고 선언한 부분을 보실수가 있습니다. 일반적으로 타입에는 T라고 붙습니다만, 요놈은 I라고 붙네요. 이것은 인터페이스 임을 의미하는 것입니다.
즉 FilterGraph는 인터페이스형 타입으로 선언되어 있음을 아셔야 합니다. 이 인터페이스를 우리는 일단 함수들의 모임을 선언한 상속가능한 타입이라고 정의해 봅시다. 즉, IGraphBuilder을 살펴보시면 인터페이스의 고유번호인 GUID가 있고, 그 아래 몇개의 함수들이 선언되어 있는 것을 보실수가 있습니다. (만일 여러분의 델파이에 DSPack을 설치하셨다면, IGraphBuilder에 마우스를 커서를 갖다놓고 Ctrl상태에서 클릭하여 직접 살펴보시기 바랍니다.)

인터페이스에 대하여 너무 어렵게 생각하지 마시기 바랍니다. 우리는 델파이에서 여러가지 타입을 선언하여 사용하고 있습니다. 대표적으로 레코드가 있겠고, 클래스가 있겠고, 또 수많은 변수형 타입이 존재할 것입니다. 위의 인터페이스도 그렇게 수많은 타입중에 하나이고, 요놈만 유별나게 앞에다 I를 붙여서 다른 모든 타입과 구분하고 있는 것입니다. (이 인터페이스에 대하여는 뒤에 필터 만들기에서 또다시 구체적으로 논의할 예정입니다. 일단은 여러가지 함수들의 모임을 정의해 놓은 타입이라고 기억해 두시기 바랍니다.) 
 
이제까지 저는 관습적으로 필터그래프를 생성하였다라고 설명하였습니다. 실제로 다른 사이트에 가보시면 알겠지만 그냥 필터그래프라고 언급해도 상관은 없습니다. 하지만 보다 정확히 말씀드리자면 필터그래프 매니저라는 COM오브젝트를 의미하여, 우리는 이 오브젝트에 존재하는 다양한 인터페이스에 대하여 살펴봐야 합니다. 

보충설명 --> 일반적으로 필터그래프라고 말한다면, 바로 필터그래프 매니저 COM 오브젝트를 의미합니다. 부르기 편하기 위하여 필터그래프라고 말하는 것인데요, 이 안에 속한 필터그래프라는 인터페이스의 한부분과 혼동하시면 안되겠습니다. 정리하자면 필터그래프 매니저라는 COM 오브젝트에는 필터그래프라는 인터페이스가 존재합니다.
우리가 일반적으로 필터그래프라고 지칭한다면 그것은 필터그래프 매니저 COM 오브젝트 안에 있는 다양한 인터페이스 중의 하나인 필터그래프를 지칭하는 것이 아니다라는 사실을 염두에 두시기 바랍니다. 요놈을 구분하지 않으면 사실 많이 헷갈립니다. 필터그래프, 필터그래프 하면서 설명을 하는 것을 보면 대부분 필터그래프 매니저인 COM 오브젝트 전체를 의미한다는 것이라고 판단하셔야 할 것입니다.)

자, COM 오브젝트에는 수많은 인터페이스들이 존재합니다. 우리는 이러한 인터페이스를 뽑아서 잘 사용하면 될 것입니다. (나중에 필터 제작에서 직접 인터페이스를 정의하여 사용하기도 할 것입니다.) 지금까지 우리는 COM을 초기화하였고, 필터그래프를(필터그래프 매니저인데요, 이하 필터그래프라고 지칭하겠습니다.) 생성하였습니다. 그리고 위에서 보시면 아시겠지만 필터그래프를 생성하면서 동시에 IGraphBuilder 라는 인터페이스를 하나 뽑았습니다.
즉, CoCreateInstance 함수를 사용하여 COM객체를 생성하면서 하나의 인터페이스를 뽑아들었습니다. 그렇다면 필터그래프 안에 있는 다른 인터페이스는 어떻게 하면 뽑을 수가 있을까요. 이제 하나의 COM 오브젝트에서 어떻게 인터페이스를 국수 뽑듯이 뽑아 먹을수가 있는지 설명하겠습니다. 


    Dong이라는 이름의 COM 오브젝트.
  ┏━━━━━━━━━━━━━━┓
  ┃                                           ┃
  ┃              A Interface              ┃
  ┃              B Interface              ┃
  ┃              C Interface              ┃
  ┃              D Interface              ┃
  ┃                   .                       ┃
  ┃                   .                       ┃
  ┗━━━━━━━━━━━━━━┛


위에서 Dong이라는 이름의 COM 오브젝트를 우리가 만들었다고 생각해 봅니다. 그리고 이것을 실제로 인스턴스화하기 위해서 CoCreateInstance 함수를 사용하였습니다. 그런데 CoCreateInstance 함수로 Dong 객체를 생성하면서 우리는 그 내부의 수많은 인터페이스 중 하나를 뽑을 수가 있습니다. 예로서 B 라는 인터페이스를 뽑았다고 가정해 봅시다. 그렇다면 나머지 인터페이스를 어떻게 뽑을 수가 있을까요. 다음과 같이 하면 됩니다. 

  B.QueryInterface(A);
  B.QueryInterface(C);
  A.QueryInterface(D);

위와같은 방식으로 얼마든지 우리는 Dong라는 COM 오브젝트에 있는 인터페이스를 뽑아서 사용할 수가 있습니다.

[4]GUID에 대하여.

위에서 필터그래프를 생성할때, 혹은 IGraphBuilder라는 인터페이스을 살펴보았을때, 우리는 고유한 번호들의 조합을 사용하고 있다는 사실을 아셨을 것입니다. 그 고유한 번호를 GUID라고 하며, 이 GUID가 COM 오브젝트를 식별하는 것으로 사용되어졌을때에는 이것을 다시 CLSID (Class ID) 라고 부릅니다. 즉, 아래에서 다시한번 필터그래프를 생성하는 로직을 살펴봅시다.


function TBaseDShow .CreateFilterGraph(var Graph: IGraphBuilder): Boolean;
var
  ID : Integer;
begin
  Result := False;
  if Failed(CoCreateInstance(CLSID_FilterGraph,
                                             nil,
                                             CLSCTX_INPROC_SERVER,
                                             IID_IFilterGraph,
                                             Graph))
  then Exit;
  Result := True;
end;


위에서 CLSID_FilterGraph 라는 것은 CLSID이며 IID_IFilterGraph 라는 것은 GUID입니다. CLSID_FilterGraph를 Ctrl 을 누른상태에서 클릭해보면 조금 이상한 수자들의 기호가 발견됩니다. 그것은 아래와 같습니니다.

CLSID_FilterGraph: TGUID = (D1:$E436EBB3;D2:$524F;D3:$11CE;D4:($9F,$53,$00,$20,$AF,$0B,$A7,$70));

위의 것은 사실 GUID의 일종인 것입니다. 이것을 다른 방식으로 표현하면 아래와 같습니다.

CLSID_FilterGraph: TGUID =  '{E436EBB3-524F-11CE-9F53-0020AF0BA770}';

위의 두개의 정의는 동일한 것입니다. 따라서 DirectShow.Pas에 선언되어있는 여러 종류의 GUID 선언을 보고서 헷갈리지 마시기 바랍니다. 여기서 중요한 것은 CLSID는 GUID가 COM 오브젝트의 식별자로 사용되어진 것을 말한다 입니다.

그렇다면 GUID라는 것은 무엇일까요. 마이크로소프트사는 전세계적으로 고유한 식별자를 가진 어떤 형태를 원하였습니다. 그것은 IP주소처럼 유일한 것이어야 하는데요, 아무리 많이 생성해도 전혀 중복될 가능성이 없는 유일한 값을 가진 어떤 형태였습니다. 델파이에서 우리는 이런 GUID를 손쉽게 만들수가 있습니다. 델파이의 유닛에 커서를 올려놓은 다음에 Ctrl + Shilt + G 를 누르면 유닛 창에 GUID가 만들어 져 있는게 보이실 것입니다.  앞으로 이짓도 밥먹듯 해야할 것이므로 이왕이면 머릿속에 기억해 두시기 바랍니다.

자, 다시 본론으로 돌아와서... 우리는 CoCreateInstance 라는 함수로 COM 오브젝트를 생성하면서 세가지의 GUID를 사용하였습니다. 그 첫번째는 COM오브젝트를 식별하기 위한 GUID(이것을 CLSID라고 하죠.)입니다. 그리고 IID_IFilterGraph라는 인터페이스 식별을 위한 GUID이며, 마지막으로 리턴값을 받을 인터페이스 자신의 실제 GUID입니다. 일반적으로 이 두개의 값은 동일해야 합니다. 즉 IID_IFilterGraph라는 인터페이스 식별자가 왔다면 인터페이스는 반드시 그 식별자에 해당하는 녀석이어야 한다는 것입니다. 다시말해 IFilterGraph라는 놈이 와야 한다는 것이죠.

확인해 보시면 아시겠지만 IID_IFilterGraph의 GUID값과 IFilterGraph라는 인터페이스 안에 정의되어 있는 GUID값은 동일한 것입니다. 이렇게 일반적으로 하나의 쌍으로 사용하고는 합니다. 그런데 위에서는 이상하게도 IID_IFilterGraph 라는 식별자를 사용해서 IGraphBuilder라는 인터페이스를 받아내고(뽑아내고) 있습니다. 이것이 가능한 이유는 IGraphBuilder라는 인터페이스가 실은 IFilterGraph그래서 상속되어진 것이기 때문입니다.
 
출처 : 델마당 dong(dongsoft)님의글

posted by 유돌이