유돌이

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

Notice

2008. 12. 29. 23:51 델파이
이제 우리는 TBaseDShow의 마지막 함수인 다이렉트쇼 카테고리에서 원하는 장치를 불러오는 함수에 대하여 이야기를 할 것입니다.

[1] 카테고리에서 입력장치 불러오기 

여기서 우리는 한가지 인식하고 넘어가야 할 것이 있습니다. 그것도 구체적이며 비주얼하게 말이죠. 일단 GraphEdit 를 실행하여 '필터삽입윈도우'를 띄웁니다. 그곳에는 수많은 카테고리가 존재할 것입니다. 먼저 DirectShow Filters라는 카테고리를 클릭하여 아래로 쭉 풀어 놓습니다.
(이 필터삽입 윈도우의 크기가 고정되어 있는게 정말 불만이다. 앞으로 여러분은 필터를 만드실 경우가 있는데요, 만드신 필터를 이 GraphEdit를 사용하여 시뮬레이트를 하여야 합니다. 그런데 필터를 레지스트리에 등록해 놓고서, 이곳 GraphEdit의 DriectShow Fitler라는 카테고리에서 찾을 때마다 참 번거롭습니다. 보시다시피 등록되어 있는 필터들이 너무 많아서 좁은 창안에 한꺼번에 보이지 않을 뿐더러... 아무튼 이래저래 불편합니다. )


DirectShow Filters 카테고리 안에 놓여진 수많은 필터를 보시면 두개의  GUID가 나란히 놓여져 있는게 보이실 것입니다. 아마도 여러분의 눈에는 하나의 GUID(혹은 다른말로 CLSID)만 보이실 지도 모르겠습니다. 이것은 '필터삽입 윈도우'가 작아서 입니다. 수평스크롤바를 클릭하여 오른쪽으로 이동시키면 나머지 GUID도 보이실 것입니다. 이것의  한 예로 다음과 같습니다. 

  Video Renderer
    DisplayName : @device:sw:{083863F1-70DE-11D0-BD40-00A0C911CE86}\
                                                                                                {70E102B0-5556-11CE-97C0-00AA0055595A}
    FileName : C:\WIndows\system32\quartz.dll
    Merit : 00400000
    pin: 00 
    Version : 2


대충 위와같이 보이실 것입니다. 자, 위에서 보시다시피 하나의 필터에 두개의 CLSID가 보입니다. 이게 웬 하늘에 봉창 떨어질 일입니까. 하나의 COM Object에 두개의 CLSID가 있다니요. 하지만 당황하지 말고, 다른 필터들을 살펴보시기 바랍니다. 모두 두개의 CLSID가 있는데요, 앞의 CLSID는 하나같이 동일한 값이라고 확인 하실 수 있습니다. 자, 앞의 CLSID는 바로 카테고리의 CLSID를 의미하는 것입니다. 이것을 정리하면 아래와 같습니다.  
   
    DisplayName : @device:sw(종류):{카테고리 클래스 관리자의 CLSID}\{필터 CLSID 또는 ID} 

만일 우리가 특정한 카테고리에서 원하는 필터나 장치의 인터페이스를 가져오고 싶다면, 우리는 제일먼저 해당하는 카테고리의 CLSID를 알아야 할 것입니다. 그렇다면 여기서 우리는 한가지 의문이 들 것입니다. DirectShow Filters 카테고리 안에 있는 필터의 경우에는 별도로 카테고리 CLSID를 알지 못해도 CoCreateInstance를 사용하여 잘만 생성하여 사용하였는데, 왜 다른 카테고리에 있는 장치(혹은 광의의 필터)의 경우에는 반드시 카테고리 CLSID를 알아야 하는 것인가 하고... 

이 의문에 대한 해답은 이렇습니다. 우리가 DShow를 사용하기 위해서는 반드시 필터그래프 매니저를 필요로 합니다. 즉, 필터그래프가 필터들을 연결시키고, 스트림의 Run, Stop, Pause등과 같은 필수적인 제어를 하기 때문입니다. 그런데 필터그래프에 필터를 추가하기 위해서는 반드시 IBaseFilter 라는 인터페이스가 필요한데요, 이 인터페이스가 모든 COM 객체나 혹은 예전방식의 어떤 장치들(VFW 방식의 압축코덱이나  혹은 WDM 디바이스 드라이버)에 존재하는 것은 아니라는 사실입니다. 

예를 든다면 Video Compressors 카테고리에 있는 코덱들은 대부분 VFW의 구조를 가지고 있습니다. 이 구조는 COM 객체가 아니라 일종의 DLL 함수들만이 뿔뿔이 존재할 뿐입니다. 그런데 만약 여기서 IBaseFilter를 가져오지 못한다면 DShow는 예전방식의 모든 압축코덱이나 혹은 입출력 디바이스 드라이버를 사용하지 못할 것인데요, 이것은 말도 안돼는 것이겠죠.(MS가 세상물정에 어둡거나 혹은 볼랜드 같이 완전히 새로운 패러다임의 작품을 만들기를 시도한다면 모르겠지만... 쩝.)

자, 결론적으로 말하자면 DShow의 구조가 아닌 COM 객체나 혹은 예전방식의 모듈에서 IBaseFilter라는 인터페이스를 바인딩해주는 서비스가 별도로 필요하게 된 것인데요, 이 서비스를 받기 위해서 카테고리의 CLSID가 필요한 것이라고 생각하시면 되겠습니다. (이 서비스가 바로 IMoniker라는 일종의 COM의 인터페이스인데요, 이것은 비단 DShow에서만 사용하는 것은 아닙니다. 하기야 수많은 DShow의 인터페이스를 반드시 DShow에서만 사용하라는 법은 없는 것과 마찬가지일 것입니다. 우리가 TList를 데이터베이스와 같이 특정한 곳에만 사용해야 한다는 법칙이 없는것과 마찬가지 일 것입니다. )

여기까지 우리는 DirectShow Filters 카테고리 이외의 카테고리에서 원하는 장치(혹은 광의의 필터)를 얻기 위해서는 반드시 카테고리 CLSID를 알아야 하며, 그 이유는 DShow 방식의 구조가 아닌 객체나 모듈에서 IBaseFitler를 얻기위한 서비스에 반드시 필요한 요소이기 때문이라고 설명하였습니다. (제가 장치를 광의의 필터라고 하는 것은 바로 위에서 설명했던 것처럼 IBaseFilter라는 인터페이스를 얻을 수 있기 때문입니다.)

우리가 RS232시리얼 통신 프로그램을 만드는데 있어서 그 통신모듈을 직접 제작할 필요가 없이 잘 알려진 컴포넌트나 라이브러리를 사용하듯이, 델파이에서 DSPack를 가지고 DShow용 어플을 제작하면서 굳이 IMoniker서비스를 받기까지의 로직을 별도로 구현할 필요가 없다고 생각합니다. 하지만 정 궁금하시다면 DSPack에 있는 소스를 보시면  별로 어렵지 않다고 생각하실 것입니다. 아무튼 우리는 DSPack에서 미리 구현되어 있는 TSysDevEnum라는 클래스를 사용하여 아주 간단히 이러한 서비스를 받아 볼수가 있습니다.  그 전에 우선 주요한 카테고리의 선언을 보시면 다음과 같습니다.  

  CLSID_VideoInputDeviceCategory   //비디오 입력장치.
  CLSID_AudioInputDeviceCategory   //오디오 입력장치.
  CLSID_VideoCompressorCategory  //비디오 압축코덱
  CLSID_AudioRendererCategory      //오디오 랜더러.


위의 것들은 모두 DSPack의 DirectShow9.pas 파일에 정의되어 있습니다. 이곳에는 위의 4가지 말고도 모든 카테고리의 정의가 선언되어 있는데요, 이것을 필요할 때마다 찾기가 번거롭습니다. 그래서 차라리 TBaseDShow의 유닛에다가 몽땅 주석과 함께 옮겨 놓던지 하는게 좋습니다. 이상하게도 자주 까먹습니다. 

자, 이제 우리가 원하는 함수의 모습은 아래와 같습니다.  (저 같은 경우는 SysEnum 변수를 TBaseDShow의 멤버객체로서 정의해 놓고 사용해 왔는데요, 왜 그랬는지 기억이 가물가물 나지 않습니다. 현재 TSysDevEnum 의 내부 로직을 살펴보니까 로컬로 사용해도 무리는 없을 것 같아서 이해하기 쉽게 변경하였습니다.)

  function TBaseDShow.GetCamFilter: IBaseFilter;
  var
    SysEnum: TSysDevEnum;
  begin
    SysEnum := TSysDevEnum.Create;
    try
      SysEnum.SelectGUIDCategory(CLSID_VideoInputDeviceCategory);
      Result := SysEnum.GetBaseFilter(0)// 가장 첫번째 장치를 가져온다.
    finally
      SysEnum.Free;
    end;
  end;



위에서 가장 첫번째 장치를 가져온다는 부분에 유의하셔야 합니다. 사실 여러분은 장치의 이름으로도 검색하실 수가 있습니다. 예를 들어 압축코덱은 수많은 종류가 PC마다 순서의 기준이 없이 등록되어 있기 때문에 위와 같이 '몇번째 놓여있는 것을 가져와' 하는 것은 위험한 일입니다. 따라서 분명히 압축코덱과 같은 것들은 카테고리에서 이름으로 찾아야 할 것입니다.
그러나 카메라의 경우는 다릅니다. 압축 코덱의 경우와는 반대로 카메라의 경우에는 특정한 USB 드라이버의 이름에 맞게 DShow 어플이 제작되어서는 안되기 때문입니다. 그래서 어쩔 수 없이 카테고리의 순서에서 찾아서 장치를 가져옵니다. 혹시 여러분이 동영상 채팅같은 것을 할 경우에 클라이언트 프로그램을 실행시킴과 동시에 다음과 같은 문구를 보신적은 없으신지요. 

   '현재 컴퓨터에 비디오 입력 장치가 1개이상이 발견되었습니다. 몇번째 장치를 사용할 것인지를 선택하세요.'

위와 같이 선택 다이알로그 창이 뜨는 것을 보신적이 있으신지요. 이것들은 모두 위와같은 이유 때문인 것입니다. 
(장치가 1개 이상인 경우가 보통은 드물지만 DShow 개발자들에게서는 흔히 일어납니다. 예를 들어서 PCI 슬롯에  TV수신카드(혹은 BT878보드)나 엠팩보드가 동시에 꽂혀있고, USB에는 USB용 카메라까지 꽂혀 있으니까요. 그래서 예전에 저같은 경우는 PC를 살때 파워서플라이를 항상 일반인 용보다 월등히 성능 좋은 것을 사용해야 했던 기억이 납니다.) 

이제, TBaseDShow를 비로소 완성하였습니다. 이것의 풀소스를 아래와 같이 적겠습니다. 

unit cBaseDShow;
interface
uses
  Windows, SysUtils, Classes, Registry, DirectShow9, ActiveX, ExtCtrls, DsUtil;
 
type
  TBaseDShow = Class
  private
  public
    //필터그래프와 인터페이스...
    FilterGraph: IGraphBuilder;
    MediaControl: IMediaControl;
    VideoWindow: IVideoWindow;
 
    constructor Create;
    destructor Destroy; override;
    function GetCamFilter:IBaseFilter;
    function CreateFilterGraph(var Graph: IGraphBuilder): Boolean;              //필터그래프를 생성한다.
    function CreateFilter(const clsid:TGUID; var Filter:IBaseFilter): Boolean;  //각종 필터를 생성한다.
    function FindPinOnFilter(const Filter: IBaseFilter;                         //필터에서 내가 원하는 핀을 찾아주는 함수.
                             const PinDir: TPinDirection; var Pin: IPin): HRESULT;
  end;
 
implementation

 
{ TBaseDShow}

 
constructor TBaseDShow.Create;
begin
  inherited Create;
  CoInitialize(nil);                                                            //COM을 초기화한다.
  CreateFilterGraph(FilterGraph);                                               //필터그래프를 생성한다.
  FilterGraph.QueryInterface(IID_IMediaControl, MediaControl);                  //필터그래프의 인터페이스.
  FilterGraph.QueryInterface(IID_IVideoWindow, VideoWindow);                //필터그래프의 인터페이스.

end;

 
destructor TBaseDShow.Destroy;
begin
  if Assigned(MediaControl) then MediaControl.Stop;
  While Assigned(VideoWindow) do VideoWindow := nil;
  While Assigned(MediaControl) do MediaControl := nil;
  While Assigned(FilterGraph) do FilterGraph := nil;
  CoUninitialize;                                                               //COM을 셧다운시킨다.
  inherited Destroy;
end;

 
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;
  //GraphEdit로 현재의 필터그래프 구성을 볼수 있게한다.
  AddGraphToRot(Graph,ID);
  Result := True;
end;

 
function TBaseDShow.CreateFilter(const clsid: TGUID; var Filter: IBaseFilter): Boolean;
begin
  Result := False;
  if Failed(CoCreateInstance(clsid,
                             NIL,
                             CLSCTX_INPROC_SERVER,
                             IID_IBaseFilter,
                             Filter))
  then Exit;
  Result := True;
end;

 
function TBaseDShow.FindPinOnFilter(const Filter: IBaseFilter;
  const PinDir: TPinDirection; var Pin: IPin): HRESULT;
var
  IsConnected : Boolean;
  hr: DWORD;
  EnumPin: IEnumPins;
  ConnectedPin: IPin;
  PinDirection: TPinDirection;
begin
  Result := S_False;
  if not Assigned(Filter) then exit;
  hr := Filter.EnumPins(EnumPin);
  if(SUCCEEDED(hr)) then
  begin
   while (S_OK = EnumPin.Next(1, Pin, nil)) do
    begin
      //핀이 연결되었는지 조사.
      hr := Pin.ConnectedTo(ConnectedPin);
      if hr = S_OK then
      begin
        IsConnected  := True;
        ConnectedPin := nil;
      end else IsConnected := False;
      //핀의 방향을 검사
      hr := Pin.QueryDirection(PinDirection);
      //매개변수의 핀방향과 동일하고 현재 연결된 상태가 아니라면 루프에서 탈출.
      if (hr = S_OK) and (PinDirection = PinDir) and (not IsConnected) then break;
      pin := nil;
    end;
    Result := S_OK;
  end;
  EnumPin := nil;
end;

function TBaseDShow.GetCamFilter: IBaseFilter;
var
  SysEnum: TSysDevEnum;
begin
  SysEnum := TSysDevEnum.Create;
  try
    SysEnum.SelectGUIDCategory(CLSID_VideoInputDeviceCategory);
    Result := SysEnum.GetBaseFilter(0)// 가장 첫번째 장치를 가져온다.
  finally
    SysEnum.Free;
  end;
end;
end. 
 

출처 : 델마당  dong님의 글(dongsoft)
posted by 유돌이