유돌이

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

2009. 1. 5. 18:13 델파이

지난회에서는 소스필터와 변환필터 각각의 종류에 대하여 설명하였습니다. 그렇다면 이제 DShow에서 제공하는 중요 기본 필터들을 살펴보고, 그 특성과 사용방법에 대하여 강의할 것입니다. 일단 여러분이 밥먹듯이 익히고 사용해야 할 주요 필터들의 항목은 다음과 같습니다. 다음의 필터는 GraphEdit의 '필터삽입윈도우'에서 DirectShow Filters 카테고리에 있는 것들입니다.  

 => Avi Mux, Avi Splitter, Color Space Converter, File Source(Async.), File Writer, Infinite Pin Tee Filter, Overlay Mixer, Sample Grabber, Smart Tee, Video Renderer

위에서 Overlay Mixer 나 Infinite Pin Tee Filte 를 제외하고는 DShow를 대변하는 주요 필터라고 할 수가 있겠습니다.
DShow용 어플을 개발하는데 있어서 거의 필수적이라고 생각하셔도 무방할 것입니다. 그럼 하나씩 알아보도록 하겠습니다. 

 (1) Avi Mux

통상 '먹스'라고 부릅니다. 여러분은 '믹싱'과 '먹싱'에 대하여 구분하셔야 합니다.
믹싱은 두개이상의 데이터를 하나에 완전히 혼합시키는 것을 의미합니다. 예를 들어서 강남콩이 반쯤 담겨있는 통에 좁쌀을 붓고서 마구 흔들어 섞어지는 형태를 의미한다고 할 수 있겠습니다. 강남콩과 좁쌀이 완전히 뒤섞여 다시 원래대로 분류하기가 힘들게 되어 버리는 것을 의미합니다. 몇달전에 비디오 스트림 안에 자막을 '믹싱'처리 해달라는 의뢰를 받은 적이 있었습니다. 이것은 즉 비디오 영상의 각 프레임마다 자막을 이미지 위에 그려넣어달라는 것을 의미합니다.

자, 그렇다면 '먹싱'이란 무엇을 의미하는 것일까요. 먹싱은 하나의 통에 강남콩과 좁쌀을 별도로 비닝봉지에 넣어 보관하는 상태를 의미합니다. '먹싱' 을 예로 들때에 대표적으로 영상과 음성의 스트림입니다. 영상과 음성은 서로 독립된 형태의 데이타로서 보존해야 하기때문에 대부분 '먹싱'처리를 합니다. 여러분의 하드디스크에 어떤 영화파일이 있다면 한번 울트라 에디트로 열어보시길 바랍니다. 영상과 음성의 데이터 사이에 일정한 간격을 두고 마치 공백처럼 느껴지는 FF값으로 가득차 있는 곳을 간혹 발견하실 수가 있을 것입니다. 이것이 바로 영상과 음성이 나뉘어지는 칸막이 비슷한 것이라고 생각하시면 되겠습니다.

우리가 동영상을 Avi로 저장하기 위해서는 이렇게 일단 '먹싱'를 해야 합니다.  위의 Avi Mux 필터를  GraphEidt 상에서 '필터삽입윈도우'를 사용하여 생성시키면 메인화면에는 Input핀 하나와 OutPut핀 하나가 있는 네모박스 형태의 비주얼한 필터가 보여질 것입니다. 자, 이상태에서 USB 카메라의 입력장치를 하나 필터로 생성시켜 봅니다. 이것은 제가 Video Capture Source 카테고리에 있다고 하였지요. 두개의 필터를 연결시켜 보시면 알겠지만, 연결하자 마자  Avi Mux 필터에는 또다른 하나의 입력핀이 생겨지는게 보이실 것입니다. 

-------------------------------------------------------------------------------------------------
참고 -> Avi Mux 필터의 입력핀은 이렇게 계속해서 생겨집니다. 현재 연결되어 있는 핀보다 항상 갯수가 하나더 만들어지게 되어 있는 것이죠. 만일 여러분이 필터개발자라면 이런식으로 동적 핀을 생성하게 만드는 것도 사실 간단한 일은 아닙니다. 동적핀을 만드는 것이 내부적으로 어려운 것은, 그 핀들을 단순히 생성시키는 것의 문제가 아니라 계속해서 또다른 입력이 들어올 수 있다는 가정하에 내부로직을 준비해야 하기 때문입니다. 
-------------------------------------------------------------------------------------------------

Avi Mux 필터의 입력핀에 비디오 스트림을 연결시키면 하나의 입력핀이 더 생겨집니다. 이때 두번째 입력핀에 오디오 스트림을 연결하고 보면 출력핀은 그대로 하나인 것을 보실 수가 있습니다. 이처럼 영상과 음성 두개의 입력 스트림이 Mux 되어 하나의 스트림으로 출력되는 것입니다. 
 
그럼 여기서 GraphEdit를 사용하여 간단하게 파일을 만드는 법을 설명하겠습니다.  


  Cam Filter ----> Avi Mux -> File Writer    
                            ▲  
  Audio Filter ------┘


위와같이 연결하시고 Play를 하시면 되겠습니다. 물론 Cam Filter는 제가 앞서말한 Video Capture Source 카테고리에 있는 USB 카메라 입력장치를 말합니다. 또한 AudioFilter는 Audio Capture Source 카테고리에 있습니다. 마지막으로 Filter Writer는 Avi Mux와 마찬가지로 DirectShow Filtes 카테고리에 있을 것입니다. Play를 하면 화면에는 어떠한 랜더링 창도 뜨질 않지만, 대신에 하드디스크에 여러분이 지정하신 파일 이름으로 영상과 음성이 먹싱되어 저장되는 중일 것입니다. 마이크가 있으시다면 목소리를 가다듬고 소리를 지르셔도 좋으실 것입니다.

자, 지금까지 '믹싱'과 '먹싱'의 차이에 대하여 알아보았으며 실제로 영상과 음성을 '먹싱'처리하여 저장하기까지 하였습니다. 

(2) Avi Splitter 

Avi Splitter는 '파서 필터'의 일종이라고 전회에 설명하였습니다. 따라서 추가 설명은 하지 않겠습니다. 

(3) Color Space Converter

이 Color Space Converter라는 필터가 아주 고마운 녀석입니다. 이 필터는 입력핀에 YUV형태의 미디어타입이 들어온것을 RGB형태로 변환합니다. 일반적으로 YUV를 RGB로 변환하는 로직을 MMX나 SSE로 최적화된 로직이 있습니다. 하지만 이런 로직을 DShow에서 사용하기 위해서는 별도의 변환필터를 만들거나 해야합니다. 그런데 DShow에서 아예 이런 필터를 준비해 뒀다는 것은 참으로 고마운 것입니다. 이것을 다른 시각에서 본다면 DShow를 사용하면 이런 형태의 서비스를 받을 수가 있다는 의미도 될 것입니다. 

사실 제가 DShow 초보시절에 바로 이 필터의 존재조차도 몰라서 직접 변환로직을 만들기위해 얼마간 끙끙거렸던 적이 있었습니다. 왜냐하면 연결하려는 앞의 필터에서 나오는 출력핀의 미디어 타입에 RGB형이 없고 죄다 YUV형 타입이었기 때문입니다. 머리가 나쁘면 손발이 고생한다고, 눈앞에 뻔히 있는 것을 별도로 만들기 위해 몇일간을 헤메었던 기억이 눈앞에 선합니다. 굳이 있는 것을 별도로 만들 필요는 없을 터이죠. 게다가 테스트한 결과 이 Color Space Converter 필터의 변환 성능은 상당히 최고급 수준입니다. 

(4) Overlay Mixer

이 Overlay Mixer 필터는 예전에 영상이나 자막을 합성할때 사용하던 필터입니다. 그래픽 카드의 오버레이 평면을 이용한 것인데요, 이것은 하나의 디스플레이 평면 위에 또다른 평면이 오버레이 되면서 합성되어지는 방식을 의미합니다. 하도 오래전에 사용해 보아서 기억이 가물가물합니다. 이 필터는 요즘 별로 사용하지 않을 거라는 생각이 듭니다.

자막을 아직도 이 필터를 사용해서 하는지 모르겠습니다. 예전에 지금의 곰플레이어가 아닌 아드레날린이라는 어플이 마악 태동할 초창기에는 자막 처리가 동영상 어플 개발자들의 관심사항 첫순위였습니다. 신화선님의 책에도 자막처리가 독립된 장으로 상당히 할당되어 나와있을 정도였으니까요.
아무튼 언듯 기억나는 것은 메인화면 위에 오버레이 평면이 겹쳐지게 되는데요, 이때 오버레이 평면에 특정한 색을 투명으로 지정을 하면 그 부분만 뻥 뚤려서 아래의 메인화면과 합성되어 보여지는 식입니다. 이것은 CPU에 전혀 부담이 없이 그래픽카드에서 처리해 주는 것입니다.
만일 자막 처리를 CPU에서 '믹싱'의 방식으로 버퍼링 처리를 하였다면 상당한 부담이 되었을 터입니다. 그당시 컴퓨터 성능이 상당히 낮았고, 웬만한 동영상 파일하나 재생 하는데에도 CPU 점유율이 20, 30%까지 치솟았던 점을 감안한다면 왜 그렇게 자막처리에 안달을 볶았는지 지금에서야 이해되는 측면도 있습니다.  아무튼 그냥 이정도만 알고 계셔도 무방하다고 생각합니다.
OverLay에 대해서 더 알고 싶으시다면 차라리 DirectX 게임관련 서적을 참고하시는 게 더 자세하다고 생각합니다. 왜냐하면 OverLay라는 것 자체가 그래픽카드에서 지원되는 자원중 하나이기 때문입니다. 

(5) Sample Grabber

이 Grabber를 사전에서 찾아보면 1. 부여잡는 사람, 강탈자, 욕심꾸러기, 2. 흥미진진한 것, 깜짝 놀라게 하는 것. 이라고 나와 있습니다. 하지만 프로그래밍에서 혹은 전산일반에서 Grabber(그래버)라고 한다면 이 말의 의미는 조금 독특한 성격의 어떤 기능을 대변하고 있습니다. 즉, 어떤 것들 중에서 하나를 추려내는 기능이라는 의미인 것입니다. 아마도 '부여잡는 사람'이라는 첫번째 의미와 비슷할 것입니다만, 그와는 또다른 차원의 전산적 의미를 내포하는 것이라 하겠습니다. 

FA라는 공장자동화 계열에는 비젼시스템이라고 있습니다. 이 시스템은 생산라인에서의 불량을 자동 체크하는 기능을 갖도록 이미지 판독을 위한 카메라와 보드등을 갖추고 있는데요, 여기서도 '그래버 기능'이라는 표현이 사용되어 집니다. 실제로 '그래버 기능'이라는 것은 연속된 스트림에서 어느 한 프레임을 '찰칵' 찍어내는 기능을 의미합니다.
아마도 이게 사실 어떤 것인지 이해하기 어려울 지도 모르겠습니다. 동영상이라는게 연속된 일련의 이미지들인데, 여기서 기껏 한장을 뽑아내는 기능을 별도로 '그래버 기능'이라고 말할 정도까지 까다로운 것인가 하고요...  결론적으로 말씀드린다면 까다롭고요, 까다로울 수 밖에 없는 이유는 각각의 R, G, B  채널과 주파수와의 상관 관계에 있습니다. 아무튼 중요한 것은 이렇듯 FA라는 산업분야의 비젼시스템에서도 '그래버'라는 용어를 사용하고 있다는 것입니다. 

그래버라는 것은 전산일반에서 사전적인 뜻 이외의 독특한 기능을 대변하고 있다는 것을 알고 계시면 좋을 것 같아서 이렇게 설명이 삼천포로 빠졌습니다. 나중에 FA의 비전시스템 SDK 프로그래밍을 하실때 이 '그래버'라는 의미를  지금 기회에 알아두시면 좋을 것 같아서 설명드린 것입니다. 

아마도 다음회부터 우리는 만들고자 하는 테스트 프로그램에서 바로 이 Grabber 필터를 사용할 것입니다. 이것을 사용하여 카메라로부터 들어온 영상 중에서 '착칵'하고 한 프레임의 사진을 뽑아내어 이미지로 저장해 보일 것입니다. 

(6) File Source(Async.)

이 필터는 글자 그대로 소스필터입니다. 보다 정확히 표현한다면 소스필터 중에서 풀모드 형식의 소스필터입니다. 우리는 전회에서 '소스필터'의 종류와 내부기능에 대하여 공부하였습니다. 이 필터 뒤에는 반드시 파서필터가 붙어야 하며, 일반적인 Avi 표준형식이라면 DShow에서 이미 제공하고 있는 위의  Avi Splitter 필터가 달라 붙게 될 것입니다. 

대부분의 풀모드 소스필터가 그러하듯이 이 필터가 하는 일이란 달랑 원하는 동영상 파일을 로딩하는 것입니다. 이런 서비스를 하기 위해서 IFileSourceFilter 라는 인터페이스를 내부적으로 가지고 있는데요, 우리는 이것을 사용하여 원하는 파일을 로딩하게끔 코딩상에 설정하실 수가 있습니다. 

만일 여러분이 GraphEdit에서 본 필터를 생성하였다면, 생성과 동시에 '파일선택 다이알로그 창'이 뜨게될 것입니다.
여기서 원하는 동영상 파일을 선택하면, GraphEdit의 메인화면에 네모난 박스형태의  File Source 필터안에 여러분이 선택한 파일이름이 표시되어 나타날 것입니다. 

DShow SDK를 설치하셨다면 여러분은 이 소스필터의 원형을 직접 살펴보실 수가 있습니다.  

  ===>  C:\DXSDK\Samples\C++\DirectShow\Filters\Async

위 디렉토리에 가시면 File Source 소스필터의 C++ 소스를 만나보실 수가 있습니다. 풀모드 소스필터임에도 불구하고 상당히 복잡하게 되어있습니다. 

-------------------------------------------------------------------------------------------------
참고 => DirectShow SDK에 있는 샘플 필터의 소스를 살펴보시면 우선 무엇인지도 모를 엄청난 양의 소스에 질겁을  하게 될 것입니다. 이것은 어쩌면 당연한 일입니다. 왜냐하면 이곳에 놓인 필터들의 성격은 굉장히 범용적인 사용을  위한 준비로서 다양한 로직을 갖춰놓고 있기 때문입니다. 따라서 여러분이 필터의 기본적인 구조에 대한 이해도 없이 마구잡이로 이곳에 있는 필터들의 소스를 분석하고자 한다면, 그것은 참으로 어리석은 일이 될 것입니다.
참고로 제가 예전에 그랬었습니다. 그냥 죽기 아니면 까무러치기로 파고 들어가면 어차피 클래스고 함수일터인데 해석하지 못할까 싶었기 때문입니다. 그러나 감히 말씀드리자면 이것은 지도없이 낯선 도시를 헤메는 것과 같은 어리석음입니다. 만일 누군가 DShow 필터개발자가 옆에 있어서, 막힐때마다 친절하게 가르침을 배울 수 있는 환경이라면 모를까, 무턱대고 필터의 소스부터 프린트해서 해독하기 위해 밤새고 그러는 것은 전혀 바람직하지 않습니다.
------------------------------------------------------------------------------------------------

(7) File Writer

이 필터의 기능은 글자 그대로 입력된 스트림을 파일로 기록하는 기능을 하고 있습니다. 여러분이 GraphEdit로 필터를 생성시키면 위의 File Source 필터와 비슷하게 '파일저장 다이알로그 창'이 뜨게 됩니다. 물론 이렇게 윈도우가 뜨는것은 얼마든지 DShow 어플의 코딩상에서 내부적으로 처리할 수가 있습니다. 

자, 그런데 이 필터를 설명드리면서 앞에서 한가지 부족했던 부분에 대하여 보충설명을 드려야 하겠습니다. 앞의 서두에서 저는 Avi Mux 필터에 대하여 단순히 영상과 음성의 스트림을 '먹싱'처리하여 하나의 스트림으로 내보내는 역활을 한다고 하였습니다. 그러나 이때 단순히 두개의 데이타를 하나로 합쳐지게 만드는 것이 아니라, Avi 표준구조에 맞게 합쳐지게 한다는 것입니다. 즉, 가장 첫머리에 Avi 헤더가 붙고, 각각의 영상 프레임이나 음성 데이터 마다 식별코드와 파일 사이즈등의 정보들이 붙어 있게 된다는 것입니다.

제가 위와같은 보충설명을 드린 것은 다음과 같은 원인을 설명하고자 하기 때문입니다. 여러분이 만일 GraphEdit상에서 카메라입력장치를 하나 생성시키고, 그 다음에 File Writer를 생성시키고서, 이 두개의 필터를 직접 연결시킨다면, 아마도 '연결을 가능하게 하는 중간 필터를 찾을 수가 없습니다'라는 메시지가 뜨는 것을 보시게 될 것입니다. 즉 카메라 입력장치든지(광의의 필터) 혹은 위에서 언급한 파일소스필터와 파서필터가 붙은 상태에서의 파서필터이든지, 곧바로 File Writer필터를 붙일 수가 없다는 것입니다. 왜냐하면 지금까지 계속해서 강조해 왔듯이 양쪽의 미디어 타입이 일치가 되지 않기 때문이며, File Writer 필터의 입력핀쪽 미디어타입에서 서브타입으로 Avi형식을 요구하고 있기 때문입니다. 즉, 연결하려는 앞쪽의 필터에서 Avi의 완성된 미디어형 타입이 있어야 한다는 것입니다.

위와같은 상황이 이해가 잘 안되신다면, 그냥 일반적으로 File Writer 필터는 앞에 꼭 Avi Mux 필터가 붙어야 한다라고 생각해주시면 되겠습니다. 이것은 마치 위의 File Source(Async.) 필터의 뒤에는 반드시 Avi Splitter필터가 붙어야 한다는 것과 함께 쌍으로 염두에 두시면 좋을 것입니다. 

(8) Smart Tee  

Smart Tee 필터(이하 스마트티 필터)는 Video Renderer 필터 다음으로 DShow에서는 밥먹듯이 사용해야하는 중요한 필터입니다. 이 필터가 필요한 이유를 들어 보겠습니다. 우리가 카메라로부터 들어온 영상을 저장과 동시에 랜더링하고자 한다고 생각해 보십시오. 그렇다면 하나의 입력 스트림을 두개로 쪼개어 흘려 보내야 합니다. 즉, 필터에는 한 개의 입력핀과 두개의 출력핀을 갖되, 출력핀에서 나오는 각각의 동영상은 모두 동일해야 한다는 것입니다. 이것을 다음과 같이 Smart Tee 필터로서 그려보겠습니다. 


            Cam Fitler -> Smart Tee  -->  Video Renderer 
                                            └---->  Avi Mux -> File Writer 


위와같은 연결이 만들어질 것입니다. 위에서 스마트 티 필터는 입력으로 들어오는 한개의 스트림을 가지고 두개로 쪼개어 아랫쪽으로 흘려보내는 역활을 하고 있습니다. 이런 역활을 하는 필터가 없다면 여러분은 직접 이런 역활의 필터를 개발해야 했을 것입니다. DShow에서 스마트 티라는 필터를 미리 마련해 두었으니 우리는 사용만 하는 되는 것이곘죠. 

(9)  Infinite Pin Tee Filter

이 Infinite Pin Tee 필터는 위에서 언급한 Smart Tee 필터와 비슷한 역활을 하는 것입니다. 즉 하나의 입력스트림을  여러개의 출력으로 분배하는 것이죠. 그러나 여러분이 일단 이 필터를 GraphEdit 상에서 생성시키면 달랑 입력과 출력이 각각 1개뿐인 필터로 보여지실 것입니다. 이때 당황하지 마시고 출력핀을 어딘가로 연결시켜 보시기 바랍니다.

그러면 연결되자마자 곧 새로운 출력핀이 한개가 더 만들어 지는게 보이실 것입니다. 그렇습니다. 이러한 방식은 Avi Mux에서 입력핀이 계속 증가하는 것과 동일한 형태인 것입니다. 다시말해서 출력핀은 언제나 연결된 핏의 갯수보다 하나가 많은 상태로 유지가 된다는 것이죠. 이렇듯 Infinite Pin Tee 필터는 Smart Tee 필터와는 다르게 입력스트림을 무한하게 여러개의 출력으로 나누어 보낼 수가 있는 것입니다.  

자, 여러분은 이제 이 Infinite Pin Tee 필터 (일명 무한필터)를 사용하여 Smart Tee 필터처럼 사용할 수도 있습니다. 
즉 아래와 같이 연결할 수 있다는 말이 되겠습니다. 


            Cam Fitler -> Infinite Pin Tee  -->  Video Renderer 
                                             └---->  Avi Mux -> File Writer 
                                             └---->   //항상 연결된 핀갯수보다 하나가 더 많게 된다. 

 

Smart Tee 필터와   Infinite Pin Tee 필터의 역활은 비슷합니다. 하지만 우리는 어떨때 Smart Tee 필터를 사용하고 어떨때 Infinite Pin Tee 필터를 사용해야 하는지에 대해서도 알아야 합니다. 그러기 위해는 필터의 내부 특성을 알아야 하는데요,  여기서는 Help에 나온 사항을 참고하겠습니다.

스마트티는 하나의 입력핀을 단지 두개의 출력핀으로 나눌 뿐입니다. 하지만 각각의 핀에는 독특한 이름이 부여되는데요, 바로 Capture 핀과  Preview 핀이라는 이름입니다. 이 각각의 이름은 중요한 차이를 가지고 있습니다. 물론 사용법도 다르게 되겠지요. 일반적으로 Captuer핀에서 흘러나오는 스트림은 Avi Mux로 연결되어서 동영상을 저장하기 위한 용도로 사용됩니다. 그리고 Preview 핀에서 흘러나오는 스트림은 Renderer 로 향해서 랜더링되어 집니다. 

그런데 만일 이 두가지를 반대로 사용한다면 어떻게 될까요. 만일 여러분이 Capture 핀으로 랜더링을 하고 Preview 핀으로 저장을 한다면 GraphEidt는 에러메시지를 보내게 될 것입니다. 그 메시지에는 '타팀스탬프가 없습니다'라는 문구가 될 것인데요, 이것이 중요한 차이입니다. 즉 에러메시지가 발생한 이유는 Preview 핀에서 스트림을 저장하기 위해 연결한 Avi Mux 필터에서는 반드시 '타임스탬프'가 존재해야 하는데요,  Preview 핀에서 흘러나오는 스트림은 '타임스탬프'가 제거된 상태이기 때문입니다. 이거 왜 이럴까요?

동영상을 랜더링 하면서 동시에 실시간 Write 한다면 필터그래프에는 지연시간이라는게 발생하게 됩니다. 따라서 만일 Preview핀에서 흘러나오는 스트림에 타임스탬프가 존재한다면 지정된 시간에 프레임이 랜더링되지 못하고 드롭되고 마는데요, 이것은 상당히 부자연스러운 차이를 만들게 됩니다. 이처럼 랜더링 되어야할 프레임이 드롭되는 것을 막고자 Preview 핀에서 강제로 타임스탬프를 제거하여 내보내는 것입니다.

일반적으로 동영상을 저장과 동시에 랜더링을 하고자 할 때에는 위와같은 이유로 무한필터보다는 스마트 티 필터를 이용하기를 권장합니다. 장난삼아 무한필터로도 테스트해 보곤 하는데요, 별차이는 없어 보였습니다. 하지만 헬프에 그렇게 나와 있으므로 굳이 무한필터를 사용하지는 마시기 바랍니다. 

++ 타임스탬프 ++
-------------------------------------------------------------------------------------------------

타임스탬프라는 것에 대해서 잠시 설명 드리겠습니다. 타임스탬프는 하나의 미디어물이 최종적으로 랜더링 되어야하는 StartTime과 EndTime을 의미합니다. 예를 들어 우리가 DShow 어플을 Play하면 그 순간부터 절대시간이 생성되어집니다. 0초, 1초, 2초, 3초... 이렇게 말이죠. 이런 상태에서 연속된 프레임에서 각각의 프레임마다 언제 랜더링되어져야 하는지를 가지고 있습니다. 만일 초당 30프레임이라면 가장 첫번째 프레임의 타임스탬프 시간은  StartTime 이 0 이 될 터이고, EndTime이 30/1000 초가 될 것입니다. 두번째 프레임은 어떻게 될까요.이것을 다음과 같이 나타내 보겠습니다.

                1번째 프레임    StartTime     0초             EndTime  0.03초
                2번째 프레임    StartTime     0.03초         EndTime  0.06초
                3번째 프레임    StartTime     0.06초         EndTime  0.09초
                4번째 프레임    StartTime     0.09초         EndTime  0.12초
                        .                    .                                     .
                        .                    .                                     .  

위와 같이 각 프레임마다 StartTime 과 EndTime을 가지고 있다는 것이죠. 그런데 간혹 이것들이 여러개의 필터들을  거치면서 제시간에 도착하지 못하는 경우가 발생될 때가 있습니다. Smart Tee 경우와 함께 일반적으로 네트워크 필터에서도 이런 경우가 발생하여, 저같은 경우도 마지막에는 결국 타임스탬프를 제거해야만 했습니다.

사실 네트워크 소스필터의 경우에는 Smart Tee와 같은 이유 때문에 타임스탬프를 제거한 것이 아니라, 전송지와 수신지와의 동시성을 위해서 어쩔 수없는 선택이었습니다. 예를 들어, 전송지에서 TCP/IP를 통해 서버로 하나의 프레임을 전송했다면 수신지에서는 다시 그 서버에서부터 하나의 프레임을 받아올 것입니다. 그런데 어떤 경우는 통신이 느려졌다가 갑자기 빨라질 경우가 생기는데, 이 경우 전송지의 소켓버퍼와 서버의 버퍼에 그동안 밀려서 쌓여있던 프레임이 한꺼번에 전달되기 때문에 문제가 발생 합니다. 만일 타임스탬프가 존재하고 순식간적으로 한꺼번에 밀려들어온 각각의 프레임에 일정시간을 동일하게 배분 한다면 전송지와 수신지와의 랜더링시간은 계속해서 차이가 벌어지게 될 것입니다. 저는 약 1시간 가까이 딜레이 되는 것을 지켜봐야 했습니다. 참으로 놀라운 딜레이 현상이었습니다.


출처 : 델마당  dong님의 글(dongsoft)

posted by 유돌이
2009. 1. 5. 18:11 델파이

지금까지 우리는 간단한 USB 카메라용 DShow 어플을 제작하였습니다. 너무도 간단한 것이었지만 이해하기가 쉽지는 않았을 터인데요, COM에 대해서는 좀더 체계적인 공부를 하라고 권해 드리고 싶습니다. 자, 이번장 부터는 DShow 의 필터에 대하여 공부해 보겠습니다. 우리는 필터들의 내부구조를 조금씩 들여다보고, 그 각각의 역할과 의미를 짚어본 다음에 DShow에서 제공하는 몇몇 기본 필터들에 대하여 공부할 것입니다. 이 지식들을 활용하여 전장에서 실습하였던 USB 카메라의 랜더링 프로그램을 수정하여 간단하게 AVI 파일로 저장하는 것과 중간의 프레임에서 원하는 이미지 사진을 뽑아내는 기능을 추가할 것입니다. 

[1] DShow 필터의 역할과 구조. -- (소스필터)   

우리는 지난회에서 DShow의 버퍼공유에 대하여 언급하였습니다. DShow에 있어서 버퍼공유가 얼마나 중요한 가에 대해서는 그당시 적절하게 설명을 하였다고 생각합니다. 그렇다면 이제 버퍼공유의 내부적인 원리를 약간 살펴보고  넘어가야 할듯 싶습니다. 이 원리는 중요성에 비하여 그리 거창하게 복잡한 것은 아닙니다. 아주 간단한 예를 들어 보겠습니다. 우리가 압축안된 AVI라는 파일을 TFileStream이라는 객체를 통하여 로딩하였다고 생각해 봅니다. 그리고 로딩한 파일에서 한 프레임에 해당하는 버퍼를 일정시간마다 읽어봅니다.
 
procedure TForm1.Run_ButtonClick(Sender: TObject);
var
  FileStm : TFileStream;
  Buff : PChar;
begin
  FileStm := TFileStream.Create('C:\무압축다이하드.avi', fmOpenRead );
  GetMem(Buff, 320*240*3);  //320 * 240의 크기에 RGB24라면...

  while FileStm.Position < FileStm.Size do
  begin
    FileStm.Read(Buff^,320*240*3);    //파일에서 하나의 프레임을 읽어낸다.
    Transfer(Buff);                             //Transfer 함수로 버퍼의 포인터만 보낸다. 
    Sleep(33);                                   // 1초당 30개의 프레임이라고 가정한다.
  end;

  FreeMem(Buff);
  FileStm.Free;
end;

 
procedure TForm1.Transfer(var Buff: PChar);
begin
  //비디오 프레임을 변환시킬 일이 있다면 변환시킨다.
  Renderer(Buff);
end;

 
procedure TForm1.Renderer(var Buff: Pchar);
begin
   //Buff로 랜더링을 한다.
end;

 

만일 DShow의 COM Object구조가 아닌, 일반 어플에서 시도를 했다면 위와 같은 구조를 생각해 볼수가 있을 것입니다. (실제로 적용한다면 AVI파일의 헤더를 읽는 부분과 위의 Renderer 함수에서 비디오 카드의 DirectDraw평면을 가져와서 그곳에 데이터를 옮기는 작업을 하게될 것입니다.) 자, 위의 프로그램은 아주 간단합니다. 압축안된 AVI 파일을 읽어서 그것을 일정시간 간격으로 계속해서 두개의 함수, Transfer과 Renderer 함수를 실행시킨다는 것입니다. 

상식적으로 위의 모든 과정은 하나의 함수안에 포함시킬 수가 있습니다. 필터도 마찬가지입니다. 우리가 소스필터나  변환필터나 랜더러필터도 각각 그 역할을 나눠놓는 것이 사용하기 편하기 때문에 한것이지, 굳이 구조적으로 반드시  그렇게 해야할 이유는 없다는 것입니다. 그렇다면 우리는 하나의 의문이 생깁니다. DShow는 버퍼공유가 중요하다고 했는데, 아니 하나의 필터에 몽땅 집어 넣을수가 있다면 굳이 버퍼공유라는 이유 때문에 복잡한 여러종류의 필터를 연결해서 사용해야만 하는 이유는 무엇인가.  

우리가 Visual C++을 한다는 것은  MFC를 사용한다는 것과 마찬가지이고, 델파이를 한다는 것은 VCL을 사용한다는 것과 마찬가지이듯이, DShow를 한다는 것은 결국 기본적으로 제공되는 수많은 DShow의 필터를 이용할 수 있다는 것과 동일한 의미일 것입니다. 즉, 'DShow는 버퍼공유를 위해서 필터형식으로 되어있다'는 것이 아니라, '동영상 어플 개발에 있어서 다양한 필터형식의 서비스를 제공함에도 불구하고 버퍼공유를 한다'는 의미로 받아들여야 할 것입니다. 

DShow에는 다양한 종류의 기본필터가 준비되어 있습니다. 이들 필터를 사용한다는 것은 어플개발을 손쉽게 할 수 있다는 의미 이외에도 윈도우즈라는 운영체제에 있어서 범용인 동영상 어플을 개발할 수 있다는 의미도 있을 것입니다.

다양한 필터형식의 범용적인 서비스를 제공하면서 동시에 하부구조 깊숙히 자리잡은 버퍼공유를 함으로서 동영상 개발을 획기적으로 진보시켰다고 볼수가 있을 것입니다. (그러나 배우기는 어렵다는 거... 쩝.)

Anyway... 이제 다시 본론으로 들어가겠습니다. 위의 예제샘플 프로그램에서 우리는 동영상 어플개발의 기본적인 구조를 살펴볼 수가 있습니다. 파일을 로딩하고, 그것을 루프를 돌려서 한 프레임씩 읽어내고, 읽어낸 것을 변형하고 랜더링한다는 것입니다. 눈치 채셨겠지만, 각각의 함수들은 모두 DShow에서 각각의 필터들의 역할을 대변하고 있습니다. 즉, 파일을 로딩하여 루프를 돌리는 첫 프로시저는 '소스필터'를 의미하고 두번째 Transfer는 말 그대로 변환필터를 의미하며 마지막으로 Renderer함수도 랜더러필터를 의미할 것입니다.  

자, 그런데 소스필터에 있어서는 약간 다르게 구조화되는 경우를 생각해 볼수가 있습니다. 즉, 로딩하는 부분과 루프를 돌려서 한 프레임씩 읽어내는 경우를 별도의 필터로 만드는 것인데요, 이렇게 별도의 필터로 나누어 만드는 방식을 풀모드라고 하고, 하나의 필터에 로딩과 루프를 모두 갖춘것을 푸쉬방식이라고 합니다.
여기서 루프는 사실 스레드를 의미한다고 생각하시면 될 것입니다. 또한 두개의 부분으로 나뉘었을때 앞의 필터를 풀모드의 소스필터라고 하며 뒤의  필터를 '파서 필터'라고 합니다. 푸쉬 소스필터 하나를 굳이 풀모드 소스필터와 파서필터로 나누어 놓는 것은 DShow 가 각부분의 필터 서비스를 좀더 세밀하게 제공하기 위함이라고 생각하시면 될 것입니다.  

소스필터를 너무 간단히 설명드렸지만, 사실 소스필터는 무지하게 복잡한 구조를 가지고 있습니다. 우선 이것은 수 없이 다양한 Avi파일의 표준구조를 읽어내야하고, 음성과 영상의 프레임을 각각 뽑아내어 동기화작업도 해야합니다. 또한 네트워크를 통해서 들어올 경우에는 소켓을 포함하여 그에 따른 로직이 준비되어 있어야 할 것입니다.
DShow 의 필터 개발자들이 우선 첫번째로 경험삼아 개발하는 것이 변환필터이고, 그 다음으로는 바로 이 네트워크 소스필터인데요 제가 처음에 말씀드렸던 신화선님의 사이트에 가시면 '네트워크 소스필터'로 인하여 울부짓는 질문들이 상당수 있을 것입니다. 네트워크 소스필터가 어려운것은 소켓 프로그래밍 때문이 아니라, 영상과 음성의 싱크문제, 각각의 스레드의 동기화 문제, 버퍼링 문제와 같은 것들입니다. 이들 모두가 소스필터에 해당한다고 보시면 될 것입니다.  


이번에는 파서필터를 직접 만나시겠습니다. 여러분이 만약 GraphEidt로 인터넷에서 다운받은 영화를 Render Media File...이라는 메뉴로 불러오셨다면 메인화면에는 수없이 많은 필터들이 연결되어 있는 것을 보실수가 있을 것입니다.
그 여러종류의 필터들 중에 유독 하나의 필터에서 두개의 Out핀이 나온것을 보실 수가 있는데요, 요놈이 바로 파서필터입니다. 이름은 아마도 'Avi Splitter'라고 적혀 있을 것입니다. 이 Avi Splitter이라는 놈은 결코 우수운 놈이 아닙니다.
 
위에서 설명드린 것처럼 수없이 다양한 Avi파일의 표준구조를 읽어내고 영상과 음성의 프레임을 각각 동기화시켜서 스레드로 푸쉬(다른 필터로 밀어내기)하고 있는  것입니다. 

지금까지 정리하자면 다음과 같습니다. 

  1) 소스필터는 두가지 종류가 있는데, 하나는 푸쉬모드 소스필터이고 다른 하나는 풀모드 소스필터이다. 
  2) 푸쉬모드 소스필터는 영상파일을 로딩하는 것과 스레드 안에서 각 프레임을 뽑어내는 기능 모두를 포함한다. 
  3) 풀모드 소스필터의 경우는 영상파일을 로딩하는 기능만을 가지고 있으며 뒤에는 스레드로 각 프레임을 뽑아내는 기능을 하는 '파서필터'가 별도로 붙는다.  

[2] DShow 필터의 역할과 구조. -- (변환필터)   

이제 변환필터에 대해서 말씀드리겠습니다. 아마도 여러분이 가장 원하시는 종목이 바로 변환필터 만들기 일 것입니다. 일단 이 변환필터를 만드는 데에는 크게 두가지의 경우가 있습니다. 첫째로, 소스필터에서 시작된 영상의 형식을 그대로 두고 버퍼의 내용만 바꾸는 것과, 둘째로 영상의 형식과 내용 모두를 바꿔치기하는 방식입니다. 전자의 것을 InPlace 변환필터라고 하고 후자의 것을 Copy 변환필터라고 합니다. 자, 이 두개가 무슨 의미가 있는지를 설명하겠습니다. 

우리가 앞의 필터에서 흘러나온 스트림을 변환시키기 위해서는 뒤로 흘려보낼 경우까지 모두 고려해야 할 것입니다.
만일 앞의 영상이 YUV의 형태였고, 뒤쪽으로 흘려보내야하는 영상이 RGB24라면 어떻게 될까요. 이경우 어쩔 수 없이 한번의 버퍼링을 반드시 해야할 것입니다. 왜냐하면 뒤쪽의 타입에 맞게 변경해줘야 하기 때문이지요. 그러나 앞과 뒤의 영상타입이 정확히 일치한다면 우리는 굳이 버퍼링을 할 필요가 없습니다. 이 경우 필터는 앞의 필터에서 사용되어진 버퍼의 포인터를 그대로 가져와 사용할 수가 있는 것입니다. 
 
동영상 스트림의 형태는 상당히 까다롭습니다. 이 형태를 '미디어형'이라고 하는데요, DShow를 하기 위해서는 반드시 이 '미디어형'의 전체 구조가 머릿속에 들어가 있어야 합니다. 이것에 대한 의미를 정확히 안다면 필터개발에 있어서 반이상을 정복하셨다고 해도 과언이 아닐 것입니다. 그런데 이 미디어형을 알기 위해서는 각각의 미디어타입의 의미를 또한 알고 있어야 합니다. 예를 들어 대체 YUV는 무엇인가에 대한 해답을 가지고 있어야 한다는 것입니다. 

간단하게 YUV에 대해서 설명해보겠습니다. 우리가 일반적으로 색을 표현할 때에는 Red, Green, Blue 이렇게 세가지의 색을 조합해서 표현하는 RGB 방식을 흔히 사용합니다. 그런데 이 방식은 Image로 표현하는데 있어서는 상당히 정확한 방식이지만, 반면에 인간이 느끼지 못하는 부분까지 구분하고 있기 때문에 정보의 취급 효율면에서는 떨어집니다. 그 효율이 아주 작은 차이라고 하더라도 동영상에서는 무시하지 못할 엄청난 차이가 됩니다. 따라서 동영상에서는 주로 이 RGB 계열의 형식을 사용하지 않습니다. 대신 색의 밝기인 Y성분과 색상인 U와 V성분으로 조절되어지는 YUV 형태를 주로 사용합니다. 이 방식을 사용하는 이유는 효율이 상당히 크기 때문입니다.
일반적으로 인간의 시각은 명도에 민감하고 색상에는 별로 민감하지 않습니다. 예를 들어 320*240 크기의 프레임 이미지라면 명도에 해당하는 Y부분을 320*240 크기로 1바이트씩 배정해 놓고 나머지 색상에 해당하는 U와 V는 각각 네 개마다 하나씩 공유 하게 되어도 큰 문제가 없다는 것이죠.    


      y           y             y            .             .              .

           uv          uv

      y           y             y

 
 
      .

 

자, 위와 같은 경우 이미지의 크기는 반으로 줄어들게 됩니다. RGB의 경우, 픽셀당 각각 1바이트 씩을 차지하므로 모두 3바이트였다면, 위와같은 형식의 YUV인 경우에는 각 픽셀당 Y가 1바이트, UV가 0.5바이트를 차지하게 되므로 전체 메모리는 반으로 줄어들게 되는 것입니다. 그런데 아이러니한 것은, 이렇게 정보의 크기가 반으로 줄어들었음에도 불구하고 RGB의 경우보다 오히려 더 선명하게 느껴진다는 것입니다. 이것은 일종의 착시현상으로 픽셀과 픽셀간의 색차 정보가 흐려지는 결과로 빚어지는 것입니다. 

위의 YUV의 형태를 인식하는 것은 중요한 첫 걸음입니다. YUV의 형태는 실제 다양한데요, 24비트로 된 것도 있고, 16 비트나 12비트, 심지어 8비트로 된 것도 있습니다. 그러나 가장 중요한 것은 이것을 사용했을때의 효능입니다. RGB에 비하여 엄청난 결과를 가져옵니다. 즉, 영상의 화질은 더 부드럽고(비록 착시현상이지만...)  CPU의 점유율은 거의 절반으로 떨어지기 때문입니다. 모든 영화 파일의 기본압축 미디어형이 바로 이 YUV형식 인것도 바로 이 때문인 것입니다. ( 일부를 제외하고 거의 모든 Mpeg의 압축을 위해서 들어가는 기본 형태는 RGB가 아닌 YUV형식이다. 반대로 그 압축된 데이터가 DeCoder 필터를 통해 압축 해제되어 나오는 미디어의 기본 형태도 바로 YUV형식중 하나이다.)

혹 어느 책에서는 YUV가 일종의 압축형태라고 표현하는 곳도 있는데요, 이것은 엄밀히 말하자면 틀린 말입니다. 하지만 그럼에도 불구하고 'YUV로 압축된 형태로 들어옵니다.'라고 표현하는 것은 절반의 데이타 양으로 거의 동일하게 표현하는 효율적인 측명을 지나치게 강조한 것이라고 할 수 있을 것입니다. 아무튼 YUV 이것을 아는게 중요합니다. 나중에 여러분이 필터를 만들게 되면 이 YUV를 직접 눈으로 보실수가 있게 됩니다. 저도 한번 샘플로 YUV를 RGB인 척 하고 랜더링한 적이 있는데요. 다음과 같은 모양이 나왔습니다. 


                  ************************************************
                  ************************************************
                  ************************************************
                  ***********************■************************
                  ***********************■************************
                  ***********************■************************
                  *********************■***■*********************
                  ********************■*****■********************
                  *******************■*******■*******************
                  ******************■*********■******************
                  ************************************************
                  ************************************************
                  ************************************************
                  ************************************************
                  ************************************************


                  ************************  ***********************
                  ***********■***********  **********■************
                  ***********■***********  **********■************
                  **********■*■**********  ********■**■**********
                  *********■***■*********  *******■****■*********
                  ************************  ***********************
                  ************************  ***********************


위에서 보시면 알겠지만, 가장 첫번째 큰 이미지가 Y값을 가진 전체 화면이 되겠고요, 나머지 두개의 작은 이미지가  각각 4개의 Y값에 대응하는 U와 V의 값들이 모여있는 화면입니다. 실제로 버퍼에 이런 식으로 저장이 되어 있는 것을 보고 정말 재미있어 했습니다.  

이야기하다보니 또 샛길로 빠졌습니다. 강의의 깊이를 조절하기가 정말 힘이 드네요. 이번 장에는 무려 5번의 새로쓰기를 하였습니다. 제 나름대로 전체적인 구조를 잡은 상태에서 진행하고 싶었는데요, 지나친 욕심이었나 봅니다. 아무튼 이번장이 중요한 것은, COM 다음으로 DShow의 배경지식이 되기 때문입니다.

변환필터에 대해서 이야기하였는데요, 두가지 형식이 있다고 하였습니다. 하나는 InPlace 변환필터, 일명 제자리 변환 필터라고 불리기도 하고요,  Copy 변환필터, 일명 복사 변환필터라고 합니다. 이 두개의 형식의 가장큰 차이는 전자의 것은 버퍼링이 없다는 것이고요, 후자의 것은 반드시 한번 이상의 버퍼링이 존재한다는 것입니다. 사실 Copy 변환필터가 내부적으로 버퍼링을 해야한다는 것은 당연한 일입니다. 앞에서 들어온 스트림의 미디어형이 뒤쪽으로 나가는 미디어형과 일치하지 않기 때문에, 그 형변환을 위해서는 버퍼링이 필요하고, 버퍼링을 하기 위해서 Copy 변환필터라는 구조가(COM Class가) 만들어진 것이기 때문이지요. 

지금까지의 설명을 종합하겠습니다. 

   1) 변환필터에는 InPlace 형과 Copy 형 두가지가 있다.
   2) InPlace형은 내부 버퍼링이 필요없고, 앞의 필터의 버퍼 포인터를 그대로 사용한다.
   3) Copy 형은 반드시 버퍼링이 필요하고, 앞의 InPut 미디어형과 뒤쪽의 OutPut 미디어형이 일치하지 않을때 사용한다. 
   4) DeCoder 압축해제필터는 일종의  Copy형 변환필터이다.
------------------------------------------------------------------------------------------------
추가해설 --> 엄밀히 이야기하자면 InPlace형 변환필터도 내부 버퍼링을 합니다. 그러나 위에서 내부 버퍼링이 필요없다고 한것은 이해의 편리를 위한 것이라고 생각하시면 될 것입니다. 좀더 정확히 표현하자면 'InPlace필터는 버퍼링을 최대한 하지 않아도 되게끔 지원한다.'는 표현이 맞을 것입니다. 자, 이것에 대해서는 후에 '할당자'를 설명하면서 논하게 될지도 모르겠습니다. 하지만 워낙 Inplace 필터의 구조가 복잡해서, 충분히 설명할 수 있을지 모르겠습니다. 

시간이 있으면 나중에 필터제작하는 시간에 '할당자'에 대한 부연설명을 하면서 보충할 수도 있을 것이지만, 아무튼 현재로는 'InPlace 필터는 내부 버퍼링을 가능한한 줄여주기 위해 지원한다' 는 정도로 이해하시면 좋을 것입니다.


출처 : 델마당  dong님의 글(dongsoft)

posted by 유돌이
2009. 1. 5. 18:09 델파이
지난 8부에서 우리는 비로소 TBaseDShow 클래스를 완성하였습니다. 이제 우리는 DShow용 어플의 기본적인 것을 모두 소화해 낸 것입니다. 이제부터는 노가닥성 코딩이라고 보아도 되겠습니다. TBaseDShow 를 이용하여 필터를 만들고 연결하고 실행하면 되는 것입니다. 이제 TBaseDShow에서 상속받아서 이 나머지 구체화 부분에 해당하는 클래스를 만들겠습니다. 현재 우리는 USB용 카메라를 동작시키기 위한 DShow용 어플을 제작하고 있으므로 이 클래스의 이름을 TCamDShow라고 하겠습니다.

우선 아래와 같은 클래스의 원형을 보시겠습니다. 

type
  TCamDShow = class(TBaseDShow)
  private
    Cam:               IBaseFilter;
    VideoRender:  IBaseFilter;
  protected
  public
    constructor Create(Screen:TPanel);
    destructor Destroy;override;
    function MakeBaseFilter:HRESULT;
    function ReleaseBaseFilter:HRESULT;
    function ConnectBaseFilter:HRESULT;
    procedure Run;
    procedure Stop;
  end;


자, 위의 클래스 원형에서 필터가(IBaseFilter형 타입의 변수) 달랑 두개 뿐이 없는 것을 보실수가 있습니다. 바로 Cam 과 VideoRender 입니다. 이 두개의 필터를 생성하고 연결해 주어야 합니다. (DShow 용 어플 제작에 있어서 필터라는 것은 IBaseFilter 형 타입의 변수를 의미 한다고 보시면 되겠습니다. 그러나 일반적으로 필터라 함은 COM Obejct  객체 자체를 말하는 것이라 생각해야 할 것입니다.). 위에서는 단지 두개뿐이지만 좀 복잡한 어플에서는 훨씬 많은 수의 필터들이 존재하고 있을 것입니다. 지금은 간단한 실습용이기 때문이라고 생각해 주세요.

위에서 두개의 필터와 함께 세개의 함수가 보이실 것입니다. 세개의 함수중 두개는 이름만으로도 이해하기가 쉬우실 것입니다. 즉, MakeBaseFilter는 각각의 필터를 만드는 곳이고,  ConnectBaseFilter는 각각의 필터를 연결하는 장소입니다. 그런데 ReleaseBaseFilter라는 함수는 무엇일까요. 이 함수는 필터들을 해제하기 위한 것입니다.  이것은 그냥 TCamDShow 객체의 소멸자에 해당 로직을 몽땅 몰아 넣어도 상관은 없습니다만, 사용의 편리성을 위해 별도로 준비한 것입니다.

마지막으로 한가지 더 눈여겨 보셔야 할 부분이 있습니다. 그것은 함수의 리턴타입입니다. HResult 라고 되어 있는데요, 이것은  DShow에서 밥먹듯이 사용해야 할 리턴타입니다. 현재 위에서 세개의 함수들이 굳이 HResult 형의 리턴 타입을 사용하지 않아도 상관은 없습니다만, 일부러 이렇게까지 한것은 이러한 관습에 익숙해지면 편리하기 때문입니다.
DShow를 넘어서 COM의 모든 인터페이스의 리턴타입이 바로 HResult입니다. 그렇기 때문에 HResult에 조금이라도 일관성을 유지하면서 익숙해진다면 좋기 때문입니다.  S_OK, S_False 이런 결과 값들을 기본으로 생각하고 있어야 할 것입니다.

자, 이제 생성자와 소멸자를 살펴보겠습니다.

constructor TCamDShow.Create(Screen: TPanel);
begin
  inherited Create;
  MakeBaseFilter;
  ConnectBaseFilter;
  VideoWindow.put_Owner(OAHWND(Screen.Handle));
  VideoWindow.put_WindowStyle(WS_CHILD or WS_CLIPSIBLINGS);
  VideoWindow.put_Width(320);
  VideoWIndow.put_Height(240);
  VideoWindow.put_Top(0);
  VideoWindow.put_Left(0);
end;

 
destructor TCamDShow.Destroy;
begin
  ReleaseBaseFilter;
  inherited Destroy;
end;


소멸자는 간단합니다. 위에서 말씀드린 것처럼 사용된 필터들을 해제하는 로직을 포함해야 하는데요, 이것을 별도의 ReleaseBaseFilter라는 함수에 넣어둔 것일 뿐입니다. 이제 생성자를 한번 보겠습니다. Inherited 아래의 문구에서 처음으로 필터를 생성하고(MakeBaseFilter함수) 그 다음으로 필터를 연결하고(ConnectBaseFilter 함수) 있습니다.

그런데 그 아래에 약간 특이한 코드가 보이는데요. 이것은 전에 말씀드렸듯이 TBaseDShow에 있는 필터그래프의
IVideoWindow 인터페이스를 사용한 것입니다. 이 인터페이스를 사용하여 비디오가 랜더링되는 창을 폼위의 어느곳 이든지 지정할 수가 있다고 설명을 드린적이 있는데요, 바로 이 코드가 그것입니다. TCamDShow 클래스의 생성자 매개변수를 TPanel형 타입으로 받아서 이것을 일종의 랜더링 스크린으로 사용하고 있습니다.

자, 이제 필터를 생성하는 함수를 보겠습니다. 함수는 아래와 같습니다.

function TCamDShow.MakeBaseFilter: HRESULT;
begin
  Result := S_OK; 

  Cam := GetCamFilter;
  FilterGraph.AddFilter(Cam,'Cam Filter'); 
  if Cam = nil then Result := S_FALSE;

 
  CreateFilter(CLSID_VideoRenderer,VideoRender);
  FilterGraph.AddFilter(VideoRender,'VdRenderFilter');
  if VideoRender = nil then Result := S_FALSE;

 
  if Result = S_FALSE then ShowMessage('MakeBaseFilter is Failed');
end;


위에서 보시면 아시겠지만 필터를 생성하자마자 필터그래프에 등록하고(혹은 Add하고) 있습니다. 단지 두개의 필터만이 사용되었기 때문에 현재는 상당히 간단해 보이지만, 사실 이 함수에는 나중에 각각의 필터들 고유의 또다른 인터페이스를 얻어내는 장소가 되기도 할 것입니다. 아무튼 중요한 것은 필터를 생성하고 필터그래프에 등록하였다는 사실입니다.  

위의 함수에서 조금 더 부언 설명을 드려야 하겠습니다. 카메라를 생성시킬때 사용한 GetCamFilter 라는 함수는 전장에서 TBaseDShow 클래스에서 소개한 적이 있었습니다. 그런데 사실 일반적으로 이렇게 Fixed하게 만들어 놓지는  않습니다. 보통 카테고리에서 특정한 장치(혹은 광의의 필터)를 얻어오기 위함 범용 함수를 만들어 놓고 사용하는데요
여기서는 이해의 편리를 위해 직설적으로 GetCamFilter라는 이름으로 간단하게 구성하게 된 것입니다. 

CreateFilter라는 함수는 기억하실 것입니다. 이것은 필터그래프를 생성하는 함수와 거의 동일하기 때문에 COM Object를 생성하는 범용함수를 만들어 놓고 사용해도 된다는 식의 설명을 드린적이 있을 것입니다. 자... 여기까지 이해가 어느정도 되셨을 것이라고 생각하겠습니다. 이제 두개의 필터를 연결하는 함수를 보시겠습니다.

function TCamDShow.ConnectBaseFilter: HRESULT;
var
  InPin : IPin;
  OutPin : IPin;
  hr : HRESULT;
begin
  Result := S_OK;
 
  FindPinOnFilter(Cam,PINDIR_OUTPUT,OutPin);
  FindPinOnFilter(VideoRender,PINDIR_InPUT,InPin);
  hr := FilterGraph.Connect(OutPin,InPin);
  if hr <> S_OK then Result := S_FALSE;
 
  OutPin := NIL;
  InPin := NIL;
 
  if Result = S_FALSE then ShowMessage('ConnectBaseFilter is Failed');
end;

위의 함수도 내용은 간단합니다. 두개의 필터 각각의 InPin과  OutPin 인터페이스를 얻어서 필터그래프로 하여금 두개의 핀을 연결시키게 하고 있습니다. 이때 FindPinOnFilter 함수는 여러분이 전장에서 TBaseDShow에 기술한 바로 그 함수입니다. FindPinOnFilter 매개변수로는 필터의 IBaseFilter 인터페이스형 변수와 핀의 방향, 그리고 마지막으로 받아낼 핀의 인터페이스형 변수가 되겠습니다. 

한가지 의문이 들수도 있을 것입니다. 위에서는 단지 두개의 필터를 연결시켰지만, 수많은 필터들을 연결시킬때에는 어떻게 할까하는 점입니다. 위의 코딩을 계속 반복해 나가면 얼마든지 필터를 연결시킬 수가 있습니다. 즉 순서대로  출력에서부터 최종 랜더링까지,  앞 필터의 출력핀을 얻고 뒷필터의 입력핀을 얻어서 연결하고, 다시 뒷필터의 출력핀을 얻고, 그 뒤뒤필터의 입력핀을 얻어서 연결하고... 이렇게 반복될 것입니다. 이 부분에 대해서는 뒤에서 동영상 파일의 랜더링 부분에 가서 구경하실 수가 있으실 것입니다.

이제 마지막으로 필터들을 소멸시키는 함수를 보시겠습니다.


function TCamDShow.ReleaseBaseFilter: HRESULT;
begin
  if Assigned(MediaControl) then MediaControl.Stop;
 
  FilterGraph.RemoveFilter(Cam);
  FilterGraph.RemoveFilter(VideoRender);
 
  While Assigned(Cam)             do Cam := nil;
  While Assigned(VideoRender)     do VideoRender := nil;
 
  Result := S_OK;
end;


위에서 필터들을 해제하기 바로 전에 필터그래프가 자신에게 등록되었던 필터들을 ReMove시키고 있는 것을 보실 수가 있습니다. 이것을 반드시 해두시라고 말씀은 못드리겠지만 해두시는 게 좋습니다.
자, 필터를 해제하는데 While문을 사용한 이유에 대해서는 전장에서 설명을 드렸으니, 여기서 다시 설명하지는 않겠습니다. 

마지막으로 우리는 두개의 함수, 즉 Run 과 Stop 함수를 보셔야 겠는데요, 이것은 아주 간단한 것입니다. 아래를 보시겠습니다. 
procedure TCamDShow.Run;
begin
  if Assigned(MediaControl) then MediaControl.Run;
end;
 
procedure TCamDShow.Stop;
begin
  if Assigned(MediaControl) then MediaControl.Stop;
end;

위에서 보시면 아시겠지만 MediaControl 인터페이스의 Run과 Stop 메소드를 사용하고 있습니다. MediaControl 는 필터그래프에서 얻은 인터페이스라고 설명을 드렸을 것입니다. 이제 모든것이 정리되었습니다. 완전한 풀 소스를 아래와 같이 보시겠습니다. 

unit cCamDShow;
interface
uses
  Windows, Dialogs, SysUtils, Classes, Registry, DirectShow9, ActiveX, ExtCtrls, DsUtil, cBaseDShow;

 
type
  TCamDShow = class(TBaseDShow)
  private
    Cam:          IBaseFilter;
    VideoRender:  IBaseFilter;
  protected
  public
    constructor Create(Screen:TPanel);
    destructor Destroy;override;
    function MakeBaseFilter:HRESULT;
    function ReleaseBaseFilter:HRESULT;
    function ConnectBaseFilter:HRESULT;
    procedure Run;
    procedure Stop;
  end;

 
implementation
 
 
{ TCamDShow }

 
constructor TCamDShow.Create(Screen: TPanel);
begin
  inherited Create;
  MakeBaseFilter;
  ConnectBaseFilter;
  VideoWindow.put_Owner(OAHWND(Screen.Handle));
  VideoWindow.put_WindowStyle(WS_CHILD or WS_CLIPSIBLINGS);
  VideoWindow.put_Width(320);
  VideoWIndow.put_Height(240);
  VideoWindow.put_Top(0);
  VideoWindow.put_Left(0);
end;

 
destructor TCamDShow.Destroy;
begin
  ReleaseBaseFilter;
  inherited Destroy;
end;

 
function TCamDShow.MakeBaseFilter: HRESULT;
begin
  Result := S_OK;
  Cam := GetCamFilter;   //카메라를 얻고...
  FilterGraph.AddFilter(Cam,'Cam Filter');  //카메라를 등록한다.
  if Cam = nil then Result := S_FALSE;
  CreateFilter(CLSID_VideoRenderer,VideoRender);  //비디오 랜더러를 얻고...
  FilterGraph.AddFilter(VideoRender,'VdRenderFilter'); //비디오 랜더러를 등록한다.
  if VideoRender = nil then Result := S_FALSE;
  if Result = S_FALSE then ShowMessage('MakeBaseFilter is Failed');
end;

 
function TCamDShow.ConnectBaseFilter: HRESULT;
var
  InPin : IPin;
  OutPin : IPin;
  hr : HRESULT;
begin
  Result := S_OK;
  FindPinOnFilter(Cam,PINDIR_OUTPUT,OutPin);     //Cam에서 첫번째 출력핀을 얻어낸다.
  FindPinOnFilter(VideoRender,PINDIR_InPUT,InPin); //랜더러에서 첫번째 입력핀을 얻어낸다.
  hr := FilterGraph.Connect(OutPin,InPin);       //필터그래프가 두개의 핀을 연결한다.
  if hr <> S_OK then Result := S_FALSE;
  hr := S_OK;
  OutPin := NIL;
  InPin := NIL;
  if Result = S_FALSE then ShowMessage('ConnectBaseFilter is Failed');
end;

 
function TCamDShow.ReleaseBaseFilter: HRESULT;
begin
  if Assigned(MediaControl) then MediaControl.Stop;
  FilterGraph.RemoveFilter(Cam);
  FilterGraph.RemoveFilter(VideoRender);
  While Assigned(Cam)             do Cam := nil;
  While Assigned(VideoRender)     do VideoRender := nil;
  Result := S_OK;
end;

 
procedure TCamDShow.Run;
begin
  if Assigned(MediaControl) then MediaControl.Run;
end;

 
procedure TCamDShow.Stop;
begin
  if Assigned(MediaControl) then MediaControl.Stop;
end;
end.  

자, TCamDShow 클래스를 사용하는 방법은 너무도 간단합니다. 델파이에서 빈프로젝트를 하나 시작합니다.
메인폼위에 Panel 하나와 버튼 두개를 올려놓습니다. 그리고 버튼의 클릭 이벤트에서 각각 다음과 같이 코딩해 놓습니다.

unit uMain;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, cCamDShow, StdCtrls, ExtCtrls;
 
type
  TfrmMain = class(TForm)
    Panel1: TPanel;
    Run_Button: TButton;
    Stop_Button: TButton;
    procedure Run_ButtonClick(Sender: TObject);
    procedure Stop_ButtonClick(Sender: TObject);
  private
  public
    CamDShow :TCamDShow;
  end;
 
var
  frmMain: TfrmMain;
implementation
                           
{$R *.dfm}

//Run 버튼을 클릭하였을 때...
procedure TfrmMain.Run_ButtonClick(Sender: TObject);
begin
  if not Assigned(CamDShow) then
  begin
    CamDShow := TCamDShow.Create(Panel1);
  end;
  CamDShow.Run;
end;


//Stop 버튼을 클릭하였을 때...
procedure TfrmMain.Stop_ButtonClick(Sender: TObject);
begin
  CamDShow.Stop;
end;
end.


지금까지의 프로젝트를 첨부파일로 올려놓았습니다. (델파이7) 
 
출처 : 델마당  dong님의 글(dongsoft) 

posted by 유돌이
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 유돌이
2008. 12. 29. 23:47 델파이
이제 여기까지 종합적인 코드를 완성해 보겠습니다.

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

implementation

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;
  Result := True;
end;
 
위의 코드를 보면 Destory부분에 약간의 예상외 로직이 보이실 것입니다. 우선 첫번째로 필터그래프를 소멸시키기 전에는 반드시 비디오 스트림의 흐름을 정지시켜야 할 것입니다. 그래서 MediaControl.Stop이라는 코드가 첫번째로 삽입된 것이고요, 두번째부터 인터페이스를 해제시키는 로직에 While문이 있다는 것에 조금 의아하셨을지도 모르겟습니다. 

DShow는 간혹가다가 원인 불명의 다운현상을 발생시킵니다. 이유는 여러가지가 있을 수 있겠습니다만, 주로 적절치 못한 구조의 필터나 혹은 WDM 장치 드라이버가 아닐까 생각합니다. 아무튼, 다운이 되는 경우는 주로 동영상을 플레이하거나 반대로 정지시키는 시점에서 발생한다는 것인데요, 상황에 따라서 완전히 다운되는 경우도 있고 아니면 잠시 대기상태에서 빠져나올 수도 있습니다. 
이때 완전히 다운되는 것은 막지 못하겠지만, 잠시 대기상태에서 빠져나오는 것은 위의 방식 처럼 While를 사용하여 해결할 수가 있다는 것이죠. 실제로 While문을 사용한 후부터 어플이 상당하게 안정화 되었음을 느낄 수 있었습니다.  밑져야 본전이니까 그냥 버릇처럼 사용하시면 될 것이라고 봅니다. 어차피 다운되면 다운 되는 것이니까요.  

  => While Assigned(FilterGraph)     do FilterGraph := nil;

[1]필터를 생성하는 함수.

이제 우리는 TBaseDShow Class에다가 다음 세가지 함수를 추가해 넣을 것입니다. 첫째는 필터를 생성하는 함수이고, 두번째는 필터의 핀을 찾아내는 함수이며, 마지막으로 각각의 다이렉트쇼 카테고리에서 음성이나 비디오 입력장치 등을 얻어 오는 함수입니다.

우선 필터를 생성하는 함수는 사실 필터그래프를 생성하는 함수와 90%이상 동일합니다. 왜냐하면 필터 자체가 하나의  COM Object이기 때문에 CoCreateInstance함수를 사용하여야 하기 때문입니다. 필터그래프를 생성했던 것과 동일하게, 만들고자 하는 함수 안에는 CoCreateInstance 함수 하나만이 달랑 위치해 있게 될 것입니다.
자, 그렇다면 굳이 이렇게 필터를 생성하는 함수와 필터그래프를 생성하는 함수를 별도로 구분하여 만들어 놓을 필요가 있을까하고 생각하실 것입니다. 두가지 모두 COM Object이기 때문에 COM Object를 생성하는 함수를 하나 만들어 두고, 필터그래프나 필터를 생성할때 동일하게 사용하면 되지 않을까하는 생각이 들기도 하실 것입니다. 결론은 그렇게 해도 됩니다. 다만 저는 이렇게 두가지 별도의 함수로 만들어 놓은 것이 실보다는 득이 더 많다고 생각한 것일 뿐입니다.

우선 첫째로, 필터그래프나 필터를 생성할 때마다 그 두가지를 분별해서 생각할 수 있게 해주며, 두번째는 사실 범용적인 COM Object를 생성하는 방식은 매개변수 하나를 더 설정해야 하는데요, 이것은 필터를 생성할 때마다 상당히 번거롭습니다. 필터가 한두개라면 모르지만 앞으로 10개 이상의 필터를 자유롭게 생성해야 하는데 동일한 매개 변수를 매번 설정하는게 참 번거롭기 때문입니다. 아무튼 이것은 코딩의 관점이니 구태여 저의 형식을 강요하지는 않겠습니다. 자, 그러면 아래의 완성된 로직을 보겠습니다.

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;

위에서 보시면 아시겠지만, 필터그래프를 생성시키는 로직과 거의 비슷합니다. 설명은 드리지 않았지만 매개 변수중의 하나인 CLSCTX_INPROC_SERVER가 의미하는 것이 바로 In Process형의 COM Server임을 나타낸다는 것을 짐작하실 것입니다. 이것을 도움말 파일에서 찾아보면 다음과 같습니다. '이 파라미터는 개체가 동적 링크 라이브러리 (DLL)로서 처리 되어 애플리케이션의 처리의 일부로서 실행되는 것을 나타낸다.' 예상했던 것과 동일한 정의가 되어있음을 확인할 수가 있습니다.

자, 이제 함수의 매개변수중 두번째 IBaseFilter를 보아 주시기 바랍니다. 이 인터페이스는 모든 필터들이 반드시 상속 받아야할 인터페이스입니다. 그러나 반드시 있어야 할 인터페이스라고 해서 없으면 필터를 생성하지 못한다라는 것은 아닙니다. (실제로 확인은 해보지 않았지만 이것도 COM  Object의 일종이니 객체가 생성되긴 할 것이라고 봅니다.)

이 인터페이스가 없으면 필터그래프에 등록할 수가 없기 때문입니다. 앞서서 우리는 필터그래프가 어떠한 역할을 하는지를 말씀드렸습니다. 즉, 필터들을 필터그래프 자신에게 등록하고 그것들을 서로 연결시켜주는 서비스를 해준다고 하였지요. 이렇게 필터그래프에 등록할때에는 반드시 공통되는 인터페이스가 필요한데요, 이것이 바로 IBaseFilter라는 인터페이스입니다. 이 부분을 미리 살짝 보여드린다면 다음과 같은 코드가 될 것입니다.  

  1)  DongFilter : IBaseFilter;      //인터페이스형 변수선언. 
  2) CreateFilter(CLSID_DongFilter,DongFilter);     //필터를 생성한다. 
  3) FilterGraph.AddFilter(DongFilter ,'DongFilter Filter');    //필터그래프에 생성한 필터를 등록한다. 
 
위에서 보시듯이 순서대로 생각하시면 되겠습니다. 먼저 필터의 IBaseFilter 형 타입의 변수를 선언합니다. 그리고 두번째로 생성하면서 동시에 IBaseFilter 인터페이스를 얻어옵니다. 마지막으로 이  IBaseFilter형 타입의 변수를 필터그래프에 추가해 줍니다.(뒤쪽의 'DongFilter Filter'라는 것은 일종의 TPanel에서 Caption  정도라고 생각하시면 되겠습니다.)  여기까지...

[2]필터의 핀 인터페이스를 얻어오는 함수.

이제부터 조금 까다로운 필터의 핀 인터페이스를 얻어오는 함수에 대하여 설명해야 하겠습니다. 그런데 이 함수를 논하기전에 우선 필터의 핀이란 대체 무엇인지를 알아야 할 것입니다. 여러분은 전에 GraphEdit을 사용해서 필터를 비주얼하게 생성시켰던 것을 기억하실 것입니다. 이 필터들의 꽁지와 꽁지를 마우스로 잡아당겨서 화살표로 연결시켜주었는데요, 바로 이 꽁지부분을 필터의 '핀'이라고 지칭하는 것입니다. 사실 GraphEdit에서 보기에는 '핀'의 모습이 거대한 필터의 크기에 비하여 너무 보잘 것이 없습니다. 그래픽상으로는 그냥 조그마한 점에 불과한 것인데요, 실제 필터의 내부에서는 배보다 배꼽이 더 크게 구현되어 있습니다. 즉, 모든 로직의 대부분이 이 필터의 '핀'에 집중되어있다는 것입니다.

이것이 무슨 의미인지를 예를 들어서 보겠습니다. 우선 아래의 간단한 클래스를 보시겠습니다.

  TDong = class(TObject)
    Img : TImage;  //멤버객체(혹은 멤버필드)
   end;

위에서 보시면 아시겠지만 TDong이라는 클래스 안에 Img라는 TImage형 타입의 멤버객체가 또다시 존재하고 있습니다. 필터의 핀도 이와 거의 동일한 존재입니다. 즉, 필터가 하나의 COM Object라 한다면 핀은 그 COM Object 안에 포함된 또다른 형태의 멤버 COM Object라는(위에서 Img와 같은) 것입니다. 여기까지 이해가 되셨다면 왜 제가 처음에 배보다 배꼽이 더 크다고 비유하셨는지 아셨을 것입니다.
위의 TDong이라는 클래스에서 TDong이라는 객체는 사실 껍데기에 불과하고 달랑 TImag형 멤버객체인 Img 변수가 더 큰 비중을 차지하는 것처럼 보여집니다. 바로 필터의 '핀'이라는 것도 이와 같은 방식으로 필터 안에 놓여져 있기 때문에 그러한 비유를 한 것입니다. 필터에 있어서는 '핀' 이 전부라고 생각해셔도 무방할 정도입니다.  이것은 나중에 필터를 집적 만들어 보시면 아시게 될 것입니다.  일단은 이정도 이해 만을 가지고 다음으로 넘어가겠습니다.

자, 그렇다면 이제 문제의 핵심을 살펴봐야 겠습니다. 우리가 어떤 COM Object 에서 인터페이스를 뽑아내기란 식은죽 먹기였습니다. 바로 QueryInterface라는 메소드를 사용해서 가능했던 것이죠. 그러나 COM Object 안에 포함되어있는 또다른 멤버 COM Object의 인터페이스를 뽑아내는 것은 간단한 일이 아닙니다.
그래서 필터에서 그것을 가능하게 하기 위하여 메소드를 하나 제공해 줍니다. 바로 FindPin 이라는 메소드 입니다. 즉 Dong 이라는 IBaseFilter 인터페이스형 변수가 있다면, Dong.FindPin(Pin이름, 받아낼 '핀' 타입변수) 이런 형식으로 핀 인터페이스를 얻을 수가 있습니다. 그러나 저는 이 방식을 사용하지 않습니다. 이것에 대하여 부연설명을 해드려야 겠습니다.

핀의 인터페이스를 얻는 방식으로는 두가지 접근방식이 있습니다. 하나는 핀의 이름으로 검색하는 것이고, 또다른 하나는 핀의 위치로 검색하여 얻어내는 것입니다. 예를 들어 다음과 같은 필터가 있다고 합시다. 입력핀은 1 개이고 출력핀은 3개 정도입니다. ( 필터에서 핀은 수없이 많이 만들 수가 있음.)
 

                                       Dong.ax 필터.
                              ┌──────────┐ 
                              │                               ■  (출력핀1)
                              │                              │
              (입력핀1)  ■                                ■  (출력핀2)
                              │                              │ 
                              │                               ■  (출력핀3)
                              └──────────┘


위에서 만일 '입력핀1'을 얻고자 한다면(정확히 말씀드리자면 입력핀1이라는 COM Object의  IPin 인터페이스) 우리는 '입력'이라는 방향을 알고 있고 위에서 첫번째 순서라는 것을 알고 있기 때문에 이러한 조건을 가지고 원하는 핀을 얻을 수가 있습니다. 또한 '출력핀3'을 얻고자 한다면 '출력'이라는 핀의 방향을 알고 있고, 위에서 세번째에 있기 때문에 이러한 조건을 가지고 '출력핀3'을 얻을 수가 있는 것입니다.

정리하자면 핀을 검색하는 방식으로는 FindPin이라는 메소드를 사용하여 찾는 것과, 핀의 방향과 순서를 조건으로 에뉴무레이트(열거)하여 찾아내는 방식, 이렇게 두가지가 있다는 것입니다. 그런데 저는 주로 후자의 방식을 사용하는데요, 여기에는 이유가 있습니다. 

솔직히 말씀드리자면 FindPin 이라는 메소드가 실은 제대로 작동하지 않는 경우가 종종 있습니다. 여기에는 필터를 개발하는 사람들의 '소홀함'도 지적될 수가 있겠는데요, 아무튼 그 이유 여하를 떠나서 상당히 자주 잘못된 핀을 얻어올 수가 있다는 사실입니다.
제가 어디서 읽었는지는 가물가물 합니다만 그곳에서도 이렇게 핀의 이름으로 검색하지 말라고 경고한 것으로 기억납니다. 아무튼 저는 핀찾기에서 '조건'으로 에뮬레이트합니다. 그리고 남들도 이러한 방식을 선호하고 있다는 사실을 종종 확인할 수가 있습니다. 아주 시간이 없고, 그래서 미칠것 같은 스트레스에 휩싸여 있었을 적에 한번 '이름'으로 찾기를 시도한 적이 있었는데요, 결국 원하는 핀을 몇시간 동안이나 찾지 못해 헤메였던 기억이 생생 합니다. 
DShow는 이렇게 에뮬레이트 방식을 주로 사용합니다. 비단 핀찾기 뿐만이 아니라 나중에 카테고리를 뒤져야 할 때에도 에뮬레이트(열거) 방식을 사용할 것입니다.  자, 이제 핀을 찾는 함수를 다음과 같이 선보이겠습니다.  
      
     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;


위의 함수 내용에 대해서는 일일이 설명을 하지 않겠습니다. 기본적인 로직에 속하는 것이기 때문입니다. 하지만 여러분이 혹시 눈치채셨을지 모르겠지만 약간은 이상하다는 생각이 드실 것입니다. 그렇습니다. 위의 함수에서는 방향을  지시하는 매개변수는 있지만 위치를 지정하는 매개변수는 존재하지 않습니다. 

사실 위의 첫 모델에서는 위치를 지정하는 매개변수가 있었습니다. 하지만 점점 제 자신에 맞게 다듬다가 보니까 이렇게 단순화 된 것입니다. FindPinOnFilter함수는 C로 된것을 델파이로 포팅한 것입니다. 제 기억으로는 원래 더 복잡했던 것으로 기억합니다. 아무튼 여러분도 자신만의 FindPinOnFilter 함수를 개발하여 사용하실 날이 도래할 것입니다.

굳이 설명을 드리자면 위에서는 핀의 순서상에서 가장 첫번째로 '접속되지 않은' 핀을 리턴한다는 것입니다. 이 함수를 사용하여 실제로 핀(IPin 인터페이스)을 얻어내야 할 것입니다.  

출처 : 델마당  dong님의 글(dongsoft)
posted by 유돌이
2008. 12. 29. 23:45 델파이
우리는 전편에서 COM의 인터페이스에 대하여 알아보았습니다. 그러나 아직까지도 인터페이스에 대하여 감이 잘 오질 않으실 것입니다. 그렇지만 조금은 참고, 중요한 몇가지에 대하여 정리해 보겠습니다.

  1) COM 오브젝트를 생성하기 위해서는 CoCreateInstance라는 함수를 사용하는데 이 함수에는 세가지의 매개변수를 지정한다. 하나는 COM 오브젝트를 식별하기 위한 GUID인 CLSID 이며, 또다른 하나는 IID_로 시작하는 인터페이스 식별자이고, 나머지는 리턴되어 받아낼 실제 인터페이스형 타입의 변수이다. 

  2) CoCreateInstance 함수를 사용하여 COM 오브젝트를 생성하면서 우리는 첫번째 인터페이스를 뽑아낸다.

  3) 하나의 인터페이스가 또다른 인터페이스를 QueryInteface를 사용하여 국수 뽑듯이 뽑아낼 수가 있다.  

  4) 인터페이스를 뽑아내기 위해서는 반드시 두가지의 매개변수가 필요한데, 하나는 IID_로 시작되는 식별자이며 또다른 하나는 리턴되어 받아낼 실제 인터페이스형 타입의 변수다. (일반적으로 I로 시작되는...)  그런데 식별자와 인터페이스 타입의 변수에 사용된 GUID는 동일한 값이거나 혹은 상속관계에 있는 값이어야 한다. 여기까지...
 
[1]필터그래프의 인터페이스.

필터그래프 매니저 COM 오브젝트(이하 필터그래프) 에는 수많은 인터페이스가 존재합니다. 헬프파일을 찾아보시면 25개 정도의 인터페이스를 발견하실수가 있을 것입니다. 이 중에서 우리는 3가지의 인터페이스만을 사용할 것입니다.
만일 여러분이 나중에 좀더 복잡한 DShow 어플을 개발하고자 할때에, 이렇게 수많은 인터페이스 중에서 필요한 것을 골라서 선택하여 사용하시면 될 것입니다. 그러나 현재로는 3가지 아주 핵심적인 인터페이스만을 생각해보기로 하죠.

첫째로 DShow 어플에서 생성되어진 수많은 필터들을 연결해주거나 끊어 주거나 해야 합니다. GraphEdit에서 여러분은 직접 필터들을 마우스로 드래그하여 화살표를 꽁지에서 꽁지로 연결해 주었을 것입니다. 이러한 연결을 해주는 서비스 인터페이스가 필요합니다. 이 인터페이스가 바로 IFilterGraph라는 놈인데요, 우리는 여기서 상속받아서 기능이 좀더 많은 IGraphBuilder라는 인터페이스를 첫시발로 CoCreateInstance을 사용해 필터그래프 생성과 동시에 뽑아내었죠. 자, IGraphBuilder라는 인터페이스가 필요한 이유는? 필터들을 연결해 주기 위해서라고 일단 간단히 생각합시다.

두번째로 필요한 것은 필터를 연결한다음에 이것들을 실행시켜줄 인터페이스가 있어야 합니다. 즉,  Run하고, Stop하고, Pause하는 기능을 가진(이것을 다른말로 스트림을 제어하는) 인터페이스가 필요합니다. 이것을 IMediaControl 이라고 합니다. 이 인터페이스는 필수입니다. 이게 없으면 필터들 연결만 하고 끝이겠죠. 동영상이 Play되지 못한다는 말이죠.

세번째의 인터페이스는 사실 없어도 되는 것이지만, 좀더 세련된 DShow 어플을 만들기 위해서는 필수입니다. 일단 IMediaControl를 사용하여 DShow를 작동시켜 동영상을  Play시키면 디폴트로 별도의 랜더링 윈도우가 뜨면서 실행되는데요, 이게 영 볼품이 없습니다. 자신이 만든 어플의 메인 폼위에서 실행되었으면 하는데요, 이렇게 영상이 랜더링 되는 위치를 설정할 수 있게 해주는 인터페이스가 바로 IVideoWIndow라는 인터페이스입니다. 이 세가지는 거의 필수라고 보시면 되겠습니다.

자, 이제 이 나머지 인터페이스를 어떻게 뽑아낼까요? 우리는 처음에 필터그래프에서 IGraphBuilder라는 인터페이스를 FilterGraph라는 변수에 받아내었습니다. 요놈을 가지고 나머지 인터페이스를 뽑아내어 봅시다. 
일단 변수선언을 다음과 같이 합니다. 

    MediaControl: IMediaControl;
    VideoWindow: IVideoWindow;

그리고 다음과 같이 QueryInterface를 사용합니다.

  FilterGraph.QueryInterface(IID_IMediaControl, MediaControl);
  FilterGraph.QueryInterface(IID_IVideoWindow, VideoWindow);

자 이렇게 나머지 두개의 인터페이스를 얻을 수가 있었습니다. 그렇다면 여기까지 한가지 의문이 들 것입니다. 우리가 필터그래프 COM 오브젝트를  CoCreateInstance 함수를 사용하여 생성하였는데, 소멸(제거)는 어떻게 하는 것이지라고 말이죠. 자, 이것은 아주 중요한 이야기니까 별도의 설명을 해야하겠습니다. 

[2]COM의 소멸. 

자, 조금 헷갈리는 분야가 나왔습니다. COM  Server는 대체 무엇이고 COM Object는 또 무엇인가. 위의 제목이 COM의 소멸이라고 했는데 그렇다면 COM Server의 소멸을 의미하는 것인가, 아니면 COM Object 의 소멸을 의미하는 것인가. 기타 등등.  여러분은 이제 COM Server와 COM Object(혹은 객체)를 분별해야만 하는 시점이  다가온 것입니다.  

COM Server의 종류에는 Out of Process 방식과  In Process 방식의 두가지가 있습니다. 전자는 Exe형식의 완전한 독립된 응용어플로 생각하시면 되고요, 후자는 DLL 형태를 취하고 있습니다. 자, 형태론적인 관점에서 보면 두개의 차이가 명확해집니다. 그리고 내부적인 차이도 사실 무척이나 다릅니다. 여기에 대해서 잠깐 쉬었다 갈까요.

우리가 일반적으로 프로그램을 실행하면 하나의 프로세스가 생성되어서 윈도우즈 운영체제로 부터 연속된 메모리 주소를 부여받게 됩니다. 32비트 컴퓨터니까 2의 32승인 약 4G바이트의 메모리가 되겠죠. 그런데 여러분도 아시다시피 아무리 많은 응용프로그램을 실행시켜도 각각의 프로세스(혹은 프로그램)은 상대방의 메모리 주소를 전혀 침범하지 않고 잘만 작동합니다. 게다가 멀티 작업까지 가능하네요. 이게 대체 어떻게 된 것일까요.
하나의 프로세스가 실행되면서 윈도우즈 운영체제로부터 받는 메모리 주소는 사실 실제적인 물리주소가 아니라 가상 메모리의 주소를 받기 때문에 가능한 것입니다. 윈도우즈는 각각의 프로세스가 절대로 상대방의 물리적인 메모리 번지를 침범하지 못하도록 세심한 주의를 기울여 가상번지를 할당해 주고 있으므로 안심하고 멀티 태스킹 작업이 가능한 것입니다.

그런데 위의 사실을 역으로 생각하면 두개의 독립된 프로세스가 서로 상대방의 메모리를 침범하기란 운영체제가 허락하지 않는한 불가능한 일입니다. 그러나 이것을 가능하게 하는 두개의 방식이 있는데요, 하나는 공유메모리를 사용하는 것이고요, 또다른 하나는 바로 COM에서의 마샬링을 사용하는 것입니다. 저는 이제까지 공유메모리만 사용해오고 있었는데요, 예전에 한번 접근속도가 느린것 같아서 마샬링이란 것을 고려해 보았던 적이 있습니다.

아무튼, 결론은요 exe형(Out of Process) 타입의 COM Server는 내부적으로 마샬링과 같은 복잡한 과정을 거친다는 것과(왜냐하면 실제로 완전히 다른 프로그램의 메모리 영역에서 서비스를 받아야 하기 때문에),  Dll형(In Process) 타입의  COM Server는 그런일이 없기 때문에 상대적으로 간단한다는 것입니다. 이러한 것은 그냥 머릿속에 배경지식으로 활용하시길 바랍니다.

자, 이야기기 또 삼천포로 빠졌습니다. 여기까지 우리는 COM Server의 두가지 큰 형태를 알아보았습니다. 여기에 한 가지를 덧붙여 말씀드리자면 다행스럽게도 우리가 사용하는 DShow는 In Process타입의 DLL형태를 취하고 있다는 것입니다.

만일 우리가 Dong.Exe라는 COM Server(Out of Process)을 개발하였다면, Dong.Exe자체를 하나의 COM Server 라 하며, 그 안에는 여러가지 종류의  COM Obect가 존재할 수 있을 것입니다. 또한 마찬가지로 Dong.dll라는 COM Server(In Process)을 개발하였다면 Dong.dll 자체를 하나의 COM Server라 할 것이고, 그 안에는 수많은 COM Object가 존재할 수 있을 것입니다. 자, 이제 COM Server와 COM Object에 대한 어느정도의 분별이 생기셨을 것입니다.

조금더 예를 든다면, 여러분은 나중에 간단한 형태의 필터를 개발하게 될 것입니다. 이것은 물론 In Porcess 형태의 일종의 COM Server입니다. 만일 여러분이 비디오 스트림의 상하를 거꾸로 변경하는 Convert.dll 필터를 개발하였다면, 바로 이 Convert.dll이 하나의 COM Server이자 동시에 달랑 하나뿐인 COM Object를 가지고 있을 뿐인 것이죠.

현재 DirectShow에는 기본 필터가 수십여가지 있습니다. 이 필터 하나하나가 각각 별도의  In Process COM Server 의 DLL 형태로 존재할까요? 그렇지가 않습니다. DirectShow는 하나의 Dll 형태의 Server에 많은 형태의 (필터그래프 매니저를 비롯한 다양한 종류의 필터) COM Object를 담아 두고 있습니다. 이 DLL 파일의 이름이 바로 Quartz.dll입니다. Quartz.dll이라는 In Process COM Server에는 수많은 DShow용 COM Object가 포함되어 있습니다.

처음에 DShow를 공부할때, 필터를 하나의 ax확장자를 가진 파일 단위로만 생각해서 헷갈렸던 기억이 납니다. 왜냐하면 DirectShow에서 제공하는 기본필터들을 찾아 보려고 CLSID로 레지스트리를 일일이 뒤졌는데 모두다 Quartz.dll이라고 설정되어 있었기 때문입니다. 필터는 하나의 COM Server가 아니라 하나의 COM Object입니다. 따라서 어떤 하나의 Dll 형 서버에 수많은 필터가(혹은 필터그래프 매니저와 같은 다른 COM Object가...) 포함되어 있을 수가 있는 것입니다.

여러분이 만일 CoCreateInstance로 COM Object를 생성시켰다면 COM 라이브러리는(여러분은 CoInitialize를 사용하여 이미 COM 라이브러리를 초기화 하셨습니다.) 매개변수 중 하나인 CLSID를 파악하여 레지스트리에서 해당 CLSID의  COM Object가 포함된 COM Server(하드 디스크 어딘가에 저장된 dll)을 로딩할 것입니다. 그런데 이미 로딩된 COM Server에 있는 어떤 COM Obejct를 또다시 CoCreateInstance를 사용하여 인스턴스화(객체화)하였다면 어떨까요? COM 라이브러리가 이미 로딩되어 있는 Dll을 다시 로딩하지는 않을 것입니다.

이제 우리는 In Process COM Server가 어떤 형식으로 로딩되는 것인지 아셨을 것입니다. 그렇다면 이제 COM Server가 어떤 방식으로 메모리에서 언로딩(소멸)되는지 알아보도록 하겠습니다.  


    Dong.DLL  In Process Server
    ┌──────────┐
    │                              │
    │  A COM Object    ─┼───▶ CoCreateInstance로 인스턴스화 ( Dong.dll이 로딩된다.)
    │  B COM Object       │
    │  C COM Object    ─┼───▶ CoCreateInstance로 인스턴스화 ( Dong.dll이 이미 로딩되어 있다.)
    │           .                  │
    │           .                  │
    └──────────┘ 

위와같이 Dong.dll이라는 이름의 COM Server가 있다고 생각해 봅니다. 우리는  CoCreateInstance를 사용하여 A와 C라는 COM Object를 위의 순서대로 인스턴스화 하였습니다. 이제 이 상태에서 A와 C라는 COM Object가 소멸되면 COM 라이브러리는 Dong.dll을 자동으로 메모리에서 언로딩할 것입니다. 즉,다시말해서 하나의 COM Server에서 사용되어졌던 모든 COM Object가 소멸되면 해당 COM Server 는 자동으로 언로딩된다는 것입니다. (실제로 하나의 COM Server를 메모리에 로딩하거나 언로딩할때 실행되는 로직이 별도로 있습니다. 그래서 로딩과 언로딩이라는 직설적인 표현보다는 생성과 소멸이라는 말이 약간은 더 어울리는 것입니다. 하지만 여기까지는 일단 자동으로 로딩되고 언로딩된다고 이해하여도 상관은 없을 것입니다.)

자... 그렇다면 이제 어떻게 해서 COM Object를 소멸시킬 수가 있을지를 생각해 봅시다. 우리가 CoCreateInstance를 사용하여 필터그래프라는 COM Object를 생성하면서 최초로 IGraphBuilder라는 인터페이스를 얻어왔던 것을 기억하실 것입니다. 그리고 이 IGraphBuilder라는 인터페이스 형 타입의 변수에서 QueryInterface를 사용하여 나머지 주요 한 두개의 인터페이스인 IMediaControl과 IVideoWindow를 얻을 수가 있었습니다. 만약, 이 상태에서 세개의 인터페이스를 모두 제거한다면 필터그래프라는 COM Object는 자동 소멸하게 될 것입니다. 즉, 이것을 정리한다면 A라는 어떤 COM Object를 생성하여 그 안에 포함된 다수의 인터페이스 중에 몇개의 인터페이스를 얻어와 사용하다가, 그 얻어왔던 인터페이스를 모두 제거한다면 A라는 COM Object는 더이상 외부에 노출된 인터페이스가 없으므로 자동 소멸한다는 것입니다. 

이제 결론에 다다랗습니다. 그렇다면 얻어온 인터페이스는 어떻게 소멸시키는가? 이것은 너무 간단해서 싱거울 정도입니다. 얻어온 인터페이스 형 타입의 변수에 nil값을 주면 됩니다. 여기까지를 한번 종합해서 정리하겠습니다.  

  1) COM Server는 Exe나 DLL과 같이 하나의 파일 단위를 의미한다. 

  2) 하나의 COM Server 안에는 수많은 COM Object가 존재할 수 있다. 

  3) COM Server는 최초로 그 안에 포함된 COM Obejct가 CoCreateInstance로 생성될 때 COM 라이브러리에 의해(여러분은 처음에 CoInitialize라는 함수로 COM 라이브러리를 초기화하셨습니다.) 자동으로 로딩(생성)되며, 반대로 생성되었던 COM Server안의 모든 COM Object가 소멸되면 자동으로 언로딩(소멸)된다.

  4) COM Object는 그 안에 포함되어 있던 수많은 인터페이스 중에서 외부로 노출되어 졌던 모든 인터페이스가 해제되면 자동으로 소멸한다. 

  5) 인터페이스를 해제하기 위해서는 얻어온 해당 인터페이스형 타입의 변수에 nil을 대압하면 된다.  


  ****************************************** 중요. ********************************************* 
  COM에 대하여 위와같은 해설은 수박 겉핥기 식이라고 할 수가 있습니다. 별도의 책을 사서 한번쯤 휘둘러 읽어보실 필요가 있습니다. 그 시간이 정 안된다면, 김용성님의 Visual C++ 6완벽가이드(일명 눈깔책)을 구입하여 그 안에 정리된 COM 부분을 익히시는 것이 상당한 도움이 될 것이라고 생각합니다.  
  *********************************************************************************************

출처 : 델마당  dong님의 글(dongsoft)


 

posted by 유돌이
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 유돌이
2008. 12. 29. 23:35 델파이

GraphEdit에 대한 설명을 위해 1,2편으로 나누었습니다. 그만큼 워낙 DShow에서는 중요한 유틸이라 설명해야할 부분이 많기 때문입니다. 
 
[1] 카메라 영상을 플레이하기.
 
강의를 하려고 보니까 GraphEdit의 왼쪽에서부터 11번째의 단축버튼을 눌렀을때 나오는 윈도우의 명칭을 무엇인가로 지칭하는게 편리할 것 같습니다. 매번 11번째 운운하면서 설명하는 것 보다는 앞으로는 '필터삽입윈도우'라고 부르겠습니다. 자, 필터삽입윈도우에 보시면  Video Capture Source 라는 카테고리가 보이실 것입니다. (계속해서 제가 카테고리라고 표현하는데는 다 이유가 있습니다. 실제로 이것은 프로그래밍 상에서 카테고리로 되어 있으며, 나중에 여러분은 이 카테고리에서 필요한 필터들을 골라내는 로직을 작성해야 할 것입니다.).
만일 여러분의 컴퓨터에 USB 카메라가 설치되어 있다면 이곳에 분명히 1개 이상의 필터가 존재해야 합니다. (DSHow를 공부할 때에는 반드시 USB 카메라가 한대 쯤은 있어야 한다.) 일단 그것을 선택하면 GraphEdit의 메인화면에 필터가 생성되어 질 것입니다. 자.. 이것은 일종의 광의의 소스필터이고, 좁게는 비디오 입력장치라고 부릅니다.

이제 여기서부터 나오는 스트림을 랜더링해야 하는데요, 그러자면 랜더링 필터가 필요합니다. 마찬가지로 필터삽입윈도우에서 이번에는 DirectShow Filters 카테고리에 들어가 봅시다. 여기에는 많은 수의 DirectShow 필터가 존재하고 있습니다. 이 말은 즉 오리지널 DShow 클래스(실은 COM 인터페이스) 내부구조를 하고 있다는 것입니다. 여기에서 Video Renderer 필터를 선택합니다. 조금 아랫쪽에 있을 것이므로 스크롤바를 밑으로 조절하셔야 할 것입니다. 

자, 이제 GraphEdit의 메인화면에는 두개의 필터가 놓여져 있을 것입니다. 왼쪽에 카메라 필터를, 오른쪽에 렌더러 필터를 위치시켜 놓은 다음에 카메라 필터의 오른쪽 꼭지에서 마우스로 콕 집어서 드래그 하여 랜더러 필터의 왼쪽 꼭지점에 갖다가 놓습니다. 이제 두개의 필터가 연결되었을 것입니다. 그런 상태에서  위쪽의 단축버튼 중에서 동영상 실행 버튼을 클릭하여 봅시다. 새로운 윈도우가 뜨면서 카메라로부터 들어온 영상이 렌더링 되는게 보여질 것입니다.   

[2] Video Renderer 필터

Video Renderer 필터의 종류가 3가지가 되는 것을 볼수 있을 것입니다. 이것에 대한 설명을 하자면 장황해 지겠으니, 가능하다면 간략하게 설명해 드리겠습니다. 일단 VMR Renderer 와 두개의 Video Renderer을 보실 수가 있을 것입니다.

이 VMR Renderer은 영상합성과 관련된 아주 중요한 필터입니다. 그래픽카드에서 이 서비스를 지원해 줘야 사용할 수가 있는 필터입니다. 이것이 무슨 말인가 하면... 일반적으로 영상을 합성한다면 버퍼링이 발생하게 됩니다. 320*240사이즈의 버퍼링이라면 문제가 되지도 않겠죠. 하지만 640*480 이상의 영상을 실시간 합성하고자 한다면 버퍼링이 엄청나게 문제가 됩니다.
왜냐하면 영상을 합성하는 알고리즘을 시스템 성능에 따라 조절해야 하기 때문입니다.
즉, 시스템 성능이 낮으면 그에 합당한 간단한 알고리즘을 사용해야 하고, 시스템 성능이 높으면 그에 따라 선명도을 증가시킬 것인가(640*480에서 720*480으로 좀더 선명한 화질의 소스에서 합성할 것인가), 아니면 합성시 거칠게 나타나는 부분을 알고리즘으로 부드럽게 처리하는  로직을 첨가할 것인가를 선택해야 합니다. 이러한 아주 긴밀하고 섬세한 선택은 물론 시스템 단가, 혹은 시장성의 선택과 결과물의 쿼리티와의 긴박한 관계에서 수많은 테스트를 거쳐서 판단하게 될 것입니다.
그러나 결론적으로 말씀을 드리자면 곧 한계가 발생하겠죠. 사람들은 환상적인 허리우드영화에 익숙해져 있기 때문에 웬만한 합성 결과물에는 반응하지 않게 되었기 때문입니다. 비록 그곳에 자신의 모습이 나온다고 할 지라도 말이죠. 

그래서 혹시 들어봤는지 모르겠지만 동영상 편집보드라는 특수한 보드를 사용하는 것입니다. '프리미어'라는 소프트웨어로 한번이라도 영상을 편집해 보신 분들은 아시겠지만, 동영상의 사이즈가 클수록 편집하는데 오랜 시간이 걸린다는 것을 잘 아실 것입니다. 이 편집보드는 가장 싸구려인 매트록스의 70만원짜리부터 수천만원이상가는 고가에 이르기까지 다양하게 시장에 나와있는 것으로 알고 있습니다.

자, 다시 돌아와서, 이러한 상태에서 영상을 합성하는 어떤 기능이 그래픽카드에 존재한다면 얼마나 좋겠습니까. 그렇다면 CPU에서 메모리에 적재된 데이터를 불러들여서  별도로 합성할 이유가 없을 터이죠. 만일 그렇게 된다면 수십만원 짜리의 영상편집보드를 별도로 구입할 필요가 없어지고 상업용 시스템 단가가 그만큼 떨어질수가 있다는 말이 됩니다.   

저희가 한창 영상합성 시스템을 개발할때에 DriectShow는 이 VMR 필터를 지원하지 않았습니다. 그래서 우리는 어쩔 수 없이 CPU를 사용해서 영상을 합성할 수밖에 없었는데요, DirectX SDK의 버전이 9.0을 넘자 VMR이라는게 생긴 것이었습니다. 그당시 얼마나 기뻤는지 모릅니다. 이것을 사용하면 CPU의 부담을 그만큼 줄일 수가 있고, 그렇다면 좀 더 복잡한 합성 알고리즘으로 합성된 영상물의 거친 부분들을 다듬을 수가 있을 터였기 때문이었습니다.

그러나 아쉽게도 그당시 그래픽카드가 VMR을 지원하는 것은 단 하나였고, 그나마 매트록스의 그래픽 카드는 지원이 되지 않았습니다. 매트록스 회사에 전화를 걸어보니 DirectShow 9.0을 완벽히 지원하려면 6개월 이상이 걸릴 것이라는 이야기를  들어야 했습니다. 물론 이 이야기는 아주 오래적의 일입니다.
제가 이런 이야기를 하는 것은 VMR Renderer이란 무엇인가를 아주 효과적으로 여러분에게 납득시킬 수가 있기 때문입니다. 아무튼 우리 회사로서는 반드시 매트록스 그래픽 카드를 사용해야만 할 터였습니다. 모든 시스템이 이 그래픽 카드에 맞춰져 있는 상태였기 때문이죠. 해서, 그당시 VMR Renderer를 결국 포기해야만 했던 경험이 있습니다. 

위의 이야기를 듣고 조금 이상하다고 생각하는 분들도 있을 것입니다. 그 분들은 DShow의 VMR Renderer을 어느정도 알고는 있으시는 분들이겠죠. 그렇습니다. VMR Renderer에서의 영상 합성의 결과물은 소프트 웨어적으로 저장할 수가 없는 것입니다. 왜냐하면 그래픽카드에서 영상이 합성되기 때문에 거기가 종착역이기 때문입니다. 물론, DriectX 의 DirectDraw 평면을 제공하기 때문에 이것을 가져와서 다시 저장할 수는 있습니다. 실제로 VMR Renderer은 이렇게 DirectDraw평명을 억세스하기위한 인터페이스를 제공하는 것으로 알고 있습니다. (하도 오래전 일이라 가물가물 하지만 본 것 같음. 아니면 말고...) 그러나 이런 방식으로 합성된 결과물을 다시 받아서 저장하는 DShow 프로그래밍은 하지를 않을 것이라고 봅니다. 이것은 로컬인 파라독스 DB를 가지고 네크워크 방식이라고 해야할까요, 아무튼 그렇게 사용하는 것과 마찬가지라고 봅니다. 차라리 그럴봐에야 직접 그래픽카드의 DreictDraw 평면을 이용하는 것이 좋지 않을까 싶네요. 어쨌든, 또다시 이야기가 삼천포로 빠졌습니다. 

VMR Renderer에서 합성된 영상을 소프트웨어적으로 저장할 수가 없다는 말은 맞습니다. 그렇다고 저장하지 못한다는 것은 아니고요. 결론적으로 말씀드려서 저장할수가 있습니다. 정확히 말하자면 DSHow의 방식으로 저장을 못한다는 말이고요, DirectDraw 평면을 직접 억세스하는 소프트웨어적이거나 혹은 우회로 돌려서 저장하는 방식이 있습니다. 즉 모니터에서 나오는 영상을 다시 자신의 컴퓨터 입력장치로 보내어 그것을 저장하는 것입니다.  

VMR Renderer을 설명하자면 한도끝도 없습니다. VMR Renderer이전에는 오버레이 믹서라는 필터를 제공하였습니다. 아마 지금도 DirectShow Filters 카테고리에 보시면 찾을 수가 있을 것입니다. 이 Overlay Mixer라는 필터는  마찬가지로 그래픽카드의 오버레이 평면을 이용하여 합성을 하는 방식입니다. 예전에는 동영상의 자막 처리를 이것으로 하였던 적이 있습니다. 지금도 그렇게 하는지는 모르겠습니다.

지금은 훨씬 다양한 방법론이 존재하기 때문입니다. VMR Renderer이 처음 나왔을때, 이것이 환상적이었던 것은 알파브렌딩 합성을 가능하게 해준다는 것이었습니다. 즉 평면의 구조가 RGB24가 아니라 ARGB32였던 것입니다. 여기서 A는 알파채널로서 아마도 포토샵을 조금만 사용해 보셨던 분들은 알파채널이 무엇인가를 알고 계실 것입니다. 이 말은 영상합성에서 그 상대적인 깊이를 마음껏 조절할 수가 있다는 의미인 것이죠. 자... 이쯤에서 VMR Renderer에 대한 설명은 접기로 하겠습니다. 

이제 나머지 두개의 동일한 이름의 Video Renderer에 대해서 설명드리겠습니다. 두개는 이름만 동일할뿐 실제로 랜더링하는 방식은 내부적으로 다릅니다. 하나는 구버전의 Video Renderer이고 하나는 나중에 VMR Renderer의 또다른 모습입니다. 즉 두개의 Video Renderer를 모두 순서대로 선택하여 GraphEdit에 생성해 놓으신다면 하나의 필터에는 입력핀쪽에 VMR 이라는 명칭이 붙어있는 것을 보실수가 있습니다. 그렇다면 대체 VMR Renderer는 무엇이고 요놈은 또 무엇인지 헷갈릴 것인데요. 간단하게 말하자면 Interlace를 받아서 처리하는 놈이라고 보시면 됩니다. 

자, 이제 종합해보겠습니다. DirectShow Fitlers 카테고리에는  렌더러 필터가 3개가 있다. 하나는 VMR Renderer9이고, 나머지 두개는 이름이 동일한 Video Renderer이다. 그렇데 나머지 두놈의 동일한 이름의 필터를 GraphEdit에다 생성해 놓고 보니 하나의 필터에는 입력핀쪽에 VMR이라고 붙어있다. 이것은 VMR Renderer9를 생성시켰을때와 동일하다. 대처 무엇때문에 이따위 짓을 해 놓았는가. 그 이유는 Interlace방식의 입력 스트림을 처리하기 위함이고, Interlace 방식이란 무엇인가는 인터넷을 찾아서 공부하시길 바랍니다. 

[3] 그래프의 저장.

현재까지 실행을 하셨다면 GraphEdit 의 메인화면에는 두개의 필터가 달랑 연결되어 있는 것을 보실수가 있을 것입니다. 이 상태를 그대로 파일로 저장했다가 나중에 다시 열어서 간단히 실행시키실 수가 있습니다. 메뉴에서 Save Graph를 선택하면 저장하기 다이알로그가 뜨는데요, 일반적인 파일저장처럼 하시면 됩니다. 이 기능은 프로그래밍적으로 사용하려고 했습니다만, 이상하게도 DirectShow의 도움말에는 그런식으로 사용하지 말라고 권고하고 있습니다. 이게 무슨 말인가 하면요...

우리는 앞으로 수많은 필터들을 생성해서 수동으로 연결해야 합니다. 적게는 대여섯개에서부터 많게는 20개 정도의 필터까지도, 혹은 그 이상이라도 필요하다면 연결해야 합니다. 그런데 이 과정에서 시간이 조금 걸립니다. 아주 짧은 시간이지만 일반 사용자들은 1초 이상의 짬을 지루해합니다. 그런데 15개 이상의 필터를 생성하여 연결하다보면 심지어 2~3초 정도가 걸리기도 합니다.(시스템 사양이 낮은 컴퓨터에서) 그런데 GraphEdit처럼 이러한 상태를 아예 파일로 저장했다가 불러들여서 실행한다면 굉장히 속도가 빠르지 않을까라고 생각했던 적이 있습니다.
실제로 그러한 코딩이 가능하고, 그러한 예제 샘플까지 존재합니다. 그러나 이상하게도 DirectShow 도움말 파일에서는 그런식으로 프로그래밍적인 사용을 하지 말라고 거의 강권하고 있습니다. 이러한 이유에 대해 개인적인 추측이 가능합니다만, 아무튼 이것은 저의 개인적인 추측일 뿐이이서 이곳에 기술하지는 않겠습니다. 

[4] Connect to Remote Graph

메뉴에 보시면 위의 제목을 한 기능이 있습니다. 이것은 참으로 유익한 기능인데요, 잘만 활용한다면 꽤 훌륭한 뚫어 기능이 됩니다. ^^. 이게 무슨 말인가 하면요, DShow 프로그래밍은 일반적인 DLL 프로그래밍과 마찬가지로 디버깅이 아주 골치가 아픕니다. DShow의 필터 하나하나도 ax 확장자만 가지고 있을뿐 실제는 DLL과 거의 비슷한 사용자 억세스 관점을 가지고 있습니다. 그래서 한번 버그가 발생하면 대체 어디에서 발생하였는지 감을 잡기가 힙듭니다. 

수십개의 필터들을 동적으로 연결시켜 놓았는데요, 그중 어느 한 부분이 연결되지 않은채 굴러간다면, 대체 어느 부분에서, 몇번째 필터에서 연결이 끊겼는지를 직접 확인해야 할 필요성이 있습니다. 여기까지는 다른 방식으로 어떻게든 알아낼 수가 있겠지요. 그런데 문제는 그 다음입니다. 분명히 연결은 제대로 되었는데도 불구하고 작동이 안되는  경우, 정말로 연결이 되었는지 눈으로 직접 보고 싶어질 때가 있습니다. 

이럴때 여러분이 만든 DShow 프로그램을 실행시킨다음 GraphEdit의 Connect to Remote Graph 메뉴를 사용하신다면, 현재 여러분이 만들어서 실행되는 DShow 프로그램의 내부 필터들의 연결을 그대로 GraphEdit에 나타내게 하실 수가 있습니다. 물론 이 기능을 위해서는 여러분의 DShow어플에 약간의 (한줄 정도의) 추가 코딩이 필요합니다. 
여기에 대해서는 뒤에 가서 간단히 실습해 보도록 하겠습니다.  
 
출처 : 델마당  dong (dongsoft) 님의 글
posted by 유돌이
2008. 12. 29. 23:33 델파이
DirectShow란 무엇인가를 극명하게 보여주는 것이 바로 이 GraphEdit입니다. 이것을 사용하는 것이 바로 DShow를 눈으로 보는 것이다라고 말할 수 있기 때문입니다. 만일 여러분이 DirectX SDK를 설치하셨고 제가 앞서 말씀드렸듯이 바탕화면에 바로가기를 설정해 놓았다면 이제 이 프로그램을 실행시켜 보시길 바랍니다.
  
[1] 대체 DirectShow란 무엇인가.
 
DShow는 필터들의 연결입니다. 이 말을 쉽게 해설하기 위해서 저는 GraphEdit라는 유틸을 사용할 것입니다. 일단 이 프로그램을 실행시켰다면 간단하게 동영상을 한번 랜더링(실행)시켜 보도록 합시다.
File 메뉴의 Render Media File... 을 실행시키고 동영상 파일하나를 선택합니다. 만일 여러분의 컴퓨터에 현재 동영상을 재생하는데 필요한 코덱이 설치되어 있다고 하더라도 이곳에서는 다음과 같은 메시지가 나올 수가 있습니다. '비디오 스트림을 재생할 수 가 없습니다. 적절한 압축 풀기 프로그램을 찾을 수가 없습니다.' 그러면서 오디오 부분만이 설정이 되어 화면에 나타날 수가 있습니다.

해결방법은 몇가지가 있겠으나, ffdshow를 설치하라고 권하고 싶습니다. 인터넷에서 다운받아서 ffdshow를 설치하시기 바랍니다. FFDShow는 오픈소스로서 공개되어 있습니다. 여기에는 XVid, DivX를 비롯하여 아무튼 Mpeg 압축의 스탠다드 코덱들이 총 망라되어 있습니다. 곰TV도 이 소스를 가지고서 만들었다는 이야기가 있는데요, 확인해 보지 않아서 모르겟습니다만 충분히 가능성 있는 이야기입니다. 만일 여러분이 Mpeg 알고리즘을 공부한다면 기초가 될 소스가 바로 이 FFDShow 소스일 것입니다. 혹시 FFShow를 찾기 힘들다는 분은 XVid 코덱을 다운받아 설치하시면 될것 같습니다. 아무튼 해결하시고요...  (FFDShow는 실행판과 소스판이 별도로 존재합니다. 이곳 자료실에 소스판을 올려놓아도 좋을까 고민이 좀 되네요.)

문제가 해결되었다고 보고요, graphedit를 보시면 화면 가득히 박스들이 보이실 것입니다. 그런데 이 박스들이 그냥 있는게 아니라 화살표를 꽁지에 꽁지를 물고 늘어져 있는 것이 보이실 것입니다. 이 박스 하나 하나가 바로 필터객체를 의미하며 화살표의 연결방향이 바로 연속적인 스트림의 진행방향을 의미합니다.

일단 여기까지 설명하고 실행을 시켜 보도록 하겠습니다. 위의 단축버튼중에서 왼쪽에서 여덟번째을 보시면 일반적인 동영상 실행 아이콘으로 표시된 스피드 버튼을 찾으실 수가 있을 것입니다. 이것을 클릭하시면 별도의 동영상 랜더링 윈도우가 뜨면서 동영상이  재생되는 것이 보일 것입니다. 자 별로 대단한 것은 아닙니다만 우리는 간단히 동영상을 로딩해서 랜더링하고 있는 것입니다. 

이제 윈도우를 종료하고 다시 GraphEdit로 돌아와 봅니다. 화면 상단에 가득 채워진 정체불명의 박스들을 한번 마우스로 집어서 이동시켜 봅니다. 여러분이 원하시는대로 움직이는데 유독 화살표는 자신이 원래 잡고있던  위치에서 벗어나지 않으려고 늘어지거나 줄어들거나 할뿐입니다.
즉 스트림의 방향은 수정할 수가 없다는 말인데요 이것은 사실 중요한 의미입니다만, 여기서는 단지 그렇다라고 이해하시면 되겠습니다. 여기서 박스(이하 필터라고 하겠습니다.)을 클릭한채 Delete키를 누르면 선택한 필터를 삭제할 수도 있습니다. 지금 삭제하지는 마십시요. 나중에 확인하도록 하고요, 여기까지를 일단 정리하여야 하겠습니다. 

[2] 필터들의 연결과 방향.

그래프 에디터에 보이는 수많은 필터들을 보시면 화살표 방향이 왼쪽에서 오른쪽으로 흘러간다는 것을 확인하실 수가 있을 것입니다. 이것을 좀더 자세히 들여다보죠. 일단 가장 왼쪽에 있는 필터를 우리는 소스필터라고 부릅니다. 말 그대로 스트림의 근원이라고 생각하시면 됩니다. 여기서부터 모든 스트림이 시작될 것입니다.

그리고 그 다음 필터를 보시면 AviSplitter 필터를(혹시 여러분이 일반적인 영화 파일이 아니라 조금 예외적인 동영상을 오픈했다면 이 필터가 보이지 않으실 수도 있습니다.) 보실수가 있는데요, 이것을 파서 필터라고 합니다. 파서필터에 대해서는 나중에 다시  설명할 기회가 있을 것입니다.

그리고 여기서 스트림이 비로소 두개로 나눠지게 되는데요, 위쪽으로 흘러가는 화살표 방향으로는 비디오 스트림이, 그리고 아랫쪽의 방향으로는 오디오 스트림이 각각 흘러가게 됩니다. 그런데 중요한 것은 여기까지의 스트림은 아직까지도 압축된 상태라는 점입니다. 다시 말하면 비디오 영상을 기준으로 각각의 프레임의 데이터 크기가 동일하지 않다는 말이죠.

예를 들어 320*240의 화면에 RGB24라면 320*240*3 byte씩의 데이터가 버퍼링되어 흘러가는 것이 아니라는 것이죠. 여기까지는 압축된 데이터이기 때문에 어떨때에는 9kByte가 되었다가  또 어떨때에는 몇백Byte가 되었다가 합니다. 물론 Mpeg 압축일 경우이고요, 만약에 동영상 스트림이 Jpeg로 압축되었다면 마찬가지로 각 프레임의 데이터 크기가 변하겠지만 Mpeg처럼 엄청나게 변하지는 않겠죠. 
( 상식에 속하지만 혹시 모르는 분을 위하여 간단히 설명하겠습니다. 동영상을 압축하는 방식으로는 일반적으로 프레임을 기준으로 앞프레임과 뒷프레임을 비교하여 그 차이점을 예측,비교하여 압축하는 기업이 있습니다. 이것의 대표적인 방식이 Mpeg방식이라고 합니다. 이러한 방식은 키프레임을 중심으로 변화량을 예측하거나 차이점만을 추려내어 압축하기 때문에 스트림의 변화가 상당히 큽니다.) 

다시 간추리자면 AviSplitter필터에서 갈라져 나온 두개의 스트림은 각각 영상과 음성의 스트림이며 여기까지는 압축된 데이터가 흘러가고 있다는 점입니다. 그리고 그 다음으로 비로소 압축을 해제하는 DeCoder필터가 붙게 되는데요, 각각 영상 DeCoder 코덱과 음성 DeCoder코덱이 붙게 됩니다.
이 각각의 DeCoder 필터에서 흘러나오는 스트림이 비로소 압축이 해제된(다른 말로는 뻥튀기되어 데이터가 엄청 불어난) 데이터이며 이것을 가지고 여러가지 합성을 하거나 변형을 하거나 할수가 있는 것입니다. 그리고 다음으로는 완전한 동영상 스트림 데이터를 랜더링할 수 있는 랜더러 필터가 붙게 되는 것이죠. 물론 각각의 비디오 랜더러 필터와 오디오 랜더러 필터를 의미합니다.  이것을 한번 부족하나마 도식으로 그려봅시다. 


  소스필터 --> 파서필터 --> 비디오 DeCoder 필터 --> 비디오 랜더러 필터. 
                             |-----> 오디오 DeCoder 필터 --> 오디오 랜더러 필터. 


자... 이제 간단하게나마 결론을 내 봅시다. DirectShow는 필터들의 연결이며 이 각각의 필터는 시작과 끝이 있다.
즉 소스필터로 시작해서 랜더러 필터로 끝난다. 여기까지 대충 정리가 되었다면 다음으로 넘어갑시다.
(우리는 나중에 이러한 필터들을 직접 코딩상에서 구현하여 연결하고 랜더링까지 할 것입니다. 간단하죠. 필터를 만들고 연결하고 랜더링한다. 이것이 DirectShow의 전부입니다. 한가지 개인적인 생각입니다만 마이크로소프트 계열의 소프트웨어적인 특성은 내부는 어쩔수 없이 복잡하지만 전체적인 구조는 간단하게라는 슬로건을 내걸고 있는 듯 싶습니다.
이것은 볼랜드 진영의 전체적인 구조는 복잡해도 좋지만 내부는 될수 있으면 간결하게라는 방향과 완전히 반대적인 성향인 것 같고요... 물론 완전히 제 개인적인 생각이지만요. 그래서 마소는 항상 시장변화에 유들유들할 수가 있는 것이고 볼랜드는 그러한 변화의 대처에 늦을 수밖에 없는 것이 아닌가 싶네요. 모든지 장점이 있다면 단점이 있듯이, 이제부터 여러분은 이 악몽같이 복잡한 DirectShow의 내부로 초대받아야 할 것입니다.)

[2] 여러가지 필터들의 카테고리.

이제 여러분은 GraphEdit의 다른 면을 보셔야 합니다. 조금더 디테일하고, 조금더 번거로우며, 조금더 난해한 구조에 익숙해져야 합니다. 동영상을 Render Media File... 로 간단하게 로딩하여 랜더링하였지만, 실은 이것들이 거저 연결된게 아닌 것입니다. 자, 이제 우리는 동영상을 랜더링하는게 아니라 USB 카메라로부터 받아 들여진 스트림을 랜더링해야 합니다. (USB카메라가 없다면 DShow를 배우기에 아직 준비가 덜 되었다는 의미가 됩니다. 가능하면 USB 카메라를 준비하셔서 그것의 해당 디바이스 드라이버를 설치하시기 바랍니다. )

GraphEdit의 메뉴중에서 New를 눌러서 기존에 있던 필터들의 연결을 제거합니다. '저장하기'라는 옵션이 뜬다면 저장하지 말고 '아니오'를 선택합니다. 이제 GraphEdit의 메인화면이 깨끗해 졌을 것입니다. 여기에서 우리는 카메라에 해당하는 소스필터를 생성해야 합니다. 이러한 필터를 우리는 직접 불러와 줘야 하는데요, GraphEdit상단의 단축버튼 중에서 왼쪽에서 11번째의 버튼을 선택합니다. 마치 네모난 상자에 양쪾으로 조그만 꼭지점이 매달려 있는 듯한 모습을 하고 있는데요, 이것이 바로 현재 사용자의 컴퓨터에 설치되어진 각종 필터들의 나열을 보여줍니다.

이 버튼을 클리하면 윈도우가 하나 뜨면서 그 캡션에 'Which Filters do you want to insert?'이라고 적혀 있을 것입니다. 이곳에서 아무거나 클릭을 하면 바로 GraphEdit의 메인화면에 선택한 필터가 생성되어 집니다. 여기서 모든 카테고리를 살펴보실 필요가 없습니다. 중요한 카테고리를 다음과 같이 정리하였습니다.

* Audio Capture Source --> 오디오를 캡쳐하기 위한 장치(혹은 광의의 필터)들의 모음입니다. 
* Audio Compressors    --> 오디오 스트림을 압축하기 위한 필터들의 모음입니다.  
* Audio Renderers         --> 오디오 스트림을 랜더링하기 위한 필터들의 모음입니다.
* DirectShow Filters        --> 가장 중요한 DirectShow용 필터들의 모음입니다. 이곳에는 다른 카테코리에 포함 되 
                                          어 역할되어지는 필터들도 포함되는데요, 이 사항에 대해서는 아래에서 해설하겠습니다.
*Video Capture Sources  --> 비디오를 캡쳐하기 위한 장치(혹은 광의의 필터)들의 모음입니다. 
*Video Compressors        --> 비디오 스트림을 압축하기 위한 필터들의 모음입니다. 

위에서 조금 깊숙히 들어가야할 필요성이 있다고 봅니다. DirectShow Filters의 카테고리에는 온갖 잡다한 필터들이 모두 포함되게 됩니다. 이곳에는 소스필터, 파서필터는 물론 비디오, 오디오 압축과 DeCoder필터들까지 포함되게 되는데요, 그렇다면 한가지 의문이 들 것입니다. 아니 엄연히 카테고리가 별도로 있는데, 어째서 DirectShow Filters라는 카테고리에는 이렇듯 다양한 필터들이 들어가 있는 것이지? 그렇다면 뭣 때문에 처음부터 카테고리를 나눠 놓은 것인가라고... 

이 질문에 대한 해답은 이렇습니다. 처음에 말씀 드렸다시피 DirectShow는 DirectX가 나오고 나서 한참 후에나 나온 존재입니다. 그 이전에 동영상 관련 프로그램을 하기 위해서는 VFW라는 API를 지원하였고요, 그것에 맞는 각각의 장치들이 존재하였던 것입니다. (아니, 엄밀히 말하면 장치 드라이버가 먼저 제공되었고 나중에 그것을 제어할 수 있는 VFW라는 API가 지원되었다라고 해야 맞을 것 같습니다.) 

여기에 나중에 DirectShow가 탄생하면서 기존의 구조를 흡수하는 방식을 취했습니다만 그것은 완전한 방식이 될수가 없었습니다. 왜냐하면 여러분도 보시다시피 DShow라는 놈이 참으로 유별나며 COM계열중에서도 그 독특성이 유별난 놈이기 때문입니다.
(사실은 DShow가 독특해서 완전한 합병을 할수가 없었다라는 표현은 정확한게 아닙니다만 DShow의 유별난점을 강조하기 위해 한 말입니다. 보다 사실적으로 말씀드리자면 기존의 VFW적인 방식은 Class방식이 아닌 라이브러리(dll) 적인 구조를 가지고 있었기 때문에 어쩔수가 없었다라고 보시면 될것 같습니다. 하지만 그럼에도 불구하고 DShow의 유별난 COM구조가 이렇게 중복 병합되는 카테고리 구조를 유발시켰다라고도 볼수가 있을 것입니다.)

아무튼 그래서 위에서 말씀드린 각각의 카테고리는 예전 VFW의 방식으로서 존재하는 것들입니다. 자, 한가지 예를 들어 봅니다. 위의 카테고리에는 Video Compressors라는 것이 있습니다. 이곳을 들여다보면 각종 압축 코덱들이 모여 있는 것을 보실 수가 있습니다.
그런데 다시 DirectShow Filters라는 카테고리를 들여다보면 여기에도 압축 코덱들이 한두개 눈에 보이실 수도 있습니다. ( 현재까지는 한개도 보이지 않을 수도 있습니다. 그러나 DirectShow관련 코덱들을 한두개 설치하다보면 어느새 이곳에도 압축코덱필터가 자리잡고 있는게 보이실 것입니다.) 자, 이 두개의 코덱의  성격은 어떤 점에서 다른 것일까요? 

예전에 저는 XVid의 소스를 해석한 적이 있었습니다. 여기서 저는 XVid라는 코덱의 소스가 실은 두가지로 되어 있는것을 보고 놀라기도 하였습니다. 그것은 내부에 하나의 완성적인 압축 로직이 있고, 이것을 DirectShow 필터 구조가 감싸고 있는 구조였기 때문입니다. 즉, 예전의 VFW 코덱의 구조를 안에 두고 그것을 다시 DirectShow 필터의 클래스 구조가 감싸안으면서 버퍼의 포인터만 연결되어져 있다는 점이었습니다. 

자, 이제 결론지어 보겠습니다. 위에서 보여드린 각각의 카테고리는 예전 VFW 방식의 분류를 의미합니다. 따라서 과거처럼 VFW의 API를 사용해서 완전히 동일하게 접근할 수가 있음을 의미합니다. 그러나 VFW방식으로는 결코 DirectShow Filters라는 카테고리에 속한 영역의 필터들에 접근할 수가 없습니다. 이들 필터들은 오직 DShow방식의 연결로서 사용되어 집니다. 또한 DShow방식은 과거 VFW 방식의 모든 카테고리에 접근하여 DShow방식으로 취급할 수  있는 서비스를 제공합니다. 이것이 바로 DShow(DirectShow) 이고, 차이입니다. 

만일 누군가가 VFW구조로 압축코덱을 개발하였다면 그는 오직 Video Compressors 카테고리에 설치할 수밖에 없습니다. 반대로 그가 DShow방식으로 압축코덱을 개발하였다면 그는 DirectShow Fitlers라는 카테고리에 코덱을 설치해야 할 것입니다. 하지만 DShow는 이 두가지 구조의 코덱을 동일한 방식으로(DShow적인 방식으로) 생성하여 실행시킬 수가 있습니다. (가물가물 한데요, DShow방식으로 압축 코덱을 개발하였다고 하더라도 Video Compressors 카테고리에 설치할 수가 있었는지... 가물가물하네요. 하지만 한가지 확실한 것은 그 반대는 안된다는 것입니다.)  

 

출처 : 델마당 dong (dongsoft) 님의 글


posted by 유돌이
2008. 12. 29. 23:32 델파이

[1]DirectShow를 하기 위한 컴퓨터 셋팅 준비하기. 

 

DShow를 하기 위해서는 준비해야 할 것들이 몇개 있습니다.
가장 중요한 것은 DirectShow SDK이고요, 델파이용 DirectShow.Pas파일을 준비해야 합니다. 그리고 불행하게도 Visual C++도 필요합니다. 그리고 Visual C++을 어느 정도는 다룰 수 있으면 좋습니다. 왜먀하면 워낙 샘플 소스가 풍부하기 때문에 인터넷에서 다운받아서 내용을 참고할 필요가 절실히 있기 때문입니다.

이것을 정리하면 다음과 같습니다.  아래의 준비사항에서 4번 항목의 도서에 대해서는 따로 말씀을 드리겠습니다. 이것은 아직까지 우리나라 유일의 DirectShow 분야의 책이기 때문에 참고할 부분이 적지않게 있기 때문에 어쩔수 없이 구입하셔야 할 항목이라서 이곳에 기재하였습니다.
그러나 COM에 대해 설명한 부분에서는 오히려 혼란을 가중시킨다고 개인적으로 생각하며, 이밖에 이 책을 효율적으로 습득하는 방법에 대해서는 뒤에서 따로 말씀을 드리겠습니다.

  1. DirectX SDK 9.0 이상 
  2. DSPack 2.34 
  3. Visual C++ 버전6이 좋습니다. 아직까지는요.   
  4. DirectShow 멀티미디어 프로그래밍, 저자:신화선, 출판사: 한빛미디어. 
  5. http://www.dshowtech.com/bbs/DirectShow/Home.php : 현재 가장 활성화된 DirectSow 사이트.
  6. DirectSow 한글 도움말 pdf 파일. (찾아서 올려놓겠습니다.) 


[2] DirectX SDK 설치하고 둘러보기.

DirectX SDK를 설치하는 방법에 대해서는 굳이 해설하지 않겠습니다. 델파이에서 DSPack을 가지고 프로그래밍하는데 굳이 SDK가 필요한가라고 의문을 품으시는 분들이 계실수도 있겠습니다만, 먼저도 말씀드렸다시피 오리지날을 한번 살펴보고 비교분석해야할 시점이 반드시 옵니다. 따라서 DirectX SDK는 반드시 설치하라고 권장하고 싶습니다. 또한 이것을 설치하는 이유는 이 속에 들어가있는 내부유틸을 절대적으로 필요로 하기 때문입니다.  

일단 SDK를 설치하시면 C:\ 루트에 DXSDK라는 디렉토리가 생깁니다. C:\DXSDK\Bin\DXUtils 여기의 디렉토리를 살펴보시면 graphedt.exe라는 유틸이 눈에 띄실 것입니다. 이것은 DShow 프로그래밍을 하는데 있어서 없어서는 안될 중요한 프로그램입니다. 따라서 이것을 바탕화면에 바로가기로 등록해 놓습니다. 

자, 이제 한번 SDK의 내부를 둘러보도록 하겠습니다. DirectX SDK에서 우리가 사용할 부분을 살펴봐야 합니다. 
C:\DXSDK\Samples\C++\DirectShow 여기 디렉토리를 보시면 되겠습니다. 앞으로는 이곳을 밥먹듯이 드나들어야 할 것입니다. 그러나 델파이로 DSPack을 가지고 프로그래밍하는  여러분은 그럴 필요가 없을지도 모르겠습니다. 하여튼 중요한 곳입니다. 특히 C:\DXSDK\Samples\C++\DirectShow\Capture\AMCap 이곳 샘플 프로그램을 컴파일하여 실행프로그램을 만들어 놓으면 좋습니다.
이것도 마찬가지로 graphedit.exe와 마찬가지로  바탕화면에 바로가기로 등록해 놓으면 좋을 것입니다. 자... 두가지 명심해야 할 것은 graphedit.exe 와 AMCap.exe 가 필요하다는 것입니다. 그 중에서도 전자의 유틸 프로그램은 반드시 필요하다는 점을 유념해 주시기 바랍니다. 

여러분이 여기까지 읽고 따라하시려고 한다면 아마도 조금 당황하실지 모르겠습니다. 왜냐하면 AMCap이 컴파일 되지 않기 때문입니다. DirectShow의 샘플 프로그램을 컴파일하기 위해서는 조금 번거로운 설정을 해주셔야 합니다. 

이것은 델파이에서 컴포넌트를 설치하였을때 수동으로 디렉토리를 설정해야 하는 것과 마찬가지입니다. 이것에 대한 설명은 뒤에서 신화선님의 참고서적을 해설하면서 언급해 드리겠습니다.  
 
[3]Visual C++을 설치하고 델파이의 DSPack 컴포넌트를 설치하시기 바랍니다. 

특히 델파이의 DSPack의 버전에 유의하시기 바랍니다. DSPack은 버전에 따라서 내부 소스가 조금 다릅니다. 예를들어서 레코드 선언에서 동일한 구조인데도 이름을 약간 변경해 놓거나 하였습니다. 따라서 이왕이면 제가 사용하려는 DSPack2.34버전을 구해서 설치하시면 좋겠습니다. 제가 시간나는데로 자료실이나 이곳에 올려 놓겠습니다. 그것을 사용하시면 되겠습니다.  (이것을 다른말로 하면 DSPack은 버전별 호환성이 없다라고 말씀드릴 수가 있습니다.

즉, DSPack의 다른 버전으로 개발하셨다면 약간의 수정을 해야 함을 의미합니다. 이 사소한 번거로움이 이것을 만드신 분의 엄청난 수고에는 티클 만큼의 흉이 되지 않음을 아셔야 할 것입니다. 우리나라에 이같은 분이 없다는 것이 정말로 아쉬운 뿐입니다. 
DSPack은 DirectShow 뿐만이 아니라 델파이에서 참으로 참고할 만한 가치가 많은 방대한 분량의 참고소스입니다. 이정도 수준의 프로그래머가 국내에도 많이 계셨으면 하는데요, 국내의 깜깜한 현실에서 이런분이 몇분이나 될지를 생각해보면... 쩝, 안타깝기 그지없습니다. "우리는 Coder다. 아니라는 증거가 있을까?")


[4]http://www.dshowtech.com/bbs/DirectShow/Home.php 

이곳 사이트를 또한 좌절과 공포감이 엄습해 올때마다 들러서 검색을 하거나 질문을 올리거나 해야 합니다. 특히 이곳 운영자인 신화선씨에게 저는 처음 DShow 프로그래밍을 할때 이메일로 몇번의 도움을 직접 받아본 적이 있습니다.

DShow 프로그래밍의 선도자적인 개척을 하신 분으로 나이는 저보다 어릴지언정 프로그래머로서 존경을 받기에는 충분하다고 생각합니다. 불행하게도 대부분의 소스가 Visual C++로 되어있고, 델파이 진영이 있기는 있으나 거의 죽어 있는 상태입니다. 제가 조금 나서볼까 하다가, 이곳 델마당에 이렇게 강좌를 올리게 된 것인데요, 아무튼 중요한 곳입니다. 익스플로러의 즐겨찾기에 반드시 등록해 놓으시길 바랍니다. 사이트 주소가 길어서 외어놓기가 힘들거든요.

아참, 이곳에서 주의하실 점이 있습니다. 그것은 이 동호회 사이트가 과거 한번의 개편을 맞으면서 일괄적으로 통합된 것이 아니라는 점입니다. 즉, 과거의 자료는 '프리백'이라는 메뉴에 보시면 찾을 수가 있습니다. 이곳에도 유용한 정보들이 있기 때문에 그냥 '질문방'이나 '강좌'란에 들어가 찾다가 없다고 서둘러 빠져나오시지 말았으면 합니다. 

'프리백'에도 예상외의 정보가 있을 수 있기 때문입니다. 이 '프리백'은 과거 사이트의 모든 정보를 그대로 옮겨놓은 곳입니다.

[5]신화선님의 DirectSow 멀티미디어 프로그래밍이라는 책에 대하여.

제가 조금 비판적인 입장에서 이 책을 설명드린다고 저를 비난하지는 말았으면 합니다. 왜냐하면 이 책이 비판받아야할 문제점과 이 책이 우리나라 DShow 프로그래밍에 있어서의 소중한 위치와는 전혀 별개의 문제이기 때문입니다.

예전에 저는 Visual C++을 공부하면서 이상엽님의 책을 보고 참으로 많은 좌절을 하였던 때가 있었습니다. 그런데  그당시 나뿐만이 아니라 상당히 많은 사람들이 그랬던 경험을 털어 놓더군요. ^^. 아무튼 좋은 책과 선도적인 책과의 구분은 분명히 있어야 한다고 생각합니다. 그당시 이상엽님의 책은 분명히 선도적인 책이었던 것은 분명합니다.
그러나 좋은 책이었다고는 말하기가 힘들다고 봐야할 것입니다. 제가 이렇게 거창하게 설명하는 이유는 바로 여러분들을 위하기 때문입니다. 즉, 이 책을 읽고서 절대로 좌절하지 말기를 바랍니다. 왜냐하면 이 책이 선도적인 책인 것은 분명하고 그것의 존재 자체를 감사해야하는 점은 명확한 사실이나, 분명 이해하기 쉬운 좋은 책은 아니기에 여러분에게 얼마간의 좌절감을 안겨드린다는 것또한 사실이기 때문입니다. 


여담입니다만 실제로 우리나라 프로그래밍의 역사에서 좋은 책은 정말로 눈씻고 찾아보기가 힘듭니다. 제가 이제껏 보아왔던 가장 좋은 책은 김용성님의 Visual C++ 6 완벽가이드라는 책입니다. 이 책의 데이타베이스 해설부분을 보시면 알겠지만, 참으로 이렇게 쉽고 간결하면서 완성도있게 표현하기가 쉽지 않음을 절실하게 느낍니다. 여러분에게 이책을 반드시 구입하라고 권하고 싶을 정도입니다.
특히 COM에 대해서 자신이 없는 분이라면 이곳에 해설된 COM 부분을 일주일 정도만 보면 대략적인 이해를 하실 정도까지 된다는 사실에 놀라실 것입니다. 제가 책장수는 아니지만, 좋은 책은 권장해 드리고 싶습니다. 그러나 분명한 것은 선도적인 책과 좋은 책을 구분해야 한다는 점입니다. 특히 번역서의 경우는 좋은 책이 되기 힘든 이유가 여기에 있습니다.

말이 많이 빗나갔습니다. DShow 공부를 하기 위해서는 신화선님의 책이 반드시 필요하다고 생각합니다. 그리고 이제부터 이 책의 내부를 조금씩 해부해 보겠습니다.  

이책을 읽으실 경우 주의하실 점은 방금전에도 언급하였지만 COM부분의 해설에 대해서는 그냥 참고수준으로 넘기라고 권하고 싶습니다. 이해가 안되신다면 억지로 몇번이고 반복해서 읽으실 필요가 없다는 것입니다. (제가 그랬다가 끝까지 이해가 안되서 엄청 고생하였습니다.) 이 COM의 해설부분은 차라리 김용성님의 Visual C++6라는 책을  구입해서 그곳에 해설된 부분을 읽으시는게 훨씬 도움이 될 것입니다.  만일 여러분이 좀더 시간이 있으시다면 COM 부분의 완성도 있는 책을 구입해서 읽으시면 좋을 터인데요, 좋은 책이 하나 있는데 지금은 절판된 상태입니다. 쩝...

아무튼 COM 뿐만이 아니라도 그렇습니다. 이 책을 읽으면서 굳이 이해가 안가신다고 좌절하거나 여러분들 이마에 주름살 새겨가면서 공부하실 필요는 없다고 생각합니다. 오히려 그 시간에 도움말 파일이나 SDK의 예제소스나 제가 앞서 말씀드렸던 사이트의 질답란을 봐가면서 연구하는 편이 훨씬 유익하다고 생각합니다. 그렇다면 대체 이 책을  왜 사라고 권하는가... 

첫째, 일단 전체를 봐야하기 때문입니다. DirctShow란 대체 무엇인가에 대한 의문에 대하여 숲을 볼수있게 해줍니다.
즉 GraphEdit.exe의 사용법, 결국 DirectShow가 필터들의 연결이라는 점, 소스필터, 변환필터, 랜더링필터 등 각각의 필터들의 특성과 그 필요성 등등에 대하여, 전체적인 모습을 관망해 볼수 있게 해줍니다. 

둘째,  이곳에 부록으로 제공하는 CD에는 Visual C++로 필터만들기에 아주 쉬운 프레임을 제공해준다는 점입니다. 
이 마법사를 사용하면 간단한 기본필터를 그냥 만들어 주기 때문에 참고하는데 아주 유용합니다. 반드시 한번은 설치하셔서 만들어 보시길 권해드립니다.  

세째, 앞에서 말씀드렸던 DirectShow SDK 필터를 컴파일하는데 있어서 환경설정을 하는 방법이 이곳에 나와 있습니다. 제가 별도로 강좌에 쓸수도 있겠습니다만, 이책 204Page에 보면 자세히 나와 있기 때문에 생략하기로 하겠습니다. 간단히 말씀드리자면 C:\DXSDK\Samples\C++\DirectShow\BaseClasses 이곳을 먼저 컴파일 하여야 하며 라이브러리 등록을 해주셔야 한다는 점입니다.

[6]한글 도움말 파일 

마지막으로 한글 도움말 파일이 있습니다. 뭐 영문도 있습니다만 굳이 한글번역판이 있는데 영어공부하고자 영문판을 읽으라고 권하지는 않겠습니다. 하지만 제가 처음 시작할 때에는 영문판 밖에 없었기 때문에 가독하는데 시간이  걸려 애를 먹었던 적이 있었습니다. 간단한 인터페이스 하나 찾는데도 일일이 이마에 주름살 새겨가며 읽어야 했던 경험을 그닥 권장하고 싶지는 않습니다. 영어공부는 별도로 하시는게...^^

이 도움말 파일이 반드시 필요한 이유는, 여러분이 어느정도 DShow의 구조에 익숙해졌다면 그 다음부텨는 스스로  인터페이스를 찾아서 코딩해야만 하기 때문입니다. 즉 DShow SDK에서 제공하는 수많은 인터페이스 중에서 자신이 원하는 것을 찾아서 유용하게 사용할 줄 알아야 한다는 점이죠. 이 때문에 이 한글 도움말 파일은 상당히 유익한 것입니다. 영문이라면 시간이 좀 걸릴 터이기 때문이죠. 


일단 2부 준비할 사항에 대해서는 이정도로 마치겠습니다. 하지만 강의 도중에 종종 필요한 부분이 있다면 추가적으로 게시할 것입니다. 
 
 
출처 : 델마당  dong님의 글(dongsoft)

posted by 유돌이
prev 1 2 next