direct show in delphi

191
鴭볁넩ꈑ 鴭볁넩ꈑ 鴭볁넩ꈑ 鴭볁넩ꈑ DirectShow DirectShow DirectShow DirectShow 뺹ꈑ溜ꅍꗂ뼍怜 뺹ꈑ溜ꅍꗂ뼍怜 뺹ꈑ溜ꅍꗂ뼍怜 뺹ꈑ溜ꅍꗂ뼍怜 본 문서는 델마당 이외의 사이트나 게시판에 링크 또는 재배포 하실 수 없습니다. - 본 문서의 내용은 원본에 내용이 추가 되거나 수정 되어 원본과 다를 수 있습니다.- 본 문서의 원본은 델마당 ( www.delmadang.com ) 시리즈 강좌 게시판에 있습니다. 본 문서의 최종 수정 날자는 2008-03-23 입니다.-------------------------

Upload: k-lonk

Post on 24-Apr-2015

314 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Direct Show in Delphi

DirectShow DirectShow DirectShow DirectShow

본 문서는 델마당 이외의 사이트나 게시판에 링크 또는 재배포 하실 수 없습니다. -

본 문서의 내용은 원본에 내용이 추가 되거나 수정 되어 원본과 다를 수 있습니다.-

본 문서의 원본은 델마당 ( www.delmadang.com ) 시리즈 강좌 게시판에 있습니다.

본 문서의 최종 수정 날자는 2008-03-23 입니다.-------------------------

이이이이 동동동동 근근근근

Page 2: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 2 -

목목목목 차차차차

1. 1. 1. 1. 서론서론서론서론 ---- 4444

[1] 시작하기에 앞서서 알아야할 백그라운드 필수 개념 - 4

[2] DirectShow의 특징. 버퍼공유 - 5

[3] 동영상 프로그래밍을 할때 주의할 점 - 7

2. 2. 2. 2. 준비할준비할준비할준비할 사항사항사항사항 ---- 9999

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

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

[3] Visual C++을 설치하고 델파이의 DSPack 컴포넌트를 설치하시기 바랍니다 - 10

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

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

[6] 한글 도움말 파일 - 13

3. GraphEdit_1 3. GraphEdit_1 3. GraphEdit_1 3. GraphEdit_1 ---- 14141414

[1] 대체 DirectShow란 무엇인가 - 14

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

[3] 여러가지 필터들의 카테고리 - 16

4444. GraphEdit_2 . GraphEdit_2 . GraphEdit_2 . GraphEdit_2 ---- 19191919

[1] 카메라 영상을 플레이하기 - 19

[2] Video Renderer 필터 - 19

[3] 그래프의 저장 - 22

[4] Connect to Remote Graph - 22

5. 5. 5. 5. 어플어플어플어플 1 1 1 1 ---- 24242424

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

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

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

[4] GUID에 대하여 - 30

6. 6. 6. 6. 어플어플어플어플 2 322 322 322 32

[1] 필터그래프의 인터페이스 - 32

[2] COM의 소멸 - 33

7. 7. 7. 7. 어플어플어플어플 3 3 3 3 ---- 38383838

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

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

8. 8. 8. 8. 어플어플어플어플 4 4 4 4 ---- 46464646

Page 3: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 3 -

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

9. 9. 9. 9. 어플어플어플어플 5555 ---- 54545454

10. 10. 10. 10. 어플어플어플어플 6666 ---- 64646464

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

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

11. 11. 11. 11. 어플어플어플어플 7777 ---- 71717171

[1) Avi Mux - 71

[2] Avi Splitter - 73

[3] Color Space Converter - 73

[4] Overlay Mixer - 73

[5] Sample Grabber - 74

[6) File Source(Async.) - 75

[7] File Writer - 76

[8] Smart Tee - 76

[9] Infinite Pin Tee Filter - 77

12. 12. 12. 12. 어플어플어플어플 8888 ---- 80808080

13. 13. 13. 13. 어플어플어플어플 9999 ---- 89898989

[1] Connect to Remote Graph... 의 실습 - 89

[2] Grabber를 사용하자 - 90

[3] DShow의 어플편을 끝내면서... - 93

14. 14. 14. 14. 필터필터필터필터 1111 ---- 101101101101

15. 15. 15. 15. 필터필터필터필터 2222 ---- 110110110110

16. 16. 16. 16. 필터필터필터필터 3333 ---- 119119119119

[1] 인터페이스 - 120

17. 17. 17. 17. 필터필터필터필터 4 4 4 4 ---- 129129129129

18. 18. 18. 18. 필터필터필터필터 5555 ---- 138138138138

19. 19. 19. 19. 필터필터필터필터 6 6 6 6 ---- 147147147147

20. 20. 20. 20. 필터필터필터필터 7 7 7 7 ---- 155155155155

[1] 미디어 협상 - 155

21. 21. 21. 21. 필터필터필터필터 8 8 8 8 ---- 162162162162

[2] 버퍼 협상 - 162

22222222. . . . 필터필터필터필터 9 9 9 9 ---- 171171171171

23232323. . . . 필터필터필터필터 10 10 10 10 ---- 183183183183

Bug List Bug List Bug List Bug List ---- 190190190190

Page 4: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 4 -

1.1.1.1. 서론서론서론서론

안녕하세요.

예전부터 한번은 DirectShow 강좌를 하고자 마음먹었습니다. 그러다가 이렇게 설을 앞두고서 일감이

없어서(간단하게 놀고 있어서 시간이 남아 돌기 때문에) 강좌를 하게 되었습니다. 욕심 같아서는 필터만

들기까지 하고싶지만, 시간이 어떻게 될지는 모르겠습니다. 그러나 우선 강좌를 시작하기에 앞서서

DirectShow 프로그래밍을 가지고 어떠한 것들을 할수 있는가, 또한 DirectShow가 왜 꼭 필요한가에

대한 궁금증과 의문점을 하나하나 살펴보도록 하겠습니다.

[1] [1] [1] [1] 시작하기에시작하기에시작하기에시작하기에 앞서서앞서서앞서서앞서서 알아야할알아야할알아야할알아야할 백그라운드백그라운드백그라운드백그라운드 필수필수필수필수 개념개념개념개념

저는 델파이 프로그래밍 1년차가 끝나기도 전에 DirectShow프로그래밍을 하게 되었습니다. 그때 제

가 무슨 정신으로 할 수 있다라고 오버액션을 취했는지 알다가도 모를일이었습니다. 그당시 저에게는

COM은 두말 할 것도 없고, 그보다 더 기초적인 OOP에 대한 개념조차도 머릿속에 들어가 있지 않았기

때문입니다. 그냥 c언어를 남들보다 조금 잘하니까 부딪혀보면 해답이 나오겠지하고 시작하였고요, 장님

코끼리 뒷다리 만지는 식으로 더듬더듬 더듬어나갔습니다.

그러나 만일 여러분이 DirectShow를 시작하고자 한다면 저처럼 이렇게 오랜 길을 장님처럼 주물럭거

리며 느려터지게 발전해 나가지 않았으면 합니다. 오히려 OOP에 대한 이해를 먼저하고, 그 다음에

COM에 대한 이해를 하고서 시작한다면 더 빠르게 목적하고자하는 목표에 다다를 수가 있을 것이라고

감히 충고합니다.

OOP와 COM의 이해는 1년 정도의 완숙도를 가질 필요가 있다고 봅니다. 즉, 사전적인 이해가 아니라

실무에서의 한 두 프로젝트의 경험에 의한 '감'이 있고나서 시작하면 좋겠다고 생각합니다. 책을 통해서

익히는데는 풀타임으로 약 2달 정도면 가능하겠지만, 이것만 가지고는 턱도 없이 부족할 것이기 때문에

OOP와 COM에 눈을 뜨고서 한 1년 정도의 필드 경험을 익힌 다음에 시작하는 것이 좋지 않을까 하는

것입니다. 왜냐하면 저의 경우에 어느날 갑자기 DirectShow필터의 소스가 한눈에 확 들어온 적이 있었

는데, 그토록 갈망했던 그 구조가 그렇게 쉽게 머릿속에 다가오자 오히려 맥이 빠질 정도였습니다. 그러

나 뒤돌아 생각해보면 다 그럴만한 이유가 있었고, 저처럼 OOP와 COM의 전반적인 이해가 없이 무턱

대고 대양에 빠져서 허우적대는 식으로 갈팡질팡하는 것 보다는 오히려더 실무 경력 1년 정도의 시간을

두고나서 시작하는 것이 훨씬 더 효과적이라는 생각이 들기 때문입니다. 마치 구구단을 완전히 암기하

지 못한 사람이 미적분을 공부하는 것과 같은 것이라고 할까요...

Page 5: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 5 -

물론 이것은 궁극적인 목표가 Filter개발이라는 점을 염두에 두고 한 말입니다. 만일 여러분이 일반적

이고 간단한 동영상 플레이어를 조작하는 어플에 만족한다면 그보다 훨씬 저렴한 노력으로 DirectShow

를 익힐 수도 있을 것이라고 격려해주고 싶습니다. 그러나 분명한 것은 DirectShow는 필터를 개발하지

못하면 그 성능을 절반도 제대로 사용할 수 없다라는 점입니다. 그러므로 최종목표는 항상 필터개발이

라는 사실을 명심하도록 하세요.

[2] DirectShow[2] DirectShow[2] DirectShow[2] DirectShow의의의의 특징특징특징특징. . . . 버퍼공유버퍼공유버퍼공유버퍼공유

DirectShow의 특징이라면 한두개가 아닐 것이다. COM으로 이뤄진 것이라는 것, 내부적으로

DirectDraw를 사용했다는 것, 등등... 그러나 가장 중요한 것은 DirectShow의 구조적인 특성일 것이고,

거기서 가장 중요한 점이 바로 버퍼를 공유한다는 점이다. 이 사실에 밑줄을 쫙 거주길 바랍니다.

사실 윈도우에서 동영상을 제어하는 방법이 꼭 DirectShow만 있는 것은 아닙니다. 예전의 API를 사

용해서 어플을 만들었던 VFW(Video for Window)라는 방식도 있지요. 즉, DirectShow와 VFW는 윈도

우즈 운영체제에서 동영상을 제어하는 어플을 만드는 두개의 유일한 방식이라고 할수가 있습니다. 좀더

엄밀하게 말하면 VFW가 먼저 나왔고 그 다음으로는 마이크로 소프트사가 하는 방식이 원래 그렇듯이

DirectShow가 VFW를 흡수통합하는 형식으로 나왔다는 점입니다.

그렇다면 어째서 마이크로소프트(이하 마소)는 DirectShow를 별도로 제시하여야 했을까요.

DirectShow(이하 DShow)는 DirectX의 일 부분으로서 SDK에 포함되어 있습니다. 즉 초기의 DirectX

에는 DirectShow가 포함되어 있질 않았다는 것이죠. 마소는 절대적으로 DShow가 필요했고, 그러한 필

요성을 절실히 느낄 수 밖에 없었을 터입니다. 그 이유가 바로 버퍼공유라는 단하나의 이유 때문이라도

말이죠.

우리가 동영상을 로딩하면 일반적으로 카메라에서 BT878류의 화면캡쳐보드나 혹은 USB를 통해서

어디론가 연속적인 이미지가 쌓일 수밖에 없습니다. 일단 USB 종류의 설명은 제외하기로 하지요. 왜냐

하면 제가 USB 드라이버를 다뤄본적이 없기 때문에 여기서는 일단 BT878이라는 화상 캡쳐보드(혹은

일반적으로 TV수신카드)를 예로 설명하겠습니다. (사실 직접 디바이스 드라이버를 제작한적은 없습니다.

그러나 드라이버 제작자와 함께 작업을 하면서 상당히 많은 사실을 접할 수가 있었습니다.)

카메라는 일반적으로 감시용카메라(아날로그)가 되겠습니다. 이것을 BT878카드에 꼽아서 DShow 어

플을 제작하는 것인데요, 주로 산업현장이나 주정차 시스템같은 곳에 많이 연계되어 사용하고 있습니다.

USB 카메라를 이용하는 화상통신 프로그램도 완전히 동일한 DShow 응용 어플이라고 할수 있겠습니다.

왜냐하면 WDM 디바이스 드라이버라면 그 위에서 작동하는 DShow는 아무튼 동일한 것이기 때문이죠.

어쨌든 좀더 복잡한 예로 BT878을 예로 들어 보겠습니다.

Page 6: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 6 -

감시용 카메라는 아날로그 신호를 만듭니다. 이것을 BT878보드에 보내게 되고요, 이 보드에서 아날

로그 신호를 디지털화 작업을 하게 됩니다. 그리고 이 디지털 데이터가 윈도우의 첫번째 버퍼링 대상이

되는데요, 윈도우는 이 데이터를 가장 신속하게 다뤄질 수가 있는 시스템 메모리에 적재합니다. 엄밀히

말하면 윈도우가 그렇게 하는 것이 아니라 WDM 디바이스 드라이버가 그렇게 한다고 볼수가 있겠죠.

아무튼... 이 시스템 메모리는 일반적인 응용 어플에서 엑세스 할수가 있는 곳이 아니고, 또한 그 때문에

굉장히 빠르고 안전한 우선권이 있다는 점만 말해주고 싶습니다. 따라서 어플에서 이 데이터를 사용하

기 위해서는 다시 일반적인 메모리에 복사(버퍼링)하는 작업이 필요하고요, 이것이 다시 그래픽카드의

메모리로 전송되게 됩니다.

이것을 다시 간단히 말하면... BT878에서 만들어진 디지탈 그래픽 데이터는 WDM 드라이버에 의하여

윈도우의 시스템 메모리에 한번의 버퍼링이 되어지고, 여기서 다시 일반 어플이 사용하는 메모리에 복

사(버퍼링)이 되며 또다시 이것이 그래픽카드의 메모리로 보내지게 된다는 점입니다. 이때 그래픽카드로

전송하는 방식은 AGP 몇배속인가 하는 방식으로 보내지기 때문에 속도가 엄청나게 빠르다는 사실을 참

고하시면 되겠습니다. 그래픽카드를 꽃는 슬롯을 눈여겨 보시면 다른 슬롯보다 조금 다른 형태임을 보

실수가 있을 것입니다. 이것을 AGP라고 합니다. PCI방식보다 훨씬 빠르다고 하죠.

결론적으로 말씀을 드리면 이렇듯 간단한 동영상 어플을 만드는데에도 영상의 버퍼링이 두번이상이나

필요하게 됩니다. 만일 어플에서 영상을 가공처리한다면 또다시 몇번의 버퍼링이 필요한 터이겠죠. 이

버퍼링이 얼마나 어마어마한 것인가를 먼저 느끼셔야 합니다. 그렇지 않다면 DShow는 아무짝에도 소용

이 없습니다. 동영상의 버퍼링은 정말로 무시무시한 괴물 같은 것입니다. 특히 영상의 크기가 배가 될수

록 버퍼링의 크기는 그것의 제곱비례하게 됩니다.

이 말이 무슨 말인가 하면... 640*480의 동영상을 받아서 처리한다고 해보죠. 초당 30프레임이고요,

RGB24를 기본으로 사용한다고 생각해봅신다.(일반적으로는 YUV지만 생각의 편리를 위해서). 1프레임

에 약 1M의 메모리가 필요하다고 한다면(640*480*3 Byte이므로 거의 1M라고 하자), 1초에 약 30M

의 데이타가 전송되어야 합니다. 만일 버퍼링이 응용어플에서 3회에 걸쳐 이뤄졌다고 해봅시다. 그렇다

면 컴퓨터는 내부적으로 초당 약 90M의 버퍼링을 처리해야 할 것입니다. 이것은 어마어마한 양의 데이

터입니다. 아마도 CPU점유율이 그리 낮지는 않으리라고 봅니다.

만일 우리가 동영상 프로그램을 개발하면서 버퍼공유를 하지 못한다면, 우리는 계속적으로 버퍼링을

해야만 할 것이고 이것은 곧 엄청난 시스템 자원을 필요로하게 됨을 의미합니다. 마소가 굳이 DShow의

서비스를 제공할 수 밖에 없는 이유가 되는 것입니다. 여러분이 간단한 동영상을 편집해 보았다면 이

위력이 얼마나 큰 것인지를 실감하실 수가 있을 것입니다. 동영상의 데이터는 일반 어플의 데이터와는

상상할 수도 없을만큼 비대합니다. 따라서 버퍼링이 곧 죽음이고, 그만큼 마소에서는 버퍼공유가 절실히

필요했다고 할수가 있을 터입니다.

Page 7: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 7 -

이즈음에서 저의 경험담을 이야기해보겠습니다. 저는 몇년전에 동영상 합성시스템을 개발한적이 있었

습니다. 그 당시 컴퓨터 사양이 CPU 1.7 ~ 2.5G 정도였습니다. 640*480영상을 백그라운드와 자신의 모

습, 그리고 앞에서 움직이는 이펙트영상, 이렇게 세가지를 동시에 크로마 합성하고자 하였으나 결국은

실패하였습니다. 왜냐하면 그 당시 컴퓨터 사양으로는 절대무리였다는 사실을 깨닭기까지 저의 기초지

식은 너무나 터무니 없었기 때문입니다. 우리는 동영상을 생각할때 버퍼링과 함께 또다른 괴물도 마주

하고 있음을 깨닭아야 합니다. 그것은 바로 압축이라는 메두사입니다. 그 알고리즘만 봐도 돌이 되어 버

린다는 괴물이죠. 결국 그당시 저는 480*480의 형태로 세가지 영상을 실시간 합성해내는데까지 성공하

였습니다. 그러나 아직도 640*480의 3가지영상을 실시간 합성 해내는 것은 쉬운 일이 아닙니다. 왜냐

하면 컴퓨터가 1초당 내부적으로 버퍼링할 수 있는 크기는 제한적이기 때문입니다. 아직까지 우리의

PC는 1초당 수G바이트씩 버퍼링을 하지 못합니다. 이것은 CPU의 속도와는 다른 마더보드의 속도와 관

련이 있습니다. 아무튼 가장 중요한 것은 DShow의 가장큰 특징이자 존재이유가 바로 버퍼공유라는 것

이며, 이것은 동영상의 어플을 제작할때 성능차원에서 기본적으로 다뤄져야할 매우 중요한 요소중의 하

나라는 점입니다.

[3][3][3][3] 동영상동영상동영상동영상 프로그래밍을프로그래밍을프로그래밍을프로그래밍을 할때할때할때할때 주의할주의할주의할주의할 점점점점. . . .

이 사항을 기고해야 하는지, 아니면 나만의 아픈 기억으로 담고 있어야 하는지 모르겠습니다. 그러나

만일 나와 비슷한 경험을 하는 분이 있다면 조금 유의하라는 점에서 드리고 싶은 말씀이 몇가지 있습니

다.

일단 동영상 프로그래밍은 상당히 재미가 있지만, 경제적인 측면에서는 상당히 그렇지 않다는 점을

알려 드리고 싶습니다. 그 이유는 몇가지가 있겠으나 일단 우리나라의 속물적인 소프트웨어 개발현실에

서 깊은 알고리즘에 대한 이해를 요하는 연구스타일의 코딩은 설 자리가 없기 때문입니다. 이게 무슨

말인가 하면, 동영상은 상당히 고급 프로그래밍 기법에 속합니다. 필터제작은 기본이고 사실 Mpeg압축

알고리즘까지는 들어가야 제대로된 사업용 어플을 생산해 낼 수가 있다고 봅니다. 그러나 우리나라의

개발 현실은 그렇지 않습니다. 무조건 뚝딱 만들어 내라고 강요하고, 그러다보니 제대로된 소프트웨어가

개발될 리가 없습니다. 여기까지는 아마도 이해가 쉬우리가 봅니다. 왜냐하면 이정도는 일반적인 우리나

라 개발현실이기 때문입니다. 그러나 슬프게도 동영상쪽은 여기서 한발 더 들어가 악조건에 파묻혀야

합니다.

동영상에 대한 아이템을 가지고 개발을 의뢰하는 사람들은 대부분 어떤 환상에 사로잡혀 있습니다.

시스템이 뒷바침 되어 있질 않음에도 불구하고, 실상은 몇백만원짜리 그래픽편집카드가 필요한 시스템

구성을 단순하게 응용 어플로 개발해달라는 식입니다. 이들은 대부분 한탕 크게 해보자는 식이고, 히트

해서 떼돈을 벌 것이라는 몽상에 사로잡혀 있는 경우가 대부분입니다. 먼저 이들을 조심해서 피해나가

Page 8: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 8 -

야 할 것입니다. (이들은 결과물이 만족하지 않을 경우에는 개발비를 지급하지 않는 경향이 있다. 턴키

가 아니더라도 직원으로서 개발한다고 하더라도 급여가 제대로 나오기가 힘들다. 왜냐하면 이들이 생각

하는 개발기간과 실제 개발기간이 너무 차이가 나기 때문에 어느정도 급여가 나오다가 중간에 조금만

더, 조금만 더 하는 식으로 버티는 꼴이 되기 쉽기 때문이다. 이 즈음에는 이미 준비해뒀던 실탄이 바닥

나 있을 터이다.)

두번째로 조심해야 할 것은 어두운 쪽의 그룹들이다. 이들은 주로 야시시한 것들, 음난한 것들 이거나

혹은 조폭들과 연계된 체인점 사업 아이템 등등이다. 이들과 거래를 하거나 이러한 회사에 입사할 때에

는 조심하는 것이 좋다. 왜냐하면 어떠한 약점을 잡아서 회사에 목메 달려고 할지 모르기 때문이다. 사

내 도청을 조심해야 하고, 특히 계약관계에서 계약서를 면밀히 살펴봐야 한다. 잘못 코끼면 일여년동안

거의 노예처럼 노동을 강요당하기 쉽다.

동영상계열의 프로그래밍은 우리나라에서는 참으로 지저분한 분야가 되어버렸다. 그 이유는 일단 개

발회사들이 영세하고, 두번째로 프로그래밍에 대한 이해부족으로 SI처럼 생각하기 때문이다. 당장 눈앞

에서 삐가번쩍하게 돌아가야 고개를 끄덕여주는 클라이언트에게 알고리즘개발이란 엄두도 낼수가 없는

거창함일 뿐이다. 따라서 나는 동영상 프로그래밍을 그리 추천하지는 않는다. 이제껏 동영상 프로그래밍

을 하면서 받아야 할 인건비의 사분의 일도 제대로 받을 수가 없었기 때문이다. 그러나 내가 이렇게 여

기에 강좌를 하는 것은 동영상 프로그래밍이 전문적인 영상합성이나 편집분야가 아니더라도 얼마든지

작은 부분으로서 유익하게 다뤄져야할 필요성이 있을 터이기 때문이다.

다이렉트쇼의 서론은 이쯤에서 접는 편이 나을것 같습니다. 그러나 강좌를 진행하다가 계속해서 서론

부분에 해주고싶은 말이 있을 때에는 추가하는 방식으로 진행해 나가겠습니다.

Page 9: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 9 -

2.2.2.2. 준비할준비할준비할준비할 사항사항사항사항

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

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 [2] DirectX SDK [2] DirectX SDK [2] DirectX SDK 설치하고설치하고설치하고설치하고 둘러보기둘러보기둘러보기둘러보기. . . .

DirectX SDK를 설치하는 방법에 대해서는 굳이 해설하지 않겠습니다. 델파이에서 DSPack을 가지고

프로그래밍하는데 굳이 SDK가 필요한가라고 의문을 품으시는 분들이 계실수도 있겠습니다만, 먼저도

말씀드렸다시피 오리지날을 한번 살펴보고 비교분석해야할 시점이 반드시 옵니다. 따라서 DirectX SDK

는 반드시 설치하라고 권장하고 싶습니다. 또한 이것을 설치하는 이유는 이 속에 들어가있는 내부유틸

을 절대적으로 필요로 하기 때문입니다.

일단 SDK를 설치하시면 C:\ 루트에 DXSDK라는 디렉토리가 생깁니다. C:\DXSDK\Bin\DXUtils

여기의 디렉토리를 살펴보시면 graphedt.exe라는 유틸이 눈에 띄실 것입니다. 이것은 DShow 프로그래

밍을 하는데 있어서 없어서는 안될 중요한 프로그램입니다. 따라서 이것을 바탕화면에 바로가기로 등록

해 놓습니다.

Page 10: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 10 -

자, 이제 한번 SDK의 내부를 둘러보도록 하겠습니다. DirectX SDK에서 우리가 사용할 부분을 살펴봐야

합니다.

C:\DXSDK\Samples\C++\DirectShow 여기 디렉토리를 보시면 되겠습니다. 앞으로는 이곳을

밥먹듯이 드나 들어야 할 것입니다. 그러나 델파이로 DSPack을 가지고 프로그래밍하는 여러분은 그럴

필요가 없을지도 모르겠습니다. 하여튼 중요한 곳입니다. 특히 C:\DXSDK\Samples\C++\DirectSh

ow\Capture\AMCap 이곳 샘플 프로그램을 컴파일하여 실행프로그램을 만들어 놓으면 좋습니다. 이

것도 마찬가지로 graphedit.exe와 마찬가지로 바탕화면에 바로가기로 등록해 놓으면 좋을 것입니다.

자... 두가지 명심해야 할 것은 graphedit.exe 와 AMCap.exe가 필요하다는 것입니다. 그 중에서도 전

자의 유틸 프로그램은 반드시 필요하다는 점을 유념해 주시기 바랍니다.

여러분이 여기까지 읽고 따라하시려고 한다면 아마도 조금 당황하실지 모르겠습니다. 왜냐하면

AMCap이 컴파일 되지 않기 때문입니다. DirectShow의 샘플 프로그램을 컴파일하기 위해서는 조금 번

거로운 설정을 해주셔야 합니다. 이것은 델파이에서 컴포넌트를 설치하였을때 수동으로 디렉토리를 설

정해야 하는 것과 마찬가지입니다. 이것에 대한 설명은 뒤에서 신화선님의 참고서적을 해설하면서 언급

해 드리겠습니다.

[3][3][3][3] Visual C++Visual C++Visual C++Visual C++을을을을 설치하고설치하고설치하고설치하고 델파이의델파이의델파이의델파이의 DSPack DSPack DSPack DSPack 컴포넌트를컴포넌트를컴포넌트를컴포넌트를 설치하시기설치하시기설치하시기설치하시기 바랍니다바랍니다바랍니다바랍니다. . . .

특히 델파이의 DSPack의 버전에 유의하시기 바랍니다. DSPack은 버전에 따라서 내부 소스가 조금

다릅니다. 예를 들어서 레코드 선언에서 동일한 구조인데도 이름을 약간 변경해 놓거나 하였습니다. 따

라서 이왕이면 제가 사용하려는 DSPack2.34버전을 구해서 설치하시면 좋겠습니다. 제가 시간나는데로

자료실이나 이곳에 올려 놓겠습니다. 그것을 사용하시면 되겠습니다. (이것을 다른말로 하면 DSPack은

버전별 호환성이 없다라고 말씀드릴 수가 있습니다.

즉, DSPack의 다른 버전으로 개발하셨다면 약간의 수정을 해야 함을 의미합니다. 이 사소한 번거로움

이 이것을 만드신 분의 엄청난 수고에는 티클 만큼의 흉이 되지 않음을 아셔야 할 것입니다. 우리나라

에 이같은 분이 없다는 것이 정말로 아쉬운 뿐입니다. DSPack은 DirectShow 뿐만이 아니라 델파이에

서 참으로 참고할 만한 가치가 많은 방대한 분량의 참고소스입니다. 이정도 수준의 프로그래머가 국내

에도 많이 계셨으면 하는데요, 국내의 깜깜한 현실에서 이런 분이 몇분이나 될지를 생각해보면... 쩝, 안

타깝기 그지없습니다. "우리는 Coder다. 아니라는 증거가 있을까?")

Page 11: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 11 -

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

이 곳 사이트를 또한 좌절과 공포감이 엄습해 올때마다 들러서 검색을 하거나 질문을 올리거나 해야

합니다. 특히 이곳 운영자인 신화선씨에게 저는 처음 DShow 프로그래밍을 할때 이메일로 몇번의 도움

을 직접 받아본 적이 있습니다.

DShow 프로그래밍의 선도자적인 개척을 하신 분으로 나이는 저보다 어릴지언정 프로그래머로서 존

경을 받기에는 충분하다고 생각합니다. 불행하게도 대부분의 소스가 Visual C++로 되어있고, 델파이

진영이 있기는 있으나 거의 죽어있는 상태입니다. 제가 조금 나서볼까 하다가, 이곳 델마당에 이렇게 강

좌를 올리게 된 것인데요, 아무튼 중요한 곳입니다. 익스플로러의 즐겨찾기에 반드시 등록해 놓으시길

바랍니다. 사이트 주소가 길어서 외어놓기가 힘들거든요.

아참, 이곳에서 주의하실 점이 있습니다. 그것은 이 동호회 사이트가 과거 한번의 개편을 맞으면서 일

괄적으로 통합 된 것이 아니라는 점입니다. 즉, 과거의 자료는 '프리백'이라는 메뉴에 보시면 찾을 수가

있습니다. 이곳에도 유용한 정보들이 있기 때문에 그냥 '질문방'이나 '강좌'란에 들어가 찾다가 없다고

서둘러 빠져나오시지 말았으면 합니다. '프리백'에도 예상외의 정보가 있을 수 있기 때문입니다. 이 '프

리백'은 과거 사이트의 모든 정보를 그대로 옮겨놓은 곳입니다.

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

제가 조금 비판적인 입장에서 이 책을 설명드린다고 저를 비난하지는 말았으면 합니다. 왜냐하면 이

책이 비판받아야할 문제점과 이 책이 우리나라 DShow 프로그래밍에 있어서의 소중한 위치와는 전혀

별개의 문제이기 때문입니다.

예전에 저는 Visual C++을 공부하면서 이상엽님의 책을 보고 참으로 많은 좌절을 하였던 때가 있었

습니다. 그런데 그당시 나뿐만이 아니라 상당히 많은 사람들이 그랬던 경험을 털어 놓더군요. ^^. 아무

튼 좋은 책과 선도적인 책과의 구분은 분명히 있어야 한다고 생각합니다. 그당시 이상엽님의 책은 분명

히 선도적인 책이었던 것은 분명합니다. 그러나 좋은 책이었다고는 말하기가 힘들다고 봐야할 것입니다.

제가 이렇게 거창하게 설명하는 이유는 바로 여러분들을 위하기 때문입니다. 즉, 이 책을 읽고서 절대로

좌절하지 말기를 바랍니다. 왜냐하면 이 책이 선도적인 책인 것은 분명하고 그것의 존재 자체를 감사해

야하는 점은 명확한 사실이나, 분명 이해하기 쉬운 좋은 책은 아니기에 여러분에게 얼마간의 좌절감을

안겨드린다는 것또한 사실이기 때문입니다.

여담입니다만 실제로 우리나라 프로그래밍의 역사에서 좋은 책은 정말로 눈씻고 찾아보기가 힘듭니다.

제가 이제껏 보아왔던 가장 좋은 책은 김용성님의 Visual C++ 6 완벽가이드라는 책입니다. 이 책의 데

Page 12: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 12 -

이타베이스 해설부분을 보시면 알겠지만, 참으로 이렇게 쉽고 간결하면서 완성도있게 표현하기가 쉽지

않음을 절실하게 느낍니다. 여러분에게 이 책을 반드시 구입하라고 권하고 싶을 정도입니다. 특히 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 이곳을 먼저 컴파일 하여야 하며 라이브러리 등록을 해주셔야 한다는 점입니다.

Page 13: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 13 -

[6][6][6][6] 한글한글한글한글 도움말도움말도움말도움말 파일파일파일파일

마지막으로 한글 도움말 파일이 있습니다. 뭐 영문도 있습니다만 굳이 한글번역판이 있는데 영어공부

하고자 영문판을 읽으라고 권하지는 않겠습니다. 하지만 제가 처음 시작할 때에는 영문판 밖에 없었기

때문에 가독하는데 시간이 걸려 애를 먹었던 적이 있었습니다. 간단한 인터페이스 하나 찾는데도 일일

이 이마에 주름살 새겨가며 읽어야 했던 경험을 그닥 권장하고 싶지는 않습니다. 영어공부는 별도로 하

시는게...^^

이 도움말 파일이 반드시 필요한 이유는, 여러분이 어느정도 DShow의 구조에 익숙해졌다면 그 다음

부텨는 스스로 인터페이스를 찾아서 코딩해야만 하기 때문입니다. 즉 DShow SDK에서 제공하는 수많은

인터페이스 중에서 자신이 원하는 것을 찾아서 유용하게 사용할 줄 알아야 한다는 점이죠. 이 때문에

이 한글 도움말 파일은 상당히 유익한 것입니다. 영문이라면 시간이 좀 걸릴 터이기 때문이죠.

일단 2부 준비할 사항에 대해서는 이정도로 마치겠습니다. 하지만 강의 도중에 종종 필요한 부분이

있다면 추가적으로 게시할 것입니다.

Page 14: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 14 -

3.3.3.3. GraphEdit_1GraphEdit_1GraphEdit_1GraphEdit_1

DirectShow란 무엇인가를 극명하게 보여주는 것이 바로 이 GraphEdit입니다. 이것을 사용하는 것이

바로 DShow를 눈으로 보는 것이다라고 말할 수 있기 때문입니다. 만일 여러분이 DirectX SDK를 설치

하셨고 제가 앞서 말씀드렸듯이 바탕화면에 바로가기를 설정해 놓았다면 이제 이 프로그램을 실행시켜

보시길 바랍니다.

[1] [1] [1] [1] 대체대체대체대체 DirectShowDirectShowDirectShowDirectShow란란란란 무엇인가무엇인가무엇인가무엇인가. . . .

DShow는 필터들의 연결입니다. 이 말을 쉽게 해설하기 위해서 저는 GraphEdit라는 유틸을 사용할

것입니다. 일단 이 프로그램을 실행시켰다면 간단하게 동영상을 한번 랜더링(실행)시켜 보도록 합시다.

File 메뉴의 Render Media File... 을 실행시키고 동영상 파일하나를 선택합니다. 만일 여러분의 컴퓨터

에 현재 동영상을 재생하는데 필요한 코덱이 설치되어 있다고 하더라도 이곳에서는 다음과 같은 메시지

가 나올 수가 있습니다. '비디오 스트림을 재생할 수가 없습니다. 적절한 압축 풀기 프로그램을 찾을 수

가 없습니다.' 그러면서 오디오 부분만이 설정이 되어 화면에 나타날 수가 있습니다. 해결방법은 몇가지

가 있겠으나, ffdshow를 설치하라고 권하고 싶습니다. 인터넷에서 다운받아서 ffdshow를 설치하시기 바

랍니다. FFDShow는 오픈소스로서 공개되어 있습니다. 여기에는 XVid, DivX를 비롯하여 아무튼 Mpeg

압축의 스탠다드 코덱들이 총 망라되어 있습니다. 곰TV도 이 소스를 가지고서 만들었다는 이야기가 있

는데요, 확인해 보지 않아서 모르겟습니다만 충분히 가능성 있는 이야기입니다. 만일 여러분이 Mpeg

알고리즘을 공부한다면 기초가 될 소스가 바로 이 FFDShow 소스일 것입니다. 혹시 FFShow를 찾기

힘들다는 분은 XVid 코덱을 다운받아 설치하시면 될것 같습니다. 아무튼 해결하시고요... (FFDShow는

실행판과 소스판이 별도로 존재합니다. 이곳 자료실에 소스판을 올려놓아도 좋을까 고민이 좀 되네요.)

문제가 해결되었다고 보고요, graphedit를 보시면 화면 가득히 박스들이 보이실 것입니다. 그런데 이

박스들이 그냥 있는게 아니라 화살표를 꽁지에 꽁지를 물고 늘어져 있는 것이 보이실 것입니다. 이 박

스 하나 하나가 바로 필터객체를 의미하며 화살표의 연결방향이 바로 연속적인 스트림의 진행방향을 의

미합니다. 일단 여기까지 설명하고 실행을 시켜 보도록 하겠습니다. 위의 단축버튼중에서 왼쪽에서 여덟

번째을 보시면 일반적인 동영상 실행 아이콘으로 표시된 스피드 버튼을 찾으실 수가 있을 것입니다. 이

것을 클릭하시면 별도의 동영상 랜더링 윈도우가 뜨면서 동영상이 재생되는 것이 보일 것입니다. 자 별

로 대단한 것은 아닙니다만 우리는 간단히 동영상을 로딩해서 랜더링하고 있는 것입니다. 이제 윈도우

를 종료하고 다시 GraphEdit로 돌아와 봅니다. 화면 상단에 가득 채워진 정체불명의 박스들을 한번 마

우스로 집어서 이동시켜 봅니다. 여러분이 원하시는대로 움직이는데 유독 화살표는 자신이 원래 잡고있

던 위치에서 벗어나지 않으려고 늘어지거나 줄어들거나 할뿐입니다. 즉 스트림의 방향은 수정할 수가

없다는 말인데요

Page 15: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 15 -

이것은 사실 중요한 의미입니다만, 여기서는 단지 그렇다라고 이해하시면 되겠습니다. 여기서 박스(이

하 필터라고 하겠습니다.)을 클릭한채 Delete키를 누르면 선택한 필터를 삭제할 수도 있습니다. 지금 삭

제하지는 마십시요. 나중에 확인하도록 하고요, 여기까지를 일단 정리하여야 하겠습니다.

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

그래프 에디터에 보이는 수많은 필터들을 보시면 화살표 방향이 왼쪽에서 오른쪽으로 흘러간다는 것

을 확인하실 수가 있을 것입니다. 이것을 좀더 자세히 들여다보죠. 일단 가장 왼쪽에 있는 필터를 우리

는 소스필터라고 부릅니다. 말 그대로 스트림의 근원이라고 생각하시면 됩니다. 여기서부터 모든 스트림

이 시작될 것입니다. 그리고 그 다음 필터를 보시면 AviSplitter 필터를(혹시 여러분이 일반적인 영화

파일이 아니라 조금 예외적인 동영상을 오픈했다면 이 필터가 보이지 않으실 수도 있습니다.) 보실수가

있는데요, 이것을 파서 필터라고 합니다. 파서필터에 대해서는 나중에 다시 설명할 기회가 있을 것입니

다. 그리고 여기서 스트림이 비로소 두개로 나눠지게 되는데요, 위쪽으로 흘러가는 화살표 방향으로는

비디오 스트림이, 그리고 아랫쪽의 방향으로는 오디오 스트림이 각각 흘러가게 됩니다. 그런데 중요한

것은 여기까지의 스트림은 아직까지도 압축된 상태라는 점입니다. 다시 말하면 비디오 영상을 기준으로

각각의 프레임의 데이터 크기가 동일하지 않다는 말이죠. 예를 들어 320*240의 화면에 RGB24라면

320*240*3 byte씩의 데이터가 버퍼링되어 흘러가는 것이 아니라는 것이죠. 여기까지는 압축된 데이터

이기 때문에 어떨때에는 9kByte가 되었다가 또 어떨때에는 몇백Byte가 되었다가 합니다. 물론 Mpeg

압축일 경우이고요, 만약에 동영상 스트림이 Jpeg로 압축되었다면 마찬가지로 각 프레임의 데이터 크기

가 변하겠지만 Mpeg처럼 엄청나게 변하지는 않겠죠.

( 상식에 속하지만 혹시 모르는 분을 위하여 간단히 설명하겠습니다. 동영상을 압축하는 방식으로는 일

반적으로 프레임을 기준으로 앞프레임과 뒷프레임을 비교하여 그 차이점을 예측,비교하여 압축하는 기

업이 있습니다. 이것의 대표적인 방식이 Mpeg방식이라고 합니다. 이러한 방식은 키프레임을 중심으로

변화량을 예측하거나 차이점만을 추려내어 압축하기 때문에 스트림의 변화가 상당히 큽니다.)

다시 간추리자면 AviSplitter필터에서 갈라져 나온 두개의 스트림은 각각 영상과 음성의 스트림이며

여기까지는 압축된 데이터가 흘러가고 있다는 점입니다. 그리고 그 다음으로 비로소 압축을 해제하는

DeCoder필터가 붙게 되는데요, 각각 영상 DeCoder 코덱과 음성 DeCoder코덱이 붙게 됩니다. 이 각각

의 DeCoder 필터에서 흘러나오는 스트림이 비로소 압축이 해제된(다른 말로는 뻥튀기되어 데이터가 엄

청 불어난) 데이터이며 이것을 가지고 여러가지 합성을 하거나 변형을 하거나 할수가 있는 것입니다. 그

리고 다음으로는 완전한 동영상 스트림 데이터를 랜더링할 수 있는 랜더러 필터가 붙게 되는 것이죠.

물론 각각의 비디오 랜더러 필터와 오디오 랜더러 필터를 의미합니다. 이것을 한번 부족하 나마 도식

으로 그려봅시다.

Page 16: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 16 -

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

오디오 DeCoder 필터 오디오 랜더러 필터.

자... 이제 간단하게나마 결론을 내 봅시다. DirectShow는 필터들의 연결이며 이 각각의 필터는 시작

과 끝이 있다. 즉 소스필터로 시작해서 랜더러 필터로 끝난다. 여기까지 대충 정리가 되었다면 다음으로

넘어갑시다. (우리는 나중에 이러한 필터들을 직접 코딩상에서 구현하여 연결하고 랜더링까지 할 것입니

다. 간단하죠. 필터를 만들고 연결하고 랜더링한다. 이것이 DirectShow의 전부입니다. 한가지 개인적인

생각입니다만 마이크로소프트 계열의 소프트웨어적인 특성은 내부는 어쩔수 없이 복잡하지만 전체적인

구조는 간단하게라는 슬로건을 내걸고 있는 듯 싶습니다. 이것은 볼랜드 진영의 전체적인 구조는 복잡

해도 좋지만 내부는 될수 있으면 간결하게라는 방향과 완전히 반대적인 성향인 것 같고요... 물론 완전

히 제 개인적인 생각이지만요. 그래서 마소는 항상 시장변화에 유들유들할 수가 있는 것이고 볼랜드는

그러한 변화의 대처에 늦을 수밖에 없는 것이 아닌가 싶네요. 모든지 장점이 있다면 단점이 있듯이, 이

제부터 여러분은 이 악몽같이 복잡한 DirectShow의 내부로 초대받아야 할 것입니다.)

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

이제 여러분은 GraphEdit의 다른 면을 보셔야 합니다. 조금더 디테일하고, 조금더 번거로우며, 조금더

난해한 구조에 익숙해져야 합니다. 동영상을 Render Media File... 로 간단하게 로딩하여 랜더링하였지

만, 실은 이것들이 거저 연결 된게 아닌 것입니다. 자, 이제 우리는 동영상을 랜더링하는게 아니라 USB

카메라로부터 받아 들여진 스트림을 랜더링해야 합니다. (USB카메라가 없다면 DShow를 배우기에 아직

준비가 덜 되었다는 의미가 됩니다. 가능하면 USB 카메라를 준비하셔서 그것의 해당 디바이스 드라이

버를 설치하시기 바랍니다. )

GraphEdit의 메뉴중에서 New를 눌러서 기존에 있던 필터들의 연결을 제거합니다. '저장하기'라는 옵

션이 뜬다면 저장하지 말고 '아니오'를 선택합니다. 이제 GraphEdit의 메인화면이 깨끗해 졌을 것입니다.

여기에서 우리는 카메라에 해당하는 소스필터를 생성해야 합니다. 이러한 필터를 우리는 직접 불러와

줘야 하는데요, GraphEdit상단의 단축버튼 중에서 왼쪽에서 11번째의 버튼을 선택합니다. 마치 네모난

상자에 양쪾으로 조그만 꼭지점이 매달려 있는 듯한 모습을 하고 있는데요, 이것이 바로 현재 사용자의

컴퓨터에 설치되어진 각종 필터들의 나열을 보여줍니다.

이 버튼을 클리하면 윈도우가 하나 뜨면서 그 캡션에 'Which Filters do you want to insert?'이라고

적혀 있을 것입니다. 이곳에서 아무거나 클릭을 하면 바로 GraphEdit의 메인화면에 선택한 필터가 생성

Page 17: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 17 -

되어 집니다. 여기서 모든 카테고리를 살펴보실 필요가 없습니다. 중요한 카테고리를 다음과 같이 정리

하였습니다.

* 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구조가 이렇게 중복 병합되는 카테고리 구조를 유발시켰다라고도 볼수가 있을 것입니다.)

Page 18: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 18 -

아무튼 그래서 위에서 말씀드린 각각의 카테고리는 예전 VFW의 방식으로서 존재하는 것들입니다. 자,

한가지 예를 들어 봅니다. 위의 카테고리에는 Video Compressors라는 것이 있습니다. 이곳을 들여다보

면 각종 압축 코덱들이 모여 있는 것을 보실 수가 있습니다. 그런데 다시 DirectShow Filters라는 카테

고리를 들여다보면 여기에도 압축 코덱들이 한두개 눈에 보이실 수도 있습니다. ( 현재까지는 한개도 보

이지 않을 수도 있습니다. 그러나 DirectShow관련 코덱들을 한두개 설치하다보면 어느새 이곳에도 압

축코덱필터가 자리잡고 있는게 보이실 것입니다.) 자, 이 두개의 코덱의 성격은 어떤 점에서 다른 것일

까요?

예전에 저는 XDiv의 소스를 해석한 적이 있었습니다. 여기서 저는 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 카테고리에 설치할 수가 있었는지... 가물가물하네

요. 하지만 한가지 확실한 것은 그 반대는 안된다는 것입니다.)

Page 19: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 19 -

4.4.4.4. GraphEdit_2GraphEdit_2GraphEdit_2GraphEdit_2

GraphEdit에 대한 설명을 위해 1,2편으로 나누었습니다. 그만큼 워낙 DShow에서는 중요한 유틸이라

설명해야할 부분이 많기 때문입니다.

[1] [1] [1] [1] 카메라카메라카메라카메라 영상을영상을영상을영상을 플레이하기플레이하기플레이하기플레이하기....

강의를 하려고 보니까 GraphEdit의 왼쪽에서부터 11번째의 단축버튼을 눌렀을때 나오는 윈도우의 명

칭을 무엇인가로 지칭하는게 편리할 것 같습니다. 매번 11번째 운운하면서 설명하는 것 보다는 앞으로

는 '필터삽입윈도우'라고 부르겠습니다. 자, 필터삽입윈도우에 보시면 Video Capture Source 라는 카테

고리가 보이실 것입니다. (계속해서 제가 카테고리라고 표현하는데는 다 이유가 있습니다. 실제로 이것

은 프로그래밍 상에서 카테고리로 되어 있으며, 나중에 여러분은 이 카테고리에서 필요한 필터들을 골

라내는 로직을 작성해야 할 것입니다.). 만일 여러분의 컴퓨터에 USB 카메라가 설치되어 있다면 이곳에

분명히 1개 이상의 필터가 존재해야 합니다. (DSHow를 공부할 때에는 반드시 USB 카메라가 한대 쯤

은 있어야 한다.) 일단 그것을 선택하면 GraphEdit의 메인화면에 필터가 생성되어 질 것입니다. 자.. 이

것은 일종의 광의의 소스필터이고, 좁게는 비디오 입력장치라고 부릅니다. 이제 여기서부터 나오는 스트

림을 랜더링해야 하는데요, 그러자면 랜더링 필터가 필요합니다. 마찬가지로 필터삽입윈도우에서 이번에

는 DirectShow Filters 카테고리에 들어가 봅시다. 여기에는 많은 수의 DirectShow 필터가 존재하고

있습니다. 이 말은 즉 오리지널 DShow 클래스(실은 COM 인터페이스) 내부구조를 하고 있다는 것입니

다. 여기에서 Video Renderer 필터를 선택합니다. 조금 아랫쪽에 있을 것이므로 스크롤바를 밑으로 조

절하셔야 할 것입니다.

자, 이제 GraphEdit의 메인화면에는 두개의 필터가 놓여져 있을 것입니다. 왼쪽에 카메라 필터를, 오

른쪽에 렌더러 필터를 위치시켜 놓은 다음에 카메라 필터의 오른쪽 꼭지에서 마우스로 콕 집어서 드래

그 하여 랜더러 필터의 왼쪽 꼭지점에 갖다가 놓습니다. 이제 두개의 필터가 연결되었을 것입니다. 그런

상태에서 위쪽의 단축버튼 중에서 동영상 실행 버튼을 클릭하여 봅시다. 새로운 윈도우가 뜨면서 카메

라로부터 들어온 영상이 렌더링 되는게 보여질 것입니다.

[2] Video Renderer [2] Video Renderer [2] Video Renderer [2] Video Renderer 필터필터필터필터

Video Renderer 필터의 종류가 3가지가 되는 것을 볼수 있을 것입니다. 이것에 대한 설명을 하자면

장황해 지겠으니, 가능하다면 간략하게 설명해 드리겠습니다. 일단 VMR Renderer 와 두개의 Video

Renderer을 보실 수가 있을 것입니다. 이 VMR Renderer은 영상합성과 관련된 아주 중요한 필터입니

Page 20: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 20 -

다. 그래픽카드에서 이 서비스를 지원해 줘야 사용할 수가 있는 필터입니다. 이것이 무슨 말인가 하면...

일반적으로 영상을 합성한다면 버퍼링이 발생하게 됩니다. 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에서의 영상 합성의 결

Page 21: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 21 -

과물은 소프트 웨어적으로 저장할 수가 없는 것입니다. 왜냐하면 그래픽카드에서 영상이 합성되기 때문

에 거기가 종착역이기 때문입니다. 물론, 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를 생성시켰을때와 동일하다. 대처 무엇때문에 이따위 짓을 해 놓았는가. 그 이

Page 22: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 22 -

유는 Interlace방식의 입력 스트림을 처리하기 위함이고, Interlace 방식이란 무엇인가는 인터넷을 찾아

서 공부하시길 바랍니다.

[3] [3] [3] [3] 그래프의그래프의그래프의그래프의 저장저장저장저장

현재까지 실행을 하셨다면 GraphEdit 의 메인화면에는 두개의 필터가 달랑 연결되어 있는 것을 보실

수가 있을 것입니다. 이 상태를 그대로 파일로 저장했다가 나중에 다시 열어서 간단히 실행시키실 수가

있습니다. 메뉴에서 Save Graph를 선택하면 저장하기 다이알로그가 뜨는데요, 일반적인 파일저장처럼

하시면 됩니다. 이 기능은 프로그래밍적으로 사용하려고 했습니다만, 이상하게도 DirectShow의 도움말

에는 그런식으로 사용하지 말라고 권고하고 있습니다. 이게 무슨 말인가 하면요...

우리는 앞으로 수많은 필터들을 생성해서 수동으로 연결해야 합니다. 적게는 대여섯개에서부터 많게

는 20개 정도의 필터까지도, 혹은 그 이상이라도 필요하다면 연결해야 합니다. 그런데 이 과정에서 시

간이 조금 걸립니다. 아주 짧은 시간이지만 일반 사용자들은 1초 이상의 짬을 지루해합니다. 그런데 15

개 이상의 필터를 생성하여 연결하다보면 심지어 2~3초 정도가 걸리기도 합니다.(시스템 사양이 낮은

컴퓨터에서) 그런데 GraphEdit처럼 이러한 상태를 아예 파일로 저장했다가 불러들여서 실행한다면 굉장

히 속도가 빠르지 않을까라고 생각했던 적이 있습니다. 실제로 그러한 코딩이 가능하고, 그러한 예제 샘

플까지 존재합니다. 그러나 이상하게도 DirectShow 도움말 파일에서는 그런식으로 프로그래밍적인 사

용을 하지 말라고 거의 강권하고 있습니다. 이러한 이유에 대해 개인적인 추측이 가능합니다만,

아무튼 이것은 저의 개인적인 추측일 뿐이이서 이곳에 기술하지는 않겠습니다.

[4] Connect to Remote Graph [4] Connect to Remote Graph [4] Connect to Remote Graph [4] Connect to Remote Graph

메뉴에 보시면 위의 제목을 한 기능이 있습니다. 이것은 참으로 유익한 기능인데요, 잘만 활용한다면

꽤 훌륭한 뚫어 기능이 됩니다. ^^. 이게 무슨 말인가 하면요, DShow 프로그래밍은 일반적인 DLL 프로

그래밍과 마찬가지로 디버깅이 아주 골치가 아픕니다. DShow의 필터 하나하나도 ax 확장자만 가지고

있을뿐 실제는 DLL과 거의 비슷한 사용자 억세스 관점을 가지고 있습니다. 그래서 한번 버그가 발생하

면 대체 어디에서 발생하였는지 감을 잡기가 힙듭니다.

수십개의 필터들을 동적으로 연결시켜 놓았는데요, 그중 어느 한 부분이 연결되지 않은채 굴러간다면,

대체 어느 부분에서, 몇번째 필터에서 연결이 끊겼는지를 직접 확인해야 할 필요성이 있습니다. 여기까

지는 다른 방식으로 어떻게든 알아낼 수가 있겠지요. 그런데 문제는 그 다음입니다. 분명히 연결은 제대

로 되었는데도 불구하고 작동이 안되는 경우, 정말로 연결이 되었는지 눈으로 직접 보고 싶어질 때가

있습니다.

Page 23: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 23 -

이럴때 여러분이 만든 DShow 프로그램을 실행시킨다음 GraphEdit의 Connect to Remote Graph 메

뉴를 사용하신다면, 현재 여러분이 만들어서 실행되는 DShow 프로그램의 내부 필터들의 연결을 그대로

GraphEdit에 나타내게 하실 수가 있습니다. 물론 이 기능을 위해서는 여러분의 DShow어플에 약간의

(한줄 정도의) 추가 코딩이 필요합니다. 여기에 대해서는 뒤에 가서 간단히 실습해 보도록 하겠습니다.

Page 24: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 24 -

5.5.5.5. 어플어플어플어플 1111

이제 본격적으로 DShow어플을 제작하도록 하겠습니다. 여기까지 여러분은 DShow에 대한 기초적인

생각을 가지고 있어야 할 것입니다. 그것은 필터를 생성하고 연결하고 랜더링한다 입니다. 여러분이 이

번장을 실습하기 위해서는 일단 델파이에 DSPack2.34버전이 설치되어 있어야 할 것입니다. 우리는 아

주 간단하게 USB 카메라에서부터 영상을 가져와서 랜더링하는 데까지 실습할 것입니다.

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

DShow 어플을 제작하는 방식에는 크게 2가지의 방법이 있습니다. 하나는 DSPack의 콤포넌트를 이

용하는 방식이고 또 다른 하나는 이런 컴포넌트를 이용하는 것이 아니라 DSPack에 있는

DirectShow.pas를 직접 사용하는 것을 의미합니다. 그리고 여기서 또다시 세분화 하자면

DirectShow.pas를 직접 사용하여 DShow 응용 어플을 개발하는 것에도 크게 두가지 방식이 있습니다.

하나는 필터들의 연결을 FitlerGraph에 의해 자동연결로서 작동하도록 개발하는 것이며, 다른 하나는

수동으로 직접 필터들을 하나하나 연결해 나가는 방식입니다.

일반적으로 여러분이 델파이에서 DirectShow용 응용프로그램을 개발한다면 DSPack의 컴포넌트를

사용해야 하는 것으로 알고 있을 것입니다. 하지만 이 방식은 절대로 사용하지 말라고 충고하고 싶습니

다. 왜냐하면 나중에 가면 전혀 쓸데없는 시간 낭비였다는 것을 후회하게 됩니다. 이 컴포넌트 의존적인

DShow의 개발은 나중에 가면 갈수록 배기량 1000cc도 안되는 티코에 수백만원(혹은 그 이상) 짜리 옵

션을 다는 것과 마찬가지로 계속적인 추가 부담을 엄청나게 요구하기 때문입니다. 그 부담은 로직상의

까다로운 구현이 될수도 있겠고, 혹은 시스템상의 리소스 부담이 될수도 있을 것입니다. (이것을 풀어서

이야기하면, 컴포넌트에서 제공하는 것 이상의 기능을 구현하기 위해서는 상당히 복잡한 둘러치기 코

딩이 필요하기 때문이고, 또는 필터 내부에서 동작하여 어플의 인터페이스를 통해 해결되어야 할 문제

를 컴포넌트 자체에서 해결하기 위해 불필요한 버퍼링을 하기가 쉽기 때문입니다. 동영상 어플에서 버

퍼링은 곧 죽음임을 명심하셔야 합니다.)

참고참고참고참고

오해의 소지가 있을 것 같아서 설명합니다. DSPack의 컴포넌트를 사용하지 않고 개발해야 한다고 주장한다해서

DSPack의 가치를 폄하하는 것은 절대로 아닙니다. DSPack은 그 자체가 델파이에서 DShow의 선생님이라고 생각

하셔야 합니다. 다양한 방식으로 컴포넌트를 구조화 하였고, 그 방대한 소스는 엄청난 참고가치가 있기 때문입니다.

물론 소스 중간중간에 약간의 버그가 있기는 합니다만, 그것은 오픈소스로 개발하면서 그정도까지의 세심한 배려를

바라는 것은 유저들의 과다한 욕심에 불과한 것이라고 봅니다.

Page 25: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 25 -

자, 이제 우리는 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 [2] DShow [2] DShow [2] DShow 클래스를클래스를클래스를클래스를 만들어만들어만들어만들어 보자보자보자보자

우선 응용어플을 만들기 위해서 기본이 되는 베이스 클래스를 제작하도록 합시다. 그 이름을

Page 26: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 26 -

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

Page 27: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 27 -

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

Page 28: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 28 -

// 생략. 위에 있으므로...

end;

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

위의 소스코드에서 보시면 아시겠듯이, 이상한 놈의 타입이 나옵니다. FilterGraph: IGraphBuilder

라고 선언한 부분을 보실수가 있습니다. 일반적으로 타입에는 T라고 붙습니다만, 요놈은 I라고 붙네요.

이것은 인터페이스 임을 의미하는 것입니다. 즉 FilterGraph는 인터페이스형 타입으로 선언되어 있음을

아셔야 합니다. 이 인터페이스를 우리는 일단 함수들의 모임을 선언한 상속가능한 타입이라고 정의해

봅시다. 즉, IGraphBuilder을 살펴보시면 인터페이스의 고유번호인 GUID가 있고, 그 아래 몇개의 함수

들이 선언되어 있는 것을 보실수가 있습니다. (만일 여러분의 델파이에 DSPack을 설치하셨다면,

IGraphBuilder에 마우스를 커서를 갖다놓고 Ctrl상태에서 클릭하여 직접 살펴보시기 바랍니다.)

인터페이스에 대하여 너무 어렵게 생각하지 마시기 바랍니다. 우리는 델파이에서 여러가지 타입을 선

언하여 사용하고 있습니다. 대표적으로 레코드가 있겠고, 클래스가 있겠고, 또 수많은 변수형 타입이 존

재할 것입니다. 위의 인터페이스도 그렇게 수많은 타입중에 하나이고, 요놈만 유별나게 앞에다 I를 붙여

서 다른 모든 타입과 구분하고 있는 것입니다. (이 인터페이스에 대하여는 뒤에 필터 만들기에서 또다시

구체적으로 논의할 예정입니다. 일단은 여러가지 함수들의 모임을 정의해 놓은 타입이라고 기억해 두시

기 바랍니다.)

이제까지 저는 관습적으로 필터그래프를 생성하였다라고 설명하였습니다. 실제로 다른 사이트에 가보

시면 알겠지만 그냥 필터그래프라고 언급해도 상관은 없습니다. 하지만 보다 정확히 말씀드리자면 필터

그래프 매니저라는 COM 오브젝트를 의미하여, 우리는 이 오브젝트에 존재하는 다양한 인터페이스에 대

하여 살펴봐야 합니다.

보충설명보충설명보충설명보충설명

일반적으로 필터그래프라고 말한다면, 바로 필터그래프 매니저 COM 오브젝트를 의미합니다. 부르기 편하기 위하여 필터그

래프라고 말하는 것인데요, 이 안에 속한 필터그래프라는 인터페이스의 한부분과 혼동하시면 안되겠습니다. 정리하자면 필터

그래프 매니저라는 COM 오브젝트에는 필터그래프라는 인터페이스가 존재합니다. 우리가 일반적으로 필터그래프라고 지칭한

다면 그것은 필터그래프 매니저 COM 오브젝트 안에 있는 다양한 인터페이스 중의 하나인 필터그래프를 지칭하는 것이 아니

다라는 사실을 염두에 두시기 바랍니다. 요놈을 구분하지 않으면 사실 많이 헷갈립니다. 필터그래프, 필터그래프 하면서 설

명을 하는 것을 보면 대부분 필터그래프 매니저인 COM 오브젝트 전체를 의미한다는 것이라고 판단하셔야 할 것입니다.

Page 29: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 29 -

자, COM 오브젝트에는 수많은 인터페이스들이 존재합니다. 우리는 이러한 인터페이스를 뽑아서 잘

사용하면 될 것입니다. (나중에 필터 제작에서 직접 인터페이스를 정의하여 사용하기도 할 것입니다.) 지

금까지 우리는 COM을 초기화하였고, 필터그래프를(필터그래프 매니저인데요, 이하 필터그래프라고 지

칭하겠습니다.) 생성하였습니다. 그리고 위에서 보시면 아시겠지만 필터그래프를 생성하면서 동시에

IGraphBuilder 라는 인터페이스를 하나 뽑았습니다. 즉, CoCreateInstance 함수를 사용하여 COM객체

를 생성하면서 하나의 인터페이스를 뽑아들었습니다. 그렇다면 필터그래프 안에 있는 다른 인터페이스

는 어떻게 하면 뽑을 수가 있을까요. 이제 하나의 COM 오브젝트에서 어떻게 인터페이스를 국수 뽑듯이

뽑아 먹을수가 있는지 설명하겠습니다.

Dong이라는 이름의 COM 오브젝트.

위에서 Dong이라는 이름의 COM 오브젝트를 우리가 만들었다고 생각해 봅니다. 그리고 이것을 실제

로 인스턴스화하기 위해서 CoCreateInstance 함수를 사용하였습니다. 그런데 CoCreateInstance 함수

로 Dong 객체를 생성하면서 우리는 그 내부의 수많은 인터페이스 중 하나를 뽑을 수가 있습니다. 예로

서 B 라는 인터페이스를 뽑았다고 가정해 봅시다. 그렇다면 나머지 인터페이스를 어떻게 뽑을 수가 있

을까요. 다음과 같이 하면 됩니다.

B.QueryInterface(A);

B.QueryInterface(C);

A.QueryInterface(D);

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

수가 있습니다.

A Interface

B Interface

C Interface

D Interface

Page 30: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 30 -

[4][4][4][4] GGGGUIDUIDUIDUID에에에에 대하여대하여대하여대하여

위에서 필터그래프를 생성할때, 혹은 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_IF ilterGraph,

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}' ;

Page 31: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 31 -

위의 두개의 정의는 동일한 것입니다. 따라서 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그래서 상속되어진 것이기 때

문입니다.

Page 32: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 32 -

6.6.6.6. 어플어플어플어플 2222

우리는 전편에서 COM의 인터페이스에 대하여 알아보았습니다. 그러나 아직까지도 인터페이스에 대하

여 감이 잘 오질 않으실 것입니다. 그렇지만 조금은 참고, 중요한 몇가지에 대하여 정리해 보겠습니다.

1) COM 오브젝트를 생성하기 위해서는 CoCreateInstance라는 함수를 사용하는데 이 함수에는 세가지의 매개변수를 지정한

다. 하나는 COM 오브젝트를 식별하기 위한 GUID인 CLSID 이며, 또다른 하나는 IID_로 시작하는 인터페이스 식별자이고,

나머지는 리턴되어 받아낼 실제 인터페이스형 타입의 변수이다.

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

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

4) 인터페이스를 뽑아내기 위해서는 반드시 두가지의 매개변수가 필요한데, 하나는 IID_로 시작되는 식별자이며 또 다른 하

나는 리턴되어 받아낼 실제 인터페이스형 타입의 변수다. (일반적으로 I로 시작되는...) 그런데 식별자와 인터페이스 타입

의 변수에 사용된 GUID는 동일한 값이거나 혹은 상속관계에 있는 값이어야 한다.

[1][1][1][1] 필터그래프의필터그래프의필터그래프의필터그래프의 인터페이스인터페이스인터페이스인터페이스

필터그래프 매니저 COM 오브젝트(이하 필터그래프) 에는 수많은 인터페이스가 존재합니다. 헬프파일

을 찾아보시면 25개 정도의 인터페이스를 발견하실수가 있을 것입니다. 이 중에서 우리는 3가지의 인터

페이스만을 사용할 것입니다. 만일 여러분이 나중에 좀더 복잡한 DShow 어플을 개발하고자 할때에, 이

렇게 수많은 인터페이스 중에서 필요한 것을 골라서 선택하여 사용하시면 될 것입니다. 그러나 현재로

는 3가지 아주 핵심적인 인터페이스만을 생각해보기로 하죠.

첫째로 DShow 어플에서 생성되어진 수많은 필터들을 연결해주거나 끊어 주거나 해야 합니다.

GraphEdit에서 여러분은 직접 필터들을 마우스로 드래그하여 화살표를 꽁지에서 꽁지로 연결해 주었을

것입니다. 이러한 연결을 해주는 서비스 인터페이스가 필요합니다. 이 인터페이스가 바로 IFilterGraph

라는 놈인데요, 우리는 여기서 상속받아서 기능이 좀 더 많은 IGraphBuilder라는 인터페이스를 첫시발

로 CoCreateInstance을 사용해 필터그래프 생성과 동시에 뽑아 내었죠. 자, IGraphBuilder라는 인터페

이스가 필요한 이유는? 필터들을 연결해 주기 위해서라고 일단 간단히 생각합시다.

두 번째로 필요한 것은 필터를 연결한다음에 이것들을 실행시켜줄 인터페이스가 있어야 합니다. 즉,

Run하고, Stop하고, Pause하는 기능을 가진(이것을 다른말로 스트림을 제어하는) 인터페이스가 필요합

Page 33: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 33 -

니다. 이것을 IMediaControl이라고 합니다. 이 인터페이스는 필수입니다. 이게 없으면 필터들 연결만 하

고 끝이겠죠. 동영상이 Play되지 못한다는 말이죠.

세 번째의 인터페이스는 사실 없어도 되는 것이지만, 좀더 세련된 DShow 어플을 만들기 위해서는 필

수입니다. 일단 IMediaControl를 사용하여 DShow를 작동시켜 동영상을 Play시키면 디폴트로 별도의

랜더링 윈도우가 뜨면서 실행 되는데요, 이게 영 볼품이 없습니다. 자신이 만든 어플의 메인 폼위에서

실행되었으면 하는데요, 이렇게 영상이 랜더링 되는 위치를 설정할 수 있게 해주는 인터페이스가 바로

IVideoWIndow라는 인터페이스입니다. 이 세가지는 거의 필수라고 보시면 되겠습니다.

자, 이제 이 나머지 인터페이스를 어떻게 뽑아낼까요? 우리는 처음에 필터그래프에서 IGraphBuilder

라는 인터페이스를 FilterGraph라는 변수에 받아내었습니다. 요놈을 가지고 나머지 인터페이스를 뽑아

내어 봅시다. 일단 변수선언을 다음과 같이 합니다.

MediaControl: IMediaControl;

VideoWindow: IVideoWindow;

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

FilterGraph.QueryInterface(IID_IMediaControl, Med iaControl);

FilterGraph.QueryInterface(IID_IVideoWindow, Vide oWindow);

자 이렇게 나머지 두개의 인터페이스를 얻을 수가 있었습니다. 그렇다면 여기까지 한가지 의문이 들

것입니다. 우리가 필터그래프 COM 오브젝트를 CoCreateInstance 함수를 사용하여 생성하였는데, 소

멸(제거)는 어떻게 하는 것이지라고 말이죠. 자, 이것은 아주 중요한 이야기니까 별도의 설명을 해야하

겠습니다.

[2][2][2][2] COMCOMCOMCOM의의의의 소멸소멸소멸소멸. . . .

자, 조금 헷갈리는 분야가 나왔습니다. COM Server는 대체 무엇이고 COM Object는 또 무엇인가.

위의 제목이 COM의 소멸이라고 했는데 그렇다면 COM Server의 소멸을 의미하는 것인가, 아니면

COM Object 의 소멸을 의미하는 것인가. 기타 등등. 여러분은 이제 COM Server와 COM Object(혹은

Page 34: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 34 -

객체)를 분별해야만 하는 시점이 다가온 것입니다.

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에 대한 어느정도의 분별이 생기셨을 것입니다.

Page 35: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 35 -

조금더 예를 든다면, 여러분은 나중에 간단한 형태의 필터를 개발하게 될 것입니다. 이것은 물론 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를 생성시켰다면 윈도우 운영체제는 매개변수중 하

나인 CLSID를 파악하여 레지스트리에서 해당 CLSID의 COM Object가 포함된 COM Server(하드 디스

크 어딘가에 저장된 dll)을 로딩 할 것입니다. 그런데 이미 로딩된 COM Server에 있는 어떤 COM

Obejct를 또다시 CoCreateInstance를 사용하여 인스턴스화(객체화)하였다면 어떨까요? 운영체제가 이

미 로딩되어 있는 Dll을 다시 로딩하지는 않을 것입니다.

이제 우리는 In Process COM Server가 어떤 형식으로 로딩되는 것인지 아셨을 것입니다. 그렇다면

이제 COM Server가 어떤 방식으로 메모리에서 언로딩(소멸)되는지 알아보도록 하겠습니다.

Dong.DLL In Process Server

A COM Object

B COM Object

C COM Object

CoCreateInstance로 인스턴스화

( Dong.dll이 로딩된다.)

CoCreateInstance로 인스턴스화

( Dong.dll이 이미 로딩되어 있다.)

Page 36: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 36 -

위와같이 Dong.dll이라는 이름의 COM Server가 있다고 생각해 봅니다. 우리는 CoCreateInstance를

사용하여 A와 C라는 COM Object를 위의 순서대로 인스턴스화 하였습니다. 이제 이 상태에서 A와 C라

는 COM Object가 소멸되면 윈도우 운영체제는 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를 생성하여 그 안에 포함된 다수의 COM Object중에 몇개의 인터페이스를 얻어와 사용하였다면,

그 얻어왔던 인터페이스를 모두 제거한다면 A라는 COM Object는 더이상 외부에 노출된 인터페이스가

없으므로 자동 솔멸한다는 것입니다.

이제 결론에 다다랗습니다. 그렇다면 언어온 인터페이스는 어떻게 소멸시키는가? 이것은 너무 간단해

서 싱거울 정도입니다. 얻어온 인터페이스 형 타입의 변수에 nil값을 주면 됩니다. 여기까지를 한번 종

합해서 정리하겠습니다.

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

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

3) COM Server는 최초로 그 안에 포함된 COM Obejct가 CoCreateInstance로 생성될 때 윈도우에 의해 자동으로 로딩(생

성)되며, 반대로 생성되었던 COM Server안의 모든 COM Object가 소멸되면 자동으로 언로딩(소멸)된다.

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

자동으로 소멸한다.

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

Page 37: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 37 -

중요중요중요중요

COM에 대하여 위와같은 해설은 수박 겉핥기 식이라고 할 수가 있습니다. 별도의 책을 사서 한번쯤 휘둘러

읽어보실 필요가 있습니다. 그 시간이 정 안된다면, 김용성님의 Visual C++ 6완벽가이드(일명 눈깔책)을 구입

하여 그 안에 정리된 COM 부분을 익히시는 것이 상당한 도움이 될 것이라고 생각합니다.

Page 38: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 38 -

7777.... 어플어플어플어플 3333

이제 여기까지 종합적인 코드를 완성해 보겠습니다.

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, Media Control); // 필터그래프의 또

다른 인터페이스 얻어오기

FilterGraph.QueryInterface(IID_IVideoWindow, VideoW indow); // 필터그래프의 또다른

인터페이스 얻어오기

end;

destructor TBaseDShow .Destroy;

begin

if Assigned(MediaControl) then MediaControl.Stop; // 비디오 랜더링을 중단한다.

While Assigned(VideoWindow) do VideoWindow := nil;

While Assigned(MediaControl) do MediaControl := nil;

Page 39: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 39 -

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;

Page 40: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 40 -

[1][1][1][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;

Page 41: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 41 -

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 정도라고 생각하시면 되겠습니다.)

Page 42: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 42 -

[2][2][2][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이름, 받아낼 '핀' 타입변수) 이런 형식으로 핀 인터페이스를 얻을 수가 있습니다. 그

러나 저는 이 방식을 사용하지 않습니다. 이것에 대하여 부연설명을 해드려야 겠습니다.

Page 43: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 43 -

핀의 인터페이스를 얻는 방식으로는 두가지 접근방식이 있습니다. 하나는 핀의 이름으로 검색하는 것

이고, 또다른 하나는 핀의 위치로 검색하여 얻어내는 것입니다. 예를 들어 다음과 같은 필터가 있다고

합시다. 입력핀은 1 개이고 출력 핀은 3개 정도입니다. ( 필터에서 핀은 수없이 많이 만들 수가 있음.)

Dong.ax 필터

위에서 만일 '입력핀1'을 얻고자 한다면(정확히 말씀드리자면 입력핀1이라는 COM Object의 IPin 인

터페이스) 우리는 '입력'이라는 방향을 알고 있고 위에서 첫번째 순서라는 것을 알고 있기 때문에 이러

한 조건을 가지고 원하는 핀을 얻을 수가 있습니다. 또한 '출력핀3'을 얻고자 한다면 '출력'이라는 핀의

방향을 알고 있고, 위에서 세번째에 있기 때문에 이러한 조건을 가지고 '출력핀3'을 얻을 수가 있는 것

입니다.

정리하자면 핀을 검색하는 방식으로는 FindPin이라는 메소드를 사용하여 찾는 것과, 핀의 방향과 순

서를 조건으로 에뉴무레이트(열거)하여 찾아내는 방식, 이렇게 두가지가 있다는 것입니다. 그런데 저는

주로 후자의 방식을 사용하는데요, 여기에는 이유가 있습니다.

솔직히 말씀드리자면 FindPin 이라는 메소드가 실은 제대로 작동하지 않는 경우가 종종 있습니다. 여

기에는 필터를 개발하는 사람들의 '소홀함'도 지적될 수가 있겠는데요, 아무튼 그 이유 여하를 떠나서

상당히 자주 잘못된 핀을 얻어올 수가 있다는 사실입니다. 제가 어디서 읽었는지는 가물가물 합니다만

그곳에서도 이렇게 핀의 이름으로 검색하지 말라고 경고한 것으로 기억납니다. 아무튼 저는 핀찾기에서

'조건'으로 에뮬레이트합니다. 그리고 남들도 이러한 방식을 선호하고 있다는 사실을 종종 확인할 수가

있습니다. 아주 시간이 없고, 그래서 미칠것 같은 스트레스에 휩싸여 있었을 적에 한번 '이름'으로 찾기

를 시도한 적이 있었는데요, 결국 원하는 핀을 몇시간 동안이나 찾지 못해 헤메였던 기억이 생생 합니

다. DShow는 이렇게 에뮬레이트 방식을 주로 사용합니다. 비단 핀찾기 뿐만이 아니라 나중에 카테고리

를 뒤져야 할 때에도 에뮬레이트(열거) 방식을 사용할 것입니다. 자, 이제 핀을 찾는 함수를 다음과 같

이 선보이겠습니다.

■ (출력 핀1)

■ (출력 핀2)

■ (출력 핀3)

(입력 핀1) ■

Page 44: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 44 -

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;

Page 45: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 45 -

pin := nil;

end;

Result := S_OK;

end;

EnumPin := nil;

end;

위의 함수 내용에 대해서는 일일이 설명을 하지 않겠습니다. 기본적인 로직에 속하는 것이기 때문입

니다. 하지만 여러분이 혹시 눈치채셨을지 모르겠지만 약간은 이상하다는 생각이 드실 것입니다. 그렇습

니다. 위의 함수에서는 방향을 지시하는 매개변수는 있지만 위치를 지정하는 매개변수는 존재하지 않습

니다.

사실 위의 첫 모델에서는 위치를 지정하는 매개변수가 있었습니다. 하지만 점점 제 자신에 맞게 다듬

다가 보니까 이렇게 단순화 된 것입니다. FindPinOnFilter함수는 C로 된것을 델파이로 포팅한 것입니다.

제 기억으로는 원래 더 복잡했던 것으로 기억합니다. 아무튼 여러분도 자신만의 FindPinOnFilter 함수

를 개발하여 사용하실 날이 도래할 것입니다. 굳이 설명을 드리자면 위에서는 핀의 순서상에서 가장 첫

번째로 '접속되지 않은' 핀을 리턴한다는 것입니다. 이 함수를 사용하여 실제로 핀(IPin 인터페이스)을

얻어내야 할 것입니다.

Page 46: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 46 -

8.8.8.8. 어플어플어플어플 4444

이제 우리는 TBaseDShow의 마지막 함수인 다이렉트쇼 카테고리에서 원하는 장치를 불러오는 함수

에 대하여 이야기를 할 것입니다.

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

여기서 우리는 한가지 인식하고 넘어가야 할 것이 있습니다. 그것도 구체적이며 비주얼하게 말이죠.

일단 GraphEdit를 실행하여 '필터삽입윈도우'를 띄웁니다. 그곳에는 수많은 카테고리가 존재할 것입니다.

먼저 DirectShow Filters라는 카테고리를 클릭하여 아래로 쭉 풀어 놓습니다. (이 필터삽입 윈도우의 크

기가 고정되어 있는게 정말 불만이다. 앞으로 여러분은 필터를 만드실 경우가 있는데요, 만드신 필터를

이 GraphEdit를 사용하여 시뮬레이트를 하여야 합니다. 그런데 필터를 레지스트리에 등록해 놓고서, 이

곳 GraphEdit의 DriectShow Fitler라는 카테고리에서 찾을 때마다 참 번거롭습니다. 보시다시피 등록되

어 있는 필터들이 너무 많아서 좁은 창안에 한꺼번에 보이지 않을 뿐더러... 아무튼 이래저래 불편합니

다. )

DirectShow Filters 카테고리 안에 놓여진 수많은 필터를 보시면 두개의 GUID가 나란히 놓여져 있

는게 보이실 것입니다. 아마도 여러분의 눈에는 하나의 GUID(혹은 다른말로 CLSID)만 보이실 지도 모

르겠습니다. 이것은 '필터삽입윈도우'가 작아서 입니다. 수평스크롤바를 클릭하여 오른쪽으로 이동시키면

나머지 GUID도 보이실 것입니다. 이것의 한 예로 다음과 같습니다.

Video Renderer

DisplayName : @device:sw:{083863F1-70DE-11D0-BD 40-00A0C911CE86}\

{70E102B0-5556-11CE-97C0-00AA0055595A}

FileName : C:\WIndows\system32\quartz.dll

Merit : 00400000

pin: 00

Version : 2

대충 위와같이 보이실 것입니다. 자, 위에서 보시다시피 하나의 필터에 두개의 CLSID가 보입니다. 이

게 웬 하늘에 봉창 떨어질 일입니까. 하나의 COM Object에 두개의 CLSID가 있다니요. 하지만 당황하

Page 47: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 47 -

지 말고, 다른 필터들을 살펴보시기 바랍니다. 모두 두개의 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를 얻기위한 서비스에 반드시 필요한 요소이기 때문이라고 설명하였습니다. (제

Page 48: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 48 -

가 장치를 광의의 필터라고 하는 것은 바로 위에서 설명했던 것처럼 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_VideoInputDeviceCa tegory);

Result := SysEnum.GetBaseFilter( 0) // 가장 첫번째 장치를 가져온다.

Page 49: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 49 -

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

Page 50: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 50 -

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, Media Control); // 필터그래프의 인터

페이스.

FilterGraph.QueryInterface(IID_IVideoWindow, VideoW indow); // 필터그래프의 인터페

이스.

end;

destructor TBaseDShow.Destroy;

begin

Page 51: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 51 -

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;

Page 52: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 52 -

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;

Page 53: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 53 -

function TBaseDShow.GetCamFilter: IBaseFilter;

var

SysEnum: TSysDevEnum;

begin

SysEnum := TSysDevEnum.Create;

try

SysEnum.SelectGUIDCategory(CLSID_VideoInputDeviceCa tegory);

Result := SysEnum.GetBaseFilter( 0) // 가장 첫번째 장치를 가져온다.

finally

SysEnum.Free;

end;

end;

end.

Page 54: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 54 -

9. 9. 9. 9. 어플어플어플어플 5555

지난 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 객체 자체를 말하는 것이라 생각해야 할 것입니다.). 위

에서는 단지 두개뿐이지만 좀 복잡한 어플에서는 훨씬 많은 수의 필터들이 존재하고 있을 것입니다. 지

금은 간단한 실습용이기 때문이라고 생각해 주세요.

Page 55: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 55 -

위에서 두개의 필터와 함께 세개의 함수가 보이실 것입니다. 세개의 함수중 두개는 이름만으로도 이

해하기가 쉬우실 것입니다. 즉, 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;

Page 56: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 56 -

소멸자는 간단합니다. 위에서 말씀드린 것처럼 사용된 필터들을 해제하는 로직을 포함해야 하는데요,

이것을 별도의 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

Page 57: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 57 -

라는 함수는 전장에서 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 인터페

이스형 변수와 핀의 방향, 그리고 마지막으로 받아낼 핀의 인터페이스형 변수가 되겠습니다.

Page 58: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 58 -

한가지 의문이 들수도 있을 것입니다. 위에서는 단지 두개의 필터를 연결시켰지만, 수많은 필터들을

연결시킬때에는 어떻게 할까하는 점입니다. 위의 코딩을 계속 반복해 나가면 얼마든지 필터를 연결시킬

수가 있습니다. 즉 순서대로 출력에서부터 최종 랜더링까지, 앞 필터의 출력핀을 얻고 뒷필터의 입력핀

을 얻어서 연결하고, 다시 뒷필터의 출력 핀을 얻고, 그 뒤뒤필터의 입력핀을 얻어서 연결하고... 이렇게

반복될 것입니다. 이 부분에 대해서는 뒤에서 동영상 파일의 랜더링 부분에 가서 구경하실 수가 있으실

것입니다.

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

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;

Page 59: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 59 -

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, Dire ctShow9, 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;

Page 60: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 60 -

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;

Page 61: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 61 -

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;

Page 62: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 62 -

end;

end.

자, TCamDShow 클래스를 사용하는 방법은 너무도 간단합니다. 델파이에서 빈프로젝트를 하나 시작

합니다. 메인폼위에 Panel 하나와 버튼 두개를 올려놓습니다. 그리고 버튼의 클릭 이벤트에서 각각 다

음과 같이 코딩해 놓습니다.

unit uMain;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Gra phics, 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}

Page 63: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 63 -

//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.

Page 64: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 64 -

10.10.10.10. 어플어플어플어플 6666

지금까지 우리는 간단한 USB 카메라용 DShow 어플을 제작하였습니다. 너무도 간단한 것이었지만

이해하기가 쉽지는 않았을 터인데요, COM에 대해서는 좀더 체계적인 공부를 하라고 권해 드리고 싶습

니다. 자, 이번장 부터는 DShow의 필터에 대하여 공부해 보겠습니다. 우리는 필터들의 내부구조를 조

금씩 들여다보고, 그 각각의 역할과 의미를 짚어 본 다음에 DShow에서 제공하는 몇몇 기본 필터들에

대하여 공부할 것입니다. 이 지식들을 활용하여 전장에서 실습하였던 USB 카메라의 랜더링 프로그램을

수정하여 간단하게 AVI 파일로 저장하는 것과 중간의 프레임에서 원하는 이미지 사진을 뽑아내는 기능

을 추가할 것입니다.

[1] DShow [1] DShow [1] DShow [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;

Page 65: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 65 -

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에는 다양한 종류의 기본필터가 준비되어 있습니다. 이들 필터를 사용한다는 것은 어플개발을

Page 66: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 66 -

손쉽게 할 수 있다는 의미 이외에도 윈도우즈라는 운영체제에 있어서 범용인 동영상 어플을 개발할 수

있다는 의미도 있을 것입니다. 다양한 필터형식의 범용적인 서비스를 제공하면서 동시에 하부구조 깊숙

히 자리잡은 버퍼공유를 함으로서 동영상 개발을 획기적으로 진보시켰다고 볼수가 있을 것입니다. (그러

나 배우기는 어렵다는 거... 쩝.)

Anyway... 이제 다시 본론으로 들어가겠습니다. 위의 예제샘플 프로그램에서 우리는 동영상 어플개발

의 기본적인 구조를 살펴볼 수가 있습니다. 파일을 로딩하고, 그것을 루프를 돌려서 한 프레임씩 읽어내

고, 읽어낸 것을 변형하고 랜더링한다는 것입니다. 눈치 채셨겠지만, 각각의 함수들은 모두 DShow에서

각각의 필터들의 역할을 대변하고 있습니다. 즉, 파일을 로딩하여 루프를 돌리는 첫 프로시저는 '소스필

터'를 의미하고 두번째 Transfer는 말 그대로 변환필터를 의미하며 마지막으로 Renderer함수도 랜더러

필터를 의미할 것입니다.

자, 그런데 소스필터에 있어서는 약간 다르게 구조화되는 경우를 생각해 볼수가 있습니다. 즉, 로딩하

는 부분과 루프를 돌려서 한 프레임씩 읽어내는 경우를 별도의 필터로 만드는 것인데요, 이렇게 별도의

필터로 나누어 만드는 방식을 풀 모드라고 하고, 하나의 필터에 로딩과 루프를 모두 갖춘것을 푸쉬방식

이라고 합니다. 여기서 루프는 사실 스레드를 의미한다고 생각하시면 될 것입니다. 또한 두개의 부분으

로 나뉘었을때 앞의 필터를 풀모드의 소스필터라고 하며 뒤의 필터를 '파서 필터'라고 합니다. 푸쉬 소

스필터 하나를 굳이 풀모드 소스필터와 파서필터로 나누어 놓는 것은 DShow가 각부분의 필터 서비스

를 좀더 세밀하게 제공하기 위함이라고 생각하시면 될 것입니다.

소스필터를 너무 간단히 설명드렸지만, 사실 소스필터는 무지하게 복잡한 구조를 가지고 있습니다. 우

선 이것은 수 없이 다양한 Avi파일의 표준구조를 읽어내야하고, 음성과 영상의 프레임을 각각 뽑아내어

동기화작업도 해야합니다. 또한 네트워크를 통해서 들어올 경우에는 소켓을 포함하여 그에 따른 로직이

준비되어 있어야 할 것입니다. DShow 의 필터 개발자들이 우선 첫번째로 경험삼아 개발하는 것이 변환

필터이고, 그 다음으로는 바로 이 네트워크 소스필터인데요 제가 처음에 말씀드렸던 신화선님의 사이트

에 가시면 '네트워크 소스필터'로 인하여 울부짓는 질문들이 상당수 있을 것입니다. 네트워크 소스필터

가 어려운것은 소켓 프로그래밍 때문이 아니라, 영상과 음성의 싱크문제, 각각의 스레드의 동기화 문제,

버퍼링 문제와 같은 것들입니다. 이들 모두가 소스필터에 해당한다고 보시면 될 것입니다.

이번에는 파서필터를 직접 만나시겠습니다. 여러분이 만약 GraphEidt로 인터넷에서 다운받은 영화를

Render Media File...이라는 메뉴로 불러오셨다면 메인화면에는 수없이 많은 필터들이 연결되어 있는 것

을 보실수가 있을 것입니다. 그 여러종류의 필터들 중에 유독 하나의 필터에서 두개의 Out핀이 나온것

을 보실 수가 있는데요, 요놈이 바로 파서필터입니다. 이름은 아마도 'Avi Splitter'라고 적혀 있을 것입

니다. 이 Avi Splitter이라는 놈은 결코 우수운 놈이 아닙니다. 위에서 설명드린 것처럼 수없이 다양한

Avi파일의 표준구조를 읽어내고 영상과 음성의 프레임을 각각 동기화시켜서 스레드로 푸쉬(다른 필터로

밀어내기)하고 있는 것입니다.

Page 67: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 67 -

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

1) 소스필터는 두가지 종류가 있는데, 하나는 푸쉬모드 소스필터이고 다른 하나는 풀모드 소스필터이다.

2) 푸쉬모드 소스필터는 영상파일을 로딩하는 것과 스레드 안에서 각 프레임을 뽑어내는 기능 모두를 포함한다.

3) 풀모드 소스필터의 경우는 영상파일을 로딩하는 기능만을 가지고 있으며 뒤에는 스레드로 각 프레임을 뽑아내

는 기능을 하는 '파서필터'가 별도로 붙는다.

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

이제 변환필터에 대해서 말씀드리겠습니다. 아마도 여러분이 가장 원하시는 종목이 바로 변환필터 만

들기 일 것입니다. 일단 이 변환필터를 만드는 데에는 크게 두가지의 경우가 있습니다. 첫째로, 소스필

터에서 시작된 영상의 형식을 그대로 두고 버퍼의 내용만 바꾸는 것과, 둘째로 영상의 형식과 내용 모

두를 바꿔치기하는 방식입니다. 전자의 것을 InPlace 변환필터라고 하고 후자의 것을 Copy 변환필터라

고 합니다. 자, 이 두개가 무슨 의미가 있는지를 설명하겠습니다.

우리가 앞의 필터에서 흘러나온 스트림을 변환시키기 위해서는 뒤로 흘려보낼 경우까지 모두 고려해

야 할 것입니다. 만일 앞의 영상이 YUV의 형태였고, 뒤쪽으로 흘려보내야하는 영상이 RGB24라면 어떻

게 될까요. 이경우 어쩔 수 없이 한번의 버퍼링을 반드시 해야할 것입니다. 왜냐하면 뒤쪽의 타입에 맞

게 변경해줘야 하기 때문이지요. 그러나 앞과 뒤의 영상타입이 정확히 일치한다면 우리는 굳이 버퍼링

을 할 필요가 없습니다. 이 경우 필터는 앞의 필터에서 사용되어진 버퍼의 포인터를 그대로 가져와 사

용할 수가 있는 것입니다.

동영상 스트림의 형태는 상당히 까다롭습니다. 이 형태를 '미디어형'이라고 하는데요, DShow를 하기

위해서는 반드시 이 '미디어형'의 전체 구조가 머릿속에 들어가 있어야 합니다. 이것에 대한 의미를 정

확히 안다면 필터개발에 있어서 반이상을 정복하셨다고 해도 과언이 아닐 것입니다. 그런데 이 미디어

형을 알기 위해서는 각각의 미디어타입의 의미를 또한 알고 있어야 합니다. 예를 들어 대체 YUV는 무

엇인가에 대한 해답을 가지고 있어야 한다는 것입니다.

간단하게 YUV에 대해서 설명해보겠습니다. 우리가 일반적으로 색을 표현할 때에는 Red, Green, Blue

이렇게 세가지의 색을 조합해서 표현하는 RGB 방식을 흔히 사용합니다. 그런데 이 방식은 Image로 표

현하는데 있어서는 상당히 정확한 방식이지만, 반면에 인간이 느끼지 못하는 부분까지 구분하고 있기

때문에 정보의 취급 효율면에서는 떨어집니다. 그 효율이 아주 작은 차이라고 하더라도 동영상에서는

Page 68: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 68 -

무시하지 못할 엄청난 차이가 됩니다. 따라서 동영상에서는 주로 이 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로 압축된 형태로 들어옵니다.'라고 표현하는 것은 절반의

Page 69: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 69 -

데이타 양으로 거의 동일하게 표현하는 효율적인 측명을 지나치게 강조한 것이라고 할 수 있을 것입니

다. 아무튼 YUV 이것을 아는게 중요합니다. 나중에 여러분이 필터를 만들게 되면 이 YUV를 직접 눈으

로 보실수가 있게 됩니다. 저도 한번 샘플로 YUV를 RGB인 척 하고 랜더링한 적이 있는데요. 다음과

같은 모양이 나왔습니다.

*************************************************

*************************************************

*************************************************

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

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

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

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

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

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

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

*************************************************

*************************************************

*************************************************

*************************************************

*************************************************

************************ ************************

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

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

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

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

************************ ************************

************************ ************************

위에서 보시면 알겠지만, 가장 첫번째 큰 이미지가 Y값을 가진 전체 화면이 되겠고요, 나머지 두개의

작은 이미지가 각각 4개의 Y값에 대응하는 U와 V의 값들이 모여있는 화면입니다. 실제로 버퍼에 이런

식으로 저장이 되어 있는 것을 보고 정말 재미있어 했습니다.

Page 70: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 70 -

이야기하다보니 또 샛길로 빠졌습니다. 강의의 깊이를 조절하기가 정말 힘이 드네요. 이번 장에는 무

려 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 필터는 내부 버퍼링을 가능한한 줄여주기 위해 지원한다' 는 정도로 이해하시면 좋을 것입니다.

Page 71: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 71 -

11.11.11.11. 어플어플어플어플 7777

지난회에서는 소스필터와 변환필터 각각의 종류에 대하여 설명하였습니다. 그렇다면 이제 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용 어플을 개발하는데 있어서 거의 필수적이라고 생각하셔도 무방할 것입니다.

그럼 하나씩 알아보도록 하겠습니다.

[[[[1111]]]] Avi Mux Avi Mux Avi Mux Avi Mux

통상 '먹스'라고 부릅니다. 여러분은 '믹싱'과 '먹싱'에 대하여 구분하셔야 합니다. 믹싱은 두개이상의

데이터를 하나에 완전히 혼합시키는 것을 의미합니다. 예를 들어서 강남콩이 반쯤 담겨있는 통에 좁쌀

을 붓고서 마구 흔들어 섞어지는 형태를 의미한다고 할 수 있겠습니다. 강남콩과 좁쌀이 완전히 뒤섞여

다시 원래대로 분류하기가 힘들게 되어 버리는 것을 의미합니다. 몇달전에 비디오 스트림 안에 자막을 '

믹싱'처리 해달라는 의뢰를 받은 적이 있었습니다. 이것은 즉 비디오 영상의 각 프레임마다 자막을 이미

지 위에 그려넣어달라는 것을 의미합니다. 자, 그렇다면 '먹싱'이란 무엇을 의미하는 것일까요. 먹싱은

하나의 통에 강남콩과 좁쌀을 별도로 비닝봉지에 넣어 보관하는 상태를 의미합니다. '먹싱'을 예로 들때

에 대표적으로 영상과 음성의 스트림입니다. 영상과 음성은 서로 독립된 형태의 데이타로서 보존해야

하기때문에 대부분 '먹싱'처리를 합니다. 여러분의 하드디스크에 어떤 영화파일이 있다면 한번 울트라

에디트로 열어보시길 바랍니다. 영상과 음성의 데이터 사이에 일정한 간격을 두고 마치 공백처럼 느껴

지는 FF값으로 가득차 있는 곳을 간혹 발견하실 수가 있을 것입니다. 이것이 바로 영상과 음성이 나뉘

어지는 칸막이 비슷한 것이라고 생각하시면 되겠습니다.

우리가 동영상을 Avi로 저장하기 위해서는 이렇게 일단 '먹싱'를 해야 합니다. 위의 Avi Mux 필터를

GraphEidt 상에서 '필터삽입윈도우'를 사용하여 생성시키면 메인화면에는 Input핀 하나와 OutPut핀 하

Page 72: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 72 -

나가 있는 네모박스 형태의 비주얼한 필터가 보여질 것입니다. 자, 이상태에서 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를 하면 화면에는 어떠한 랜더링 창도 뜨질 않지만, 대신에 하드디스크에 여러분

이 지정하신 파일 이름으로 영상과 음성이 먹싱되어 저장되는 중일 것입니다. 마이크가 있으시다면 목

소리를 가다듬고 소리를 지르셔도 좋으실 것입니다.

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

장하기까지 하였습니다.

Page 73: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 73 -

[[[[2222]]]] Avi Splitter Avi Splitter Avi Splitter Avi Splitter

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

다.

[[[[3333]]]] Color Space Converter Color Space Converter Color Space Converter Color Space Converter

이 Color Space Converter라는 필터가 아주 고마운 녀석입니다. 이 필터는 입력핀에 YUV형태의 미

디어타입이 들어 온것을 RGB형태로 변환합니다. 일반적으로 YUV를 RGB로 변환하는 로직을 MMX나

SSE로 최적화된 로직이 있습니다. 하지만 이런 로직을 DShow에서 사용하기 위해서는 별도의 변환필터

를 만들거나 해야합니다. 그런데 DShow에서 아예 이런 필터를 준비해 뒀다는 것은 참으로 고마운 것입

니다. 이것을 다른 시각에서 본다면 DShow를 사용하면 이런 형태의 서비스를 받을 수가 있다는 의미도

될 것입니다.

사실 제가 DShow 초보시절에 바로 이 필터의 존재조차도 몰라서 직접 변환로직을 만들기위해 얼마

간 끙끙거렸던 적이 있었습니다. 왜냐하면 연결하려는 앞의 필터에서 나오는 출력핀의 미디어 타입에

RGB형이 없고 죄다 YUV형 타입이었기 때문입니다. 머리가 나쁘면 손발이 고생한다고, 눈앞에 뻔히 있

는 것을 별도로 만들기 위해 몇일간을 헤메었던 기억이 눈앞에 선합니다. 굳이 있는 것을 별도로 만들

필요는 없을 터이죠. 게다가 테스트한 결과 이 Color Space Converter 필터의 변환 성능은 상당히 최

고급 수준입니다.

[[[[4444]]]] Overlay Mixer Overlay Mixer Overlay Mixer Overlay Mixer

이 Overlay Mixer 필터는 예전에 영상이나 자막을 합성할때 사용하던 필터입니다. 그래픽 카드의 오

버레이 평면을 이용한 것인데요, 이것은 하나의 디스플레이 평면 위에 또다른 평면이 오버레이 되면서

합성되어지는 방식을 의미합니다. 하도 오래전에 사용해 보아서 기억이 가물가물합니다. 이 필터는 요즘

별로 사용하지 않을 거라는 생각이 듭니다.

자막을 아직도 이 필터를 사용해서 하는지 모르겠습니다. 예전에 지금의 곰플레이어가 아닌 아드레날

린이라는 어플이 마악 태동할 초창기에는 자막 처리가 동영상 어플 개발자들의 관심사항 첫순위였습니

다. 신화선님의 책에도 자막처리가 독립된 장으로 상당히 할당되어 나와있을 정도였으니까요. 아무튼 언

듯 기억나는 것은 메인화면 위에 오버레이 평면이 겹쳐지게 되는데요, 이때 오버레이 평면에 특정한 색

을 투명으로 지정을 하면 그 부분만 뻥 뚤려서 아래의 메인화면과 합성되어 보여지는 식입니다. 이것은

Page 74: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 74 -

CPU에 전혀 부담이 없이 그래픽카드에서 처리해 주는 것입니다. 만일 자막 처리를 CPU에서 '믹싱'의

방식으로 버퍼링 처리를 하였다면 상당한 부담이 되었을 터입니다. 그당시 컴퓨터 성능이 상당히 낮았

고, 웬만한 동영상 파일하나 재생 하는데에도 CPU 점유율이 20, 30%까지 치솟았던 점을 감안한다면

왜 그렇게 자막처리에 안달을 볶았는지 지금에서야 이해되는 측면도 있습니다. 아무튼 그냥 이정도만

알고 계셔도 무방하다고 생각합니다. OverLay에 대해서 더 알고 싶으시다면 차라리 DirectX 게임관련

서적을 참고하시는 게 더 자세하다고 생각합니다. 왜냐하면 OverLay라는 것 자체가 그래픽카드에서 지

원되는 자원중 하나이기 때문입니다.

[[[[5555]]]] Sample Grabber Sample Grabber Sample Grabber Sample Grabber

이 Grabber를 사전에서 찾아보면 1. 부여잡는 사람, 강탈자, 욕심꾸러기, 2. 흥미진진한 것, 깜짝 놀라

게 하는 것.이라 고 나와 있습니다. 하지만 프로그래밍에서 혹은 전산일반에서 Grabber(그래버)라고 한

다면 이 말의 의미는 조금 독특한 성격의 어떤 기능을 대변하고 있습니다. 즉, 어떤 것들 중에서 하나를

추려내는 기능이라는 의미인 것입니다. 아마도 '부여잡는 사람'이라는 첫번째 의미와 비슷할 것입니다만,

그와는 또다른 차원의 전산적 의미를 내포하는 것이라 하겠습니다.

FA라는 공장자동화 계열에는 비젼시스템이라고 있습니다. 이 시스템은 생산라인에서의 불량을 자동

체크하는 기능을 갖도록 이미지 판독을 위한 카메라와 보드등을 갖추고 있는데요, 여기서도 '그래버 기

능'이라는 표현이 사용되어 집니다. 실제로 '그래버 기능'이라는 것은 연속된 스트림에서 어느 한 프레임

을 '찰칵' 찍어내는 기능을 의미합니다. 아마도 이게 사실 어떤 것인지 이해하기 어려울 지도 모르겠습

니다. 동영상이라는게 연속된 일련의 이미지들인데, 여기서 기껏 한장을 뽑아내는 기능을 별도로 '그래

버 기능'이라고 말할 정도까지 까다로운 것인가 하고요... 결론적으로 말씀드린다면 까다롭고요, 까다로

울 수 밖에 없는 이유는 각각의 R, G, B 채널과 주파수와의 상관 관계에 있습니다. 아무튼 중요한 것은

이렇듯 FA라는 산업분야의 비젼시스템에서도 '그래버'라는 용어를 사용하고 있다는 것입니다.

그래버라는 것은 전산일반에서 사전적인 뜻 이외의 독특한 기능을 대변하고 있다는 것을 알고 계시면

좋을 것 같아서 이렇게 설명이 삼천포로 빠졌습니다. 나중에 FA의 비전시스템 SDK 프로그래밍을 하실

때 이 '그래버'라는 의미를 지금 기회에 알아두시면 좋을 것 같아서 설명드린 것입니다.

아마도 다음회부터 우리는 만들고자 하는 테스트 프로그램에서 바로 이 Grabber 필터를 사용할 것입

니다. 이것을 사용하여 카메라로부터 들어온 영상 중에서 '착칵'하고 한 프레임의 사진을 뽑아내어 이미

지로 저장해 보일 것입니다.

Page 75: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 75 -

[[[[6666]]]] File Source(Async.) File Source(Async.) File Source(Async.) 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 필터개발자가 옆에 있어서, 막

힐때마다 친절하게 가르침을 배울 수 있는 환경이라면 모를까, 무턱대 고 필터의 소스부터 프린트해서 해독하기 위해 밤새고

그러는 것은 전혀 바람직하지 않습니다.

Page 76: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 76 -

[[[[7777]]]] File Writer File Writer File Writer 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필터가 붙어야 한다는 것과 함께 쌍으로 염두에 두시면 좋을 것입니다.

[[[[8888]]]] Smart TSmart TSmart TSmart Tee ee ee ee

Smart Tee 필터(이하 스마트티 필터)는 Video Renderer 필터 다음으로 DShow에서는 밥먹듯이 사용

해야하는 중요한 필터입니다. 이 필터가 필요한 이유를 들어 보겠습니다. 우리가 카메라로부터 들어온

영상을 저장과 동시에 랜더링하고자 한다고 생각해 보십시오. 그렇다면 하나의 입력 스트림을 두개로

쪼개어 흘려 보내야 합니다. 즉, 필터에는 한 개의 입력핀과 두개의 출력핀을 갖되, 출력핀에서 나오는

각각의 동영상은 모두 동일해야 한다는 것입니다. 이것을 다음과 같이 Smart Tee 필터로서 그려보겠습

니다.

Page 77: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 77 -

Cam Fitler Smart Tee Video Renderer

Avi Mux File Writer

위와같은 연결이 만들어질 것입니다. 위에서 스마트 티 필터는 입력으로 들어오는 한개의 스트림을

가지고 두개로 쪼개어 아랫쪽으로 흘려보내는 역활을 하고 있습니다. 이런 역활을 하는 필터가 없다면

여러분은 직접 이런 역활의 필터를 개발해야 했을 것입니다. DShow에서 스마트 티라는 필터를 미리 마

련해 두었으니 우리는 사용만 하는 되는 것이겠죠.

[[[[9999]]]] Infinite Pin Tee Filter Infinite Pin Tee Filter Infinite Pin Tee Filter 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에 나온 사항을 참고하겠습니다.

스마트티는 하나의 입력핀을 단지 두개의 출력핀으로 나눌 뿐입니다. 하지만 각각의 핀에는 독특한

Page 78: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 78 -

이름이 부여되는데요, 바로 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초

: : :

Page 79: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 79 -

위와 같이 각 프레임마다 StartTime 과 EndTime을 가지고 있다는 것이죠. 그런데 간혹 이것들이 여러개의 필터들

을 거치면서 제시간에 도착하지 못하는 경우가 발생될 때가 있습니다. Smart Tee 경우와 함께 일반적으로 네트워크

필터에서도 이런 경우가 발생하여, 저같은 경우도 마지막에는 결국 타임스탬프를 제거해야만 했습니다.

사실 네트워크 소스필터의 경우에는 Smart Tee와 같은 이유 때문에 타임스탬프를 제거한 것이 아니라, 전송지와 수신지와의

동시성을 위해서 어쩔 수없는 선택이었습니다. 예를 들어, 전송지에서 TCP/IP를 통해 서버로 하나의 프레임을 전송했다면

수신지에서는 다시 그 서버에서부터 하나의 프레임을 받아올 것입니다. 그런데 어떤 경우는 통신이 느려졌다가 갑자기 빨라

질 경우가 생기는데, 이 경우 전송지의 소켓버퍼와 서버의 버퍼에 그동안 밀려서 쌓여있던 프레임이 한꺼번에 전달되기 때문

에 문제가 발생 합니다. 만일 타임스탬프가 존재하고 순식간적으로 한꺼번에 밀려들어온 각각의 프레임에 일정시간을 동일하

게 배분 한다면 전송지와 수신지와의 랜더링시간은 계속해서 차이가 벌어지게 될 것입니다. 저는 약 1시간 가까이 딜레이 되

는 것을 지켜봐야 했습니다. 참으로 놀라운 딜레이 현상이었습니다.

Page 80: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 80 -

12.12.12.12. 어플어플어플어플 8888

이번장에서 우리는 전에 만들어 보았던 카메라 랜더링 프로그램을 수정하여, 랜더링과 동시에 저장하

고, 원한다면 중 스트림의 중간에서 원하는 샷을 이미지로 캡쳐해 볼 것입니다. 이를 위하여 다음과 같

은 필터를 추가로 생성해야 할 것입니다.

===> Smart Tee Fitler, Avi Mux Fitler, File Writer Fitler, Sample Grabber Fitler

이 모든 필터들이 연결된 모습은 아래와 같습니다.

Cam Filter Smar Tee Avi Mux File Writer

Sample Grabber Video Renderer

위와같은 모습을 만들기 위해서 우리는 4개의 추가 IBaseFilter 인터페이스형 변수를 선언해야 합니

다.

SmartTee: IBaseFilter;

AviMux: IBaseFilter;

FileWriter: IBaseFilter;

Grabber: IBaseFilter;

위와같은 선언을 TCamDShow 클래스에 추가해 줍니다. 그런데 여기서 한가지 짚고 넘어가야 할 것

이 있습니다. 우리가 FileWriter와 Grabber를 사용하기 위해서는 필터그래프에 적용될 IBaseFilter 인

터페이스 이외에 다른 인터페이스를 하나 더 필요로 합니다. FileWriter의 경우, 우리는 저장할 파일의

디렉토리와 파일명을 설정할 필요가 절대적으로 있습니다. 이런 서비스를 위해서 FileWrite의 경우는

IFileSinkFilter2 라는 인터페이스를 하나 더 필요로 하게 됩니다. 여러분이 만약 도움말 파일에서 File

Writer에 해당하는 부분을 찾아보시면 필터 인터페이스 항목에 다음과 같은 항목이 나열되어 있을 것입

니다.

Page 81: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 81 -

필터인터페이스 IAMFilterMiscFlags, IBaseFilter, IFileSinkFilter, IFileSinkFilter2, IPersistStream

이 항목을 읽고서 IFileSinkFilter 를 사용하시지 말고, 이번에는 IFileSinkFilter 의 내용을 좀더 살

펴보시기 바랍니다. IFileSinkFilter 의 설명에 다음과 같이 표현되어 있습니다. '하위 호환성을 유지할

필요가 없는 경우는, 이 인터페이스는 아니고 IFileSinkFilter2 을 사용한다.' 일반적으로 보통

IFileSinkFilter2 를 사용하고 있음을 알고 계시면 좋을 것입니다.

FileWriter와 마찬가지로 Grabber도 또한 자신의 고유한 임무, 즉 연속된 프레임에서 어느 한 프레임

의 사진을 찰칵하고 뽑고자 한다면 그에 따른 서비스 인터페이스를 하나더 찾아 와야 합니다. 이 기능

을 해주는 인터페이스가 바로 ISampleGrabber라고 하는데요, 요놈이 우리가 IBaseFilter 형타입으로

선언했던 Grabber과 동일한 성격의 이름을 가지고 있습니다. 뭔가, 다른 변수이름으로 바꿔서 선언해주

고 싶은데요, 마땅한게 떠오르질 않네요. 그냥 인터페이스를 축약하는 의미로 뒤에 Inf 를 붙여서

GrabberInf : ISampleGrabber 이렇게 선언해 주기로 하겠습니다. 자, 여기까지 이해를 하셨다면, 이제

위에서 선언한 4가지의 인터페이스형 변수외에 2가지가 더 추가됨을 아실 것입니다.

SmartTee: IBaseFilter; // 필터그래프에서 사용될 SmartTee 의 필터표준 인터페이스

AviMux: IBaseFilter; // 필터그래프에서 사용될 Avi Mux 의 필터표준 인터페이스

FileWriter: IBaseFilter; // 필터그래프에서 사용될 FileWriter 의필터표준 인터페이스

Grabber: IBaseFilter; // 필터그래프에서 사용될 Grabber 의 필터표준 인터페이스

GrabberInf: ISampleGrabber; // 그래버의 서비스용 인터페이스

FileSink: IFileSinkFilter2; //File Writer 의 서비스용 인터페이스.

이제 위의 6가지 인터페이스형 타입의 변수를 선언하셨다면, 이제 남은 것은 이것을 생성하고 연결하

고 소멸시키는 로직을 구현하는 일입니다.

먼저 생성하는 부분을 보시겠습니다. 우리가 지난번에 만들었던 TCamDShow 클래스의

MakeBaseFilter 함수에 추가하시면 되겠습니다. 로직은 다음과 같습니다.

function TCamDShow.MakeBaseFilter: HRESULT;

Page 82: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 82 -

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, 'VdRender Filter' ); // 비디오 랜더러를 등록.

if VideoRender = nil then Result := S_FALSE;

//--------------------------- 추가부분 -------------------------------

// 스마트티 필터를 생성하고 추가한다.

CreateFilter(CLSID_SmartTee, SmartTee);

FilterGraph.AddFilter(SmartTee, 'SmartTee Filter' );

if SmartTee = nil then Result := S_FALSE;

// 먹스 필터를 생성하고 추가한다.

CreateFilter(CLSID_AviDest,AviMux);

FilterGraph.AddFilter(AviMux, 'AviMux Filter' );

if AviMux = nil then Result := S_FALSE;

// 그래버 필터를 생성하고 추가한다.

CreateFilter(CLSID_SampleGrabber,Grabber);

FilterGraph.AddFilter(Grabber, 'Grabber Filter' );

if Grabber = nil then Result := S_FALSE;

// 그래버의 서비스용 인터페이스를 뽑아낸다.

Grabber.QueryInterface(ISampleGrabber, GrabberInf);

GrabberInf.SetBufferSamples(True); // 요게 없으면 실행이 안된다. 쩝...

// 파일라이터 필터를 생성하고 추가한다.

CreateFilter(CLSID_FileWriter,FileWriter);

Page 83: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 83 -

FilterGraph.AddFilter(FileWriter, 'FileWriter Filter' );

if FileWriter = nil then Result := S_FALSE;

//FileWriter 의 서비스용 인터페이스를 뽑아내서 파일명을 설정한다.

FileWriter.QueryInterface(IID_IFileSinkFilter2, Fil eSink);

FileSink.SetFileName( 'C:\dong.avi' , nil);

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

end;

위의 코드를 보시면, 4개의 새로운 필터는 앞서 보았던 Cam과 VideoRenderer필터를 생성하는 방식

과 동일합니다. 따라서 새로울 것이 없는 것이고요, 눈여겨보셔야 할 것은 서비스 인터페이스를 뽑아오

는 로직에 있습니다. FileWriter의 경우 FileWriter.QueryInterface(...) 방식으로 또다른 인터페이스를

뽑아오고 있습니다. 즉, 자신도 FileWriter COM 객체의 IBaseFitler 형 인터페이스에 불과한 주제에 또

다른 인터페이스를 불러오고 있다는 것입니다.

자, 이렇게 인터페이스가 QueryInterface를 사용해서 또다른 인터페이스를 불러올 수 있다는 사실을

이제는 아주 머릿속에 깊이 각인시켜 놓도록 하십시오.

보충보충보충보충 혹은혹은혹은혹은 여담여담여담여담

위에서 다른 모든 필터의 CLSID 선언은 변수 이름과 동일한데 유독 AviMux만 CLSID_AviDest라고 선언되어 있는

것을 보실수가 있습니다. 이것은 BaseClass.pas에 이렇게 선언되어 있기 때문인데요, 앞으로 종종 이런 경우가 생

기실 것입니다. 그래서 많이 헷갈려요. 저같은 경우는 아예 GraphEdit 의 '필터삽입윈도우'에서 CLSID 값을 직접

노트로 적고나서 BaseClass.pas 파일에서 Ctrl + F 로 단어찾기식으로 찾아서 확인하곤 합니다. 왜냐하면 언젠가

이것을 확인하지 않아서 몇시간 진땀을 빼었던 기억이 있기 때문입니다. SampleGrabber는 CLSID_Sample Grabber

로, SmartTee는 CLSID_SmartTee 이런식으로 앞에 CLSID 접미사만 붙이면 된다고 생각하시면 언젠가 저처럼 곤

혹스런 경험을 하시게 될지도 모릅니다. AviMux는 아예 CLSID_Mux나 CLSID_AviMux와 같이 선언된게 없기에 다행

이지만, 간혹가다가 AAA라는 필터가 있는데 실제로 CLSID_AAA라고 선언된 부분이 있는게 있습니다. 기억이 가물

가물해서 생각이 나질 않습니다만... 이 경우 실제로 컴파일도 되고 하는데 필터그래프에서 이상하게 필터들과 연결

이 안되요. 고생고생 하다가 직접 CLSID 값을 확인하고서야 두가지 값이 GraphEdit의 '필터삽입윈도우'에서 보았던

값과 다르다는 것을 알게 되었습니다. 이게 아마 버전문제 때문인지, 그랬을 것입니다. 아무튼, 제가 하고싶은 요지

는 이렇습니다. 아예 별도의 유닛을 만들거나 아니면 TCamDShow의 부모클래스인 TBaseDShow 클래스가 있는

유닛에다가 중요하고 자주 사용되는 CLSID 선언 식별자를 줄줄이 적어 놓는 것이 좋다라는 것입니다.

Page 84: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 84 -

두번째는, 이것은 여담에 속할것 같습니다. 바로 그래버에 관한 사항인데요, 이 강의를 위해서 저는 먼저 샘플 프

로그램을 만들어 놓아야 했습니다. 그런데 예전에 만들어 놓았던 Grabber에 관한 부분을 이상하게 찾지 못했습니

다. 언제부터인가 그래버를 별로 사용하지 않고, 그냥 필터를 만들어서 필터 내부에서 필요한 동작을 하게끔 만드

는데 익숙해졌기 때문입니다. 몇십분을 찾다 찾다 못찾아서 아예 새로 만들기로 하였는데요... 이상하게 분명히 작

동해야 하는데 안되는 것이었습니다. 이럴때 사람 환장할 노릇이지요. 해서 하나하나 코드를 단순화 시키고, 그래도

안되서 DSPack 소스도 들여다보고, 그래도 안되서 인터넷 사이트의 C++ 샘플 코드를 보았는데요... 아이고, 예전

에 고생했던 부분에서 또다시 똑같이 고생하고 말았던 것입니다. 위에서 보시면 알겠지만 GrabberInf를 얻어오고서

그 다음칸에 중요한 로직이 있습니다. 그 로직은 다음과 같습니다.

===> GrabberInf.SetBufferSamples(True); // 요게 없으면 실행이 안된다. 쩝...

어째서 이것을 매번 까먹는지 모르겠습니다. 아주 단순한 사항인데요 깜박해서 근 한시간동안을 헤메었네요. 위의 코드가 없

으면 그래버가 정상작동이 되질 않습니다. 반드시 위와같이 인터페이스를 얻어오자마자 설정해 놓도록 명심하시길 바라겠습

니다.

자, 위에서 네가지의 필터의 변수 선언과 두개의 서비스 인터페이스 선언, 그리고 생성까지를 살펴보

았습니다. 일반적으로 프로그래밍의 동작순서로는 '선언 -> 생성 -> 연결 -> 소멸'와 같은 단계를 거치

지만, 코딩를 하는 순서로는 생성 다음에 곧바로 '소멸'부분을 먼저 작성하는게 좋을 것입니다. 그렇다면

다음은 소멸 부분을 보시겠습니다. 아래의 코드는 전회에 작성되었던 부분에 추가한 것입니다.

function TCamDShow.ReleaseBaseFilter: HRESULT;

begin

if Assigned(MediaControl) then MediaControl.Stop;

FilterGraph.RemoveFilter(Cam);

FilterGraph.RemoveFilter(VideoRender);

//---------------------------- 추가부분 ----------------------------

FilterGraph.RemoveFilter(SmartTee);

FilterGraph.RemoveFilter(AviMux);

FilterGraph.RemoveFilter(FileWriter);

FilterGraph.RemoveFilter(Grabber);

Page 85: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 85 -

While Assigned(Cam) do Cam := nil;

While Assigned(VideoRender) do VideoRender := nil;

//---------------------------- 추가부분 -----------------------------

While Assigned(SmartTee) do SmartTee := nil;

While Assigned(AviMux) do AviMux := nil;

While Assigned(Grabber) do Grabber := nil;

While Assigned(FileWriter) do FileWriter := nil;

While Assigned(FileSink) do FileSink := nil;

While Assigned(GrabberInf) do GrabberInf := nil;

Result := S_OK;

end;

위에서 보시다시피 각각의 필터 인터페이스를 소멸시키기전에 MediaControl을 사용해서 필터그래프

를 정지시키고 있습니다. 그리고 그 다음으로 필터그래프에 추가되었던 필터들을 제거하고, 마지막으로

nil을 대입하여 인터페이스를 완전히 소멸시키고 있습니다. 위의 로직은 전회에서 모두 설명드렸던 것들

입니다. 이제 마지막으로 연결하는 부분을 보여드리겠습니다. 이것 또한 전회에서 보여드렸던 기본 로직

과 거의 동일한 선상의 연속입니다.

function TCamDShow.ConnectBaseFilter: HRESULT;

var

InPin : IPin;

OutPin : IPin;

hr : HRESULT;

begin

Result := S_OK;

// 카메라와 스마트티를 연결한다.

FindPinOnFilter(Cam,PINDIR_OUTPUT,OutPin); //Cam 에서 첫번째 출력핀을 얻어낸다

FindPinOnFilter(SmartTee,PINDIR_InPUT,InPin); //SmartTee 에서 첫번째 입력핀을 얻는다

hr := FilterGraph.Connect(OutPin,InPin); // 필터그래프가 두개의 핀을 연결한다

if hr <> S_OK then Result := S_FALSE;

Page 86: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 86 -

OutPin := NIL;

InPin := NIL;

// 스마트티와 먹스를 연결한다.

FindPinOnFilter(SmartTee,PINDIR_OUTPUT,OutPin); //SmartTee 에서 첫째 출력핀을 얻는다

FindPinOnFilter(AviMux,PINDIR_InPUT,InPin); //AviMux 에서 첫번째 입력핀을 얻는다

hr := FilterGraph.Connect(OutPin,InPin); // 필터그래프가 두개의 핀을 연결한다

if hr <> S_OK then Result := S_FALSE;

OutPin := NIL;

InPin := NIL;

// 먹스와 파일라이터를 연결한다.

FindPinOnFilter(AviMux,PINDIR_OUTPUT,OutPin); //AviMux 에서 첫번째 출력핀을 얻어낸다

FindPinOnFilter(FileWriter,PINDIR_InPUT,InPin); //FileWriter 에서 첫째 입력핀을 얻는다

hr := FilterGraph.Connect(OutPin,InPin); // 필터그래프가 두개의 핀을 연결한다.

if hr <> S_OK then Result := S_FALSE;

OutPin := NIL;

InPin := NIL;

// 스마트티와 그래버를 연결한다.

FindPinOnFilter(SmartTee,PINDIR_OUTPUT,OutPin); //SmartTee 에서 둘째 출력핀을 얻는다

FindPinOnFilter(Grabber,PINDIR_InPUT,InPin); //Grabber 에서 첫째 입력핀을 얻는다

hr := FilterGraph.Connect(OutPin,InPin); // 필터그래프가 두 개의 핀을 연결한다

if hr <> S_OK then Result := S_FALSE;

OutPin := NIL;

InPin := NIL;

// 그래버와 랜더러를 연결한다.

FindPinOnFilter(Grabber,PINDIR_OUTPUT,OutPin); //Grabber 에서 첫번째 출력핀을 얻는다

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

Page 87: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 87 -

end;

위의 코드에서 연결되는 순서가 중요하다는 것을 인식하셔야 합니다. 즉, SmartTee필터에서 두개의

OutPut 핀이 각각 연결되는 순서가 차례로 이루어지고 있다는 것입니다. SmartTee 필터와 AviMux필

터가 먼저 연결되고 나서 나중에 SmartTee 필터와 Grabber 필터가 연결되었다는 것은, 다시말해서

SmarTee 필터의 첫번째 핀인 Capture핀이 Avi Mux 필터와 연결되었고, 나중에 두번째 핀인 Preview

핀이 Grabber핀에 연결되었음을 의미합니다. 만약 위의 코드에서 순서를 바꾼다면 SmarTee 필터의

Captuer핀에 Grabber 필터가 연결되고, Preview 핀에 AviMux필터가 연결되어서 다음과 같은 순서의

연결이 됨을 의미합니다.

Cam Filter Smar Tee Sample Grabber Video Renderer

Avi Mux File Writer

위와같이 연결되었을 경우, 문제가 발생할 것이라고 저는 지난회에 설명을 드렸습니다. 즉, SmartTee

필터의 Preview 핀에서 나오는 프레임에는 '타임스탬프'가 제거된 상태이기 때문에 AviMux로 들어가서

는 오류를 일으키거나 아예 저장되지 않는다고 말이죠. 실제로 제가 이번에 또 실수를 해서 두개를 거

꾸로 연결하였습니다. 그랬더니 Avi 파일이 생성은 되었으나 기본 헤더정보만이 있는 껍데기 파일만이

생성될뿐, 기본적인 동영상 데이터는 하나도 저장되질 않았습니다. 자, 연결되는 순서가 중요하다고 말

씀을 드렸습니다. 그런데 이렇게 순서를 중요하게 생각하지 않아도 되게끔 여러분이 FindPinOnFilter

함수를 원형 그대로 사용하실 수도 있습니다. 제가 지난회에서 말씀드렸다시피 이 함수의 C++ 소스 원

형에는 핀의 순서를 지정해서 찾을 수 있게끔 되어있었다고 하였습니다. 저의 경우는 그냥 사용하기 편

리하기 위하여 FindPinOnFilter 함수의 핀위치 지정 매개변수를 생략하는 방식으로 단순화 시켰다고 말

씀을 드렸습니다. 여러분이 이 부분에 대하여 직접 살펴보기를 원하신다면 신화선님의 책에 원형이 나

와 있을 것으로 기억이 되는데요, 지금 열심히 한번 찾아보았습니다만 눈에 띄질 않네요. 이상하지만 어

쨌든, 아마도 어딘가에 있을 것입니다. 위의 ConnectBaseFilter 함수를 보시면 눈치 채셨겠지만, 실제

는 위와같이 동일한 코드가 줄줄이 반복되는게 여간 불필요한 것이 아니라는 사실을 아실 것입니다. 제

가 위와같이 반복적인 코드로 만들어 놓은 것은 여러분의 이해를 돕고자 함이었습니다. 여러분은 실제

사용에서는 위의 코드를 단순화시켜서 별도의 함수를 하나 내어서 사용하시는 것이 더 편리할 것입니다.

제가 다음과 같이 한번 만들어 보았습니다.

function TCamDShow.ConnectBaseFilter: HRESULT;

Page 88: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 88 -

var

hr : HRESULT;

function ConnectFilter(A, B: IBaseFilter): HRESULT;

var

InPin : IPin;

OutPin : IPin;

begin

FindPinOnFilter(A,PINDIR_OUTPUT,OutPin);

FindPinOnFilter(B,PINDIR_InPUT,InPin);

Result := FilterGraph.Connect(OutPin,InPin);

if Result <> S_OK then ShowMessage( ' 필터 연결에 실패하였습니다.' );

end;

begin

Result := S_OK;

if S_OK <> ConnectFilter(Cam, SmartTee) then Exit;

if S_OK <> ConnectFilter(SmartTee, AviMux) then Exit;

if S_OK <> ConnectFilter(AviMux, FileWriter) then Exit;

if S_OK <> ConnectFilter(SmartTee, Grabber) then Exit;

if S_OK <> ConnectFilter(Grabber, VideoRender) then Exit;

end;

위와같이 단순화 시키는게 더 편하다는 설명을 위해서 샘플용으로 한번 만들어 본 것입니다. 실제는

사실 이것보다 좀 더 복잡한 로직이 필요합니다. 왜냐하면 디버깅을 위한 것이기 때문인데요, 몇번째 순

서에서 연결이 쫑났는지를 알아내는 로직도 포함시키면 실무에 상당히 편리하기 때문입니다. 제가 사용

하는 것은 이것보다는 그래서 조금 복잡합니다. 아무튼 이 부분의 디버깅에 대해서는 아주 재미있는 부

분이 남아 있습니다. 바로 GraphEidt 의 Connect to Remote Graph... 라는 서비스를 이용하는 것인데

요, 이것에 대해서는 예전회에서 간단히 설명을 드렸습니다. 그당시 나중에 직접 실습할 기회를 갖게 될

것이라고 말씀을 드렸고요, 이제 이번에 한번 정말 재미있는 실습을 하겠습니다. 이 실습은 지면 관계로,

다음 13부에서 실행하도록 하겠습니다.

추가사항추가사항추가사항추가사항 ===> 위에서 저는 필터의 생성부분에, 즉 MakeBaseFilter 라는 함수에서 아예 IFileSinkFilter2 인터페이

스를 뽑아냄과 동시에 저장할 파일을 FileSink.SetFileName('C:\dong.avi', nil); 와 같이 지정하였습니다. 여러분은

TCamDShow 클래스에 별도의 함수를 만들어서 외부 응용어플에서 지정할 수 있도록 만들어 놓으시면 좋을 것입

니다.

Page 89: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 89 -

13.13.13.13. 어플어플어플어플 9999

이제 여기까지 하시고 실행을 하신다면, 여러분의 모습이 담긴 동영상이 C:\Dong.avi 로 저장되는

것을 확인하실 수가 있으실 것입니다. 그런데 압축을 하지 않은 상태이기 때문에 작은 시간이었음에도

불구하고 파일 사이즈가 엄청날 것입니다. 만일 여러분이 압축을 하고자 하신다면 AviMux 필터 앞에서

압축코덱 하나를 새로 만들어서 연결하시면 될 것입니다. 자, 여기서 Grabber 로직을 구현하기 전에 전

회에 말씀드렸던 Connect to Remote Graph... 라는 서비스를 한번 실습해 보겠습니다.

[1][1][1][1] Connect to Remote Graph... Connect to Remote Graph... Connect to Remote Graph... Connect to Remote Graph... 의의의의 실습실습실습실습

일단 여러분이 이 서비스를 받기 위해서는 지금까지 작성하였던 로직에 단 한줄의 추가 코딩이 필요

합니다. 가장 먼저 작성하였던 TBaseDShow 클래스의 CreateFilterGraph 함수에 아래와 같이

AddGraphToRot(Graph,ID)를 추가해 놓습니다.

function TBaseDShow.CreateFilterGraph( var Graph: IGraphBuilder): Boolean;

var

ID : Integer;

begin

Result := False;

if Failed(CoCreateInstance(CLSID_FilterGraph,

nil,

CLSCTX_INPROC_SE RVER,

IID_IFilterGraph ,

Graph))

then Exit;

//GraphEdit 로 현재의 필터그래프 구성을 볼수 있게 하기 위해 추가합니다.

AddGraphToRot(Graph,ID);

Result := True;

end;

자, 위와같이 추가를 하셨으면 지금까지 작성한 프로그램을 컴파일하고 실행시켜 봅니다. Run 버튼을

Page 90: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 90 -

누르시면 이제 메임폼의 Panel에 카메라로부터 들어오는 영상이 플레이 될 것이며, 동시에 여러분의 컴

퓨터 하드의 C:\에 Dong.avi라는 파일이 실시간 저장될 것입니다. 이 상태에서 GraphEdit를 실행시키

십시오. 그리고 GraphEdit의 File 메뉴로 가셔서 아래의 'Connect to Remote Graph...' 서브메뉴를 선택

합니다. 작은 선택 다이알로그창이 뜨는데요, 여기에는 현재 작동중인 한개의 Graph가 올라와 있을 것

입니다. 'OK'를 선택하시면 여러분의 GraphEdit의 메인화면에는 현재 작동시키고 있는 프로그램 내부의

모든 필터들이 비주얼하게 연결된 상태로 보여질 것입니다.

이 서비스가 필요로되는 상태가 간혹 있습니다. 예를 들어서 참으로 미스테리우스하게도 모든 필터들

이 분명히 연결이 되었는데도 불구하고 작동이 안되거나, 지난밤 퇴근하기 직전에 분명히 랜더링까지

모두 테스트하고 퇴근했는데 다음날 아침 실행해보니 작동불능으로 빠진다거나... 이 모든 현상이 물론

과로와 스트레스로 프로젝트를 진행함에 있어서 집중력이 떨어지기 때문에 발생하는 것이겠습니다만,

어찌되었든, 이런상황에서 현재 실행중에 있는 프로그램의 내부 Graph의 수많은 필터들의 연결 모습을

직접 눈으로 확인하는게 정신건강에 좋을 때가 있을 것입니다. 이럴때 간혹가다가 사용하시라고 권해

드리고 싶습니다. 그러나 너무 자주 무리하게 사용하시지는 말시길 바랍니다. 왜냐하면 이 서비스를 사

용하다가 프로그램이 다운될 가능성이 무려 20%정도에 달하기 때문입니다. 만일 여러분의 자작 필터가

중간에 끼여 있는 경우라면, 또한 그 자작 필터가 완성도가 떨어지는 경우라면 'Connect to Remote

Graph...'서비스를 받다가 중간에 다운될 가능성은 몇배로 커질 것입니다. 따라서 간혹가다가, 정신이 아

주 지치고 무기력할때, 이상하게 필터들의 연결이 분명 다 이루어 졌는데도 불구하고 작동이 안되어서

한번 눈으로 직접 확인하고자 싶을때, 그럴 때 간혹가다가 사용하시기를 권해 드립니다.

[[[[2222]]]] GrabberGrabberGrabberGrabber를를를를 사용하자사용하자사용하자사용하자

여러분이 처음 DShow 어플을 시작하면서 호시심을 갖는 것이 대부분 이 Grabber일 것입니다. 그러

나 이러한 호기심은 필터제작을 집적 해내기 시작하면서 관심을 잃어버리게 되는데요, 저도 마찬가지여

서 예전에 만들어 놓았던 샘플 소스를 찾기가 힘들게 되었습니다. 제가 이 강의를 하기 위해서 새로 만

든 것은 그래서 DSPack에 사용된 원형 그대로라는 것을 말씀 드리겠습니다. 일단 다음과 같은 함수를

보시겠습니다.

function TCamDShow.GetBitmap(Bitmap: TBitmap): Boolean;

var

BufferLen : Integer;

hr: HRESULT;

BIHeaderPtr: PBitmapInfoHeader;

MediaType: TAMMediaType;

Page 91: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 91 -

BitmapHandle: HBitmap;

DIBPtr: Pointer;

DIBSize: LongInt;

function GetDIBLineSize(BitCount, Width: Integer): Integer;

begin

if BitCount = 15 then

BitCount := 16;

Result := ((BitCount * Width + 31) div 32) * 4;

end;

begin

Result := False;

//Bitmap 가 nil 이면 종료.

if not Assigned(Bitmap) then Exit;

// 미디어타입을 제대로 가져오지 못했으면 종료.

hr := GrabberInf.GetConnectedMediaType(MediaType);

if hr <> S_OK then Exit;

// 현재 미디어타입의 주요타입이 Video 가 아니라면 종료. 당연히 Video 여야 한다.

//Audio 면 안되겠지요.

if not IsEqualGUID(MediaType.majortype, MEDIATYPE_Video) then Exit;

try

BIHeaderPtr := Nil;

if IsEqualGUID(MediaType.formattype, FORMAT_VideoInfo) then

begin

if MediaType.cbFormat = SizeOf(TVideoInfoHeader) then // check size

BIHeaderPtr := @(PVideoInfoHeader(MediaType.pbForma t)^.bmiHeader);

end

else if IsEqualGUID(MediaType.formattype, FORMAT_VideoInfo2 ) then

begin

if MediaType.cbFormat = SizeOf(TVideoInfoHeader2) then // check size

BIHeaderPtr := @(PVideoInfoHeader2(MediaType.pbForm at)^.bmiHeader);

end;

// check, whether format is supported by TSampleGra bber

Page 92: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 92 -

if not Assigned(BIHeaderPtr) then Exit;

BitmapHandle := CreateDIBSection( 0, PBitmapInfo(BIHeaderPtr)^,

DIB_RGB _COLORS, DIBPtr, 0, 0);

if BitmapHandle <> 0 then

begin

try

if DIBPtr = Nil then Exit;

// get DIB size

DIBSize := BIHeaderPtr^.biSizeImage;

if DIBSize = 0 then

begin

with BIHeaderPtr^ do

DIBSize := GetDIBLineSize(biBitCount, biW idth) * biHeight * biPlanes;

end;

BufferLen := 0;

hr := GrabberInf.GetCurrentBuffer(BufferLen , Nil);

if (hr <> S_OK) or (BufferLen <= 0) then Exit;

// copy buffer to DIB

if BufferLen > DIBSize then BufferLen := DIBSize;

hr := GrabberInf.GetCurrentBuffer(BufferLen , DIBPtr);

if hr <> S_OK then Exit;

Bitmap.Handle := BitmapHandle;

Result := True;

finally

if Bitmap.Handle <> BitmapHandle then DeleteObject(BitmapHandle);

end;

end;

finally

FreeMediaType(@MediaType);

end;

end;

자, 위의 함수를 TCamDShow 클래스에 새로 기입해 놓으시길 바랍니다. 위의 소스는 DSPack의

Grabber 컴포넌트가 사용중인 GetBitmap 함수의 원형을 제가 조금 다듬은 것입니다. 원래 제가 가지고

있던 소스는 이것보다 훨씬 단순했던 것으로 기억하는데요, 어찌되었든 위의 소스가 훨씬 범용적인 가

Page 93: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 93 -

치를 가지고 있으므로 그냥 전체를 몽땅 사용하시는게 좋을 것이리라 생각합니다. 각각의 코드는

Bitmap의 구조를 생성시키는 부분이라 별도로 강의를 하지는 않겠습니다. 또한 MediaType을 가져오는

부분에 대해서는 앞으로 필터만들기 강좌에서 밥먹듯이 다뤄야 할 부분이기 때문에 여기서 별도로 다루

지는 않겠습니다. MediaType을 여러분은 어느정도 익숙해지셔야 하는데요, 응용어플 수준에서는 아직

때가 아니라는 생각이 들기 때문에 일단은 여기까지로 해설은 접도록 하겠습니다.

자, 위와같이 제가 계획했던 모든 USB 카메라 Play 프로그램이 완성되었습니다. 지금까지 만든

Class를 사용하시는 방법은 다음과 같습니다.

1) 폼위에 Panel 1개와 TImage 1개를 각각 올려 놓는다. 이때 크기는 320 * 240 으로 합니다.

2) 3개의 버튼을 준비한다. 각각 Run, Stop, Snapshot 이라고 지정합니다.

3) Run, Stop의 버튼 이벤트 핸들러 프로시저는 전과 동일하게 작성합니다.

4) Snapshot 버튼의 이벤트 핸들러 프로시저는 다음과 같습니다.

procedure TfrmMain.Button1Click(Sender: TObject);

begin

CamDShow.GetBitmap(Image1.Picture.Bitmap);

end;

이제 여러분이 실행중에 Snapshot 버튼을 누르시면 TImage 위에 캡쳐된 이미지가 보이실 것입니다.

여기까지 그래버의 사용법에 대하여 마치겠습니다.

[3][3][3][3] DShowDShowDShowDShow의의의의 어플편을어플편을어플편을어플편을 끝내면서끝내면서끝내면서끝내면서... ... ... ...

지금까지 여러분은 DShow의 기본적인 프로그래밍 과정을 터득하셨습니다. 지금까지의 과정에서 부족

했던 부분은 여러분의 과제물로 남겨놓고 싶습니다. 즉, 카메라로부터의 입력이 아니라 동영상 파일을

로딩하여 플레이 하는 것과, 압축필터를 연결하여 저장하는 방식은 반드시 한번 정도 코딩해 보시기를

권해 드립니다. 아마도 여기까지 무리없이 달려오셨다면 이정도 수준은 무난한 것이라고 생각합니다. 여

러분은 이제 두개의 클래스 유닛을 확보하고 계실 것입니다. TBaseDShow 가 구현된 Pas파일과

TCamDShow가 구현된 Pas 파일이 그것입니다. TBaseDShow는 전과 동일한 것이니 여기서 풀소스를

Page 94: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 94 -

새로 보이는 것은 낭비일 것 같습니다. 하지만 TCamDShow는 추가된 부분이 적지 않으니 그 풀소스를

아래와 같이 옮겨 놓겠습니다. 참고하시길 바랍니다.

unit cCamDShow;

interface

uses

Windows, Dialogs, SysUtils, Classes, Registry, Dire ctShow9, ActiveX, ExtCtrls,

DsUtil, cBaseDShow, Graphics;

type

TCamDShow = class(TBaseDShow)

private

Cam: IBaseFilter;

VideoRender: IBaseFilter;

SmartTee: IBaseFilter;

AviMux: IBaseFilter;

Grabber: IBaseFilter;

FileWriter: IBaseFilter;

GrabberInf: ISampleGrabber;

FileSink: IFileSinkFilter2;

public

constructor Create(Screen:TPanel);

destructor Destroy; override;

function MakeBaseFilter:HRESULT;

function ReleaseBaseFilter:HRESULT;

function ConnectBaseFilter:HRESULT;

procedure Run;

procedure Stop;

function GetBitmap(Bitmap: TBitmap): Boolean;

end;

implementation

Page 95: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 95 -

{ 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, 'VdRender Filter' ); // 비디오 랜더러를 등록한다.

if VideoRender = nil then Result := S_FALSE;

Page 96: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 96 -

//---------------------------- 추가부분 --------------------------------------

// 스마트티 필터를 생성하고 추가한다.

CreateFilter(CLSID_SmartTee, SmartTee);

FilterGraph.AddFilter(SmartTee, 'SmartTee Filter' );

if SmartTee = nil then Result := S_FALSE;

// 먹스 필터를 생성하고 추가한다.

CreateFilter(CLSID_AviDest,AviMux);

FilterGraph.AddFilter(AviMux, 'AviMux Filter' );

if AviMux = nil then Result := S_FALSE;

// 그래버 필터를 생성하고 추가한다.

CreateFilter(CLSID_SampleGrabber,Grabber);

FilterGraph.AddFilter(Grabber, 'Grabber Filter' );

if Grabber = nil then Result := S_FALSE;

Grabber.QueryInterface(ISampleGrabber, GrabberInf );

GrabberInf.SetBufferSamples(True); // 요게 없으면 실행이 안된다. 쩝...

// 파일라이터 필터를 생성하고 추가한다.

CreateFilter(CLSID_FileWriter,FileWriter);

FilterGraph.AddFilter(FileWriter, 'FileWriter Filter' );

if FileWriter = nil then Result := S_FALSE;

FileWriter.QueryInterface(IID_IFileSinkFilter2, F ileSink);

FileSink.SetFileName( 'C:\dong.avi' , nil);

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

end;

function TCamDShow.ConnectBaseFilter: HRESULT;

var

hr : HRESULT;

function ConnectFilter(A, B: IBaseFilter): HRESULT;

var

InPin : IPin;

OutPin : IPin;

Page 97: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 97 -

begin

FindPinOnFilter(A,PINDIR_OUTPUT,OutPin);

FindPinOnFilter(B,PINDIR_InPUT,InPin);

Result := FilterGraph.Connect(OutPin,InPin);

if Result <> S_OK then ShowMessage( ' 필터 연결에 실패하였습니다.' );

end;

begin

Result := S_OK;

if S_OK <> ConnectFilter(Cam, SmartTee) then Exit;

if S_OK <> ConnectFilter(SmartTee, AviMux) then Exit;

if S_OK <> ConnectFilter(AviMux, FileWriter) then Exit;

if S_OK <> ConnectFilter(SmartTee, Grabber) then Exit;

if S_OK <> ConnectFilter(Grabber, VideoRender) then Exit;

end;

function TCamDShow.ReleaseBaseFilter: HRESULT;

begin

if Assigned(MediaControl) then MediaControl.Stop;

FilterGraph.RemoveFilter(Cam);

FilterGraph.RemoveFilter(VideoRender);

//---------------------------- 추가부분 --------------------------------------

FilterGraph.RemoveFilter(SmartTee);

FilterGraph.RemoveFilter(AviMux);

FilterGraph.RemoveFilter(FileWriter);

FilterGraph.RemoveFilter(Grabber);

While Assigned(Cam) do Cam := nil;

While Assigned(VideoRender) do VideoRender := nil;

//---------------------------- 추가부분 --------------------------------------

While Assigned(SmartTee) do SmartTee := nil;

While Assigned(AviMux) do AviMux := nil;

While Assigned(Grabber) do Grabber := nil;

While Assigned(FileWriter) do FileWriter := nil;

Page 98: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 98 -

While Assigned(FileSink) do FileSink := nil;

While Assigned(GrabberInf) do GrabberInf := 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;

function TCamDShow.GetBitmap(Bitmap: TBitmap): Boolean;

var

BufferLen : Integer;

hr: HRESULT;

BIHeaderPtr: PBitmapInfoHeader;

MediaType: TAMMediaType;

BitmapHandle: HBitmap;

DIBPtr: Pointer;

DIBSize: LongInt;

function GetDIBLineSize(BitCount, Width: Integer): Integer;

begin

if BitCount = 15 then

BitCount := 16;

Result := ((BitCount * Width + 31) div 32) * 4;

end;

begin

Result := False;

//Bitmap 가 nil 이면 종료.

if not Assigned(Bitmap) then Exit;

Page 99: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 99 -

// 미디어타입을 제대로 가져오지 못했으면 종료.

hr := GrabberInf.GetConnectedMediaType(MediaType);

if hr <> S_OK then Exit;

// 현재 미디어타입의 주요타입이 Video 가 아니라면 종료. 당연히 Video 여야 한다.

//Audio 면 안되겠지요.

if not IsEqualGUID(MediaType.majortype, MEDIATYPE_Video) then Exit;

try

BIHeaderPtr := Nil;

if IsEqualGUID(MediaType.formattype, FORMAT_VideoInfo) then

begin

if MediaType.cbFormat = SizeOf(TVideoInfoHeader) then // check size

BIHeaderPtr := @(PVideoInfoHeader(MediaType.pbForma t)^.bmiHeader);

end

else if IsEqualGUID(MediaType.formattype, FORMAT_VideoInfo2 ) then

begin

if MediaType.cbFormat = SizeOf(TVideoInfoHeader2) then // check size

BIHeaderPtr := @(PVideoInfoHeader2(MediaType.pbForm at)^.bmiHeader);

end;

// check, whether format is supported by TSampleGra bber

if not Assigned(BIHeaderPtr) then Exit;

BitmapHandle := CreateDIBSection( 0, PBitmapInfo(BIHeaderPtr)^,

DIB_RGB _COLORS, DIBPtr, 0, 0);

if BitmapHandle <> 0 then

begin

try

if DIBPtr = Nil then Exit;

// get DIB size

DIBSize := BIHeaderPtr^.biSizeImage;

if DIBSize = 0 then

begin

with BIHeaderPtr^ do

DIBSize := GetDIBLineSize(biBitCount, biW idth) * biHeight * biPlanes;

end;

BufferLen := 0;

Page 100: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 100 -

hr := GrabberInf.GetCurrentBuffer(BufferLen , Nil);

if (hr <> S_OK) or (BufferLen <= 0) then Exit;

// copy buffer to DIB

if BufferLen > DIBSize then BufferLen := DIBSize;

hr := GrabberInf.GetCurrentBuffer(BufferLen , DIBPtr);

if hr <> S_OK then Exit;

Bitmap.Handle := BitmapHandle;

Result := True;

finally

if Bitmap.Handle <> BitmapHandle then DeleteObject(BitmapHandle);

end;

end;

finally

FreeMediaType(@MediaType);

end;

end;

end.

***********************************************************************************************

여기까지 수고 많으셨습니다. 그럼 다음 필터편에서 찾아뵙기로 하겠습니다. 건강하시기 바랍니다. ^^

***********************************************************************************************

Page 101: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 101 -

14.14.14.14. 필터필터필터필터 1111

지금부터 델파이로 DShow 필터를 개발하는데 있어서의 강의를 진행해 나가기로 하겠습니다. 제가 이

강의를 처음 시작헀던 이유는, VC++ 과는 다르게 유독 델파이 만이 DShow의 혜택을 받지 못하고 있

다는 생각이 들었기 때문입니다. 사실 제가 처음 DShow를 시작했을때가 SDK 버전이 8.0 이었는데요,

그당시의 DSPack 버전에 있는 참고소스로는 델파이로 필터를 개발하기가 사실상 어려웠습니다. 왜냐하

면 굳이 VC++ 필터소스를 가지고 델파이로 일일이 포팅해야 했는데요, 그 과정이 너무 지리하여 차

라리 VC++을 더 익히는게 낳을 지경이었기 때문입니다. 하지만 이제 세월은 흘러 흘러, DSPack에서

도 많은 필터샘플 소스를 제공하고 있습니다. 아직까지도 VC++용으로 준비되어 있는 SDK의 기본 예

제보다는 상당히 부족한 편이지만, 어느정도 할만하다는 입장이 되어버린 것입니다. 이러한 생각을 하면

서 예전 에 MS를 풍자한 만화의 한 부분이 떠오릅니다. MS의 수석 프로그래머들이 아주 어려운 개발

패키지를 내놓고서는 팅가팅가 하고 있던 차에 볼랜드 진영에서 다 풀어서 사용하고 있다는 소식을 접

하자, 그렇게 놀고 있던 두명의 프로그래머가 자리에서 벌떡 일어나서 한다는 소리가... '아니 걔들이 벌

써 다 이해했어? 그렇다면 이제 더 복잡하게 만들어야 겠는걸?' 이렇게 말하며 서로를 쳐다보는 것이었

는데요, DShow 프로그래밍을 할 때마다 이 만화가 생각나서 조금은 언짢은 기분이 되기도 합니다.

이야기가 또다시 삼천포로 빠졌습니다. 그렇습니다. 이제 델파이에서도 DShow용 필터만들기가 어느

정도 할만하다는 수준까지 올라왔습니다. 그런데 이것은 어느정도 할만하다는 수준을 넘어서 이제

VC++과 당당히 어깨를 겨룰만큼 장단점을 가지고 있게 된 것입니다. 그 첫번째 장단점이 컴파일의 속

도입니다. VC++로 필터를 개발하면 아시겠지만 컴파일 속도가 상당히 느려터집니다. 그러나 델파이로

컴파일을 하면 속도가 거의 느껴지지 않을 정도로 순신간에 이뤄집니다. VC++과 마찬가지로 인라인

어셈블리를 사용할 수 있으며 동영상 변환로직의 핵심부분을 MMX나 SSE로 최적화 할수있다는 장점도

거의 대등할 것입니다. 이제 우리는 DSPack에 실려있는 풍부한 참고용 소스를 가지고 이제까지 서러워

했던 분야에서 진정으로 독자적인 필터 개발을 시도할 수가 있게 된 것입니다. 하지만 아직까지 단 하

나의 아쉬운 점이라면 DShow의 도움말에 나와있는 모든 소스가 C++이기 때문에 결국, VC++도 함께

알고는 있어야 한다는 것이 약간의 딜레마라는 정도입니다. (물론 VC++ 프로그래머가 이 말을 들으면

비웃을 수도 있겠습니다. 쩝...)

우리가 델파이로 필터를 만드는 것을 그렇게 어렵게 생각하지 않았으면 합니다. 일단 껍데기를 만들

어 보겠습니다. 여러분은 다음과 같이 전형적인 DLL Project 파일을 연상하시면 되겠습니다. File ->

New -> Other -> New 탭 -> DLL Wizard 를 선택하셔도 되고요, 아니면 기본 프로젝트에서 폼파일과

유닛파일을 제거하고 남은 프로젝트로 파일을 다음과 같이 변경하셔도 되겠습니다.

library TestTrans;

Page 102: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 102 -

uses

BaseClass;

{$E ax}

exports

DllGetClassObject,

DllCanUnloadNow,

DllRegisterServer,

DllUnregisterServer;

begin

end.

자, 위에서 보시면 전형적인 DLL 프로젝트인 것을 아실 것입니다. 그러나 export에는 달랑 4개의 함

수만이 외부로 노출시켜 놓았네요. 이 4개의 함수는 앞으로 여러분이 델파이로 필터를 개발하면서 영원

히 수정하지 않아도 될 것들입니다. 이미 BaseClass.Pas에 잘 구조화 되어 만들어져 있기 때문입니다.

하지만 그 역활에 대해서는 한번쯤 알아보는게 좋을 것입니다.

여러분이 필터를 만들고서 윈도우에 등록시키기 위해서는 Regsvr32라는 명령어를 사용하실 터인데요,

이것을 사용하실때 실행되는 명령이 바로 위에서의 DllRegisterServer 입니다. 이 함수를 별도로 개발

하실수도 있겠습니다만, 굳이 그럴 필요가 없을 것입니다.

참고참고참고참고

위의 4가지 함수의 형식과 이름은 In Process COM Server의 약속된 구조입니다. 따라서 함수의 작동원리는 여러

분이 별도로 작성하실 수가 있겠으나 가능하면 BaseClass.pas에 이미 정의되어 있는 것을 사용하시면 좋을 것입니

다. 만일 여러분이 ActiveX를 개발하신다면 마찬가지로 위와같은 네가지 함수가 Export 되어 있는 것을 보실 수가

있을 것입니다. 그러나 ActiveX에서 네가지 함수가 선언된 유닛은 BaseClass.pas가 아니라 ComServ.Pas로 표시되

어 있으며 서로 다른 형태로 만들어져 있는 것을 확인하실 수가 있을 것입니다. 여기서 여러분은 COM 프로그래밍

이 형식화된 인터페이스의 약속일 뿐이지 그 내용과 기능까지 완전히 정해져 있는 것이 아니라는 점을 반드시 유념

해야 할 것입니다. BaseClass.pas에 선언되어 있는 이 네가지 기능의 함수는 또다시 아주아주 예전 버전에서는 완

전히 다른 형태로 만들어져 있는 것을 확인하실 수가 있습니다.

Page 103: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 103 -

가장 아랫부분에 선언되어 있는 DllUnregisterServer 함수는 당연히도 DllRegisterServer 의 반대입니다. 즉

RegSvr32 함수의 -u 옵션으로 필터의 등록을 해지할 때 실행되는 것입니다. 여기서 한가지 확인을 하고 넘어가겠

습니다. 만일 여러분이 aaa.ax라는 필터를 개발하셨다면 여러분은 레지스트리에 다음과 같이 등록하실 수가 있습니

다.

윈도우 실행 -> Regsvr32 C:\aaa.ax

반대로 이미 등록되어 있는 필터를 등록해지하기 위해서는 다음과 같이 하시면 됩니다.

윈도우 실행 -> Regsvr32 -u C:\aaa.ax

Regsvr32는 밥먹듯이 사용해야만 하는 윈도우즈의 실행명령어이므로 항상 머릿속에 외워두고 계셔야 합니다. 아

니면 어디엔가 적어 놓도록 하십시오. 저는 처음 DShow개발을 하면서 이 함수를 간간히 까먹곤 했습니다. 지금은

너무도 당연한 것이겠지만, 또 모르지요 SI나 FA쪽 프로그래밍 반년만 하다보면 자꾸만 까먹는게 아주 습관이 되어

버렸습니다.

추가추가추가추가

아시고 계시는 분들도 있겠지만 모르시는 분들을 위하여 {$E ax} 를 설명하겠습니다. 이것은 여러분의 프로젝트를

컴파일하였을 때 그 확장자가 ax를 가지도록 합니다. 이게 상당히 편리합니다. 여기다 {$E dll} 이렇게 선언해 놓으

면 확장자가 dll인 파일이 만들어지게 됩니다. 저는 DShow의 범용 필터를 개발할 때에는(아직까지 한번도 범용

DShow 필터를 개발하지는 않았음) ax 확장자를, 특정한 DShow 용 어플에 맞는, 맞춤용 필터를 개발할 때에는 dll

이라는 확장자를 그냥 사용하고 있습니다.

이제 나머지 두개의 함수 DllGetClassObject, DllCanUnloadNow 에 대하여 설명하겠습니다. 먼저

도움말 파일에 있는 설명을 옮겨보겠습니다.

DllGetClassObject : 클래스 팩토리의 인스턴스를 생성한다.

DllCanUnloadNow : DLL 가 안전하게 언로드 가능한가 어떤가를 문의한다

위에서 클래스 팩토리란 COM 개체의 생성 처리를 특화한 또다른 COM 개체를 의미합니다. 이 클래

스 팩토리에 관한 사항은 COM 프로그래밍 일반에 관한 사항이므로 여기서는 설명을 생략하기로 하겠

습니다. 여러분이 예전에 DShow 어플편에서 보셨던 CoCreateInstance 함수를 사용하면 COM 라이브

러리에서 CoGetClassObject 함수가 실행되며 이것이 DllGetClassObject 를 실행하고, 여기서 클래스

팩토리를 만들면서 필터라는 COM Obejct의 인스턴스를 생성하게 되는 것입니다. 이것을 도식화 하면

Page 104: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 104 -

다음과 같습니다.

CoCreateInstance COM 라이브러리의 CoGetClassObject DllGetClassObject 클래스 팩토리

필터

DllCanUnloadNow 함수는 위에서는 DLL이 안전하게 언로드 가능한가를 문의한다라고 되어 있는데요,

이것은 너무나 간단한 설명인것 같습니다. 하지만 물론 틀린 말도 아닙니다. 자, 우리가 COM Server를

실행시켜서 그 안에 있는 COM Object 를 인스턴스화 시키면 COM Server 내부에서는 일종의 카운터가

작동하게 됩니다. 즉, 아무개 COM Object의 인스턴스가 2개 만들어 졌고, 또 다른 아무개 COM Object

의 인스턴스가 3개 만들어 졌고... 이런식으로 카운트 정보를 가지고 있는 것입니다. 그러다가 현재

COM Object의 활성화된 인스턴스가 아무것도 없다고 판단되면 자동으로 COM Server가 언로딩되는

것인데요, 이때 현재 언로드할 수 있느냐고 물어보는 것이 바로 이 함수입니다.

BaseClass.pas에 선언되어 있는 함수의 원형은 정말로 간단합니다.

function DllCanUnloadNow: HResult; stdcall;

begin

if (ObjectCount = 0) and (FactoryCount = 0) then

result := S_OK else result := S_FALSE;;

end;

위와 같이 현재 오브젝트 카운트가 0 이고 팩토리 카운트가 0 이라면 S_OK를, 그렇지 않다면

S_FALSE를 반환하고 있습니다. COM 라이브러리는 이 함수를 실행시켜 현재 COM Server를 언로딩

시켜야 할지 어떨지를 수시로 확인하는 것입니다. 우리는 DSHow의 가장 처음에 COM 을 초기화한다고

해서 CoInitialize, 반대로 COM을 소멸시키기 위하여 CoUninitialize라는 명령어를 사용하였습니다. 이

것이 바로 COM 라이브러리라는 것입니다.

자, 여기까지 여러분은 가장 Base적인 부분을 살펴보았습니다. 이 부분은 필터개발을 하면서 거의 수

정하지 않아도 될 것들입니다. 사실 COM 프로그래밍의 가장 기본적인 부분이지만, 오히려 너무나 쉽게

갖다 사용하는데 적응이 되어서 이해하기가 가장 껄끄러운 부분이기도 합니다. 저도 이 부분을 강의하

는데, 여러가지 책을 또다시 뒤적거려 예전의 기억을 추려내야 할 정도였습니다. 아무튼 여러분이 델파

이로 프로젝트를 하면서 프로젝트파일을 거의 거들떠 보지도 않는 것처럼, 필터개발을 하면서도 이 부

Page 105: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 105 -

분에 대해서는 마찬가지로 거들떠보지 않아도 될 것들입니다.

자, 이제 여러분은 아주 아주 간단한 필터를 하나 만들어 보겠습니다. 여러분의 현재 프로젝트에서 유

닛을 하나 추가하시고, 그 유닛의 이름을 uMain 라고 이름붙여 보겠습니다. 일단 Interface부분의 Uses

에다가 필요한 유닛을 선언해야 겠습니다. 자 무엇이 있을까요. 먼저 WIndows와 ActiveX가 필요합니다.

그리고 나머지는 앞으로 버릇 처럼 자동으로 선언해 놔야할 중요한 것들, BaseClass, DirectShow9,

DSUtil 입니다. 이 세가지를 추가 선언해 두기로 하겠습니다. 필터개발에 있어서 아주 핵심이 되는, 꼭

필요한 유닛이라고 기억해 두시기 바랍니다.

그 다음 필요한 것은 여러분이 개발할 필터의 CLSID를 만드시는 일입니다. 예전에 제가 델파이에서

GUID를 만드는 법을 이야기한 적이 있습니다. 델파이 Editor 에다 커서를 올려놓고 Ctrl + Shif + G 키

를 동시에 누르면 된다고 하였습니다. 이렇게 만들어지는 GUID는 비단 필터의 CLSID로서만 사용되어

지는 것이 아니라, 필터의 인터페이스 ID로도 사용되어 지기 때문에 앞으로 너무도 반복적으로 활용하

셔야 하는 기능입니다. 여러분이 필터를 만들었지만 자신이 만든 필터에 인터페이스가 없다면 외부의

응용어플에서 컨트롤할 수가 없을 것입니다. 따라서 인터페이스까지는 기본이며, 여기다 만일 범용의 필

터개발을 생각하신다면 프로퍼티페이지를 추가로 만드셔야 하는데요, 여기에도 GUID가 사용되 어지는

것입니다.

다음은 필터의 CSLID를 선언하는 방식을 나타내었습니다.

const

CLSID_TTestTrans: TGUID = '{5C4B9332-ED86-4465-9024-6C167D552AF4}' ;

이제 가장 중요한 본론으로 들어가보겠습니다. 하지만 현재의 단계에서는 너무도 간단하니 먼저 실망

하지 마시길 바랍니다. 여러분은 COM Obejct 의 클래스를 선언하고 구현해야 합니다. 일단 다음과 같

이 선언해 보겠습니다.

type

TTestTrans = class(TBCTransInPlaceFilter)

function Transform(Sample: IMediaSample): HRESULT; override;

function CheckInputType(mtin: PAMMediaType): HRESULT; override;

end;

Page 106: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 106 -

TTestTrans라는 COM 클래스를 선언하였습니다. 그런데 이것을 자세히 보니

TBCTransInPlaceFilter라는 놈에서 상속을 받은 것입니다. 그렇습니다. BaseClass.pas에는 참으로 많

은 종류의 필터 객체가 선언되고 구현되어 있습니다. 여러분은 이것들 중에서 필요한 것을 골라 상속

받아서 여러분이 원하는 필터를 개발하실 수가 있는 것입니다. 일단 이 상속관계에 관해서는 뒤에 가서

좀더 상세한 해설을 하기로 하겠습니다. 여기서는 단지 상속 받아서 원하는 새로운 종류의 필터를 개발

한다라는 정도만 이해하시면 되겠습니다.

위에서 두개의 함수가 override되어 있습니다. Transform 함수는 부모에서 abstract로 선언되어 있기

때문에 반드시 재정의 되어져야 할 것입니다. 그리고 또다른 하나인 CheckInputType 함수에 대하여는

후에 필터의 연결에서 핀협상에 관하여 설명할때 자세히 말씀드릴 것입니다. 일단은 이렇게 두가지 함

수를 override하여 다음과 같이 구현합니다.

function TTestTrans.CheckInputType(mtin: PAMMediaType): HRES ULT;

begin

result := S_OK;

end;

function TTestTrans.Transform(Sample: IMediaSample): HRESULT ;

begin

result := NOERROR;

end;

위에서 보시다시피 아주 간단합니다. 아무런 일도 하지 않고 S_OK의 값을 리턴하고 있습니다. 여기

서 NOERROR을 확인해보시면 아시겠지만 S_OK와 마찬가지로 0 값입니다. 제가 이렇게 같은 값이면

서도 별도로 표현한 것은 우리가 사용하는 BaseClass.pas에 위의 두가지 표현이 뒤섞여서 사용되고 있

기 때문에, 지금부터 익숙해지시라는 생각에서였습니다. 이 두가지 값은 절대적으로 동일하지만 의미상

사용되어지는 곳은 약간 다르다라는 점을 유념하시면 좋겠습니다. 예전에 한번 BaseClass.pas에서

NOERROR 를 모두 S_OK로 바꿔버릴까 생각하다가 그냥 원본대로 사용하기로 하였는데요, 그 정확한

이유를 까먹어서 지금은 생각이 안나네요. 쩝...

이제 마지막으로 대미를 장식해야 하겠습니다. 여러분의 유닛의 가장 아랫부분에 initialization 를 선

언하시고 그 밑에 아래와 같이 코딩하셔야 할 것입니다.

Page 107: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 107 -

initialization

TBCClassFactory.CreateFilter( TTestTrans, '_TestTrans' , CLSID_TTestTrans,

CLSID_LegacyAmFilterCa tegory, MERIT_DO_NOT_USE, 0,

nil);

위의 구문은 본 COM Server가 로딩되자마자 실행되는 부분입니다. 이게 어째서 DllGetClassObject

보다 먼저 실행되느냐는 참으로 설명하기 난감한 부분입니다. 이것은 마치 나중에 아시겠지만 악몽같은

InPlace 필터의 내부구조를 설명하는 것의 1/3 정도 복잡함을 가지고 있습니다. 이걸 꼭 이렇게

initialization 부분에 선언해서 클래스 팩토리를 무지하게 꼬아놔야 했느냐는 점에 대하여는, 정답이 없

습니다. 하지만 이해하고 나면 이방법 밖에는 없겠네하고 머리를 끄덕여지게 만드는 그런 것입니다. 만

일 제가 이것의(클래스 팩토리의) 내부구조까지 설명한다면 이것은 DShow의 범위를 넘어서서 COM의

기초부분이 되기 때문에 얼렁뚱땅 넘어가고자 합니다. 단지, 여러분이 이 부분에 추가로 많은 COM

Object, 즉 필터라는 COM 클래스를 몇개든지 선언해 놓을 수가 있다는 것입니다. 하지만 앞으로 강의

할 내용는 위에서와 같이 오로지 1 COM Server 에(하나의 ax파일에) 달랑 1개의 필터 COM 개체를

(COM 클래스를) 만들어 놓을 것입니다.

자, 이제 위의 함수에서 하나하나 매개변수에 대하여 설명하겠습니다. 가장 처음에 있는 매개변수인

TTestTrans 라는 것은 클래스의 이름을 의미합니다. 여러분은 처음에 TTestTrans =

class(TBCTransInPlaceFilter) 이렇게 선언하셨는데요, 이것의 앞에 있는 클래스 이름을 의미합니다.

두번째 매개변수인 문자열 '_TestTrans' 요놈을 조금 유의깊게 보십시오. 이것은 여러분이 필터를 레지

스트리에 등록하고서 GraphEdit 상에서 '필터삽입윈도우'로 찾았을때 표시되어 나타나는 이름입니다. 이

것을 헷갈리지 마시기 바랍니다. 즉, 자신이 만든 필터객체의 이름을 TTestTrans라고 해놓고, 위의 문

자열 매개변수에다가 'good'이라고 해놓았으면서도 GraphEdit의 '필터삽입윈도우'에서는 이상하게도, 자

꾸만 TTestTrans라는 이름으로 찾게 된다는 것입니다. 이게 자신이 갈고딲으며 만들고 있는 COM 클

래스가 너무도 깊게 각인되어 별도의 Display 이름이 존재한다는 것을 간혹 망각한다는 것이죠. 이것은

체력이 많이 떨어졌을 때, 상당한 집중력을 요하게 되는 스트레스를 유발한다고 보시면 되겠습니다.

AnyWay...그러니까 제가 드리고 싶은 말은 가능하면 COM 클래스의 이름과 Display 이름인 문자열 매

개변수를 동일하게 맞춰 놓으싶사 하는 것이죠. 그런데 왜 위에서는 Display 이름 앞에 _(언더바)를 붙

였느냐고 물으실 것입니다. 이것은 일종의 꽁수입니다. 여러분이 만일 '필터삽입윈도우'를 보시면, 지금

은 시작단계이시니까 DirectShow Filters 카테고리에 등록되어 있는 필터의 갯수가 몇개 없으실 것이지

만, 저같은 경우는 한 화면을 넘어가고 있습니다. 필터를 개발하면서 조금 수정한 다음에 다시

GraphEdit를 작동시켜 '필터삽입윈도우'에서 자신이 개발하여 등록한 필터를 찾는, 이 반복적인 노가닥

에서 조금이라도 편하게 하기 위해서는, 가능한한 카테고리의 윗쪽에 표시되었으면 좋겠다는 것입니다.

이때 _(언더바)를 앞에 붙이시면 카테고리의 맨 윗 부분에 표시되어 나타나게 되는 것입니다. 이 꽁수는

Page 108: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 108 -

사실 제가 만들어낸 것이 아닙니다. DSPack의 샘플 예제중에 RGB24 Video Renderer 프로젝트를 보시

면 이런식으로 되어 있는 것을 보실 수가 있으실 것입니다. 저는 처음에 이것을 보고 씨익 웃었습니다.

그리고 나서 어느 순간부터 따라하게 되었습니다.

세번째 매개변수인 CLSID_TTestTrans 은 여러분이 첫부분에 Ctrl + Shit + G로 만드셨던 CLSID

의 상수값 입니다. 여기다가 지정을 해 놓아야 합니다. 그리고 네번째 매개변수인

CLSID_LegacyAmFilterCategory는 여러분의 필터가 등록될 카테고리의 GUID입니다. 이것도 예전에

설명을 드렸습니다. '필터삽입윈도우'의 DirectShow Filters 라는 카테고리에 등록되어 있는 필터들에게

만 이상하게도 두개의 GUID값이 연속해서 있는데 앞의 것이 카테고리 ID이고 뒤의 것이 필터 고유의

CLSID라고 하였지요. 자, 그리고 마지막으로 'MERIT_DO_NOT_USE, 0, nil' 이렇게 세가지 매개변 수

는, 일단 디폴트로 이렇게 놓고 사용한다라고 기억해 두시기 바랍니다. 여기까지의 풀 소스를 아래와 같

이 작성하여 놓겠습니다.

참고참고참고참고

세번째 매개변수인 CLSID의 지정이 잘못된 예제가 DSPack의 Demo 프로젝트에 있습니다. 말씀드린 RGB24 Video

Renderer 이라는 프로젝트인데요, 이 데모에서 클래스팩토리의 TBCClassFactory.CreateFilter 함수의 세 번째 매개변수가

자신의 필터 고유의 CLSID로 사용하려고 선언해둔 CLSID_DelphiVideoRenderer 이 아니라 CLSID_VideoRenderer라는,

DShow의 기본 랜더러의 고유 CLSID를 사용하고 있습니다. 이 때문에 데모예제를 컴파일하여 등록시키면 이 필터는 등록되

어지는데 반하여 중요한 DShow의 기본 랜더러필터는 사라지게 됩니다. 이 문제에 대하여는 뒤쪽에 Buglist 라는 부분에다

기술해 놓았습니다.

Unit uMain;

interface

uses

Windows,ActiveX, BaseClass, DirectShow9,DSUtil;

const

CLSID_TTestTrans: TGUID = '{5C4B9332-ED86-4465-9024-6C167D552AF4}' ;

type

TTestTrans = class(TBCTransInPlaceFilter)

Page 109: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 109 -

function Transform(Sample: IMediaSample): HRESULT; override;

function CheckInputType(mtin: PAMMediaType): HRESULT; override;

end;

implementation

{ TTestTrans }

function TTestTrans.CheckInputType(mtin: PAMMediaType): HRES ULT;

begin

result := S_OK;

end;

function TTestTrans.Transform(Sample: IMediaSample): HRESULT ;

begin

result := NOERROR;

end;

initialization

TBCClassFactory.CreateFilter(TTestTrans, '_TestTrans' , CLSID_TTestTrans,

CL SID_LegacyAmFilterCategory,

MERIT_DO_NOT_USE, 0, nil);

end.

Page 110: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 110 -

15. 15. 15. 15. 필터필터필터필터 2222

아, 이제 여러분은 첫번째 필터를 개발하신 것입니다. TestTrans 필터는 제자리필터로서 버퍼공유를

하기 위해 엄청난 포스를 내부에 숨기고 있습니다. 이것은 좋은말로 표현하면 그렇고요, 직설적으로 말

씀드리자면 갖은 지랄 발광을 하고 있습니다. 하지만 불행하게도 VC++ 원본의 제자리 변환필터와 델

파이의 제자리 변환필터의 내부작동은 약간 틀립니다. 이 원인과 해결에 대하여 현재 살펴보고 있습니

다만, 제가 이 강의를 하면서 시간을 너무 써 버렸기 때문에 올 연말에나 가서 손봐야 할거 같습니다.

하지만 희소식도 있습니다. 그동안 DSPack의 BaseClass.pas로 필터를 개발하면 이상하게도 GraphEdit

상에서 GRF파일로 저장이 되지 않았는데요, 이 현상은 이번기회에 잡아 내었습니다. 이거는 상당히 어

렵게 잡아낸 버그인데요, 제가 DSPack에 조금이나마 공헌한 것 같아 약간의 자부심 또한 느끼고 있습

니다. 아시는 분은 알겠지만 그동안 GRF 파일로 저장이 되지 않아서 참 많은 어려움이 있었습니다. 예

를 들어서 필터를 개발할 때에는 조금씩 수정하고 그 수정된 것을 GraphEdit에서 시뮬이이트 해봐야

합니다. 물론 별도의 전용 어플을 만들어서 시뮬레이트 할 수도 있겠지만, 보통 프로젝트에 필터를 한

두개 만들어서 붙이는게 아니어서, 각각의 필터에 해당하는 개발전용 DShow 어플을 따로 만들어 두는

것은 프로젝트 디렉토리를 과도하게 살찌우는, 그래서 복잡함을 가속하게하는 낭비와 비슷한지라...

AnyWay, 아무튼 필터개발 중에 간단한 시뮬레이트 작업은 GraphEdit로 하게 되는데요, 여기서 이

GRF파일로 저장이 되지 않으면 매번 필터삽입윈도우에서 하나씩 불러와서 연결하고 해야 합니다. 이게

간단한 것 같지만 사람 환장하게 짜증나는 일이지요. 그냥 GRF파일로 저장해서 간단히 불러오면 끝나

는 것인데요, 그간 DSPack의 BaseClass.pas로 필터개발을 하였다면 이 방법을 사용하지 못해서 상당

한 부담이 되었던 터였습니다. 자꾸만 이야기가 또다시 삼천포로 빠졌습니다. 이 문제의 해결 방법은 이

강좌 맨 뒷 부분에 올려 놓은 BugList 에 있습니다. 참조하시면 좋을 것입니다.

이제 우리는 TTestTrans 라는 제자리필터(In Place 필터) 를 개발하였습니다. 아직까지 이 필터는 내

부적으로 아무런 작동을 하고 있지 않습니다만, 일단 이것을 윈도우에 등록하고 GraphEdit 에서 실행해

보겠습니다. 하지만 그전에 앞서서 ax파일(COM 서버) 이름과 COM 클래스의 이름, 그리고 필터삽입윈

도우에 표시될 디스플레이 이름에 대하여 정리해 보겠습니다. 이 세가지의 이름을 헷갈리지 않으셨으면

합니다. 여러분이 프로젝트를 처음 만드셨을 때, 프로젝트 파일 이름이 바로 ax확장자를 가진 파일 이

름으로 컴파일되어 나오게 될 것입니다. 아래를 보시겠습니다.

library TestTrans;

uses

BaseClass;

Page 111: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 111 -

{$E ax}

exports

DllGetClassObject,

DllCanUnloadNow,

DllRegisterServer,

DllUnregisterServer;

begin

end.

위의 프로젝트를 컴파일하면 TestTrans.ax라는 파일이 생성될 것입니다. 즉, Library TestTrans 라

는 부분에 지정되어있는 것이지요. 그런데 여러분이 하나의 COM Server에다가 여러개의 필터를 개발

하고자 한다면 이 프로젝트 이름을 좀더 광범위한 의미의 것으로 사용하셔야 할 것입니다. 예를 들어서

MyFilter와 같이 말이죠. 하지만 제가 전에도 말씀드렸다시피 우리는 하나의 COM Server에 오로지 하

나의 필터를 만들어 놓을 것이므로 생성될 파일의 이름이 될 프로젝트 이름과 COM 클래스 이름을 동

일하게 맞춰주면 편리하다는 것입니다.

지난회에서 우리는 TTestTrans라는 클래스 이름을 사용하였고, 이것에 따른 디스플레이 이름으로

_TestTrans로 클래스 팩토리의 CreateFilter 함수에 지정하였습니다. 자, 이것을 다른말로 하면 이렇

습니다. 먼저 프로젝트 파일을 컴파일하면 생성될 In Process COM Server의 파일이름이 있고요, 그

다음으로 그 안에 정의된 COM 클래스의 이름이 있고요, 그 다음으로 클래스 팩토리의 CreateFilter에

서 두번째 매개변수에 지정될 디스플레이 될 이름이 있습니다. 만일 하나의 COM Server에 단지 하나

의 COM Object 인 필터를 개발하고자 한다면 이 세가지 이름을 일관성있게 동일한 선상에서 유지하면

아주아주 좋다는 것입니다. 실제로 이 세가지를 따로 놀게 만들면( 어쩌다 너무나 심한 업무적인 스트레

스가 쌓이는 상태에서 간혹 이 이름설정을 소홀히 하다보면 나중에 몇배로 고통스러운 댓가를 받게 되

므로...) 헷갈리기가 쉽습니다. 유념해 두시면 좋을 것 같아서 별것도 아닌것 같지만 설명이 길어졌나 봅

니다.

아무튼 지금까지 저의 강의를 따라 오셨다면 TestTrans.ax 파일이 컴파일 되어 생성 되었을 것입

니다. 이제 이것을 윈도우즈에 등록하겠습니다. 윈도우즈 시작의 실행에서 Regsvr32 C:\TestTrans.ax

라고 기입하고 엔터를 칩니다. 그러면 'C:\TestTrans.ax의 DllRegister Server 성공'이라는 메시지가

화면에 뜰 것입니다. 이제 GraphEdit를 실행시켜서 필터삽입윈도우를 열고 DirectShow Filters 카테고

리에서 여러분이 만드신 필터를 찾아 보십시요. 지난회에서 설명드렸듯이 디스플레이 이름을 카테고리

의 상위에 표시하기 위해 _(언더바)를 붙이셨다면, '_TestTrans' 라는 이름으로 표시되어 나타날 것입니

다. 이것을 선택하여 GraphEdit에다 생성하여 놓습니다. 그리고 다음과 같이 다른 필터를 생성하여 연

Page 112: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 112 -

결하고 실행해 보겠습니다.

Cam _TestTrans VideoRenderer

현재 위와같이 연결하고 실행한다고 해서 어떤 특별한 화면조작을 실행하지는 않습니다. 이제 우리

는 본격적으로 아주아주 재미있고 흥미있는 부분으로 한걸음 내딧여 보겠습니다. 즉, 화면을 조작하는

로직을 필터 내부에 기입하는 것입니다. 일단 먼저 위와같이 연결하신 다음에 GraphEdit의 Save Graph

메뉴로 현재의 그래프를 저장해 놓으시길 바랍니다. 우리는 이것을 TestTrans.GRF 파일로 저장해 놓을

것입니다.

참고참고참고참고

이제 우리는 필터를 수정하고자 합니다. TTestTrans의 CheckInputType 함수를 수정하게 될 것인데요, 이렇게 수정하고서 다

시 재컴파일할 때마다 번거로운 작업을 반복하게 됩니다. 이것을 가장 단순화 시키면 좋을 것입니다. 먼저 프로젝트의 옵션

에서 Directories/Conditionals 탭을 보시면 OutPut 디렉토리를 설정할 수 있게 되어 있습니다. 만일 여러분이 필터저장고로

사용될 디렉토리를 별도로 만들어 놓으셨다면 모르겠지만, 일단 이곳에 'C:\'로 설정해서 컴파일 되어 생성되는 ax파일이

항상 C:\ 디렉토리에 만들어지게 해놓습니다. 그리고 RegSvr32를 실행할 때에는 C:\ 루트 디렉토리에 있는 ax파일을 등

록하는 것이죠. 이렇게 하면 여러분은 델파이로 간단히 프로젝트 파일을 수정해서 컴파일 하기만 하면 됩니다. GraphEdit에

서는 필터를 로딩하여 생성할때마다 수정된 COM 객체를 만들게 될 것이니까요. 하지만 이때 주의하셔야 할 점이 있습니다.

즉, 위와같이 설정해 놓고서 필터 프로젝트를 수정하여 재컴파일 할때에 GraphEdit가 현재 실행되어 있으면 안된다는 것입

니다. 정확히 말하자면 수정할 필터를 현재 GraphEdit상에다 생성해 놓은 상태에서 델파이로 재컴파일하면 Could not

Create Output file 이라는 Fatal Error 메시지가 발생할 것입니다. 이점만 주의하신다면 이렇게 설정해 놓고서 작업하시면 편

리할 것입니다.

이제 우리는 연속해서 들어오는 각각의 프레임을 수정하고자 합니다. 그러나 이 재미있는 작업을 하

기 위해서 한가지 준비작업을 해야 합니다. 생각해보시면, 우리는 현재 TestTrans 필터의 앞쪽 스트림

의 미디어 타입 형식이 어떨지를 확신하지 못합니다. TestTrans는 In Place(제자리) 필터에서 상속 받

았으므로 앞의 스트림의 미디어 타입을 그대로 뒷쪽으로 넘겨주고 있기 때문입니다. 그래서 우리는 앞

쪽의 필터에서 TestTrans 필터의 입력핀과 연결될 때, 미디어 타입을 확실히 정해놓고서, 그 정해진 타

입이 아니면 애시당초 연결되지 못하도록 만들어야 합니다. 사실 이렇게 미디어타입을 원하는 것으로

설정해 놓으려는 것은 연속되는 프레임 이미지를 수정하고자 할때 YUV, RGB24, RGB32 등 각각의 버

퍼구조에 해당하는 모든 방식의 알고리즘을 준비해 놓기가 버겁기도 하겠지만, 모든 방식에 최적화된

알고리즘을 개발하기가 현실적으로 어렵기 때문이기도 할 것입니다.

Page 113: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 113 -

이제 우리는 필터의 소스 중에서 uMain 유닛의 CheckInputType 함수를 아래와 같이 수정할 것입니

다. 미디어 타입에 관해서는 이번 실습이 끝나는 대로 상세한 설명을 별도로 드리겠습니다. 다만 여기서

는 아래와 같이 코딩하시고 한번 쭉 훑어보시기 바랍니다.

function TTestTrans.CheckInputType(mtin: PAMMediaType): HRES ULT;

var

pvi: PVIDEOINFO;

begin

Result := S_FALSE;

if IsEqualGUID(mtin.majortype, MEDIATYPE_Video) and

IsEqualGUID(mtin.subtype, MEDIASUBTYPE_RGB24) and

IsEqualGUID(mtin.formattype, FORMAT_VideoInfo) then

begin

pvi := mtin.pbFormat;

if (pvi.bmiHeader.biWidth = 320 ) and

(pvi.bmiHeader.biHeight = 240 ) and

(pvi.bmiHeader.biCompression = BI_RGB) then result := S_OK;

end;

end;

위와같이 우리는 미디어 타입을 고정시켜 놓았습니다. 위의 함수에서 원하는 미디어타입이 아닐 경우

리턴 값으로 S_False 값을 돌려주고 있습니다. 일단 여기까지 수정을 하셨다면 본격적으로 연속되는 스

트림 각각의 프레임 이미지를 변경하는 작업을 하겠습니다. 제자리변환 필터에서 Transform 함수는

abstract 으로 선언되었기 때문에 반드시 Override해야 된다고 설명하였습니다. 제자리변환 필터는 바

로 이 함수에서 버퍼의 포인터를 가져와서 마음껏 주무를 수가 있습니다. 일단 아래와 같이 수정하겠습

니다.

function TTestTrans.Transform(Sample: IMediaSample): HRESULT ;

var

pBuff: PByte;

pData: PChar;

x, y : Integer;

Page 114: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 114 -

begin

Sample.GetPointer(pBuff);

pData := PChar(pBuff);

for x := 0 to 319 do

for y := 0 to 239 do

begin

Byte(pData[ 1]) := 0;

pData := pData + 3;

end;

result := NOERROR;

end;

위의 경우 Sample.GetPointer을 이용하여 버퍼의 포인터를 가져오고 있습니다. 이때 포인터의 타입이

PByte가 되어있는데요, 저같은 경우는 포인터 연산에 있어서 워낙 PChar에 익숙해져 있는터라 가져온

PByte타입의 포인터를 아예 PChar로 옮겨담고 있습니다. 계속해서 For 문을 보시겠습니다. 두개의

For 문이 이중으로 겹쳐져 있는데요, 이것은 아마도 이미지 변환작업을 조금이나마 하셨다면 이해하실

것입니다. 320 * 240 사이즈의 이미지를 의미하는 루프문이라는 것이고요, 그 안을 보시겠습니다. 현재

우리는 미디어타입으로 RGB24로 설정해 놓았습니다. 따라서 들어오는 모든 각각의 프레임은 RGB24형

태의 이미지 버퍼구조를 가지고 있을 것입니다. pData := pData + 3 이라는 부분은 바로 이렇게 Red,

Green, Blue의 한 바이트 씩을 합친 3바이트를 건너뛰라는 것이고요, Byte(pData[1]) := 0 라는 것은

Green 바이트 마다 값으로 0 값을 넣으라는 것입니다. 즉, 다시말해서 320*240 사이즈의 RGB24 타

입의 이미지에서 각각의 픽셀의 Green 값을 모두다 0으로 하라는 것입니다. 이렇게 변경하고서 컴파일

하겠습니다. 그리고 GraphEdit를 실행시키고요, 우리가 예전에 TestTrans.GRF 파일로 저장해 놓았던

것을 파일 메뉴의 아랫단에서 재실행 시킵니다. 그러면 예전에 연결해 놓았던 필터들의 모습이 자동으

로 만들어지며 보여지실 것입니다. 실행하시고 결과를 즐기시기 바랍니다. 여러분이 생각했던 대로 결

과가 나오셨다면 기쁠것이고요, 그렇지 않다고 해도 놀라울 것입니다. 이것이 바로 동영상 프로그래밍을

하는 가장큰 재미입니다. 'DShow를 즐겨라.' 저는 이렇게 말씀드리고 싶습니다. 이 말을 드리고 싶어서

이렇듯 먼 강좌 여행을 힘겹게 이끌어온 것일지도 모르겠습니다. DShow를 참으로 즐기시기 바랍니다.

위에서 간단히 픽셀의 Green 값을 0을 처리하는 로직을 테스트 하였지만 여러분은 좀 더 다양한 로

직을 만들어서 실습하시기 바랍니다. 윤곽선 알고리즘이나 블러드 처리 같은 간단한 로직은 인터넷 상

에서 C++로 된 것을 다운받으실 수가 있습니다만, 될수있으면 '디지털 영상처리'와 같은 전문서적을 구

입하여 보시길 권해드립니다. 자, 여기까지 간단한 실습을 끝내었습니다.

Page 115: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 115 -

이제 여러분에게 미디어의 타입에 대하여 말씀드려야 하겠습니다. 보다 정확하게는 미디어타입을 저

장하여 사용하고 있는 TAMMediaType 레코드에 대하여 알아보겠습니다. 이 레코드의 원형은 다음과

같습니다.

TAMMediaType = record

majortype: TGUID;

subtype: TGUID;

bFixedSizeSamples: BOOL;

bTemporalCompression: BOOL;

lSampleSize: ULONG;

formattype: TGUID;

pUnk: IUnknown;

cbFormat: ULONG;

pbFormat: Pointer;

end;

위와같이 선언되어 있는데요, 각각의 멤버가 의미하는 것을 알아보겠습니다. 가장 첫번째인

majortype 은 미디어타입의 가장큰 범위를 구분짓는 멤버입니다. 일반적으로 미디어라 함은 비디오, 오

디오, MIDI 데이터, 고유한 성격의 바이트 스트림, 등등이 있을 수 있겠습니다. 쉽게 말해서 비디오냐

오디오냐를 결정하는 것이라고 생각하시면 됩니다. 여기서 비디오라면 MEDIATYPE_Video 를, 오디오

라면 MEDIATYPE_Audio 이라고 기입하시면 되겠습니다. 이 상수값은 DirectShow9.pas에 정의되어

있습니다. 그렇다면 이곳에 정의되어 있는 또다른 majortype 으로는 무엇이 있는지 한번 살펴보겠습니

다.

MEDIATYPE_Video, MEDIATYPE_Audio, MEDIATYPE_Text, MEDIATYPE_Midi,

MEDIATYPE_Stream, MEDIATYPE_Interleaved, MEDIATYP E_File,

MEDIATYPE_ScriptCommand, MEDIATYPE_AUXLine21Data, MEDIATYPE_VBI,

MEDIATYPE_Timecode, MEDIATYPE_LMRT, MEDIATYPE_URL _STREAM

위와 같이 여러가지 대분류가 있겠습니다. 이제 다음으로 소분류의 미디어 타입을 살펴보겠습니다. 레

코드의 두번째 멤버변수인 subtype 이라는 놈이 바로 소분류를 의미합니다. 이 소분류는 대분류 미디어

Page 116: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 116 -

타입의 영역안에서 설정되어져야 하는 것입니다. 예를 들어서 대분류인 majortype 에는 오디오라고 정

해놓고서 소분류인 subtype에는 RGB24라고 설정하면 안되겠습니다. 소분류인 subtype은 이렇게 대분

류에서 정의한 미디어타입을 다시 상세하게 분류하여 정의한 것입니다. 예를 들어 대분류인 majortype

가 비디오라고 할때 이것의 소분류인 subtype에는 RGB24, RGB32, YUV, 등등이 있을 것입니다. 또한

대분류가 이번에는 오디오라고 할때 이것의 소분류는 PCM이나 WAV 등이 있을 수 있을 것입니다.

PCM과 WAV의 차이점에 대하여는 인터넷을 보시고 학습하시기 바랍니다. 이 소분류는 워낙 종류가 많

기 때문에 DirectShow9.pas에 정의되어 있는 것을 여기다 옮겨 놓지는 않겠습니다. 하지만 여러분은

한번 어떤 것들이 있는지 살펴봐야 할 것입니다.

레코드의 세번째 멤버변수인 bFixedSizeSample 라는 변수는 Boolean 형으로서 이 값이 True일 경

우 레코드의 다섯번째 멤버변수인 lSampleSize의 값이 유효한 것을 의미합니다. 오디오의 경우는 보통

True로 설정되지만 비디오의 경우 비압축 비디오는 True, 압축 비디오는 False로 설정해 놓습니다. 이

변수는 잠시 후에 lSampleSize를 설명할 때 다시한번 자세한 해설을 해드리겠습니다.

레코드의 네번째 멤버변수인 bTemporalCompression 은 현재 미디어 샘플이 압축된 상태인지 아닌

지를 의미하는 Boolean형 변수입니다. 이 값이 False 이면 압축이 되지 않았다는 의미로서 모든 프레

임이 KeyFrame 이라는 것을 나타내게 됩니다. 이것은 당연한 말이 되겠습니다. 압축을 하지 않았다는

것은 모든 프레임이 무손실의 완전한 데이터를 가지고 있다는 것을 의미하고요, 일반적으로 우리가

KeyFrame 이라는 의미는 압축하였을 때 기준이 되는 전혀 손실 되지 않은 데이터의 프레임(화면)을

의미하는 것이므로, 결국 모든 프레임이 KeyFrame이라는 의미는 모든 프레임 이 손실되지 않은 완전

한 프레임(화면)을 의미하므로 결국 압축되지 않았다는 것을 의미한다는 것입니다. 따라서 말을 이상하

게 뱅뱅 돌리지 말고 정리하자면 압축되지 않으면 모든 프레임이 KeyFrame 이 되는 것은 당연하여,

반대로 모든 프레임이 KeyFrame 이다라는 것은 압축되지 않았다는 것을 의미할 것입니다. 결국 동일

한 의미라는 것입니다.

다섯번째인 ISampleSize는 위에서 bFixedSizeSample 라는 변수를 설명할때 언급하였습니다. 이것은

샘플의 사이즈, 즉 바이트 단위 메모리 버퍼의 크기를 의미하는데요, 앞서 말한 Boolean형 변수인

bFixedSizeSample 의 값이 True일 경우 이 변수가 의미하는 바대로 데이터가 실제로 저장되어 있다라

는 것입니다. 이 bFixedSizeSample 라는 놈을 직역하자면 고정된 사이즈의 샘플인지 아닌지에 대한 정

보를 담기위한 것이라는 의미 이고요, 이것이 만일 고정되었다는 의미의 값이(True) 있다면

ISampleSize 에 저장되어 있는 값이 반드시 의미가 있어야 한다는 말입니다. 이것이 무슨 말이가를 한

번 생각해 보기로 하죠. 만일 여러분이 소스필터를 만들어서 스레드로 하나씩 프레임을(화면을) 뒤쪽으

로 푸쉬할때, 여러분은 그 전에 반드시 미디어타입을 셋팅해 놓아야 할 것입니다. 이때, 만일 여러분이

생성한 미디어타입이 비디오이고, 압축을 전혀하지 않은 완전한 이미지 한장 한장을 뒤쪽으로 연속해서

푸쉬한다면, 이러한 사실을 미디어타입에 기록해 놔야만 하는 것입니다. 즉, '내가 보내는 미디어 타입은

완전히 동일한 크기의 데이터를 앞으로 쭈우욱 연속해서 가지고 있을 터이니 참고하라' 라고 한다면,

Page 117: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 117 -

bFixedSizeSample 에는 True를, ISampleSize는 실제 이미지가 차지하는 버퍼의 Size 값을 지정해 놓

아야 한다는 것입니다.

일단 여기까지 설명을 드리고요, 이 나머지에 대해서는 설명방식을 달리 하겠습니다. 좀 복잡한 방식

이기도 하기 때문입니다. 우리가 미디어 타입의 대분류를 '비디오'로 그리고 소분류를 'RGB24'로 정해

놓았다고 하더라도 자질구레하게 세세한 항목들은 아직도 설정해 놓아야 할 것들이 많습니다. 예를 들

어서 프레임 사이즈가 640*480 인지, 아니면 320*240인지를, 만약 이 샘플이 압축되었다면 어떤 방식

으로 압축되어졌는지 등등의 정보가 추가적으로 기록되어져야 할 것입니다. 그런데 이렇게 추가적인 정

보가(미디어의 포맷타입이) 고정적인 형태로 존재하는 것은 아닙니다. 예를 들어서 비디오 타입의 추가

정보와 오디오 타입의 추가정보의 형태가 다를 것이고, 설사 비디오 타입의 추가정보(미디어의 포맷타

입) 라고 하더라도 여러가지 형태로서 존재할 수가 있기 때문입니다. 위의 TAMMediaType 레코드의

가장 마지막 멤버변수인 pbFormat 는 타입이 Pointer 로 되어 있습니다. 즉, 추가정보를 가지고 있는

또다른 레코드형 변수의 포인터를 이곳에 설정해 놓을 수가 있다는 것입니다. 그런데 이 추가정보의 형

태로 약속되어진 레코드가 다양하기 때문에 이것의 종류를 별도로 지정해 놓아야 하는데요, 이것을 지

정해 놓는 곳이 바로 여섯번째 매개변수인 formattype 인 것입니다. 그러면 여기서 이 formattype에서

지정되어 실제로 pbFormat 에 등록되어지는 추가정보형태(미디어의 포맷타입)에는 어떠한 것들이 있는

가를 살펴보겠습니다. 아래의 것들은 모두 DirectShow9.pas에 정의되어 있는 것입니다.

FORMAT_None, FORMAT_VideoInfo, FORMAT_VideoInfo2, FORMAT_WaveFormatEx,

FORMAT_MPEGVideo, FORMAT_MPEGStreams, FORMAT_DvIn fo

위와 같이 다양한 포맷타입을 설정해 놓으실 수가 있습니다. 예를 들어 formattype 에

FORMAT_VideoInfo을 설정해 놓았다고 한다면 여러분은 반드시 pbFormat 변수에다가

FORMAT_VideoInfo 형 구조에 해당하는 레코드의 포인터를 지정해 놔야 한다는 것입니다. 그렇다면

우리가 앞으로 주로 사용할 FORMAT_VideoInfo 형 구조의 레코드를 맛보기로 보시면 아래와 같습니다.

PVideoInfo = ^TVideoInfo;

tagVIDEOINFO = record

rcSource: TRect;

rcTarget: TRect;

dwBitRate: DWORD;

dwBitErrorRate: DWORD;

AvgTimePerFrame: TReferenceTime;

Page 118: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 118 -

bmiHeader: TBitmapInfoHeader;

case Integer of

0: (bmiColors: array[ 0..iPALETTE_COLORS- 1] of TRGBQuad);

1: (dwBitMasks: array[ 0..iMASK_COLORS- 1] of DWORD);

2: (TrueColorInfo: TTrueColorInfo);

end;

앞으로 여러분은 formattype 에 FORMAT_VideoInfo를 설정하시고, 위에서와 같이 TVideoInfo 레코

드를 생성하여(레코드를 생성한다는 말은 메모리를 별도로 할당한다는 의미입니다.) 그것의 포인터를

pbFormat 에다가 설정해 놓는 작업을 하셔야 할 것입니다.

마지막으로 cbFormat라는 멤버변수가 있는데요, 이것은 이렇게 각각의 추가정보(미디어 타입)에 해당

하는 레코드형의 크기를 지정해 놓는 장소입니다. 어쩌면 이런 의문을 가지게 될 지도 모르겠습니다.

formattype 에다가 레코드의 형태가 될 Type을 지정해 두는데 굳이 이렇게 별도의 변수를 만들어서

추가정보 레코드가 차지할 버퍼의 크기를 별도로 저장 할 필요가 있느냐 하는 것입니다. 필터 내부에서

미디어타입을 가지고 여러가지 작업을 하는데 있어서 만일 formattype의 값으로서만 레코드의 사이즈

를 추즉하려 한다면, 필터는 모든 formattyp에 대한 레코드의 구조에 대하여 미리 준비하고 있어야 합

니다. 만일 해당 미디어타입에 종속되는 필터들만이 사용된다면 문제가 없겠습니다만, 범용적인 필터들

의 경우, 예를 들어 단순히 미디어타입을 복사해서 뒤쪽으로 보내기만 할 뿐인 역할을 하는 In Place와

같은 종류의 필터들인 경우에는 모든 formattyp 타입에 대한 정보를 가지고 있어야만 하는 것입니다.

이것은 범용 필터의 가능성을 상당히 제한하는 조건이 된다는 것을 알수 있으실 것입니다. 따라서 별도

로 해당 레코드의 크기를 알아 낼 수 있는 변수인 cbFormat 이 필요한 것입니다.

이제 추가정보가 TVideoInfo라는 레코드에 담겨 있다면 이것의 사이즈를 cbFormat에 어떻게 기록하

는지 다음과 같이 보여드리겠습니다.

MediaType.cbFormat := SizeOf(TVideoInfo);

자, 여기까지 미디어타입에 대하여 설명하였습니다. 15부를 종료하겠습니다. 여러분 수고하셨습니다.

Page 119: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 119 -

16.16.16.16. 필터필터필터필터 3333

지난회에서 미디어타입에 대하여 공부하셨지만, 아직 완전히 끝난 것은 아닙니다. 필요할 때마다 부분

부분 추가적인 해설을 해 나가도록 하겠습니다. 그런 의미로 이제 지난회에서 CheckMediayType 함수

에 사용된 코드에 대하여 약간의 추가설명을 드리도록 하겠습니다. 아래를 보시겠습니다.

function TTestTrans.CheckInputType(mtin: PAMMediaType): HRES ULT;

var

pvi: PVIDEOINFO;

begin

Result := S_FALSE;

if IsEqualGUID(mtin.majortype, MEDIATYPE_Video) and

IsEqualGUID(mtin.subtype, MEDIASUBTYPE_RGB24) and

IsEqualGUID(mtin.formattype, FORMAT_VideoInfo) then

begin

pvi := mtin.pbFormat;

if (pvi.bmiHeader.biWidth = 320 ) and

(pvi.bmiHeader.biHeight = 240 ) and

(pvi.bmiHeader.biCompression = BI_RGB) then result := S_OK;

end;

end;

위에서 보시면 아시겠지만, IsEqualGUID이라는 요상한 함수가 등장합니다. 사실 GUID의 상수선언에

대하여 처음에 설명을 드리려고 하였으나, 이제서야 기회가 왔나 봅니다. GUID는 여러개의 연속된 값이

배열된 상수입니다. 이것을 우리는 다음과 같이 선언한 적이 있습니다.

const

CLSID_TTestTrans: TGUID = '{5C4B9332-ED86-4465-9024-6C167D552AF4}' ;

위에서 TGUID란 다음과 같은 레코드 타입입니다.

Page 120: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 120 -

TGUID = packed record

D1: LongWord;

D2: Word;

D3: Word;

D4: array[ 0.. 7] of Byte;

end;

즉, 이것을 설명하자면 TGUID라는 레코드형 상수에 초기값을 선언해 넣은 것입니다. 여러분이 만일

델파이의 레코드 값 초기화 선언에 익숙하지 않다면 이번 기회에 익숙해지시길 바랍니다. AnyWay... 이

렇게 선언되는 각종 GUID 값은 비교하기가 여간 까다롭지가 않습니다. 그래서 IsEqualGUID 이라는 별

도의 함수가 있다는 것을 기억해 두시기 바랍니다. 이 함수는 델파이의 SysUtils.pas 유닛에 선언되어

있습니다. 앞으로 이 함수를 상당히 자주 필요로하게 될 것입니다.

이렇게 CheckInputType 함수에서 IsEqualGUID 라는 함수를 이용하여 매개변수로 받은 mtin 라는

미디어타입에 대하여 조사를 하고 있습니다. majortype이 MEDIATYPE_Video 인지, subtype이

MEDIASUBTYPE_RGB24 인지, 그리고 심지어 formattype이 FORMAT_VideoInfo인지도 확인하고 있

습니다. 이 마지막 확인이 절대적으로 필요한 이유는 아래의 추가조건문에서 mtin.pbFormat의 내용을

확인하기 위해서입니다. 즉, 만일 formattype이 FORMAT_VideoInfo라면 pvi: PVIDEOINFO 와 같이

레코드의 포인터형 변수를 선언하고 pbFormat 로부터 대입 받아서 그것의 내용을 하나하나 확인할 수

가 있다는 것입니다. 지난회에서 저는 FORMAT_VideoInfo 이라는 미디어의 포맷타입에 해당하는 레

코드가 바로 TVideoInfo라고 설명 드렸었습니다. PVIDEOINFO 란 바로 TVideoInfo의 포인터형 이라

는 의미입니다. 이렇듯 줄줄이 엮어 내어서 짝짜궁을 맞춰 확인할 수가 있는 것입니다.

참고참고참고참고

15부를 써넣고 보니까 부실한 해설이 조금 있었습니다. 감기몸살 기운 때문에 제정신이 아닌 상태에서 물 흐르듯이 써내려

간 후유증인가 봅니다. 써놓고 나서도 계속해서 조금씩 가다듬고 있으니 참고하시기 바랍니다.

[1][1][1][1] 인터페이스인터페이스인터페이스인터페이스

이쯤에서 여러분에게 COM 인터페이스에 대하여 설명하도록 하겠습니다. 이 인터페이스 부분은 가장

나중에 하려고 마음먹었는데요 생각해보니까 지금 실습을 하시는 편이 오히려 COM에 대한 전반적인

이해를 돕는 지름길이라는 생각이 들었습니다. 이 중요한 실습을 마음이 급하다고 해서 건너뛰시지 말

Page 121: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 121 -

기를 당부드립니다. 물론 COM 프로그래밍에 익숙하신 분이라면 상관이 없겠습니다.

일단 필터에다 인터페이스를 설치(?)하려면 여러분은 별도의 유닛이 필요합니다. 예를 들면 C의 헤더

파일 비슷한 것입니다. 이것은 필터에서도 사용하고 응용어플에서도 공통으로 사용되어야 할 것입니다.

우리는 이 유닛의 이름을 조금 구식이지만 uCommon이라고 명명하겠습니다. 그리고 그 안에서 앞으로

우리가 사용할 인터페이스를 정의해 놓을 것입니다. 여기서는 정의만 해 놓습니다. 이것을 구현하는 것

은 필터내부가 될 것입니다. 응용어플에서는 단순히 인터페이스를 사용만 할 것입니다. 그러나 여러분도

아시다시피 우리가 지난회에서 작성하였던 TCamDShow 클래스 안에 인터페이스를 사용하는 함수를 별

도로 만들어 놔야 할 것입니다. 여기서의 로직은 인터페이스의 구현이 아니라 인터페이스를 사용하기

위한 함수구현이라는 표현이 맞을 것입니다. 그렇다면 이제 uCommon의 인터페이스 정의를 시작해 보

도록 하겠습니다.

먼저 인터페이스를 만들기 위해서는 또다른 GUID가 필요합니다. Ctrl + Shift + G 키를 눌러서 새로

운 GUID를 발생시키도록 하겠습니다. 인터페이스의 이름을 ITestInf라고 명명합니다. 앞에 I(영문자 아

이)가 붙은 것은 인터페이스라는 것을 의미합니다. 자, 다음과 같이 uCommon의 전체를 보시겠습니다.

너무도 간단하지만 껍데기만 존재하는 인터페이스를 선언한 것입니다.

unit uCommon;

interface

uses

Windows;

type

ITestInf = interface(IUnknown)

[ '{6BE5C524-3A19-41BE-B979-11DCB80679CF}' ]

end;

implementation

end.

이제 우리는 인터페이스에 어떠한 기능을 정의해 놓을 것인가에 대하여 고민해 보겠습니다. 지난회에

서 우리는 제자리 변환 필터를 개발하였습니다. 이 필터는 모든 픽셀의 Green값을 0으로 변경하는 것

Page 122: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 122 -

이었습니다. 하지만 너무 단순해서 재미가 없었습니다. 이제 우리는 외부의 응용어플에서 동적으로 이

Green 값을 수시로 변경할 수 있는 인터페이스를 하나 만들어 보겠습니다. 이것만 있으면 인터페이스

가 너무 단순하니까 또다른 기능을 하나 부여하겠습니다. 화면에다 원하는 글자를 써넣어 보겠습니다.

외부 어플에서 인터페이스를 통하여 그때그때 지정한 텍스트를 화면에 믹싱되게 하는 것입니다. 예전에

저는 '믹싱'과 '먹싱'의 차이점에 대하여 설명하였습니다. 문자를 이미지 버퍼 위에 그려넣는 로직은 간

단히 델파이의 TBitmap 클래스를 사용하여 처리하였습니다. 이제 우리가 필요한 두가지의 인터페이스

를 정리해 보겠습니다. 하나는 Integer값을 지정하는 인터페이스 함수이고, 또다른 하나는 Text를 지정

할 수 있는 인터페이스 함수여야 한다는 것입니다. 그럼 다음과 같이 최종 인터페이스 선언을 보시겠습

니다.

unit uCommon;

interface

uses

Windows;

type

ITestInf = interface(IUnknown)

[ '{6BE5C524-3A19-41BE-B979-11DCB80679CF}' ]

function SetText(Value:PWideChar):HResult; stdcall;

function GetText( Out Value:PWideChar):HResult; stdcall;

function SetGreenValue(Value:DWORD):HResult; stdcall;

function GetGreenValue( out Value:DWORD):HResult; stdcall;

end;

implementation

end.

자, 위의 인터페이스 선언을 보시면 아시겠지만 SetText는 문자를 지정하는 함수이고 SetGreen은 상

수값을 지정하는 함수입니다. 그런데 특이하게도 Set 과 Get 이 쌍둥이처럼 나란히 선언되어 있습니다.

여러분에게는 현재 Set 계열의 함수만이 필요한지 모르겠으나 이번은 실습이라는 것을 유념해 두시기

바랍니다. 실습용으로 이 두가지 종류의 함수를 선언하고 구현하고 실행해 볼 것입니다. 이제 인터페이

스의 선언 형식을 눈여겨 봐주시길 바랍니다. 인터페이스는 리턴값을 가지는게 좋습니다. 거의 대부분의

Page 123: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 123 -

인터페이스 함수가 그래서 Function 형식으로 되어 있으며 무슨 약속처럼 HResult라는 형식으로 리턴

값을 선언하고 있습니다. 그러나 물론 Procedure 형태로 선언할 수도 있습니다만 가능하면 Function

으로 해두시길 권해 드립니다. 만일 나중에 조금이나 수정하려한다면 필터내부와 어플에서 동시에 수정

작업을 해야 하는데요, Function 으로 통일하면 생각하기도 편리하거니와 수정작업도 수월해지기 때문

입니다. 그리고 끝으로 stdcall 을 반드시 붙여주는 것을 잊지 말도록 하세요. 여기까지 인터페이스의

선언에 대하여 설명드렸습니다.

자, 이제 필터 내부에서 인터페이스를 구현해 보도록 하겠습니다. 가장 먼저 필터가 선언되어 있는 유닛

의 Uses 문에 uCommon 을 추가해 놓아야 할 것입니다. 그리고 In Place(제자리) 변환필터의 클래스

선언을 보시면 다음과 같을 것입니다. TTestTrans = class(TBCTransInPlaceFilter)

. 이제 여기다 우리가 만들 인터페이스를 다음과 같이 동시에 선언하도록 하겠습니다. TTestTrans =

class(TBCTransInPlaceFilter, ITestInf)

. 여기까지 변경한 다음에 한번 컴파일을 해보시길 바랍니다. 그러면 델파이는 컴파일 에러를 발생시킬

것입니다. 인터페이스가 클래스의 선언문에 포함되어 있는데 실제는 구현되지 않았기 때문에 컴파일러

가 놀란 것입니다. 이제 다음과 같이 TTestTrans 클래스의 선언을 최종 변경해보도록 하겠습니다.

unit uMain;

interface

uses

Windows, Graphics, BaseClass, ActiveX, DirectShow9, DSUtil, Dialogs, uCommon;

const

CLSID_TTestTrans: TGUID = '{5C4B9332-ED86-4465-9024-6C167D552AF4}' ;

type

TTestTrans = class(TBCTransInPlaceFilter, ITestInf)

private

FText : String;

FGreenValue : Integer;

FBmp : TBitmap;

protected

// ITestInf Interface...

function SetText(Value:PWideChar):HResult; stdcall;

Page 124: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 124 -

function GetText( Out Value:PWideChar):HResult; stdcall;

function SetGreenValue(Value:DWORD):HResult; stdcall;

function GetGreenValue( out Value:DWORD):HResult; stdcall;

public

constructor Create(ObjName: string; unk: IUnKnown; out hr: HRESULT);

constructor CreateFromFactory(Factory: TBCClassFactory;

const Controller: IUnknown); override;

destructor Destroy; override;

function Transform(Sample: IMediaSample): HRESULT; override;

function CheckInputType(mtin: PAMMediaType): HRESULT; override;

end;

implementation

.

.

( 생략)

위에서 보시면 uCommon 유닛에 선언해둔 함수를 고스란히 protected 아래에 재선언해 놓고 있습니

다. 이제 우리는 이 함수 하나하나를 구현해야 할 것입니다. 그런데 그 전에 클래스의 private 부분을

보시면 세개의 멤버변수가 추가되어 있음을 아실 것입니다. 설명드렸듯이 문자를 설정해 놓을 FText와

Green값을 지정해 놓을 FGreenValue 멤버변수입니다. 이 외에 TBitmap형의 FBmp 멤버객체가 있는

데요, 이것은 글자를 이미지 버퍼위에 새겨넣기 위한 것입니다.

본격적으로 인터페이스 함수를 구현하기에 앞서 한가지 설명드려야 할 사실이 있습니다. 바로 클래스

의 생성자에 관련한 부분입니다. 일반적으로 응용어플에서는 constructor Create 이렇게 정의해 놓고

객체를 Create해서 생성하곤 합니다. 그러나 BaseClass.pas에 선언된 필터 COM 클래스들은 약간 다른

방식으로 생성되게 만들어져 있습니다.

여러분은 COM을 설명하는 부분에서 클래스 팩토리에 관련된 사항을 아주 잠깐 들으셨을 것입니다.

실제로 필터라는 COM Object는 클래스 팩토리에 의하여 생성되어지는 것이라고 말이죠. 그런데 이때

클래스 팩토리는 필터의 Create 생성자를 이용하지 않고, 또다른 constructor 인 CreateFromFactory

를 이용하고 있습니다. 즉, 필터에는 두개 이상의 constructor가 있으며 이중 반드시 하나는 공통적으로

CreateFromFactory 라는 생성자가 선언되어 있다는 것입니다. 그래서 BaseClass.pas에 선언된 클래

스 팩토리가 COM 클래스의 CreateFromFactory 생성자를 이용하여 필터객체를 생성하는 것입니다.

Page 125: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 125 -

자, 이렇게 클래스 팩토리가 일반적인 Create생성자를 사용하지 않고 CreateFromFactory 라는 별도의

생성자를 사용하여 객체를 생성하는 데에는 이유가 있습니다. CreateFromFactory 의 내부구현을 살펴

보면 아시겠지만 이것은 단순한 전달자 역활을 하고 있습니다. 내부에서 또다른 생성자인 Create를 실

행하는 것이 전부인 것입니다. 일반적으로 Create문의 형식은 필터마다 가지각색입니다. 매개변수의

수와 형태가 상속을 받으면서 더 추가되거나 다양화되기 때문입니다. 이것은 필터들의 행동양식이 틀리

기 때문에 어쩔수 없을 것입니다. 이제 클래스 팩토리가 어째서 Create 생성자를 사용하지 않고

CreateFromFactory 생성자를 사용하여야 하는지 조금은 이해가 되실 것입니다.

CreateFromFactory 생성자의 매개변수는 달랑 두개입니다. 이것은 어느 필터의 선언에나 고정되어

있습니다. 따라서 클래스팩토리는 다양한 필터들의 COM 클래스를 인스턴스화하는데 있어서 이렇게 통

일된 형식의 생성자가 별도로 필요하게 되었던 것입니다. 그러나 이러한 설명은 오로지 DSPack에 있는

BaseClass.pas 유닛을 사용함에 있어서만이 유효하다는 것을 명심하시기 바랍니다. 예를 들어 VC++

에서는 또다른 형태의 클래스팩토리가 구현되어 있습니다. 또한 DSPack의 예전 초기 버전에서도 이것

과는 다른 형태의 클래스 팩토리가 구현되어 있습니다. 지난회에서 설명드렸다시피 클래스 팩토리를 구

현하는 방식이 조금씩 변할수가 있음을 생각하시고, VC++의 원본 필터 클래스를 비교하면서 헷갈리지

마시길 바라겠습니다.

제가 CreateFromFactory 에 대한 설명을 상당히 할애하며 굳이 한 이유는 우리가 DSPack의

BaseClass.pas 유닛을 사용하여 필터 COM 클래스를 새롭게 상속받아 만들고자 할때, 특히 생성자에

유념할 필요가 있기 때문입니다. 우리는 현재 인터페이스를 만들고자 하는데요 여기에는 FText나

FGreenValue와 같은 멤버변수가 새롭게 정의되어 있어야 하고요, 이 정의된 멤버변수가 초기화할 장소

가 필요하게 되었던 것입니다. 그러나 현재 이런 이유 때문에 굳이 생성자가 반드시 재상속되어야 한다

고 말하는 것은 아닙니다. 이번것은 단순한 필요에 의한 것이며, 앞으로 여러분은 중요한 멤버객체의 생

성이나 메모리 초기화 등과 같은 부분에 있어서 기존의 델파이 클래스와 마찬가지로 Create생성자에서

하시되, 딴따라같이 생긴 CreateFromFactory 생성자를 보고 당황하지 마시길 바라는 의미로 설명을

드렸던 것입니다.

이제 여러분은 다음과 같이 두개의 생성자를 구현해 보아야 할 것입니다. 선언은 위에서 먼저 해놓았

으니 참고하시길 바랍니다.

constructor TTestTrans.Create(ObjName: string; unk: IInterface; out hr:

HRESULT);

begin

inherited Create(ObjName, unk, CLSID_TTestTrans, hr);

Page 126: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 126 -

FText := '' ;

FGreenValue := 0;

FBmp := TBitmap.Create;

FBmp.PixelFormat := pf24bit;

FBmp.Width := 320 ;

FBmp.Height := 240 ;

end;

constructor TTestTrans.CreateFromFactory(Factory: TBCClassFacto ry; const

Controller: IInterface);

var hr : HResult;

begin

Create(Factory.Name, Controller, hr);

end;

destructor TTestTrans.Destroy;

begin

FBmp.Free;

inherited Destroy;

end;

소멸자는 굳이 설명드리지 않겠습니다. Create 생성자에서 두개의 멤버변수를 초기화 했으며 FBmp

멤버객체를 생성하고 있습니다. 여기까지 생성자에 관한 부분은 마치겠습니다.

이제 본격적으로 네개의 인터페이스 멤버함수의 구현부분을 아래와 같이 보시겠습니다.

// ITestInf Interface.

function TTestTrans.GetGreenValue( out Value: DWORD): HResult;

begin

Value := FGreenValue;

result := S_OK;

end;

function TTestTrans.GetText( out Value: PWideChar): HResult;

Page 127: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 127 -

begin

Value := StringToOleStr(FText);

result := S_OK;

end;

function TTestTrans.SetGreenValue(Value: DWORD): HResult;

begin

FGreenValue := Value;

result := S_OK;

end;

function TTestTrans.SetText(Value: PWideChar): HResult;

begin

FText := Value;

result := S_OK;

end;

자, 위와같이 별 쓸데없이 result 값으로 온통 S_OK가 반환되고 있습니다. 이쯤되면 왜 굳이 인터페

이스 함수를 반환값이 있는 Function으로 만들었는지 회의감이 들수도 있을 것입니다. 그러나 여러분은

그럼에도 불구하고 function으로 구현하시길 바랍니다. 가장큰 이유는 다른 모든 인터페이스가

Function으로 되어있기 때문에 헷갈리지 않아서 좋으며, 또다른 이유는 만약 수정되어야 할 필요가 있

을시에 리턴값이 없는 Procedure에서 Function으로 수정하는 것 보다는 Fcuntion에서 위와같이

result := S_OK 처럼 놔두면 되기 때문입니다. AnyWay... 그건 그렇다 치고... 위에서 조금 유별난 기능

함수를 보시겠습니다. StringToOleStr 라는 함수입니다. COM에서는 2바이트인 문자열을 사용하고 있습

니다. 따라서 델파이의 String형을 PWideChar로 변경해 주기 위해서 StringToOleStr함수를 사용하고

있습니다.

이제 마지막으로 제자리변환 필터의 변경된 Transform 함수를 아래와 같이 보시겠습니다.

function TTestTrans.Transform(Sample: IMediaSample): HRESULT ;

var

pBuff: PByte;

pData: PChar;

x, y : Integer;

Page 128: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 128 -

begin

Sample.GetPointer(pBuff);

pData := PChar(pBuff);

CopyMemory(FBmp.ScanLine[ 239 ],pData, 320* 240* 3);

FBmp.Canvas.TextOut( 100 , 100 ,FText);

CopyMemory(pData,FBmp.ScanLine[ 239 ], 320* 240* 3);

for x := 0 to 319 do

for y := 0 to 239 do

begin

Byte(pData[ 1]) := FGreenValue;

pData := pData + 3;

end;

result := NOERROR;

end;

위에서 보시듯이 수정된 부분은 두군데입니다. 하나는 이미지위에 텍스트를 써넣기 위해 FBmp를 이

용한 부분이고요, 또 다른 하나는 FGreenValue값을 지정해 두는 부분입니다.

여기까지 16부를 마치겠습니다. 지금까지 만들었던 제자리 변환 필터와 그 안에 구현해 놓은 인터페

이스를 가지고 어플 편에서 사용하여 보겠습니다. 여기까지 수고하셨습니다.

Page 129: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 129 -

17.17.17.17. 필터필터필터필터 4444

이번회에서는 지난회까지 만들었던 제자리 변환필터의 인터페이스를 가지고 응용 어플에서 이용하는

방법에 대하여 설명하겠습니다. DShow 어플편에서 우리는 TBaseDShow라는 클래스와 여기서 상속받

은 TCamDShow라는 클래스를 만들어서 응용어플을 만들어 보았습니다. 그당시 우리는 다음과 같이 두

개의 버전을 업그레이드 형식으로 변경해 나갔습니다.

(1차버전) Cam Video Renderer

(2차버전) Cam Smar Tee Avi Mux File Writer

Sample Grabber Video Renderer

우리가 이번에 새롭게 만들고자 하는 DShow 어플은 1차버전에서 출발하도록 하겠습니다. 혹시 1차

버전을 별도로 준비하지 않으신 분들은 2차버전을 복사하시어 1차버전을 만들어 놓으시길 바랍니다.

TCamShow에서 Cam과 Video Renderer 만 놔두시고 나머지는 필터와 인터페이스는 모두 삭제를 하시

면 되곘습니다. 그래버의 기능도 마찬가지로 필요하지 않을 것입니다. 이제 1차 초기 버전을 여러분이

가지고 계시다는 가정하에 시작해 보도록 하겠습니다. 이번 3차 버전 DShow 응용어플의 구성은 아래

와 같습니다.

(3차버전) Cam TestTrans Video Renderer

3차버전의 프로젝트를 오픈합니다. 그리고 지난회에서 여러분이 만드셨던 uCommon을 프로젝트에 연

결해 놓도록 하십시오. 프로젝트 Path을 설정하셔도 좋고, 똑같은 유닛을 복사해서 옮겨 놓으셔도 좋습

니다. 그리고 TCamDShow 클래스가 있는 유닛을 오픈합니다. 이곳의 Uses문에 uCommon을 추가해

놓습니다. 여기까지 기본적인 설정작업을 하셨습니다. 이제 여러분은 uCommon에 정의되어 있는

ITestInf 인터페이스를 사용하는 함수를 구현하여야 할 것입니다. 그러기 위해서는 먼저 TestTrans 필

터를 생성하고 이 필터에서 QueryInterface를 사용하여 ITestInf 인터페이스를 뽑아내야 할 것입니다.

먼저 TestTrans 필터를 생성하고 Cam TestTrans Video Renderer 형태로 연결해 보도

록 하겠습니다.

Page 130: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 130 -

이 전에 제가 설명을 드리지 않은 것이 있네요. 여러분이 지난회에서 uCommon 유닛을 만들면서 그곳

에 인터페이스만 정의해 놓으셨던 것을 압니다. 그리고 TestTrans의 CLSID가 선언된 부분이 아마도

필터 클래스 유닛의 Type 문 바로 위에 Const 로서 정의되어 있을 것입니다. 지난회에서 여러분은 이

TestTrans의 CLSID가 선언된 것을 uCommon에 옮겨 놔야만 했습니다. 제가 깜빡 잊고 설명을 빠트렸

습니다. 그러니까 필터와 응용어플에서 공통으로 사용되어야 하는 것은 인터페이스 뿐만이 아니라

TestTrans의 CLSID도 마찬가지로 공유되어야 한다는 것입니다. 여기서 이 문제로 인하여 uCommon의

전체를 다시 보여주는 것은 지면낭비일 것 같아서 생략하기로 하겠습니다. 여러분은 필터 프로젝트로

돌아가셔서 그냥 TestTrans의 CLSID가 선언된 부분을 uCommon.pas 유닛에 옮겨 놓기만 하면 되는

것입니다.

이 강의는 여러분에게 메뉴얼로서 존재하는 것이 아니라 DShow의 관점을, 그것도 제가 이제까지 고

민하고 터득한 관점을 제시하여 주기 위한 것이라는 점을 이해해 주셨으면 합니다. 여러분은 이 강의에

사용된 기초적인 DShow 로직만 가지고는 그 이상 올라갈 수가 없을 것입니다. 제가 원하는 것은 이 로

직과 제가 제시한 관점을 가지고 스스로 연구하여 자신만의 세계를 구축해 나가는 것입니다. 만일 어느

정도 윤곽이 잡혔다면 여러분 스스로 DShow의 도움말 파일을 읽으면서, 혹은 BaseClass.pas에 선언된

수많은 클래스와 로직을 연구하면서, 더 많은 사실을 접하고 더 깊은 DShow에 빠져들 수 있을 것입니

다. (개인적으로 저는 DShow에 빠져들기 보다 DShow를 공부하면서 동영상의 특성에 대하여 알아가기

를 희망합니다. DShow는 동영상 자체의 특성이, 즉 미디어 포맷이라든가, 미디어 타입과 같은 형식적

특성에서 부터 타임 스탬프, 버퍼공유와 스레드 동기화 문제 등과 같은 내부적 특성을 상당부분 잘 소

화해 내고 있습니다. 따라서 사회생활을 하면서 인간관계를 배우듯이 DShow를 공부하면서 이런 동영상

의 기초적인 특성을 배우는 것 또한 오히려 더욱 중요한 항목이 될 수 있을 것입니다. )

이제 TCamDShow 클래스의 private에다가 TestTrans 필터의 IBaseFilter 인터페이스형 멤버변수를

다음과 같이 선언합니다. TestTrans: IbaseFilter 이렇게요. 그런데 우리는 여기서 TestTrans 필터

의 ITestInf 인터페이스도 함께 사용하여야 하기 때문에 이부분에 ITestInf 인터페이스형 변수도 함께

선언해 놓습니다. TestInf : ITestInf 이렇게 말이죠. 그런데 여기서 private 인데도 불구하고 델파이의

통상적인 관습인 F를 멤버변수에 붙이지 않았습니다. 제가 예제 프로그램을 만들면서 세세한 부분에 소

홀히 하였음을 양해해 주시기 바랍니다. 여러분은 F를 별도로 붙이셔도 상관이 없을 것입니다.

자, 이제 TestTrans 필터를 MakeBaseFilter 프로시저 안에서 생성해 보겠습니다. 또한 생성과 동시에

ITestInf 인터페이스를 QueryInterface 를 이용하여 뽑아내 보겠습니다. 코드는 다음과 같습니다.

//TestTrans 필터를 생성하고 추가한다.

CreateFilter(CLSID_TTestTrans, TestTrans); //TestTrans 랜더러를 얻고...

FilterGraph.AddFilter(TestTrans, 'TestTrans Filter' ); //TestTrans 를 등록한다.

Page 131: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 131 -

if TestTrans = nil then Result := S_FALSE;

TestTrans.QueryInterface(ITestInf,TestInf); //TestFrans 의 ITestInf 인터페이스를 얻

는다.

위의 코드는 별도의 해서을 하지 않겠습니다. 이미 DShow 어플편에서 충분히 이해하셨을 것이라 생

각합니다. 이렇게 필터를 생성하였으면 이제 필터를 연결하는 작업이 필요합니다. Cam

TestTrans Video Renderer 이렇게 연결하여 보겠습니다.

function TCamDShow.ConnectBaseFilter: HRESULT;

var

hr : HRESULT;

function ConnectFilter(A, B: IBaseFilter): HRESULT;

var

InPin : IPin;

OutPin : IPin;

begin

FindPinOnFilter(A,PINDIR_OUTPUT,OutPin);

FindPinOnFilter(B,PINDIR_InPUT,InPin);

Result := FilterGraph.Connect(OutPin,InPin);

if Result <> S_OK then ShowMessage( ' 필터 연결에 실패하였습니다.' );

end;

begin

Result := S_OK;

if S_OK <> ConnectFilter(Cam, TestTrans) then Exit; //Cam 과 TestTrans 를 연결한

다.

if S_OK <> ConnectFilter(TestTrans, VideoRender) then Exit; //TestTrans 와

VideoRender 를 연결한다.

end;

자, 위와 같이 기존에 Cam과 VideoRender의 직접 연결에서 하나더 추가하여 중간에 TestTrans 필

터의 연결이 이뤄지고 있습니다. 이제 마지막으로 필터와 인터페이스를 차례로 해제하여 줍니다. 해제하

는 것은 ReleaseBaseFilter 함수에서 해주시면 되겠습니다.

Page 132: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 132 -

function TCamDShow.ReleaseBaseFilter: HRESULT;

begin

if Assigned(MediaControl) then MediaControl.Stop;

FilterGraph.RemoveFilter(Cam);

FilterGraph.RemoveFilter(VideoRender);

FilterGraph.RemoveFilter(TestTrans);

While Assigned(Cam) do Cam := nil;

While Assigned(VideoRender) do VideoRender := nil;

While Assigned(TestTrans) do TestTrans := nil;

While Assigned(TestInf) do TestInf := nil;

Result := S_OK;

end;

여기까지 수정하고 응용어플을 실행하셔도 모든 픽셀의 Green값이 0으로 초기화된 화면이 보이실 것

입니다. 이제 여러분이 여기서 화면에 원하는 문자를 써넣고, 원하는 Green 값을 실행중에 자유롭게 지

정해 놓으실 수 있어야 할 것입니다.

TCamDShow 클래스에 우리는 두가지의 실행 프로시저를 만들 것입니다. 이것은 필터에서의 인터페

이스 구현이 아니라 구현된 인터페이스를 사용하기 위한 로직입니다. 프로시저의 내용은 다음과 같습니

다.

procedure TCamDShow.SetGreenValue(Value: Integer);

begin

if Assigned(TestInf) then TestInf.SetGreenValue(Value);

end;

procedure TCamDShow.SetText(Value: String);

begin

if Assigned(TestInf) then TestInf.SetText(StringToOleStr(Value));

Page 133: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 133 -

end;

위에서 TestInf가 nil 이 아니면 각각의 인터페이스 멤버함수를 실행하고 있습니다. 여기서 SetText

프로시저를 봐주시기 바랍니다. TestInf.SetText 의 매개변수 안에서 또다시 StringToOleStr 를 사용하

고 있습니다. 지난회에 말씀드렸듯이 COM의 문자열은 2바이트 WideChar 이기 때문에 이를 위해 변환

해 주기 위해서 입니다. 자, 이제 여기까지 했다면 이제는 TCamDShow를 사용하는 메인 어플에서 약

간의 수정을 해야겠습니다.

일단 메임폼위에 여러분은 두개의 컴포넌트를 추가로 위치시켜야 합니다. 하나는 TrackBar 이고 또

다른 하나는 Edit나 Memo와 같이 Text를 전송할 컨트롤입니다. Memo나 Edit 옆에는 Button을 하나더

추가로 붙여 놓으면 좋겠습니다. 자, 이렇게 위치시켰다면 여기서 TrackBar 의 기본설정을 조금 변경

합니다. TrackBar 의 Max를 255로 하고요, Min의 값을 0으로 설정합니다. 이것은 Green값의 범위가

1바이트를 넘지 않기 때문입니다. 여기까지 했다면 이제 남은 것은 각 컴포넌트의 이벤트 핸들러를 작

성하는 일만 남았습니다. 먼저 TrackBar의 OnChange 이벤트 핸들러와 Memo나 Edit 옆에 위치시켜

놓은 버튼의 OnClick 이벤트 핸들러 프로시저를 아래와 같이 작성합니다.

procedure TfrmMain.TrackBar1Change(Sender: TObject);

begin

if not Assigned(CamDShow) then Exit;

CamDShow.SetGreenValue(TrackBar1.Position);

end;

procedure TfrmMain.Button1Click(Sender: TObject);

begin

if not Assigned(CamDShow) then Exit;

CamDShow.SetText(Memo1.Text);

end;

위에서 CamDShow가 nil이면 빠져나오고 있습니다. 만약 여러분이 CamDShow를 작동시키지 않은

상태에서 버튼을 누르면 에러가 발생하는 것을 예방하기 위함입니다. 나머지는 CamDShow 를 해서

TestTrans 필터의 ITestInf 인터페이스 함수를 이용할 수 있게 만들어진 멤버함수를 호출하고 있습니

다. 이렇게 해서 여러분은 자신이 만든 필터와 그것의 유니크한 인터페이스를 응용어플에서 실행하실

수가 있는 것입니다. 자, 이제 여러분이 만드시 어플을 실행시켜 보시기 바랍니다. TrackBar가 움직일

Page 134: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 134 -

때마다 화면의 색상이 변경되는 것을 눈으로 직접 확인하실 수가 있을 것입니다.

또한 Memo에 어떤 문자를 기입하신 후에 버튼을 누르면 그 내용이 곧바로 카메라 영상위에 씌여져

나오는 것을 보실 수가 있습니다. 현재는 문자의 크기와 종류가 단순하지만 여러분이 필터의 내부 로직

을 변경하거나, 혹은 별도의 인터페이스를 추가로 설정하여 만드신다면 여러가지 재미있는 기능이 구현

될 수 있을 것입니다.

여기까지 필터의 인터페이스 부분을 마치겠습니다. 아래에는 수정된 TCamDShow 의 풀소스를 보여

드리겠습니다. 나머지 메인폼의 수정된 부분은 굳이 별도의 풀소스로 보여드릴 필요는 없을 것 같습니

다. TBaseDShow 클래스는 현재까지 (1차버전), (2버전) , (3차버전) 모두에서 동일한 것입니다.

unit cCamDShow;

interface

uses

Windows, Dialogs, SysUtils, Classes, Registry, Dire ctShow9, ActiveX, ExtCtrls,

DsUtil, cBaseDShow, Graphics, uCommon;

type

TCamDShow = class(TBaseDShow)

private

Cam : IBaseFilter;

VideoRender : IBaseFilter;

TestTrans : IBaseFilter;

TestInf : ITestInf;

public

constructor Create(Screen:TPanel);

destructor Destroy; override;

function MakeBaseFilter:HRESULT;

function ReleaseBaseFilter:HRESULT;

function ConnectBaseFilter:HRESULT;

procedure Run;

procedure Stop;

procedure SetGreenValue(Value:Integer);

Page 135: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 135 -

procedure SetText(Value: String);

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;

//TestTrans 필터를 생성하고 추가한다.

CreateFilter(CLSID_TTestTrans, TestTrans); //TestTrans 랜더러를 얻고...

Page 136: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 136 -

FilterGraph.AddFilter(TestTrans, 'TestTrans Filter' ); //TestTrans 를 등록한다.

if TestTrans = nil then Result := S_FALSE;

TestTrans.QueryInterface(ITestInf,TestInf); //TestInf 인터페이스를 얻어온다.

// 랜더러 필터를 생성하고 추가한다.

CreateFilter(CLSID_VideoRenderer, VideoRender); // 비디오 랜더러를 얻고...

FilterGraph.AddFilter(VideoRender, 'VdRender Filter' ); // 비디오 랜더러를 등록한다.

if VideoRender = nil then Result := S_FALSE;

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

end;

function TCamDShow.ConnectBaseFilter: HRESULT;

var

hr : HRESULT;

function ConnectFilter(A, B: IBaseFilter): HRESULT;

var

InPin : IPin;

OutPin : IPin;

begin

FindPinOnFilter(A,PINDIR_OUTPUT,OutPin);

FindPinOnFilter(B,PINDIR_InPUT,InPin);

Result := FilterGraph.Connect(OutPin,InPin);

if Result <> S_OK then ShowMessage( ' 필터 연결에 실패하였습니다.' );

end;

begin

Result := S_OK;

if S_OK <> ConnectFilter(Cam, TestTrans) then Exit;

if S_OK <> ConnectFilter(TestTrans, VideoRender) then Exit;

end;

function TCamDShow.ReleaseBaseFilter: HRESULT;

begin

if Assigned(MediaControl) then MediaControl.Stop;

FilterGraph.RemoveFilter(Cam);

Page 137: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 137 -

FilterGraph.RemoveFilter(VideoRender);

FilterGraph.RemoveFilter(TestTrans);

While Assigned(Cam) do Cam := nil;

While Assigned(VideoRender) do VideoRender := nil;

While Assigned(TestTrans) do TestTrans := nil;

While Assigned(TestInf) do TestInf := 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;

procedure TCamDShow.SetGreenValue(Value: Integer);

begin

if Assigned(TestInf) then TestInf.SetGreenValue(Value);

end;

procedure TCamDShow.SetText(Value: String);

begin

if Assigned(TestInf) then TestInf.SetText(StringToOleStr(Value));

end;

end.

17부를 마치겠습니다. 여러분 수고하셨습니다.

Page 138: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 138 -

18. 18. 18. 18. 필터필터필터필터 5555

지금까지 여러분은 DShow 응용어플에서 시작하여 필터와 필터의 인터페이스까지 강의를 들으셨습니

다. 여기까지는 DShow의 기초부분에 해당하기 때문에 별 어려움 없이 따라오셨으리라 생각합니다. 하

지만 이제부터 조금 차원이 다른 DShow의 깊이에 대한 해설이 필요하기 때문에 걱정스러운 마음이 앞

서기도 합니다. DShow의 내부구조가 복잡한 것은, 동영상의 스트리밍 서비스가 그렇게 간단하지 않기

때문입니다. 여러분은 이제 DShow의 핵심이라 할 수 있는 필터의 내부구조로 안내 받으셔야 합니다.

이것은 분명 흥분되고 긴장되는 일이긴 하지만 상당한 끈기와 집착으로 집중력있게 매달려야만 이해할

수 있는 것이기도 합니다. 저는 가능한한 커다란 숲을 보여주기 위해 노력할 것입니다. 그러나 이러한

노력에도 불구하고 제 자신에게 허용된 개인적 시간의 한계로 인하여 다소 행간의 인터벌이 넓게 느껴

지거나 그로인해 이해의 어려움을 겪게 될지도 모르겠습니다. DShow를 하루아침에 완성할 수는 없습니

다. 그리고 누군가의 강의로 인하여 순식간에 높은 위치에 도달할 수도 없을 것입니다. 제가 할 수 있는

일이란 일종의 안내자 역할로서, 여러분의 노력이 최대한 효과적으로 보람된 결과를 얻으실 수 있게 도

와주는 것이라고 생각하고 싶습니다.

자, 이제 필터 내부의 동작 특성에 대하여 설명하겠습니다. 그 첫번째 과정으로서 우리는 지난회에 만

들었던 In Place (제자리) 변환필터의 동작 특성에 대하여 살펴볼 것입니다. In Place 필터는 DShow의

모든 필터들 가운데 가장 복잡한 필터에 속할지도 모르겠습니다. 우리는 너무도 간단히 TestTrans라는

변환필터를 TBCTransInPlaceFilter 로 부터 상속받아 생성하였지만, 실은 TBCTransInPlaceFilter 클

래스 자체가 내부적으로 엄청나게 복잡한 필터연결 과정을 거치고 있는 것입니다.

In Place 변환필터(이하 제자리 변환필터라고 하겠습니다.)는 버퍼를 공유하는 특성이 있습니다. 즉,

앞의 필터와 뒤의 필터의 미디어 데이터 메모리 버퍼를 공유하려고 합니다. 예를 들면 다음과 같습니다.

위와같은 연결상태가 있다고 가정해 보겠습니다. 즉, 제자리 변환필터가 몇개씩이든지 계속해서 연결

되어 있는 상황이 만들어져 있는 것입니다. 일단 Cam 과 첫번째 제자리 변환필터인 In Place(1)이 연결

되었을 때, 둘사이에는 공유 버퍼가 하나 별도로 생성되게 됩니다. Cam과 In Place(1)은 그 공유버퍼

를 이용하여 미디어 데이터 작업을 순서적으로 진행해 나가려고 할 것입니다. 그런데 여기서 끝나는 것

이 아니라 뒤에 또다시 동일한 종류의 변환필터인 In Place (2) 가 연결되고 있습니다. In Place(1)과

In Place(2)가 연결되는 순간에 아주 재미있는 현상이 발생합니다. 즉 일단은 둘사이에 공유버퍼가 별도

로 만들어지는 것은 전과 동일합니다. 그러나 둘 사이의 공유버퍼가 만들어지는 순간에 In Place(1) 내

Cam In Place(1) In Place(2) Video Renderer

Page 139: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 139 -

부에서는 자신과 이미 연결되어 있는 Cam 필터와의 재연결을 하라고 강요합니다. 이렇게해서 Cam과

In Place(1)과는 재연결이 이뤄지게 되는데요, 이때 공유버퍼는 별도로 또다시 할당되어 지는게 아니라

In Place(1)과 In Place(2) 둘 사이에 만들어졌던 공유버퍼를 가져오게 되는 것입니다. 이런식으로 랜더

러까지 연결되어지는 것인데요, 이것을 순서적으로 표현하면 다음과 같습니다.

(1) Cam ─(연결, 공유버퍼 A 할당 )─▶ In Place(1) --> In Place(2) --> Video Renderer

상태 : Cam ─A─▶ In Place(1) --> In Place(2) --> Video Renderer

(2) Cam ─A─▶ In Place(1) ─(연결, 공유버퍼 B 할당 ) ─▶ In Place(2) --> Video Renderer

상태 : Cam ─A─▶ In Place(1) ─B─▶ In Place(2) --> Video Renderer

(3) Cam ─(재연결, 공유버퍼 B 가져옴 )─▶ In Place(1) ─B─▶ In Place(2) --> Video Renderer

상태 : Cam ─B─▶ In Place(1) ─B─▶ In Place(2) --> Video Renderer

(4) Cam ─B─▶ In Place(1) ─B─▶ In Place(2) ─(연결, 공유버퍼 C 할당 ) ─▶ Video Renderer

상태 : Cam ─B─▶ In Place(1) ─B─▶ In Place(2) ─C─▶ Video Renderer

(5) Cam ─B─▶ In Place(1) ─(재연결, 공유버퍼 C 가져옴 )─▶ In Place(2) ─C─▶ Video Renderer

상태 : Cam ─B─▶ In Place(1) ─C─▶ In Place(2) ─C─▶ Video Renderer

(6) Cam ─(재재연결, 공유버퍼 C 가져옴 )─▶ In Place(1) ─C─▶In Place(2) ─C─▶ Video Renderer

상태 : Cam ─C─▶ In Place(1) ─C─▶ In Place(2) ─C─▶ Video Renderer

Page 140: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 140 -

위의 도식을 보시면 상당히 복잡함을 느끼실 것입니다. 그렇다고 하더라도 여러분은 반드시 이 흐름

을 이해하셔야 합니다. 위에서 점선화살표 '-->' 는 아직 연결되지 않았음을 의미합니다. 반대로 실선

화살표 ─▶는 이미 연결된 상태라는 것을 의미합니다. 또한 중간에 영문자가 들어간 것은 설정된 버퍼

의 기호를 의미합니다. 위의 도식을 다시한번 설명드리자면 처음에 Cam 과 In Palce(1) 의 연결에서 공

유버퍼 A 가 만들어 졌습니다. 이어서 In Place(1)과 그 다음의 필터인 In Place(2) 가 연결되면서 새

롭게 공유버퍼 B가 만들어 졌는데요, 이 두개의 필터가 연결되면서 이미 연결되어 있는 자신의 상위 필

터와의 연결을 다시하라고 명령합니다. 즉 Cam과 In Place(1)과의 접속을 다시하게 되는 것입니다. 그

런데 이때 공유버퍼를 새롭게 만드는 것이 아니라 만일 하위 필터가 연결되어 있다면 그 하위 필터의

연결에서 사용된 공유버퍼를 가져와서 설정하게 됩니다. 이런 식으로 끝없이 반복되어지는 것이죠.

자, 그렇다면 여러분은 한가지 의문이 생기실 것입니다. 아니 대체 왜 재접속을 하면서까지 아래에서

부터 공유버퍼를 거꾸로 가져오려고 하는 것인지, 차라리 상위 필터의 공유버퍼를 하위 필터와의 접속

시 가져와서 사용하게 되면 불필요한 재접속도 없고 편리하지 않겠는가에 대한 의문입니다. 즉, 아래와

같습니다.

(1) Cam ─A─▶ In Place(1) --> In Place(2) --> Video Renderer

(2) Cam ─A─▶ In Place(1) ─A─▶ In Place(2) --> Video Renderer

(3) Cam ─A─▶ In Place(1) ─A─▶ In Place(2) ─A─▶ Video Renderer

위와같이 재접속이 없이 그냥 상위 필터와의 접속시에 생성되었던 공유버퍼를 그대로 하위필터와의

연결에서 사용하는 방식도 있을 것입니다. 이와같은 방식은 불필요한 재접속이 이뤄지지 않기 때문에

훨씬 간단하며 이해하기도 편리한 것입니다. 그렇다면 대체 왜 In Place (제자리) 변환필터는 재접속이

라는 걸끄러운 방식을 택해야만 했을까요. 이 의문에 대한 해답은 우리가 실제로 재접속이 필요없는 간

단한 형태의 변환필터를 직접 한번 제작해보고나서 해소되어 져야 할 것입니다.

본격적으로 또 다른 변환필터의 제작 실습에 들어가기 앞서서 몇가지 추가 참고사항에 대하여 설명하

여야 겠습니다. DShow에서는 일반적으로 Up 스트림이라거나, Down 스트림이라는 말을 사용합니다.

번역된 도움말 파일에는 이것을 상위 혹은 하위와 같은 말로서 표현하고 있습니다. 이번 강좌에서 저는

간혹 상위 필터라든지 하위 필터라는 말을 사용하기도 하였습니다. 이 모든 표현은 결국 하나의 의미를

가지고 있습니다. 기준이 되는 지점보다 위쪽에 있던지 아니면 아랫쪽에 있던지를 의미하는 것입니다.

DShow에서의 위쪽은 스트림의 근원, 즉 소스필터를 향하는 방향을 의미하며, 반대로 아랫쪽은 랜더링

되거나 저장되는 방향을 의미하고 있습니다. 별것은 아니지만 간혹가다가 헷갈릴때가 있습니다. 상위

Page 141: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 141 -

(Up) 스트림이 자꾸만 반대 방향으로 연상되는 것입니다. 이것은 GraphEdit에서 대부분 랜더필터와

Writer 필터가 오른쪽에 위치하기 때문인데요, 습관적으로 글쓰는 방향인 오른쪽이 상위라고 각인되어

있는 분에게는 종종 헷갈릴 때가 많습니다. 간단한 참고사항이었습니다.

이쯤에서 여러분에게 할당자라는 것에 대하여 설명을 드려야 하겠습니다. 위에서 저는 필터와 필터간

에 사용되어지는 메모리를 공유버퍼라고 직설적으로 표현 하였지만 정확하게 DShow 식으로 말하자면

올바른 표현이 아닙니다. 물론 100%로 틀린 표현도 아니지만, 일종의 은유적인 한 방식이라고 생각해

주셨으면 합니다. DShow에서 필터간의 메모리 공유는 할당자라는 것을 통해 이뤄지게 됩니다. 이것을

설명하기 전에 어째서 동영상의 버퍼 공유가 복잡한지에 대한 설명을 먼저 해야 겠습니다. 예전에 저는

BT878 캡쳐보드의 디바이스 드라이버 프로그래머와 함께 작업한 적이 있었습니다. 그 당시 저에게는

DShow의 버퍼링을 한번이라도 더 줄이는게 지상최대의 과제였습니다. 어느날 저는 동료 디바이스 프로

그래머에게 BT878에서 흘러나오는 데이터가 처음으로 버퍼링되는 시스템 메모리의 포인터를 넘겨 줄

수 있느냐고 물었습니다. 그는 한동안 나를 멀뚱히 쳐다보더니 고개를 가로지으며 이런 말을 하였습니

다. '그건 간단한 일이 아닙니다. 일단 시스템 메모리에서 일방 응용어플에게 직접 포인터를 건네줄 수가

있는지 알아봐야 겠습니다만, 설사 그게 가능하다고 하더라도 대체 어떤 메모리 포인터를 넘겨줘야 하

는지 알 수가 없습니다.' 그의 대답에 이번에는 내가 고개를 갸우뚱했습니다. '아니 어떤 메모리라니요?

그냥 이미지 데이터가 저장되어 있는 메모리를 말하는 것인데요.' 그러자 그가 조금 언성을 높이며 설명

했습니다. '우리가 BT878 보드에서 데이터를 가져올 때에는 버퍼를 하나만 만들어 놓고 있는 것은 아닙

니다. 실제로 BT878은 한번에 두개의 영상을 짝수, 홀수로 가지고 있어서 이것을 나누어 가지고 와야

합니다. 그러니까 한번에 연속되는 두개의 프레임의 이미지가 각각 짝수 홀수로 하나의 버퍼에 저장되

어 있는 것입니다. 다음과 같이 말이죠.

(1) ───────── 1번 프레임 1라인 (2) ───────── 1번 프레임 2라인

--------------- 2번 프레임 1라인 --------------- 2번 프레임 2라인

───────── 1번 프레임 3라인 ───────── 1번 프레임 4라인

--------------- 2번 프레임 3라인 --------------- 2번 프레임 4라인

───────── 1번 프레임 5라인 ───────── 1번 프레임 6라인

--------------- 2번 프레임 5라인 --------------- 2번 프레임 6라인

───────── 1번 프레임 7라인 ───────── 1번 프레임 8라인

--------------- 2번 프레임 7라인 --------------- 2번 프레임 8라인

위와 같은 구조로 연속해서 들어오게 됩니다. 이것을 하나의 이미지로 만들기 위해서는 1번 프레임과

2번 프레임이 이미모두 들어와 있어야만 가능합니다. 즉 1번 프레임만 들어온 상태에서는 두개의 이미

지가 짝수라인이 없는 상태로 만들어지게 되는 것입니다. 그래서 저희는 시스템 메모리에 통산 2의 배

수개 만큼의 충분한 버퍼를 마련해 둬야 합니다.

Page 142: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 142 -

위와 같은 하드웨어적인 문제 말고도 소프트웨어적인 문제도 있습니다. 저희가 넘겨준 버퍼의 포인터

를 해당 소프트웨어에서 모두 사용했는지에 대한 결과를 알아야 합니다. 왜냐하면 현재 소프트웨어가

해당 버퍼의 데이터를 사용하는 중간에 저의 디바이스 드라이버가 바로 그 버퍼에 덮어 쓸수가 있기 때

문입니다. 이건 간단한 일이 아닙니다.' 이것은 약 6 여년 전의 일이라 그 개발자의 설명을 충분히 올

바르게 기억하고 있는지는 모르겠습니다. 하지만 저도 몇번 디바이스 드라이버의 개발서를 공부해본 적

이 있기 때문에 기본적인 개념에서 벗어나 있지는 않는다고 생각합니다.

이렇게 동영상에 있어서 버퍼공유란 간단한 작업이 아닙니다. 하드웨어적으로 인터리브하게 들어오는

문제도 문제이지만, 만일 버퍼가 한개 뿐이라면 현재 사용중인 버퍼에 다음 프레임을 덮어씌워도 될지

안될지를 알아야 하는 근본적인 문제가 발생하기 때문입니다. DShow는 여러개의 필터가 연결되는 방식

으로 동작되고 있습니다. 하드웨어의 성능상의 문제로 인하여 중간 중간에 시간지연이 생길 가능성이

무척 높은 구조입니다. 따라서 이러한 위험성을 낮춰줄 완충작용을 하는 버퍼링을 생각해야 하는데요,

이러한 서비스를 해주는 것이 바로 DShow에 있어서 할당자라는 것입니다.

할당자는 COM Object 입니다. 필터나 필터그래프처럼 독립된 단위의 COM 객체입니다. 만일 필터 A

와 필터 B 가 연결된다면 그 과정에서 할당자 COM 객체가 CoCreateInstance 를 이용하여 생성되게

됩니다. 다시말하면 A와 B라는 필터가 공유하게 되는 것은 공유버퍼가 아니라 바로 이 할당자라는 것

입니다. 여러분이 만약 BaseClass.pas 유닛을 열고 검색으로 검색하신다면 대체 어디에서 메모리를 설

정하고 해제하는 것인지 찾으실 수가 없을 것입니다. COM에서의 메모리 할당을 해주는

CoTaskMemAlloc함수가 두군데에서 보이기는 하지만, 이것은 미디어 데이터를 담기 위한 메모리 할당

이 아닌 것을 금방 아실 수가 있을 것입니다. 게다가 GetMem이나 AllocMem 같은 API 함수는 눈에

띄지 조차 않을 것입니다. 그렇다면 대체 어디서 새로운 버퍼의 할당이 이뤄지고 해제가 이뤄지는 것일

까요. BaseClass.pas 유닛에서 GetBuffer라는 단어를 검색해보시면 상당히 군데군데 FAllocator라는 멤

버객체로부터 실행되고 있는 멤버함수가 보이실 것입니다.. 바로 이 GetBuffer가 메모리를 할당하는 역

활을 합니다. GetBuffer를 실행하고 있는 FAllocator 는 IMemAllocator 라는 인터페이스형 타입의 변

수로서, 바로 할당자가 CoCreateInstance 함수로서 인스턴스화 되어질때 처음으로 뽑아내어지는 인터

페이스인 것입니다.

자, 그런데 할당자가 직접적으로 GetBuffer를 사용하여 버퍼를 만들고 관리하는 것이 아닙니다. 할당

자인 FAllocator은 GetBuffer를 통해서 또다시 '미디어 샘플'이라는 또다른 COM 객체를 생성시킵니다.

미디어 샘플 COM 객체가 비로소 버퍼를 생성 관리하는 것인데요, 바로 이것을 가지고 앞의 필터와 뒤

의 필터가 버퍼를 공유하게 되는 것입니다. 여러분은 지난회에서 In Place 제자리 변환 필터의

Transform 프로시저에서 Sample.GetPointer(pBuff) 이렇게 하여 버퍼의 포인터를 얻어왔을 것입니다.

바로 이때 Sample의 타입이 IMediaSample 인터페이스형인 것입니다. 이것을 다시한번 설명하면 이렇

습니다.

Page 143: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 143 -

두개의 필터가 연결되어질때, 하위 필터의 입력핀에서 할당자라는 것을 생성합니다. 이것은 하나의

COM 객체로서 미디어의 버퍼풀이라는 것을 관리합니다. DShow의 응용어플이 Play되면서 할당자는

GetBuffer 메서드를 사용하여 또 다른 COM 객체인 미디어 샘플을 생성하고 그것의 IMediaSmaple 인

터페이스를 건네줍니다. 여러분은 바로 이 미디어 샘플의 IMediaSmaple 인터페이스를 통하여 버퍼의

포인터, 크기, 샘플시간 등을 얻어서 실질적인 작업을 하실 수가 있는 것입니다.

자, 할당자에 대해서는 일단 여기까지만 설명드리겠습니다. 여러분은 나중에 버퍼협상 부분에 가서 또

다른 관점의 버퍼공유에 대한 이해를 가지고 있어야 할 것입니다. 일단 우리는 재연결이 필요없는 간단

한 형태의 또다른 In Place형 제자리 변환 필터를 만들어 볼 것입니다.

그런데 제가 응용어플 편에서 필터에 있어서 모든 기능은 핀에 집중되어 있으며 필터 자체는 껍데기

에 불과한 것이라는 설명 을 드렸을 것입니다. 그래서 여러분은 지난회에 In Place 제자리 변환필터를

만들면서 약간 헷갈리시지 않았나 생각됩니다. 왜냐하면 In Place 변환필터에는 어떠한 핀 COM 멤버객

체도 찾아 볼수가 없었기 때문입니다. DShow의 변환필터 구조는 유독 핀의 기능을 몸체로 집중시키고

있습니다. 이게 무슨 말이가 하면, 두 종류의 핀을 내부에 만들어 놓고서 그것의 실행을 본체로 넘겨주

는 방식을 취하고 있다는 것입니다. 예를 들어 아래와 같은 클래스가 있다고 생각해 보겠습니다.

TDelivery = class

InputImg: TInputImage;

OutPutImg: TOutPutImage;

procedure Delivery(Img:TImage);

end;

위에서 보시듯이 InputImg와 OutputImg라는 두개의 멤버객체가 존재하고 있습니다. 두개의 멤버객체

는 모두 TImage에서 상속받아서 만들어졌다고 가정하겠습니다. 이런 상황에서 InPutImg에 어떤 이미지

가 씌여지자마자, 그것을 또 다른 멤버객체인 OutPutImg에 복사하기 위해서는 어떻게 해야 할까요.

InPutImg 멤버객체는 반드시 TDelivery 부모객체의 포인터를 어떻게든 가지고 있어야 할 것입니다. 그

래야만 TDelivery 부모객체를 통해서 OutPutImg 객체의 멤버에 도달할 수가 있기 때문입니다. 그런데

여기서 이런 방식을 실제적으로 구현하는 것으로는 두가지 방법론이 있습니다. 즉, InPutImg의 멤버함수

에서 부모객체의 포인터를 통해서 OutPutImg를 직접 제어하는 방식과 또다른 하나는 부모객체에 필요

한 모든 함수를 만들어 놓고서 InPutImg 에서는 그냥 부모객체의 멤버함수를 불러내는 것입니다. 현재

DShow의 변환필터에서 사용되는 방법이 이 두번째 방식인 것인데요, 그래서 내부를 살펴보면 핀들의

멤버함수가 주된 역활을 하는 것이 아니라 필터자체의 멤버함수가 주요 역활을 수행하고 있는 것처럼

Page 144: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 144 -

보입니다. 그런데 저는 이런 방식을 아주 싫어합니다. 소스의 가독율이 떨어질 뿐만이 아니라, 어느 한

COM 클래스를 수정하려면 나머지도 몽땅 뜯어 고쳐야 하기 때문입니다. 그래서 이번에 실습하는 일명

'무재접속 변환필터'는 각기 독립적인 역활의 COM 클래스로서 설계될 것입니다. (그렇다고 해서

DShow의 변환필터 구조가 잘못되었다는 것은 아닙니다. 이렇게 본체에 집중시키는 클래스 구조는 이것

이 굉장히 완성적인 형태의 구조라면 하위 클래스로 상속되어질때 많은 편리성을 제공하기 때문입니다.

여러분도 아시겠지만 우리가 가장처음 InPlace 변환필터를 개발하였을때 단지 두개의 멤버함수만이 재

정의 되었을 정도로 단순화된다는 것입니다. )

이제 본격적인 프로젝트를 실습하겠습니다. 여러분은 새로운 Library 프로젝트를 델파이에서 생성하

도록 하겠습니다. 그리고 이 프로젝트의 이름을 BaseTrans라고 명명하겠습니다. 그렇다면 다음과 같은

프로젝트 파일이 만들어져 있어야 할 것입니다.

library BaseTrans;

uses

BaseClass;

{$E ax}

exports

DllGetClassObject,

DllCanUnloadNow,

DllRegisterServer,

DllUnregisterServer;

begin

end.

그리고 세개의 유닛을 추가로 생성하여 각각의 이름을 uMain, uInput, uOutpin 이라고 설정하고 저장

하겠습니다. 자 이런 상태에서 하나씩 재미있는 과정을 밟아가며 살펴보겠습니다. 먼저 우리는

BaseTrans 필터의 CLSID를 생성하고 정의해 놓아야 합니다.

const

CLSID_BaseTrans: TGUID = '{4B2B57B4-4551-4D53-BD7C-81D25AC18D2A}' ;

Page 145: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 145 -

이제 다음과 같이 클래스를 선언해 보겠습니다.

type

TBaseTrans = class(TBCBaseFilter)

private

public

constructor Create(Name: string;

Unk : IUnKnown;

Lock: TBCCritSec;

const clsid: TGUID

);

constructor CreateFromFactory(Factory: TBCClassFactory;

const Controller: IUnknown); override;

destructor Destroy; override;

function GetPin(n: Integer): TBCBasePin; override;

function GetPinCount: integer; override;

end;

위의 선언은 지난번과 거의 동일한 것입니다. 차이가 있다면 상속을 TBCTransInPlaceFilter 에서가

아니라 TBCBase Filter에서 받은 것이라는 점과 두개의 멤버함수인 GetPin과 GetPinCount가 추가 되

었다는 점입니다. 이 두개의 멤버 함수는 반드시 override가 필요한 것입니다. 그 이유에 대해서는 후에

설명하도록 하겠습니다. 일단 이것들의 구현을 보시겠습니다.

constructor TBaseTrans .Create(Name: string; Unk: IInterface;

Lock: TBCCritSec; const clsid: TGUID);

var

hr: HRESULT;

begin

inherited create(Name,Unk,Lock,CLSID_DongTransform);

end;

Page 146: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 146 -

constructor TBaseTrans .CreateFromFactory(Factory: TBCClassFact ory;

const Controller: IInterface);

begin

Create(Factory.Name,Controller, TBCCritSec.Create, Factory.ClassID);

end;

destructor TBaseTrans .Destroy;

begin

inherited;

end;

function TBaseTrans .GetPin(n: Integer): TBCBasePin;

begin

result := nil;

end;

function TBaseTrans .GetPinCount: integer;

begin

Result := 0;

end;

위에서 CreateFromFactory 생성자가 필요한 이유에 대하여는 지난회에 설명을 드렸습니다. 여기까

지 코딩하시고 본 회차 강의가 너무 길어지기 때문에 일단 18부의 강의를 여기서 마치겠습니다. 여러분

수고하셨습니다.

Page 147: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 147 -

19.19.19.19. 필터필터필터필터 6666

지난회에 우리는 재접속하지 않는 베이스 변환필터를 만들다 말았습니다. 그럼 나머지 initialization를

다음과 같이 코딩하겠습니다.

initialization

TBCClassFactory.CreateFilter(TBaseTrans, '_BaseTrans' , CLSID_BaseTrans,

CLSID_LegacyAmFilterCategory, MERIT_DO_NOT_USE, 0, nil);

자, 위의 내용은 이미 모두 설명을 드렸던 것입니다. 첫번째 매개변수는 COM 클래스의 이름을 의미

하고, 두번째는 디스플레이용 이름을, 세번째 매개변수는 클래스의 CLSID가 들어가는 자리이며, 네번째

매개변수는 DSHow 의 카테고리 ID를 의미한다고 말이죠. 여기까지 하고서 컴파일하고, 생성된 필터를

Regsvr32를 이용하여 레지스트리에 등록합니다. 그리고서 GraphEidt 를 실행하고 '필터삽입윈도우'를

이용하여 방금 만드신 _BaseTrans를 생성시켜 보십시오.

여러분은 이제 양팔이 잘려나가서 몸뚱이만 남은 필터를 보고 계십니다. 즉, 필터의 핀이 전혀 없고

오로지 사각박스 형태의 단순하고도 기본적인 필터 COM 객체를 생성하신 것입니다. 이것은 더이상 단

순화 될 수 없는, 가장 기초적인 구조의 필터라고 생각하시면 되겠습니다.

이제부터 우리가 해야할 일은 오로지 핀이라는 COM 클래스를 만들어서 필터 본체에 멤버객체로 가

져다 놓는 것입니다. 그런데 여기서 한가지 알아둬야할 사항이 있습니다. 일반적으로 필터본체의 가장

상위 클래스가 TBCBaseFilter라고 한다면 핀의 가장 상위 클래스는 TBCBasePin 이라고 할 수 있을

것입니다. 그러나 Pin의 경우에 있어서는 이것에서부터 상속 받아서 한단계 아래에 있는

TBCBaseInputPin 과 TBCBaseOutputPin 에서 각기 상속을 받아야 할 것입니다. 그 이유는 다음과 같

습니다.

아직 여러분에게 설명은 하지 않았지만 필터와 필터가 연결될때 세가지 협상을 순차적으로 하게 됩니

다. 그 가장 첫번째 협상이 핀 협상이라는 것으로서 이것은 필터의 본체, 즉 TBCBaseFilter 클래스에

서 완성된 로직을 가지고 있기 때문에 이 부분에 대한 더이상의 추가 보완이 필요치 않습니다. 우리는

그냥 TBCBaseFilter 을 상속하기만 하면 되는 것입니다. 그런데 두번째 협상과 세번째 협상은 오직 핀

객체에서 이뤄지게 되는데요, 이것들이 각기 미디어 협상과 버퍼 협상이라는 것입니다. 여기서 미디어

협상은 TBCBasePin인 최상위 클래스에서 이뤄지지만, 버퍼협상 과정은 이것에서부터 각기 상속을 받

은 TBCBaseInputPin 과 TBCBaseOutputPin COM 클래스에서 이뤄지고 있다는 것입니다. 이렇게 기

Page 148: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 148 -

본적인 협상과정의 하나인 버퍼협상을 핀의 가장 기초 클래스에 두지 않고, 각기 고유성격의 하위 클래

스에 두게된 이유에 대하여는 아마도 필터그래프 자체의 내부성격에 기인하고 있는 것이라고 짐작만 할

뿐입니다.

자, 이런 이유로 인해서 여러분은 TBCBaseInputPin 과 TBCBaseOutputPin에서 상속받아서 각각의

핀 클래스를 개발할 수밖에 없다는 것입니다. 만일 버퍼협상에 대한 필터그래프의 동작패턴이 좀더 융

통성이 있었다면 여러분은 어쩌면 TBCBasePin에서 상속받아서 좀더 독창적인 Input 핀과 Output 핀

클래스를 개발하실 수 있지 않을까 생각해보기도 합니다. 미디어 협상과 버퍼협상에 관해서는 좀더 프

로젝트를 진행해나가는 중간중간에 추가 해설을 달아 드릴 것입니다.

여러분은 현재 한개의 프로젝트 파일과 세개의 유닛파일을 확보하고 계십니다. uMain, uInput,

uOutput이라는 pas 파일입니다. 이것들은 각각의 클래스를 선언하고 구현할 수 있는 편리함을 가지고

있습니다. 그러나 DSPack의 필터 샘플 예제에서는 하나의 유닛에 필터, Input핀, Output핀 클래스를 모

두 선언하고 구현해 놓은 것을 보실 수가 있습니다. 이렇게 할 수밖에 없는 이유는 델파이의 독특한 컴

파일 환경 때문에 기인한 것입니다. 다시말하면 uInput 클래스에서 uMain에 정의된 필터의 객체 포인터

를 얻어와야 하는데요, 만일 uInput 유닛의 uses 에 uMain을 추가한다면 컴파일 순환참조 에러가 걸리

기 때문입니다. 즉, uMain의 uses 문에는 어쩔수 없이 uInput가 추가되어 있어야 할 터이고요, uInput에

서 uMain의 클래스를 참조하려면 마찬가지로 uses 문에 uMain이라고 추가되어야 하는데요, 이렇게 한

다면 순환참조 에러에 빠져들기 때문이라는 것입니다.

이렇게 순환참조 에러가 골치덩이기는 하지만 그렇다고 방법이 없는 것은 아닙니다. 여러분도 다들

아시겠지만 Implementation 아래에다 uses문을 만들어 선언하면 될 것입니다. 제가 생각하기에

DSpack의 모든 샘플예제는 너무도 충실히 VC++ SDK의 원본을 그대로 따르고 있습니다. 각각의 코

딩 내용 뿐만이 아니라 심지어 매개변수와 멤버볌수, 멤버함수의 이름이 완전히 동일한 수준에 도달해

있는 것입니다. 이것은 VC++ SDK 원본과의 혼란을 최대한 줄여보겠다는 원 제작작의 고마운 배려가

아닌가 싶습니다. 그러나 우리가 여기서 한단계 나아가 저마다의 필터개발 프로젝트를 진행해 나가려

한다면, 굳이 이러한 배경상황에 빠져들 필요는 없을 것입니다. 자, uMain, uInput, uOutput 이 세개의

유닛으로 나눠놓는 것은 약간의 구닥다리 같은 부분이 필연적으로 생기겠지만, 프로젝트를 진행해 나가

는데 있어서 참으로 편리한 것입니다. 이것을 하나의 유닛에 모두 선언하고 구현하는게 편리하다면 굳

이 말리지는 않겠습니다.

이제 각각의 Input핀과 Output 핀 클래스를 만들어 보겠습니다. 이것은 현재 너무도 간단한 것입니다.

몇줄 안되기 때문에 전체를 이곳에 옮겨 보도록 하겠습니다.

//======== uInput.pas 파일의 내용.==========

Page 149: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 149 -

unit uInput;

interface

uses

BaseClass, DirectShow9, DSUtil;

type

TInputPin = Class (TBCBaseInputPin) //TBCBasePin

private

public

function CheckMediaType(mt: PAMMediaType): HRESULT; override;

end;

implementation

function TInputPin.CheckMediaType(mt: PAMMediaType): HRESULT ;

begin

result := S_OK;

end;

end.

//======== uOutput.pas 파일의 내용.===========

unit uOutput;

interface

uses

BaseClass, DirectShow9, DSUtil;

type

TOutputPin = Class (TBCBaseOutputPin) //TBCBasePin

private

public

function CheckMediaType(MediaType: PAMMediaType): HRESULT; override;

end;

Page 150: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 150 -

implementation

function TOutputPin.CheckMediaType(MediaType: PAMMediaType): HRESULT;

begin

Result := S_OK;

end;

end.

위에서 보시면 알겠지만 각각 TBCBaseInputPin과 TBCBaseOutputPin에서 상속을 받고 있으며 단지

CheckMedia Type 멤버함수 하나만을 Override 하고 있을 뿐입니다. 사실 이것도 현재로서는 불필요한

것입니다만 너무 맨숭맨숭할 것 같아서 일단 한번 붙여본 것입니다. 현재 우리는 어떻게든 핀을 생성해

서 필터본체에 갖다 붙이는 것이 최우선이기 때문에 핀의 본래 의무사항들을(멤버함수들을) 구현하지는

않고 있는 것입니다.

자, 여기까지 하고서 다시 uMain.pas 유닛으로 돌아가 보겠습니다. 이곳에 정의되어 있는

TBaseTrans 클래스에 방금 여러분이 새롭게 정의하신 Input 핀과 outPut 핀 클래스를 참조하기 위해

서는 Interface 아래의 uses문에 uInput과 uOutput 이렇게 각기 두개의 유닛을 추가해야 할 것입니다.

그리고 TBaseTrans의 private 구분에다가 각각의 멤버변수를 아래와 같이 선언해 놓아야 할 것입니다.

type

TBaseTrans = class(TBCBaseFilter)

private

FInputPin : TInputpin;

FOutputPin : TOutputPin;

.

.

( 생략)

여기까지 하고서 또다시 설명드려야 할 부분이 생겼습니다. 핀의 멤버객체를 어디서 처음 생성해야

하는지 고민해야한다는 것입니다. 일반적으로 여러분은 Create 생성자를 떠올리시겠지만, DSPack의

BaseClass.pas 유닛에 정의되어 있는 기본 필터 클래스들은 꼭 그렇지만은 않기 때문입니다. 물론

Page 151: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 151 -

Create 생성자에 핀 클래스를 생성시키는 필터도 있을 것입니다만 그것보다는 필터의 GetPin 이라는

멤버함수 안에서 이상하게도 핀 객체를 생성시키고 있는 것을 보실 수가 있으실 것입니다. 이러한 것에

대한 정확한 이유는 아직까지 파악할 수는 없었습니다. Create생성자나 GetPin 멤버함수나 모두 동일한

생성결과를 가져오기 때문에 저는 굳이 GetPin 멤버함수에 생성시키지는 않습니다. 게다가 만약에 Pin

클래스 안에서 메모리 할당과 같은 일을 수행하였고, GetPin 멤버함수에서 각각의 Pin 객체를 생성하였

다면, 메모리를 해제하기 위해 또다시 Destroy 소멸자에서 Pin 객체를 Free해줘야 하는데요, 이것은 소

스코드가 결코 나이스하게 일관성을 유지하는데 어려움을 주게 됩니다. 자, 말이 좀 꼬인것 같습니다.

이것을 다시 풀어서 말씀 드리겠습니다.

DSPakc의 BaseClass.pas 유닛에 선언되어 있는 기초 필터 클래스들은 대부분 핀 멤버객체를 생성시

키는 장소로서 GetPin이라는 멤버함수를 애용하고 있습니다. 이것이 가능한 이유는 어차피 필터가 연결

되어 지거나, 혹은 생성되어지면서 한번 이상은 GetPin이라는 멤버함수가 불려지기 때문입니다. 그런데

이렇게 GetPin이라는 멤버함수에서만 반드시 핀을 생성시켜야 하느냐하면, 꼭 그렇지만은 않다는 것입

니다. 여러분이 상식적으로, 그리고 일상적으로 델파이 프로그래밍에서 그래왔듯이, Create 생성자에서

필터객체가 생성됨과 동시에 핀객체도 생성되게 할 수도 있다는 것입니다. 그렇다면 어째서 DSPack에

서는 GetPin 멤버함수에서 핀을 생성시키는 것을 선호하는 것인가, 이 의문에 대한 해답을 아직까지는

분별하고 있지 못합니다. 어쩌면 S_OK와 NOERROR 의 값이 모두 0임에도 불구하고 두개로 나눠 놓고

사용하는 것처럼, 아주 작은 차이로 인해서 GetPin 쪽을 더 선호하는 것이 아닐까 생각하기도 합니다.

아무튼 무슨 이유에선지 모르지만 S_OK와 NOERROR를 나눠놓고 사용하고 있는 것에 대한 이유를 예

전에 한번 발견했다가 아무생각없이 까먹었듯이, 그렇게 중요하고 엄청난 차이가 아닌것만은 사실이지

않을까 싶습니다.

자, 이러한 관점에서 볼때 우리는 두가지 중에서 한가지를 선택해야 합니다. DSPack 이 선호하는

GetPin에서 Pin을 생성하느냐, 아니면 일상적으로 델파이 프로그래밍에서 해왔듯이 Create생성자에서

생성하느냐의 문제라는 것입니다. 그런데 만약 여러분이 코딩상의 관습에 상당부분 젖어 있다면, 이러한

관습적인 일관성을 유지하는 것이 얼마나 편리한 것이지를 잘 알고 계실 것입니다. 일반적으로 우리는

클래스를 설계할 때에 Create생성자에 각종 멤버변수나 멤버객체를 초기화하곤 합니다. 그리고

Destroy 소멸자에 반대로 초기화한 각종 멤머를 해제하는 작업을 하게 되는 것이죠. 그런데 만약

GetPin이라는 함수에서 Pin 객체를 생성하였다면, 우리는 이것을 소멸시키고자 할때 어쩔 수 없이

Destroy라는 소멸자를 동일하게 사용하게 되는데요, 이것은 다음과 같이 연상을 해야 합니다.

* GetPin(핀 생성) Destroy(핀소멸)

그런데 위와같은 것은 아무래도 다음과 같은 연상에 비하여 비선형적인 관습을 강요한다는 것입니다.

Page 152: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 152 -

* Create(핀 생성, 기타 멤버변수 초기화) Destroy(핀 소멸, 기타 멤버변수 소멸)

이처럼 아무래도 후자쪽이 더 연상하기 편리하다고 생각하지는 않는지요. 저는 그렇게 생각하기 때문

에 주로 후자쪽을 애용하고 있습니다.

별것도 아닌 것을 참으로 많이 설명을 하였다는 생각이 들기도합니다. 하지만 제가 이렇게까지 설명

을 열심히 드린것은 여러분이 BaseClass.pas 유닛에 선언되어 있는 기초 클래스를 해석하려할때, 핀의

생성이 Create 생성에 있지 않고 GetPin 멤버 함수에 있다는 사실에 놀라거나 혹은 심지어 두려워하시

지 말라는 것입니다. 저는 초기에 이러한 사실에 대하여 많은 이해부족으로 소스파악에 어려움을 겪기

도 하였기 때문입니다.

자, 이제 여러분의 핀을 Create 에서 생성시키고 Destroy에서 소멸시켜 보도록 하겠습니다. 위와 같

이 각각의 Pin을 생성하고 소멸시키는 로직을 구현하였습니다. 핀의 생성자 매개변수에는 TBCCritSec

라는 객체의 생성의 보이는데요, 이것은 스레드를 동기화 시킬때 사용하는 크리티컬섹션을 클래스화한

것입니다. 이것을 사용해 보고나서는 참 편리해서 다른 프로젝트에서도 스레드를 동기화 시킬 필요가

있을 때에는 델파이의 TCriticalSetion을 사용하지 않고 이 객체를 복사해서 사용하곤 합니다. 이것의

소스를 보시면 아시겠지만 크리티컬섹션이 조금 더 직관적으로 클래스화 되어 있다는 것을 아실 수가

있으실 것입니다.

AnyWay... 여기까지 코딩을 하시고 컴파일을 하셔도 필터의 핀은 GraphEdit상에 나타나지 않을 것입

니다. 아직까지도 GraphEdit상에는 밋밋한 네모난 상자 그대로일 것입니다. 여기서 우리는 이제 필터

내부에 생성되어져 있는 핀 객체를 외부로 끄집어 내야합니다. 이것을 해주는 함수가 바로 GetPin 함수

인 것입니다. 필터그래프는 필터가 생성되거나 필터들을 연결하거나, 혹은 필터의 속성페이지를 보여주

려고 할 때마다 이 GetPin 멤버함수를 수없이 실행시키는 습성이 있습니다. 즉, 이 함수를 실행시켜서

현재 필터의 내부에 생성되어진 핀 객체의 인터페이스를 끊임없이 요구하는 것입니다. 우리가

GraphEdit 상에서 BaseTrans의 핀을 외부로 보여주기 위해서는 바로 이 함수에서 핀의 포인터를

Result로 넘겨주면 되는 것입니다. 다음과 같이 말이죠.

function TBaseTrans .GetPin(n: Integer): TBCBasePin;

begin

case n of

0 : Result := FInputPin;

Page 153: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 153 -

1 : Result := FOutputPin;

else

Result := nil;

end;

end;

위의 코드를 보시면 알겠지만 GetPin의 매개변수로 들어오는 n값에 대하여 핀을 순차적으로 내보내

고 있습니다. 그렇다면 이 n값은 대체 어디서 결정되어져 있는 것일까요. 필터그래프가 각각의 필터에서

핀을 검색하고자 할때, 다음의 두가지 상황을 생각해 볼 수가 있을 것입니다. 첫째, GetPin함수를 루프

로 돌려서 nil값이 Result로 반환될때까지 수없이 반복하는 것과, 둘째, 이미 핀갯수가 몇개인지 알고

있어서 그 갯수만큼 루프를 돌려서 GetPin에서 필터의 포인터를 얻어오는 것이 있을 것입니다. 불행하

게도 DShow에서는 두번째의 방법을 사용합니다. 따라서 여러분이 여기까지만 코딩하고 컴파일하여

GraphEdit상에서 BaseTrans을 생성시킨다면, 아직까지도 필터의 모습은 핀이 없이 맨숭맨숭한 상태일

것이라는 점입니다. 이제 여러분은 핀의 갯수를 지정해 줘야 하는 것입니다. 그것은 다음과 같이 너무도

간단하게 하실 수가 있습니다.

function TBaseTrans .GetPinCount: integer;

begin

Result := 2;

end;

위와같이 GetPinCount의 Result 값에다 현재 필터내부에 생성되어 있는 핀의 객체갯수를 지정해 놓

으면 되는 것입니다. 자, 여기까지 하시고 컴파일 하겠습니다. 이제 GraphEdit에서 필터삽입윈도우를 통

해서 BaseTrans 필터를 생성해 보시기 바랍니다. 여러분의 필터가 비로소 두 팔이 달린것과 같이 보여

지는게, 이제야 뭔가 완성되어 가는구나라고 생각하실지도 모르겠습니다. 분명 여러분은 DShow 필터의

제모습에 상당히 근접해가고 있습니다. 하지만 아직까지도 필터는 모습만 찾았을 뿐, 기본적인 기능은

한가지도 수행하지 못하고 있을 것입니다. 그것은 우리가 핀 클래스를 만들면서 아무런 수행 함수를 구

현해 놓지 않았기 때문입니다. 실상, 이제부터가 본 경기라고 생각하시면 좋을 듯 싶습니다.

이제 여러분에게 필터가 연결될때에 어떤 협상과정이 벌어지는 것인가에 대하여 말씀드리겠습니다.

강의 초반에 언급하였듯이 협상과정은 세가지이며 순차적으로 이뤄지게 됩니다. 핀협상 -> 미디어협상

-> 버퍼협상 이렇게 말이죠. 그런데 핀 협상과정은 필터그래프가 주관하는 것으로서 우리가 실질적으로

관여할 성질의 것이 아닙니다. 단지 DShow의 깊이 있는 이해라는 측면에서 가치가 있는 것인데요, 여

Page 154: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 154 -

기서는 이 부분에 대한 해설은 다루지 않도록 하겠습니다.

참고참고참고참고

사실, TBCBaseFilter에 구현되어 있는 핀협상 관련 함수들이 별로 없을 뿐더러, 비록 있다고 하더라도 이것들이 연계적으로

필터 내부에서 협상이 이뤄지는 것이 아니라서(필터그래프 내부에서 이뤄지는 것처럼 보이기때문에) 그 정확한 과정을 알아

내기 위한 정보가 상당히 부족하기 때문 입니다. 신화선님의 DirectShow 멀티미디어 프로그래밍이라는 책에서도 이 핀협상

과정이 설명되어 있으나(508Page에 서술), 원론적인 차원의 해설에 그치고 있는 것 같습니다. 좀더 내부적인 다양한 변화의

원인에 대한 해설이 부족한 것인데요, 제가 이런식의 언급을 하는 것은 핀협상 과정이 실제로는 훨씬 복잡한 매커니즘으로

작동하고 있는게 아닌가 하는 의심이 들기 때문입니다. 어쩌면 앞서 말씀드렷듯 이 협상의 마지막 과정인 버퍼협상이

TBCBasePin 라는 핀의 최상위 클래스가 아니라 각각의 고유한 성격을 가진 한 단계 아래의 TBCBaseIntputPin과

TBCBaseOutputPin에서 정의되고 구현되어져야만 하는 이유에 대한 의문에 대하여 근본적인 원인을 제공하고 있는 것은 아

닌가하는 의심을 갖게 한다는 점입니다. 이 부분에 대해서는 본 프로젝트의 실습이 종료될 즈음에, 왜 이런 생각을 하는가에

대한 실습결과를 보고 판단해 보도록 하겠습니다.

Page 155: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 155 -

20.20.20.20. 필터필터필터필터 7777

지난회에서 여러분은 필터의 기초적인 몸통과 팔을 만드셨습니다. 이제 그 팔을 힘차게 구동시켜야

할 것입니다. 그런데 필터의 핀에 어떤 역활을 설정하기 위해서는 기본적인 협상과정을 이해하셔야

할 것입니다. 협상과정의 첫 번째인 핀협상에 대해서는 이미 설명드린바와 같습니다. 이제 두번째인 미

디어 협상에 대하여 설명을 하겠습니다.

[1][1][1][1] 미디어미디어미디어미디어 협상협상협상협상. . . .

필터의 핀협상에서 미디어 협상은 상당히 복잡한 과정을 거치게 되어 있습니다. 이 복잡한 과정을 함

수 하나하나 추적해 들어가는 식의 설명방식 보다는 좀더 추상적이고 포괄적인 접근방식을 시도해 보겠

습니다. 일단 핀의 미디어 협상이 어떻게 해서 시작되는지를 설명드리겠습니다.

우리가 두개의 필터를 연결한다는 것은 상위 필터의 Output 핀으로부터 하위 필터의 Input 핀을 연결

한다는 것으로 생각하셔야 합니다. 여러분이 아무리 GraphEdit 상에서 하위 필터의 Input핀을 먼저 마

우스로 클릭하여 드래그 시켜 상위 필터의 OutPut 핀에 갖다 댄다고 해서, 필터의 연결의 방향이 하위

필터의 Input핀에서 상위필터의 Output핀으로 향하지는 않는다는 것입니다. 이것은 무슨 말인가 하면,

두개의 필터가 연결될때 Connect라는 함수가 실행되는데요, 이것이 실행되는 것은 상위 필터의 Output

핀이라는 것입니다.

현재 우리는 두가지 종류의 핀객체를 생성하였습니다. 이것들 각각은 TBCBaseInputPin 클래스와

TBCBaseOutput Pin 클래스로부터 상속받은 것인데요, 이들의 공통부모가 바로 TBCBasePin이라고 설

명드렸습니다. Connect함수는 바로 이 Pin 클래스의 최상의 객체에 해당하는 TBCBasePin 클래스의 멤

버함수로 구현되어 있기 때문에 헷갈리지 마시라는 의미로 설명을 드린 것입니다. 전에도 말씀 드렸다

시피 미디어 협상의 과정은 바로 이 TBCBasePin 클래스에 구현되어 있으며 이것은 상황에 따라서

Output 핀을 의미하기도 하고, Input 핀을 의미하기도 한다는 것입니다.

아무튼 핀협상이 진행되는 과정에서 Connect라는 함수가 불려지게 됩니다. 그런데 이 Connect라는

함수는 상위 필터의 Output 핀에서 실행되는 것입니다. Connect 함수의 두가지 매개변수를 보시면, 첫

번째는 상대방핀의 IPin인터페이스형 변수 타입으로, 두번째는 미디어 협상에 사용될 미디어형 변수 타

입으로 설정되어 있습니다. 그런데 여기서 Connect 라는 함수 자체가 항상 상위 필터의 Output 핀이라

고 하였으므로 이 첫번째 매개변수는 항상 하위 필터의 Input 핀이라고 생각하셔도 될 것입니다. 여기까

지를 비주얼하게 표현하면 다음과 같습니다.

Page 156: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 156 -

상위필터의 OutputPin 하위필터의 InputPin

OutputPin.Connect( InputPin, 미디어타입)

필터간의 미디어협상은 바로 이 Connect라는 함수로부터 시작되어지는 것입니다. 그렇다면 이제 미

디어협상이 어떠한 방식으로 진행되는지를 보시겠습니다.

제가 이번 강의를 진행함에 있어서 웬만하면 이미지를 그리지 않으려고 많은 노력을 하였습니다만,

이 미디어 협상 과정에 대해서는 어쩔 수 없었습니다. 자, 이것을 하나하나 설명해 드리겠습니다. 위에

서 Connect함수가 시작되면서 그 안의 매개변수로 받은 하위필터의 인터페이스 변수를 이용하여 하위

필터 Input핀 객체의 GetMediatype 함수를 실행시키고 있습니다. 여기서는 가져올 수 있는 미디어 타

입을 한개 가져오는 것입니다. 그리고 이것을 다음의 CheckMediaType에서 확인하고 있는데요, 상대방

에서 가져온 미디어타입을 왜 상대방의 CheckMediaType함수를 실행해서 당연한 것을 확인하느냐라고

물으신다면, 이것은 어쩔수가 없는 상황입니다. 왜냐하면 뒤에 보시면 아시겠지만, GetMediaType 함수

를 실행하는 것이 상대방 핀에서 뿐만이 아니라 자기 자신에게서도 GetMediaType 을 실행시켜 미디어

형을 가져올 수가 있기 때문입니다. 즉, 가져온 미디어타입이 상대방 것일 수도 있고, 자기 자신의 것

Page 157: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 157 -

일 수도 있으므로, 이것을 검사하는 것도 자기자신과 상대방의 CheckMediaType을 동시에 실행시켜야

한다는 것입니다.

이것을 다시 설명하면 이렇습니다. 일단 Connect 함수가 실행되면 그 안의 매개변수 중 하나는 반드

시 상대방 핀의 IPin 인터페이스 일 것인데요, 이것을 이용해서 상대방의 GetMediaType함수를 실행시

켜서 상대방이 제시할 수 있는 미디어 타입을 일단 가져옵니다. 그런데 이것을 Check(확인)하는 것은

자기 자신의 CheckMedaiType과 상대방의 CheckMediaType 두군데에서 모두 확인해야 합니다. 만일

두군데중 하나라도 S_OK가 리턴되지 않는다면, 이번에는 자기 자신에서 GetMediaType 함수를 실행시

켜서 자기 자신의 핀에서 제시할 수 있는 미디어 타입을 한개 꺼내들게 됩니다. 그리고 이것을 가지고

또다시 자기 자신과 상대방핀의 CheckMediaType 함수를 실행시켜서 모두다 만족하는 S_OK 가 나오

는지를 확인합니다. 여기서 최종적으로 S_OK가 나오면 미디어협상이 이뤄진 것이고, 이것이 S_False가

된다면 미디어 협상이 결렬된 것입니다.

자, 사실 이러한 설명은 상당히 개념적인 것입니다. 여러분이 BaseClass.pas에서 Connect함수를 추

적해 들어가다보면 TBCEnumMediaTypes가 생성되어지면서, 이것의 내부 멤버함수인 Next 가 계속해

서 불려지는 것을 보실 수가 있을 것입니다. 그런데 이것이 너무도 복잡하여 일일이 설명을 할 수가 없

기 때문에 중요한 요점을 간추려 위와같은 설명을 만들어 낸 것입니다. 즉, 위의 설명은 여러분이 미디

어 협상에 있어서 GetMediaType, CheckMediaType, SetMediaType 이라는 세개의 함수들이 가장 핵

심적인 역활을 한다는 것을 이해시키기 위한 것입니다. (하지만 실제 처리에 있어서 여러분은

SetMediaType 함수를 Override하실 필요가 전혀 없습니다. 이 SetMediaType함수를 언급한 것은 설

명의 마무리를 강조하고자 함이었습니다. 여러분이 실제 미디어협상 과정에서 핀 클래스를 설계하시는

데 있어서는 두가지 함수인 GetMediaType과 CheckMediaType함수만을 생각하시면 될 것입니다.)

Pin 클래스를 개발함에 있어서 우리는 두가지 협상과제만 신경쓰면 될 것입니다. 하나는 미디어협상

이고 다른 하나는 버퍼협상입니다. 그런데 위에서도 말씀드렸다시피 미디어 협상에서는 두가지 함수인

GetMediaType과 CheckMediaType 함수의 Override 만을 신경쓰시면 됩니다. 그렇다면 이것을 어떻

게 신경써야 하는지를 설명드리겠습니다.

다른각도에서 우리는 필터를 세가지 종류로 분류할 수 있을 것입니다. 최상위 필터와 중간단계 필터,

그리고 최하위 필터라고 말이죠. 이렇게 세가지로 나눈 이유는 최상위 필터는 최상위이기 때문에 Input

핀이 없다는 것과, 최하위 필터는 최하위 이기 때문에 반대로 Output 핀이 없다는 것입니다. 그리고 중

간단계 필터는 Input 핀과 Output핀이 동시에 만드시 존재해야한 하는 것일 터입니다. DShow에서 최상

위 필터는(Input 핀이 없는 필터)는 OutPut핀을 통해서 최초의 미디어 타입을 설정해서 내보내는 것이

좋을 것입니다. 만약, 스트림의 최초 출발지점에서 미디어타입을 설정하지 않고 하위 필터에서 구걸하여

얻어온다면, 최상위필터는 그만큼 하위필터에 의존적이 되기 때문입니다. 즉, 하위 필터에서 자신이 현

재 내보내고 있는 미디어의 타입형을 GetMediaType으로 돌려주지 않는다면 최상위필터는 연결되지 못

Page 158: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 158 -

할 운명에 처할 것이기 때문입니다. 그래서 일반적으로 최상위 필터의 Output 핀 클래스에는

GetMediaType함수를 Override하여 자신이 서비스할 미디어타입의 종류를 내어줄 준비를 하고 있는

편이 좋다는 것입니다.

이제 중간단계의 필터를 살펴보겠습니다. 중간단계의 필터는 Input핀에서 굳이 GetMediaType을 만

들어 놓을 필요가 있을까요? 만일 자신보다 상위필터의 OutPut 핀에서 미디어타입이 GetMediaType함

수로 지정되어 있다면, 그 보다 하위 필터에서는 굳이 GetMediaType을 Override할 필요가 없을 것입

니다. 그냥 CheckMediaType을 Override하여 그 안에서 자신이 현재 받아들여서 처리할 수 있는 미디

어타입의 조건만을 제시하는 편이 더 낳을 것입니다.

말이 조금 뱅뱅 돈 것같은 느낌이 있습니다. 자, 정리하자면 이렇습니다. 일반적으로 Output핀에서

CheckMediaType과 GetMediatype을 선언해 놓고, 아래로 흘려보낼 스트림의 미디어 타입을 정의해

놓는다면 굳이 Output핀과 연결될 하위 필터의 Input핀에서 GetMediaType을 또다시 정의할 필요는 없

다는 것입니다. 그래서 일반적으로 필터의 Input 핀에서는 ChecktMediaType만을 Override하고

Output핀에서는 CheckMediaType과 GetMediaType을 모두 Override하는 것입니다. Input핀과 output

핀 모두에서 CheckMediaType을 Override해야하는 것은 위의 그림에서 보시면 아시겠지만,

GetMediaType에서 가져온 미디어형이 현재 Input 핀에서 가져온 것인지, Output 핀에서 가져온 것인

지모르기 때문에 두군데 모두에서 CheckMediatype을 사용해서 확인해야 하기 때문입니다.

자, 그렇다면 이제 우리의 BaseTrans 필터의 소스를 수정해 보겠습니다. 일단 각각의 Input핀과

Output핀의 클래스에서는 모두 CheckMediaType함수가 Override되어 있습니다. 이것은 상위필터에서

전해주는 모든 미디어형을 받아들이겠다는 것이며, 동시에 하위 필터에서 제시하는 모든 미디어형을 받

아들이겠다는 것입니다. 그런데 여기서 만약 하위 필터의 Input핀에서 GetMediaType함수가 Override

되어 있지 않다면, 즉, 하위필터의 Input핀에서도 마찬가지로 자신보다 상위필터의 미디어형을 확인만

할뿐, 자신에게 필요한 미디어형을 GetMediaType함수를 통해서 스스로 만들어 제시하지 않는다면, 둘

사이의 미디어협상은 결렬될 것입니다. 이렇게 DShow에서는 일반적으로 Input핀은 상위 필터의 미디어

형을 CheckMediaType을 통해 검사만 할뿐, 스스로 미디어형을 제시하지는 않는다고 하였습니다.

그렇다면 우리는 반드시 BaseTrans 필터의 Output핀에다 GetMediaType을 Override해서 미디어형

을 손수 만들어 제시해야만 하는 것입니다. 그런데 현재 BaseTrans필터의 목적은 재접속을 하지않는

제자리 변환필터용으로 개발되고 있습니다. 즉, 상위필터에서 흘러나온 스트림을 그대로 하위 필터로 전

달만하는 역활을 수행한다는 것이죠. 따라서 OutPut핀의 미디어형은 자기자신의 Input핀 미디어형과 동

일할 것입니다. 아니 동일해야만 하는 것입니다. 이렇기 때문에 Output핀의 GetMediaType 멤버함수에

서는 새로운 미디어형을 만들어내지 않고, Input핀 클래스에 셋팅되어 있는 미디어형을 그대로 가져와

복사할 것입니다. 그런데 이때, 우리는 Input핀의 클래스를 참조해야 하는데요, 그러기 전에 먼저 uMain

에 선언되어 있는 TBaseTrans 필터 본체를 참조해야 합니다. 지난회에 저는 델파이의 상호참조 에러

Page 159: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 159 -

에 대하여 말씀드렸습니다. 우리는 상호참조를 피하기 위해 implementation아래에 uses문을 추가하고,

그곳에 uMain을 참조할 수 있도록 다음과 같이 설정할 것입니다.

implementation

uses

uMain;

var

BaseTrans: TBaseTrans;

자, 위와같이 uMain 유닛을 참조하라고 선언하였습니다. 그리고 그 아래에 BaseTrans를 전역변수로

선언하였습니다. 이것이 핀의 생성과 동시에 초기화되기 위해서 우리는 Output핀에 생성자를 다음과 같

이 만들어 놓아야 할 것입니다.

constructor TOutputPin.Create(ObjectName: string; Filter: TBCBaseFilter;

Lock: TBCCritSe c; out hr: HRESULT;

const Name: WideString);

begin

inherited Create(ObjectName, Filter, Lock, hr, Name);

BaseTrans := TBaseTrans(Filter);

end;

위와같이 Output핀의 생성자에서 BaseTrans 전역변수가 초기화되고 있습니다. 이제 우리는 이 변수

를 통해서 Input 핀의 멤머에 자유롭게 접속할 수 있을 것입니다. 자, 그런데 그러기 위해서 우리는 한

가지 더 준비해야할 사항이 있습니다. 즉, 현재의 단계에서 BaseTrans.FInputpin 이런식으로 접속할 수

가 없습니다. 왜냐하면 FInputPin이 Private에 선언되어 있기 때문입니다. 따라서 TBaseTrans의

public에다 다음과 같이 Property형 타입으로 하나 선언해 주고, 그것을 사용해야 할 것입니다.

property InputPin: TInputpin read FInputPin write FInputpin;

Page 160: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 160 -

위와같이 TBaseTrans의 Public에 선언해 주었다면, 우리는 TOutputpin 클래스에서

BaseTrans.Inputpin 이런 방식으로 엑세스 할 수 있을 것입니다. 일단 여기까지 준비단계를 마쳤다면

본격적으로 GetMediaType함수를 Override해 보도록 하겠습니다. 함수의 내용은 아래와 같으며 그 선

언은 생략하였습니다.

function TOutputPin.GetMediaType(Position: integer; out MediaType:

PAMMediaType): HRESULT;

begin

if Position > 0 then

begin

result := VFW_S_NO_MORE_ITEMS;

Exit;

end;

if BaseTrans.InputPin.IsConnected then

begin

CopyMediaType(MediaType,BaseTrans.InputPin.CurrentM ediaType.MediaType);

Result := S_OK;

end else

result := VFW_S_NO_MORE_ITEMS;

end;

위에서 보시면 Position이라는 매개변수가 보입니다. 이것은 GetMediaType이 한개의 미디어형만 정

의할 수 있는 것이 아니라는 것을 보여줍니다. 즉, 여러분은 무한대의 미디어형을 Position 이용하여 이

곳에서 만들어 내보낼 수가 있는 것입니다. 그러나 일반적으로 상용필터가 아닌 이상, 이것은 힘든 작업

을 터입니다. 왜냐하면 많은 미디어형을 내보낼 수가 있다는 것은, 필터내부에서 그 만큼 다양한 미디어

형을 처리할 수 있다는 의미이기 때문입니다.

GetMediaType의 리턴값을 주목해 주시기 바랍니다. 준비된것이 있을 때에는 S_OK를, 준비된게 없

을 때에는 반드시 VFW_S_NO_MORE_ITEMS를 되돌려줘야 합니다. 여기서 S_FALSE가 아닙을 유념하

도록 합니다. 그리고 이제 가장 중요한 아래의 항목을 보시면, 일단 Input핀이 접속되어 있는지 아닌지

를 확인하고 있습니다. 만약 접속되어 있지 않다면 준비된게 없다는 메시지를 리턴값으로 돌려줘야합니

다. 왜냐하면 우리는 현재 Input핀에 정의되어 있는 미디어형을 그대로 복사할 것이기 때문입니다.

Page 161: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 161 -

위에서 미디어형을 복사하는 것으로는 CopyMediaType가 사용되고 있음을 기억해 두기바랍니다. 이

함수는 참으로 요긴한 함수입니다. 왜냐하면 미디어타입은 간단한 레코드타입처럼 CopyMemory로 복사

할 수가 없는 성질의 것이 포함되어 있기 때문입니다. 지난회에서 저는 DShow의 미디어형에 대해서 간

단히 설명드린바가 있습니다. 그 당시 추가정보가 포함된 또다른 레코드형 데이터를 포인터형으로 가지

고 있다고 하였습니다. CopyMediaType함수를 보시면 알겠지만 그 추가정보사항까지도 별도로 복사하

고 있습니다.

자, 여기까지 미디어협상에 대하여 마치겠습니다. 지금까지 코딩된 상태에서 컴파일하고 실행해보시면

아마도 상위 필터와의 연결은 될 것입니다. 그러나 아직까지도 하위 필터와의 연결이 되지 않고 에러가

발생할 것입니다. 이것은 아직도 협상의 마지막 과정을 처리하지 않았기 때문에 발생하는 것으로서, 우

리는 마지막으로 버퍼협상에 대하여 처리를 해야만 하는 것입니다. 버퍼협상에 대해서는 다음장에서 시

작하도록 하겠습니다. 여러분 수고하셨습니다.

Page 162: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 162 -

21.21.21.21. 필터필터필터필터 8888

[2][2][2][2] 버퍼협상버퍼협상버퍼협상버퍼협상

이제 버퍼협상에 대하여 설명하겠습니다. 지지난회에서 저는 할당자에 대한 것을 잠시 언급한 적이

있었습니다. 우리가 DShow에서 버퍼공유를 한다는 것은 실제적인 버퍼의 포인터를 공유하는 것이 아니

라, 이 할당자를 공유함으로서 가능해지는 것이라는 설명을 드렸었습니다. 자, 그렇다면 이 할당자를

어떻게 공유하는가에 대한 과정을 이제부터 언급하고자 합니다.

미디어협상이 Connect라는 함수에서부터 시작되었듯이 버퍼협상은 CompleteConnect라는 함수에서

시작되고 있습니다. 미디어협상이 성공적으로 완료되었을 때, 비로소 CompleteConnect 함수가 불려지

게 되는데요, 여기서 TBCBasePin에 정의되어 있는 CompleteConnect 를 보시면 아무런 내용도 없이,

리턴값인 NOERROR만이 되돌려지고 있을 것입니다. 제가 전에도 말씀드렸듯이, 미디어협상은 각 Input

핀과 Output핀의 조상인 TBCBasePin에서 이뤄지지만 유독 버퍼협상의 과정은 그 아랫단계의 자식클래

스인 TBCBaseInputPin과 TBCBaseOutputPin 에서 각기 이뤄지고 있다는 것입니다. 자, 이것은 무엇

을 의미하는 것일까요. 이것은 중요한 방향성을 예고하고 있다고 생각하시면 될 것 입니다.

예를 들어서 미디어협상은 방향성이 없었습니다. TBCBasePin 클래스에 정의되어 있는 미디어협상의

구현내용을 살펴보면 자신이 어떨때는 Input핀으로서, 또 어떤때는 Output핀으로서의 기능을 수행할 수

있다는 가능성을 염두에 두고서 설계된 것이었습니다. 그렇기 때문에 GetMediaType을 처음에는 상대

입력핀에서 실행하였다가 이것이 실패하면 자기 자신의 GetMediaType 함수를 실행하였고,

CheckMediaType역시 이러한 양자간의 실행을 염두에 두고 두개 핀 모두에서 동시에 실행하여 미디어

형을 검사하였던 것입니다. 여기서 단지, 우리가 필터를 개발함에 있어서 일종의 관습이랄까, 혹은 개발

의 편리성에 의하여 굳이 Input핀에다가 GetMediaType 함수를 만들어 놓지 않고, 다만

CheckMediaType함수로서만 상위필터의 Output핀에서 GetMediaType으로 이미 정의되어 있는 미디어

형을 선택하는 방식을 따랐을 뿐이라는 것입니다. 자,이렇게 미디어협상은 방향성이 없었다기 보다는 설

계가 양방향이었다는 것을 의미한다고 할 것입니다. 그런데 실제 구현에 있어서는 Output핀에서 미디어

형을 내놓는다는 일종의 관례라고 할까요, 아니면 편리성이라고 할까요, 그런면에 의해 만들어지는 측면

이 있다라고 설명을 드렸었습니다.

그런데 버퍼협상은 확실히 방향성이 있는 구조로 설계되어 있습니다. 예를 들면 미디어협상이 완료되

는 시점에서 불려지는 CompleteConnect함수는 TBCBaseOutputPin 에만 Override되어 있을 뿐,

TBCBaseInputPin에는 의도적으로 Override되어 있지 않기 때문입니다. 일단 시작부터가 확실히 편애

하고 있는 것입니다. 자, 그렇다면 어째서 버퍼협상에서는 이렇게 방향성을 가지게끔 설계되어 있을까요.

이 의문에 대한 해답은 필터그래프의 핀협상 과정에 있을 것이라는 추측만이 가능할 것 같습니다.(현재

Page 163: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 163 -

저의 이해력으로는 그렇습니다.) 아마도 이렇게 방향성을 가진 근본 이유가 In Place 제자리 변환필터에

있어서 재연결을 반드시 필요로하게 만든 원인이 아닐까 생각하고 있습니다. 이것에 대한 좀더 자세한

설명은 실습이 끝난후에 흥미로운 결과를 보여드린 후에 다시 언급해 드리겠습니다.

일단 TBCBaseOutputPin 에서 CompleteConnect함수가 불려지게 되면, DecideAllocator를 실행하라

고 명령합니다. 이 DecideAllocator는 바로 할당자를 결정해라라는 의미의 함수인데요, 여기서 이 함수

의 매개변수로 설정되어 있는 상대편핀의 IPin인터페이스를 이용하여 상대편핀의 GetAllocator함수를

실행하고 있습니다. 그러니까 하위필터의 Input핀 클래스의 GetAllocator멤버함수를 실행시키도록 한다

는 것입니다. 자, 할당자는 이곳에서 최초로 생성되어지게 됩니다. 즉, 버퍼협상의 최초 시작은 비록 상

위필터의 Output핀에서 였으나, 할당자의 실제 생성은 건너편 하위필터의 Input핀이라는 것입니다.

Input핀의 GetAllocator함수에서는 현재 Input핀 자체에 할당자가 설정되어 있다면 그것을 되돌려주

도록하고 그렇지않다면 새로운 할당자 COM 객체를 CoCreateInstance 함수를 사용하여 생성하고 있습

니다. 자, 이거 왜 이렇게 만들어 놓았을까요? 핀이 접속을 한다면 어차피 새로운 할당자를 생성하면 그

만일 것인데, 왜 굳이 하위 필터의 Input핀에서 자신에게 기존에 할당자가 있는지 없는지를 조사해서,

있으면 새로 만들지 않고 있는 것을 돌려주고, 없다면 새로운 것을 만들어서 리턴해주는... 왜 이러한 방

식으로 구현되어 있을까요?

이것은 다분히 재연결을 염두에 두고 구현한 설계인 것입니다. 즉, 언젠가 자신들을 상속받아서 탄생

하게될 후손 클래스에서 재연결이 반드시 필요할 것이라는 것을 알고서 미리 준비한 것입니다. 그렇다

면 이것을 준비할 수밖에 없는 원인은 어디에 있는 것일까요? 필터 본체의 최상의 객체

인 TBCBaseFilter 클래스에는 재연결을 하게끔 하는 ReconnectPin이 준비되어 있습니다. 그렇다면

기본핀에서 상속받은 TBCBaseinputPin의 GetAllocator에 준비되어 있는 로직보다도 훨씬 전부터 재

연결이라는 사항은 마련되어 있었다는 이야기가 됩니다. 여기서 재접속이 단지 하나의 옵션에 불과

한 것이 아니라 보다더 구조적인 차원에 기인한다라는 사실을 느끼실 것입니다.

AnyWay... 하위필터 Input핀의 GetAllocator함수에서 할당자를 얻은 상위필터 Output핀의

DecideAllocator는 계속해서 할당자의 버퍼의 형태를 규정지으라는 함수를 실행하고, 이것이 성공적으

로 수행되었다면, 그때 비로소 상대편 하위필터의 Input핀에게 할당자를 셋팅하라고 명령합니다. 이것을

정리하면 다음과 같습니다.

Page 164: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 164 -

위에서 우리는 DecideBufferSize를 주의해서 보셔야 할 것입니다. 이 함수는 GetAllocator에서 생성

한 할당자를 가져와서, 거기다 버퍼의 형태를 설정하고 있습니다. 즉, 버퍼의 크기와 버퍼의 갯수와 같

은 기본적인 속성을 할당자에 설정해주고 있는 것입니다. 만약 이 설정작업이 제대로 끝나지 않는다면

그 다음단계인 NotifyAllocator 단계로 넘어갈 수 가 없습니다. 이 NotifyAllocator은 모든 설정작업

이 완료된 할당자를 비로소 하위필터 Input 핀에 설정하는 작업을하는 것입니다. 즉 하위필터에 할당자

를 공유할 수 있게 마련된 FAllocator라는 IMemAllocator 인터페이스 형 멤버변수에 설정된다는 것입

니다. 만약 이 작업이 완료되지 않는다면 상위필터와 하위필터의 할당자 공유는 실패할 것입니다.

자, 여기까지 할당자의 생성과 공유에 대하여 알아보았습니다. 그러면 이제 이 버퍼협상 과정에 있어

서 우리가 반드시 Override해서 설정해야만 하는 것은 무엇이 있는지 아셔야 할 것입니다. 위에서 보셨

듯이 할당자를 생성하는 것은 별로 어려운 일이 아닙니다. 또다른 COM 객체를 생성해서 상위필터와 하

위필터가 공유한다는 것은 얼마든지 기본로직에서 제공해줄 수 있는 것입니다. 그러나 할당자의 속성설

정 작업, 즉, 버퍼의 크기나 갯수의 설정 같은 작업은 필터 고유의 성격에 따라서 제각각이기 때문에 반

드시 개발자가 Override 해서 별도로 구현해 줘야하는 것입니다. 이제 우리는 이러한 역활을 하

는 DecideBufferSize를 Override해 보겠습니다. 즉, 다음과 같이 하면 되겠습니다.

function TOutputPin.DecideBufferSize(Alloc: IMemAllocator;

propInputRequest: PAllocatorProperties): HRESULT;

var

Actual: TAllocatorProperties;

begin

Page 165: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 165 -

propInputRequest.cBuffers := 1;

propInputRequest.cbBuffer := 1;

result := Alloc.SetProperties(propInputRequest^, Actual);

end;

위에서보시면 고개를 갸우뚱하실지도 모르겠습니다. 버퍼의 크기를 의미하는 cbBuffer에다 1Byte를

설정해 놓았기 때문입니다. 자, 이것이 가능한 이유는 앞으로 설명드리겠지만, 우리는 현재 이 할당자를

전혀 필요하지 않기 때문입니다. 왜냐하면 우리는 상위필터의 할당자에서 생성된 버퍼를 가지고 그대로

하위필터에 푸쉬할 것이기 때문에 하위 필터와의 버퍼협상에서 발생한 할당자와 그것에서 발생되는 버

퍼는 아무런 가치가 없기 때문입니다. 즉, 아래와 같은 도식을 보겠습니다.

Cam 필터 ─(할당자A)─▶ 변환필터 ─(할당자B)─▶ Video Renderer.

우리가 만드는 일명 '재접속하지 않는 제자리 변환필터'는 현재 할당자를 두개 만들고 있습니다. 그러

나 물론 상위 필터인 Cam과의 버퍼협상에서 만들어지는 할당자를 어떻게든 가지고와서 하위필터와의

버퍼협상에 이용할 수는 있을 것입니다. 그러나 이러한 방식은 어차피 한계에 부닥치기 때문에 불필요

한 코딩만 낭비할 뿐입니다. 자, 그 이유에 대하여 설명하겠습니다.

상위필터와의 협상과정에서 생성된 할당자는 DShow가 Play되면서 비로소 새로운 버퍼를 생성합니다.

즉, Play되기 전까지는 그냥 COM객체로서만 있을 뿐, 전혀 미디어 데이터를 담을 어떠한 버퍼도 준비

하고 있지 않다는 것입니다.

Play가 되면서 최상위 필터의(소스필터) 기동 스레드가 작동되고, 이 스레드 안에서 순차적으로 프레

임 데이터가 만들어지면서 이것을 담을 버퍼가 필요하게 되는데요, 이때 바로 할당자에게 GetBuffer멤

버함수를 사용하여 새로운 버퍼를 만들라고 명령하는 것입니다. 그런데 여기서 GetBuffer로 만들어지는

것은 순순한 메모리 버퍼와 그것의 포인터가 아니라, 어찌되었든 생성된 메모리버퍼를 포함하고 있

는 또다른 COM 객체인 미디어 샘플인 것인데요, GetBuffer의 두번째 매개변수를 통해서 버퍼의 포인터

를 받는 것이 아니라, 이 미디어샘플의 IMediaSmaple 인터페이스를 얻어낸다는 것입니다. 뭐 그냥 쉽

게 버퍼의 포인터를 얻어낸다라고 포괄적으로 생각하셔도 되겠습니다.

자, 이제 우리의 BaseTrans 필터는 상위필터에서 받아지는 이 버퍼의 포인터를 어떻게든 하위필터에

전달하는 방법을 구현해야 하는 것입니다. 그런데요 TBCBaseInputPin 클래스를 살펴보면 Receive라는

함수를 발견하실 수가 있으실 것입니다. 이곳이 바로 상위필터에서 흘러나오는 버퍼의 포인터, 혹은

Page 166: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 166 -

IMediaSmaple 인터페이스형 변수가 매개변수로 전달되어지는 것입니다. 그렇다면 우리의 생각은 아주

연속적으로 이어지게 됩니다. 생각해보시기 바랍니다. 어떤 필터든지 Input핀의 Receive 함수로 버퍼의

포인터에 해당하는 IMediaSmaple형 타입의 변수가 전달된다는 것은 바꿔 말하면 우리가 하위필터

Input핀의 Receive 함수를 실행시켜서 매개변수로 현재 가지고 있는 미디어 버퍼의 포인터에 해당하

는 IMediaSmaple형 타입의 변수를 설정하면 된다는 것입니다. 간단하지요. 이것을 그림으로 표현하면

다음과 같을 것입니다. 에고 또 그림 만들어야 겠습니다.

위의 그림을 보시면 두개의 필터가 연결된 상태에서 상위 필터의 Input핀의 Receive함수에서 또다

시 직접적으로 상위 필터 Input핀의 Receive함수를 실행시키려 하고 있습니다. 이것이 가능할까요? 일

단 이것이 가능하기 위해서는 상위 필터가 어떻게든 하위필터의 Input핀 인터페이스를 가지고 있어야

합니다. 두개의 필터가 접속될때 상위필터의 Output핀과 하위필터의 Input핀은 서로 상대방의 Pin인터

페이스를 가지고 있습니다. 따라서 이것을 이용하면 될 것입니다.

즉, 상위필터의 Input 핀의 Receive함수 내부에서 일단 자기자신의 Output핀에 접속해서 그곳의 멤

버변수의 하나인 하위필터의 Input핀 인터페이스를 이용할 수가 있을 것이라는 점입니다. 자, 그런데 고

맙게도 필터의 Output핀의 최상위 클래스라고 할수 있는 TBCBaseOutputPin 클래스에 이런것을 예상

해서 미리 Deliver라는 함수를 준비해 두고 있습니다. 그러니까 우리는 Output핀에 접속해서 멤버함수

인 Deliver함수를 실행시키기만 하면, 이것이 자동적으로 하위필터 Input핀의 Receive함수를 실행시켜

주는 것입니다.

여러분이 만약 위에서 DecideBufferSize까지만 코딩을 완성하고 컴파일하여 실행시킨다면, 여러분의

필터가 포함된 GraphEdit의 필터그래프는 Play되어도 Display 윈도우에 아무런 것도 나타나지 않을 것

입니다. 이것은 상위필터의 화면을 하위필터에 전송하는 로직을 구현하지 않았기 때문인데요, 우리는 방

금전에 언급한 방식으로 이것을 구현해 보도록 하겠습니다.

Page 167: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 167 -

여러분은 지난회에서 필터의 Output핀에서 필터 자신인 TBaseTrans를 참조하여 Input핀에 도달한

적이 있으실 것입니다. 바로 GetMediaType 함수 내에서 Input핀의 미디어타입을 복사하기 위해서 그

랬었는데요 기억이 나시는지 모르겠습니다. 이제는 그 반대의 경우가 되겠으니 전에 하셨던 준비작업을

그대로 반복하셔야 할 것입니다. 즉, 순서는 다음과 같습니다.

1. Input핀 클래스의 implementation아래에 Uses uMain 을 추가한다.

2. 다시 그 아래에 var BaseTrans:TBaseTrans를 추가한다.

3. Input핀 클래스의 생성자를 재선언하고 그곳에 위의 BaseTrans를 초기화한다.

4. TBaseTrans 클래스에 property OutputPin : TOutputPin read FOutputPin write FOutputPin 을 추가한다.

자, 여기까지 한 다음에 다음과 같이 Receive 함수를 Override해 보겠습니다. 클래스의 선언문은 생

략하기로 하겠습니다.

function TInputPin.Receive(pSample: IMediaSample): HRESULT;

begin

BaseTrans.OutputPin.Deliver(pSample);

result := S_OK;

end;

위와같이 너무도 간단하게 구현되었습니다. 이것은 필터본체를 통해서 Output핀의 Deliever함수를 실

행하는 것입니다. 전에도 말씀드렸다시피 이 Deliver함수는 현재 자신과 연결된 하위필터 Input핀 객체

의 Receive함수를 실행시키고 있는 것입니다. 자, 여기까지 코딩하셨으면 현재 모든 작업이 완료된 것

입니다. 컴파일하고 실행해 보시기 바랍니다. (제가 컴파일하고 실행하라는 말은 실제로 컴파일하고

GraphEdit을 실행해서 필터삽입윈도우를 통하여 방금 컴파일한 따끈따끈한 필터를 생성하고 그것을 이

용하여 간단한 DShow Graph를 연결하여 실행해 보라는 것을 의미하는 것입니다. 여기서는 Cam ->

BaseTrans -> Video Renderer 이렇게 연결된 그래프가 되겠습니다.)

지금까지 무리없이 따라오셨다면 화면에는 Cam에 찍히는 화상이 실제로 보이실 것입니다. 우리는 현

재 재접속을 하지 않는, 아주 초보적이고 기본적인 제자리 변환필터를 만들어 본 것입니다. 여기까지 오

Page 168: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 168 -

면서 필터의 협상과정을 공부하셨고, 미디어타입에 대하여 익히셨으며, 핀의 기본적인 상속관계 등, 많

은 것을 아시게 되었을 것입니다. 우리가 필터를 개발한다는 것은 크게 두가지 범주로 나눠놓고 볼수가

있습니다. 가장 처음에는 BaseClass.pas유닛에 정의되어 있는 기본 필터 클래스를 상속받아서 나름대로

의 필터를 구현하는 방법입니다. 이것은 손쉽고 편리하지만, 반대로 필터의 내부사정에 대하여 많은 궁

금증을 그대로 남겨놓게 되곤 합니다. 또한 영상합성필터와 같이 다수의 입력핀이 합쳐져서 하나의 아

웃풋으로 만들어지는 것과 같은, 독특한 구조의 필터개발은 불가능하다라는 약점이 있을 수가 있겠습니

다. 두번째로 가장 최상의 클래스인 TBCBaseFilter, TBCBaseInputPin, TBCBaseOutputPin 등에서 직

접 상속을 받아서 하나하나 차례대로 구현을 하는 것입니다. 그러나 이러한 방식은 너무도 생각할 것이

많이 있기 때문에 조심스러운 시간이 훨씬 많이 필요로하게 된다는 약점이 있으며, 또한 DShow 필터

의 전체구조를 어느정도 꿰뚫지 않고서는 오히려 첫번째 방식보다 못하게 될지도 모르는 위험을 내포하

고 있습니다.

저는 여러분에게 일단 첫번째 방식을 권해 드리고 싶습니다. BaseClass.pas 유닛에 정의되어 있는 수

많은 기본 필터 클래스를 잘만 활용한다면 거의 만들지 못하는 필터는 없을 것이라고 생각합니다. 물론

여러분은 경험이 늘어갈 수록 조금씩 더 많은 Override를 수행할 것이고, 더 많은 상위 클래스의 기본

함수를 수정하여 사용하게 될 것입니다. 이렇게 한단계씩 발전해나간다면 어느새 DShow의 필터 개발이

그리 어려운 작업이 아니라는 것을 아시게 될 것입니다.

지금까지 여러분이 만드셨던 BaseTrans 제자리 변환필터는 한가지 근원적인 문제점을 가지고 있습니

다. 물론 실행은 잘 될 것입니다. 그러나 만약 범용적이고 상용의 필터개발을 하신다면 이런 구조의 필

터는 상당한 위험성을 내포하고 있다고 할 것입니다. 저는 강의 첫 부분에서 버퍼협상을 해설하면서 상

위 필터와 공유했던 할당자를 그대로 가져와서 하위필터와의 협상에 어떻게든 사용할 수 있을 것이라고

말씀드렸습니다. 하지만 굳이 그렇게 하지 않은 이유는 보다 근본적인 차원의 문제가 있기 때문에, 그

문제를 해결하지 못한다면 아무런 소용이 없을 것이라는 점입니다. 자, 이제 그 근본적인 문제점에 대하

여 말씀드리겠습니다.

여러분은 현재 GraphEidt에서 다음과 같이 연결한 상태로 있을 것입니다. Cam -> BaseTrans ->

VideoRenderer 이런 상태에서 다시 필터삽입윈도우를 열어서 BaseTrans 필터를 한개더 생성시키도록

하십시오. 그리고 GraphEdt에서 왼쪽으로부터 시작해서 12번째 단축키로 현재 연결되어 있는 필터들의

연결을 끊도록 합니다. 그리고 다시 방금 끊어졌던 BaseTrans 필터와 Cam필터와 연결을 하고서

BaseTrans의 속성창을 보시겠습니다. (속성창을 보는 방법을 설명하지 않았지만 GraphEdit를 가지고

실습하다보면 자연스럽게 아시고 계실 것입니다. 필터를 클릭한 상태에서 마우스 왼쪽 버튼을 누르면

될 것입니다.) 속성창에서 두번째 페이지인 Output핀을 보면 현재 아무런 미디어형도 나와있지 않습니

다. 여기에 아무런 미디어형이 잡혀있지 않다는 것은, 곧 하위필터와 어떠한 연결도 하지 못한다는 말과

거의 같은 것입니다. 자, 이게 왜 없는 것일까요? 그렇다면 애초에 없었던 것일까요? 그것은 아닐 것입

Page 169: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 169 -

니다. 자, 그렇다면 이번에는 아까와 마찬가지로 GraphEdit의 12번째 단축키를 이용하여 필터들의 연결

을 해제합니다. 그리고 이번에는 두번째에 생성시킨 BaseTrans 필터와 Cam을 연결하고서 마찬가지로

속성창을 보시겠습니다. 속성창의 Output핀 미디어형을 보시면 이번에는 한개가 나와 있는 것을 확인하

실 수가 있으실 것입니다. 이말은 즉, 하위필터와 연결할 수가 있다는 것입니다. 실제로 Video

Renderer필터와 연결을 시도한다면, 무리없이 연결될 것입니다.

만일 여러분이 BaseTrans 필터를 여러개 생성하신다면, 그 마지막에 생성된 BaseTrans만이 정상적

으로 연결되어 질 수 있을 것입니다. 이것에 대한 근본적인 원인은 아마도 핀협상에 따른 구조적인 문

제일 것이라는 추측만이 있을 뿐입니다. 그렇다면 핀협상에서는 대체 어떠한 일이 벌어지는 것일까요?

우리는 지금까지 핀협상 -> 미디어 협상 -> 버퍼협상 이런식으로 생각해 왔습니다. 그러나 이것을 다

른 측면에서 살펴볼 필요가 있을 것입니다.

위와같이 생각해 볼수가 있을 것입니다. 즉, 핀협상이라는 거대한 구조 안에서 미디어 협상이 이뤄지

며, 또한 미디어 협상이라는 구조안에서 버퍼협상이 이뤄진다는 시각입니다. 사실 미디어협상과 버퍼협

상은 순차적으로 이뤄지는 것이겠지만, 생각해보면 미디어협상이 완료되지 않으면 버퍼협상 근처에도

가지 못하기 때문에 이렇게 생각할 수도 있다는 것입니다. 굳이 위와같은 방식으로 생각하는 것은, 핀과

핀이 연결을 시도할때 상당히 많은 Loop를 반복적으로 돌면서 협상을 이어나간다는 것입니다. 그 가장

큰 Loop가 핀협상이며, 그 안에서 미디어협상이라는 Loop가 또다시 실행되고 있다는 것입니다. 그렇다

면 여기서 제가 말하는 핀협상이란 무엇을 말하는 것일까요?

GraphEdit상에서 다음과 같이 연결을 시도하시기 바랍니다. Cam -> BaseTrans -> Video Mixing

Renderer 이렇게 연결을 시도하면 그 중간에 Color Space Converter가 대뜸 끼어드는 것을 보실 수가

있을 것입니다. 즉, Video Mixing Renderer은 RGB32 미디어포맷 구조를 지원하기 때문에 필터그래프

Page 170: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 170 -

가 자동적으로 Color Space Converter를 삽입하여 미디어형을 변환한 것입니다. 이러한 서비스 지원에

대한 핀협상의 구조를 현재로서는 정확하게 파악하기 어렵습니다. 다만 이것은 필터그래프의 가장 기

본적인 서비스이며, 이러한 서비스가 DShow에서 상당히 중요한 '덕목? '으로 자리 잡고 있다는 것입니

다.

자, 이제 결론에 이르러야 하겠습니다. 우리는 이제까지 두가지 종류의 제자리 변환필터를 개발하였습

니다. 하나는 BaseClass.pas유닛에서 제공하는 기본 필터를 상속받아서 만든 것이고, 또다른 하나는 직

접 가장 상위 클래스를 상속받아서 처리한 것입니다. 특히 두번째 필터에서 우리는 제자리 변환 필터

고유의 특성이라 할 수 있는 재연결을 하지 않고도 직접 버퍼의 포인터를 랜더러쪽에 전달해 줄 수 있

었던 것입니다. 그런데 만들고 나니 근본적인 문제가 발생 합니다. 동일한 필터를 재생성 하였을 때, 가

장 마지막에 생성된 필터만이 정상적인 연결이 이뤄지는 것이었습니다.

필터그래프라는 COM 객체 내부에서 어떻게 핀들의 연결을 지원하는지는 구체적으로 알려져 있지 않

습니다. 다만 필터그래프의 기본적인 서비스 항목 중 하나인, 중간에 연결가능한 최대한의 필터를 찾아

서 끼워넣기하는 기능이 상당한 의도로 기초구조에 뿌리박혀 있지 않나 하는 생각을 가지게 합니다. 그

리고 저의 개인적인 생각으로는, 이러한 DShow의 기본적인 생각의 강요로 인하여 제자리 변환필터가

그처럼 재연결을 할 수밖에 없는 복잡한 구조로 만들어 질 수 밖에 없었지않나 추측할 다름입니다. 물

론 이것은 저의 개인적인 생각입니다. 저의 이런 추측이 옳든, 그렇지 않든 여러분은 지금까지 저의 강

의를 따라 오시면서 많은 것을 배우셨을 것입니다. 그리고 저보다 더 많은 발전을 기대하며 저의 추측

이 실은 오해에서 비롯된 것이라든지, 아니면 또다른 구조적인 문제로 인해서 발행한 것이라든지 하

는 해답을 스스로 찾으시길 희망합니다.

자, 이쯤에서 21부를 마치고자 합니다. 여러분 수고하셨습니다. 다음회에는 지금까지 BaseTrans 필

터의 풀 소스와 부수적인 필터 개발 이야기에 대해서 조금 이야기 하고자 합니다.

Page 171: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 171 -

22.22.22.22. 필터필터필터필터 9999

이번 장에서는 지난회까지 만들었던 BaseTrans의 풀소스를 올려놓겠습니다. 예전에 고우영만화가 생

각납니다. 간혹가다가 화면 반쯤을 차지하는 단순한 그림을 그려놓고, 그 아래에 다음과 같은 주석을 적

어놓곤 했었습니다. '이래도 되는 건가. 만화가가 너무 날로 먹으려하네.' 지금 저도 이와 비슷한 심정인

것 같습니다. 본 강의를 몇개월에 걸쳐 끌어 오는 동안 심심이 많이 피폐해지게 되었습니다. 애초에 시

작을 너무 쉽게 생각하였던 저의 불찰도 있었습니다만, 보다 정확한 강의를 위해서 일일이 하나하나 확

인하는 절차를 거쳐야 했던 것도 상당한 어려움의 하나였습니다. 그러나 이러한 어려움을 겪으면서도

실제로 문제를 밝혀낸 것도 있고(DSPack의 제자리 변환필터의 동작이상), 밝혀낸 문제점을 수정한 것

(GraphEdit에서 필터가 GRF파일로 저장되지 않는 현상) 도 있어서 나름대로 보람된 시간이었지 않나

싶습니다. 자, 이제 BaseTrans 필터의 풀 소스를 다음과 같이 적어놓겠습니다. 각각의 유닛에 정의된

것들입니다.

// 필터 본체 클래스...

unit uMain;

interface

uses

BaseClass, DirectShow9, Windows, DSUtil, uInput, uO utput;

const

CLSID_BaseTrans: TGUID = '{6EE3D5B6-08A5-4978-B85E-7016C9BA7A16}' ;

type

TBaseTrans = class(TBCBaseFilter)

private

FInputPin : TInputpin;

FOutputPin : TOutputPin;

public

constructor Create(Name: string;

Unk : IUnKnown;

Lock: TBCCritSec;

Page 172: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 172 -

const clsid: TGUID

);

constructor CreateFromFactory(Factory: TBCClassFactory;

const Controller: IUnknown); override;

destructor Destroy; override;

function GetPin(n: Integer): TBCBasePin; override;

function GetPinCount: integer; override;

property InputPin : TInputpin read FInputPin write FInputpin;

property OutputPin : TOutputPin read FOutputPin write FOutputPin;

end;

implementation

{ TBaseTrans }

constructor TBaseTrans.Create(Name: string; Unk: IInterface;

Lock: TBCCritSec; const clsid: TGUID);

var

hr : HRESULT;

begin

inherited create(Name,Unk,Lock,CLSID_BaseTrans);

FInputPin := TInputpin.Create( 'Input Pin' , self, TBCCritSec.Create, hr,

'Input' );

FOutputPin := TOutputPin.Create( 'Output Pin' , self, TBCCritSec.Create, hr,

'Output' );

end;

constructor TBaseTrans.CreateFromFactory(Factory: TBCClassFacto ry;

const Controller: IInterface);

begin

Create(Factory.Name,Controller, TBCCritSec.Create, Factory.ClassID);

end;

destructor TBaseTrans.Destroy;

begin

FreeAndNil(FInputPin);

Page 173: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 173 -

FreeAndNil(FOutputPin);

inherited;

end;

function TBaseTrans.GetPin(n: Integer): TBCBasePin;

begin

case n of

0 : Result := FInputPin;

1 : Result := FOutputPin;

else

Result := nil;

end;

end;

function TBaseTrans.GetPinCount: integer;

begin

Result := 2;

end;

initialization

TBCClassFactory.CreateFilter(TBaseTrans, '_BaseTrans' , CLSID_BaseTrans,

CLSID_LegacyAmFilterCategory, MERIT_DO_NOT_USE, 0, nil);

end.

//Input 핀의 클래스.

unit uInput;

interface

uses

BaseClass, DirectShow9, DSUtil;

type

TInputPin = class(TBCBaseInputPin)

Page 174: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 174 -

private

public

constructor Create(ObjectName: string; Filter: TBCBaseFilter; Lock:

TBCCritSec;

out hr: HRESULT; Name: WideString);

function CheckMediaType(mt: PAMMediaType): HRESULT; override;

function Receive(pSample: IMediaSample): HRESULT; override;

end;

implementation

uses

uMain;

var

BaseTrans:TBaseTrans;

constructor TInputPin.Create(ObjectName: string; Filter: TBCBaseFilter;

Lock: TBCCritSec; out hr: HRESULT; Name: WideString);

begin

inherited Create(ObjectName, Filter, Lock, hr, Name);

BaseTrans := TBaseTrans(Filter);

end;

function TInputPin.CheckMediaType(mt: PAMMediaType): HRESULT ;

begin

result := S_OK;

end;

function TInputPin.Receive(pSample: IMediaSample): HRESULT;

begin

BaseTrans.OutputPin.Deliver(pSample);

result := S_OK;

end;

end.

Page 175: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 175 -

//Output 핀의 클래스.

unit uOutput;

interface

uses

Windows, BaseClass, DirectShow9, DSUtil, ActiveX;

type

TOutputPin = class(TBCBaseOutputPin)

private

public

constructor Create(ObjectName: string; Filter: TBCBaseFilter;Lock:

TBCCritSec;

out hr: HRESULT; const Name: WideString);

function CheckMediaType(MediaType: PAMMediaType): HRESULT; override;

function GetMediaType(Position: integer; out MediaType: PAMMediaType):

HRESULT; override;

function DecideBufferSize(Alloc: IMemAllocator; propInputReq uest:

PAllocatorProperties): HRESULT; override;

end;

implementation

uses

uMain;

var

BaseTrans:TBaseTrans;

constructor TOutputPin.Create(ObjectName: string; Filter: TBCBaseFilter;

Lock: TBCCritSec; out hr: HRESULT; const Name: WideString);

begin

Page 176: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 176 -

inherited Create(ObjectName, Filter, Lock, hr, Name);

BaseTrans := TBaseTrans(Filter);

end;

function TOutputPin.CheckMediaType(MediaType: PAMMediaType): HRESULT;

begin

Result := S_OK;

end;

function TOutputPin.GetMediaType(Position: integer;

out MediaType: PAMMediaType): HRESULT;

begin

if Position > 0 then

begin

result := VFW_S_NO_MORE_ITEMS;

Exit;

end;

if BaseTrans.InputPin.IsConnected then

begin

CopyMediaType(MediaType,BaseTrans.InputPin.CurrentM ediaType.MediaType);

Result := S_OK;

end else

result := VFW_S_NO_MORE_ITEMS;

end;

function TOutputPin.DecideBufferSize(Alloc: IMemAllocator;

propInputRequest: PAllocatorProperties): HRESULT;

var

Actual: TAllocatorProperties;

begin

propInputRequest.cBuffers := 1;

propInputRequest.cbBuffer := 1;

result := Alloc.SetProperties(propInputRequest^, Actual);

end;

end.

Page 177: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 177 -

이제 여러분에게 마지막 실습을 권해드리고자 합니다. 본 실습은 DShow의 필터개발에 있어서 실무적

으로 무척 가치가 있을 것이라고 생각합니다. 여러분은 이번 기회에 YUV 미디어 포맷에 대한 좀 더 깊

은 이해를 하실 수가 있으며, 버퍼설정과 미디어설정의 구체적인 소스코드를 구경하실 수 있을 것입니

다. 이번 실습은 BaseTrans 변환필터의 연장선에서 Copy 복사 변환필터를 구현해 볼 것입니다.

우선 여러분은 새로운 프로젝트를 만들지 마시고, 기존의 BaseTrans 프로젝트를 Copy해서 BackUp

해 두시기 바랍니다. 그리고나서 BaseTrans 프로젝트의 소스를 조금씩 수정해 나가는 방식으로 진행해

나갈 것입니다. Copy 변환 필터는 제자리 변환필터와는 여러가지로 차이가 있습니다. 일단 Input과

Output핀이 있어야 한다는 것은 동일한 것이겠지만, 상위 필터의 미디어타입을 그대로 하위필터와의

연결에 사용하는 제자리 변환필터와는 다르게, Copy 변환 필터는 상위필터와는 완전히 다른, 새로운 미

디어 타입을 정의해서 하위필터와 연결할 것입니다. 이것이 무엇을 의미하는 것일까요? 새로운 미디어

타입을 정의해서 하위필터에 스트림을 흘려보낸다는 것은 새로운 미디어 포맷이 생성 되어야 함을 의미

하는 것이고, 새로운 미디어 포멧이 된다는 것은 새로운 버퍼가 필요하다는 것이고, 새로운 버퍼가 필

요하다는 것은 필연적으로 Copy 변환필터의 내부에서 상위필터의 스트림 데이터를 새로운 버퍼에 복사

하는, 제가 누누이 될 수 있으면 가능한 피해야 한다고 강조했던, 바로 그 버퍼링이 만드시 필요하다는

의미인 것입니다.

물론 복사변환 필터를 만드는데 있어서 반드시 새로운 미디어 타입을 정의할 필요는 없을 것입니다.

복사변환 필터도 상위 필터의 미디어타입을 그대로 가져와 하위필터에 전달할 수도 있을 것입니다. 그

러나 이것은 결코 현명한 행동은 아닙니다. 제자리 변환필터로서 개발되어도 될 것을, 굳이 복사변환 필

터로 개발하여 내부 버퍼링을 일으키기 때문입니다.

자, 그러면 이제 우리가 만들고자 하는 복사변환 필터의 구체적인 내용을 살펴보겠습니다. 새로운 복

사변환 필터는 이제까지의 RGB 포멧과는 다르게 YUV를 입력으로 받아들이게 됩니다. 그리고 이것을

RGB 포맷으로 하위필터에 전달하게 되는데요, 여기서 미디어 포맷 타입 캐스팅이 일어나느냐 하면, 그

렇지 않다는 것입니다. 저는 이번 강좌의 언젠가쯤에 YUV에 대하여 설명드린적이 있었습니다. 그 당시

YUV를 RGB인척 화면에 디스플레이해 보았는데, 각각의 Y, U, V 채녈이 독립된 화면으로 조각난 채 보

여지는게 참으로 신기하였다고 말씀드린 적이 있었습니다. 이번 마지막 프로젝트로 저는 이것을 보여

드리고자 합니다. 복사변환필터의 구조와 함께 YUV에 대한 경혐을 얻는다면 일거양득이라고 생각해서

입니다. 여러분은 이번 실습에서 YUV가 실제로 버퍼에 어떤 구조로 적재되어 있는지를 눈으로 직접 확

인하실 수가 있을 것입니다. 일단 YUV에 대한 좀더 디테일한 해설을 해보겠습니다.

YUV는 종류가 상당히 많습니다. 오히려 RGB보다 더 복잡하고 까다로운 포맷들로 넘쳐나고 있습니다.

이런 YUV를 크게 두가지의 범주로 나누어 생각해 볼 수가 있습니다. 하나는 Packed형이고 다른 하나

Page 178: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 178 -

는 Planar형입니다. 우선 Packed형은 RGB 포맷과 동일하게 각각의 Y, U, V 값이 연속되어 있습니다.

그러나 Planar형은 각각의 Y,U,V 값이 연속적으로 저장되어 있는것이 아니라, 하나 하나의 화면 평면으

로서 별도로 나눠져 있습니다. 제가 예전에 말씀드린 것은 바로 이 Planar형의 YUV 포맷을 말씀드린

것이었습니다. 그런데 RGB처럼 연속된 순서로 이루어진 YUV형태도 있는데, 왜 굳이 모든 YUV형 포멧

이 Planar형인 것처럼 설명을 할 필요가 있었겠느냐는 의문이 들 수도 있을 것입니다. 하지만 그것은

YUV의 특이함을 강조하기 위함이었을을 양해해 주시기 바랍니다. 아래는 Packed형과 Planar형 각각에

해당 하는 YUV 포맷을 구분해 본 것입니다. 참고하시길 바랍니다.

Packed형 -> MEDIASUBTYPE_AYUV, MEDIASUBTYPE_UYVY, MEDIASUBTYPE_Y411,

MEDIASUBTYPE_Y41P, MEDIASUBTYPE_Y211, MEDIASUBTYPE_YUY2,

MEDIASUBTYPE_YVYU, MEDIASUBTYPE_YUYV

Planar형 -> MEDIASUBTYPE_IF09 , MEDIASUBTYPE_IYUV , MEDIASUBTYPE_YV12,

MEDIASUBTYPE_YVU9

이제 BaseTrans 프로젝트에서 여러분이 수정해야할 곳을 보시겠습니다. OutPut핀의 GetMediaType

함수와 DecideBufferSize함수, 그리고 Input핀의 CheckMediaType함수와 Receive함수가 되겠습니다.

우선 Output핀 클래스의 GetMediaType 함수를 수정해야만 하는 이유는 간단합니다. 종전의 함수는 상

위 필터와의 연결협상에 사용되어진 미디어타입을 그대로 복사해서 설정하는 아주 단순한 코딩으로 이

뤄져 있습니다. 이번에 여러분은 직접 미디어타입 구조체를 생성시키고, 그 하나하나의 값들을 셋팅하셔

야 할 것입니다. 일단 GetMediaType 함수의 전체 코드는 아래와 같습니다.

function TOutputPin.GetMediaType(Position: integer; out MediaType:

PAMMediaType): HRESULT;

var

pvi: PVIDEOINFO;

begin

if Position > 0 then

begin

result := VFW_S_NO_MORE_ITEMS;

Exit;

end;

Page 179: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 179 -

if (MediaType = nil) then

begin

Result := E_POINTER;

Exit;

end;

MediaType.cbFormat := SizeOf(TVideoInfo);

pvi := CoTaskMemAlloc(MediaType.cbFormat);

if (pvi = nil) then

begin

Result := E_OUTOFMEMORY;

Exit;

end;

ZeroMemory(pvi, MediaType.cbFormat);

pvi.bmiHeader.biCompression := BI_RGB;

pvi.bmiHeader.biBitCount := 24;

pvi.bmiHeader.biSize := SizeOf(TBitmapInfoHeader) ;

pvi.bmiHeader.biWidth := 320 ;

pvi.bmiHeader.biHeight := 240 ;

pvi.bmiHeader.biPlanes := 1;

pvi.bmiHeader.biSizeImage := GetBitmapSize(@pvi.b miHeader);;

pvi.bmiHeader.biClrImportant := 0;

pvi.bmiHeader.biClrUsed := 0;

pvi.bmiHeader.biXPelsPerMeter := 0;

pvi.bmiHeader.biYPelsPerMeter := 0;

SetRectEmpty(pvi.rcSource);

SetRectEmpty(pvi.rcTarget);

MediaType.majortype := MEDIATYPE_Video;

MediaType.formattype := FORMAT_VideoInfo;

MediaType.bTemporalCompression := False;

MediaType.bFixedSizeSamples := True;

MediaType.subtype := GetBitmapSubtype(@pvi.bmiHea der);

MediaType.pbFormat := pvi;

Page 180: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 180 -

MediaType.lSampleSize := pvi.bmiHeader.biSizeImag e;

Result := S_OK;

end;

미디어타입에 대해서는 전회에서 설명을 드렸으므로 다시 설명하지는 않겠습니다. 이제 곧바로

DecideBufferSize 함수의 전체코드를 보시겠습니다. 아래와 같습니다.

function TOutputPin.DecideBufferSize(Alloc: IMemAllocator;

propInputRequest: PAllocatorProperties): HRESULT;

var

pvi: PVIDEOINFOHEADER;

Actual: TAllocatorProperties;

begin

if (Alloc = nil) or (propInputRequest = nil) then

begin

Result := E_POINTER;

Exit;

end;

pvi := AMMediaType.pbFormat;

propInputRequest.cBuffers := 1;

propInputRequest.cbBuffer := pvi.bmiHeader.biSize Image;

Assert(propInputRequest.cbBuffer <> 0);

Result := Alloc.SetProperties(propInputRequest^, Actual);

if Failed(Result) then Exit;

if (Actual.cbBuffer < propInputRequest.cbBuffer) then

begin

Result := E_FAIL;

Exit;

end;

Assert(Actual.cBuffers = 1);

Page 181: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 181 -

Result := S_OK;

end;

위의 코드의 핵심은 Alloc.SetProperties일 것입니다. 이 메서드를 실행함으로서 할당자의 속성을 설

정하고 있습니다. AMMediaType는 하위필터와의 미디어협상에서 정의된 미디어타입으로서, pbFormat

에 지정된 추가설정 레코드를 pvi 에 받아서 biSizeImage를 얻어내고 있습니다. 바로 이것을 알아야먼

버퍼의 크기를 propInputRequest.cbBuffer에 설정할 수 있기 때문에, 어쩔수 없이 번잡한 코딩이 이뤄

질 수 밖에 없었습니다. 우리는 예전에 cbBuffer를 1로 설정한 적이 있었습니다. 수정되기 이전

BaseTrans 변환필터에서는 하위필터와의 버퍼협상에서 생성되는 할당자를 필요치 않았기 때문에 가능

한 일이었습니다. 그러나 이제는 완전히 새로운 미디어타입에 새로운 버퍼를 실제로 생성해야 하기 때

문에 이 크기 설정은 정확해야 할 것입니다.

이제 Input핀 클래스로 가보겠습니다. 이곳의 CheckMediaType함수도 수정해야 하는데요, 지금까지

는 서브타입으로서 RGB 포맷을 받아들였다면, 이제는 YUV 형식만을 받아들여야 할 것입니다.

CheckMediaType함수의 전체 소스는 아래와 같습니다.

function TInputPin.CheckMediaType(mt: PAMMediaType): HRESULT ;

var

pvi: PVIDEOINFO;

begin

Result := S_False;

if IsEqualGUID(mt.majortype, MEDIATYPE_Video) and

IsEqualGUID(mt.subtype, MEDIASUBTYPE_YV12) then Result := S_OK;

end;

자, 위에서 저는 Planar형 YUV 타입중의 하나인 YV12를 선택하길 강요하고 있습니다. 만일 여러분

이 여러가지 종류의 Planar형 YUV 미디어 타입을 테스트 하고자 한다면 위의 MEDIASUBTYPE_YV12

을 MEDIASUBTYPE_IF09, MEDIASUBTYPE_IYUV ,MEDIASUBTYPE_YVU9 등으로 변경하셔도 될

것입니다. 그러나 USB 카메라의 사양이 개개인 마다 다르기 때문에 이번 실습에서는 제가 별도로 샘플

동영상을 마지막회인 23부에 올려놓도록 하겠습니다. 이 샘플 동영상은 위의 코드에서와 같이

MEDIASUBTYPE_YV12 서브타입으로 이뤄진 것으로서, 여러분의 컴퓨터에 ffdshow가 미리 설치되어

있어야 할 것입니다. 제가 강좌 초반에 ffdshow 을 될 수있으면 설치 해 놓으시라고 권해드 린바가 있

었을 것입니다. (본 샘플동영상은 ffdshow의 DivX로 압축되어 만들어 졌습니다. 굳이 ffdshow가 없다고

Page 182: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 182 -

하더라도 DivX용 압축 비디오를 Decoder할 수 있으면 가능할 것입니다. 하지만 이때 Decoder의

Output핀 속성을 여시고서 미디어 SubType이 YV12인지를 확인하시길 바랍니다.)

Page 183: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 183 -

23. 23. 23. 23. 필터필터필터필터 10101010

안녕하세요. 이제 강좌의 마지막회를 시작하겠습니다. 지난회에서 저는 이곳에 샘플 동영상을 올려 놓

겠다라고 말씀 드렸었는데요, 이곳에 올려 놓을 수 있는 파일의 최대 사이즈가 200K이기 때문에 안타

깝게도 올려 놓을 수가 없었습니다. 따라서 여러분이 직접 샘플 동영상 파일을 만드셔야 할 것 같습니

다. 만드는 방법은 간단합니다. 여러분의 컴퓨터에 ffdShow가 설치되어 있다면, Cam -> ffdshow video

encoder -> Avi Mux -> File Writer 이렇게 하시면 될 것입니다. 자, 이제 이렇게해서 생성된 샘플파일

이 있다고 가정한 상태에서 마지막 실습을 진행해 나가도록 하겠습니다.

이제 가장 핵심적인 Input 핀 클래스의 Receive 함수를 수정해 보겠습니다. 일단 완성된 코드를 아래

와 같이 보시고 난 후에 설명을 드리겠습니다.

function TInputPin.Receive(pSample: IMediaSample): HRESULT;

var

OutSample : IMediaSample;

SrcBuffer, DestBuffer: PByte;

DataSize: LongInt;

hr : HResult;

begin

result := S_False;

hr := BaseTrans.OutputPin.Allocator.GetBuffer(Out Sample, nil, nil, 0);

if hr <> S_OK then Exit;

pSample.GetPointer(SrcBuffer);

DataSize := pSample.GetActualDataLength;

OutSample.GetPointer(DestBuffer);

CopyMemory(DestBuffer, SrcBuffer, DataSize);

BaseTrans.OutputPin.Deliver(OutSample);

result := S_OK;

end;

자, 위에서 보시면 GetBuffer로 하위필터에 미디어 데이터를 담아서 전달할 버퍼를 생성하라고 요구

하고 있습니다. 그런데 이 함수는 변환필터 자신의 반대편 핀인 Ouput핀 클래스에서 설정된 할당자

COM 객체의 서비스로서 실행되고 있습니다. 할당자는 GetBuffer을 실행시켜서 메모리 버퍼를 직접 반

Page 184: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 184 -

환하는 것이 아니라, 또다시 미디어샘플 이라는 COM 객체를 생성해서 그것의 인터페이스인

IMediaSample를 OutSample에 담아서 반환해주고 있습니다. 우리는 이것을 이용하여 하위필터 Input핀

객체의 Receive에 전달해 주면 되는 것인데요, 전에도 말씀드렸다시피 이런 전달 역할을 하는 Deliver

라는 함수가 Output핀의 클래스에 이미 마련되어 있기 때문에 우리는 이것을 이용하면 된다고 하였습니

다. 조금 설명이 복잡해졌습니다. 이것을 정리하면 다음과 같습니다.

1) 필터자신의 Output핀이 하위필터와 버퍼협상에서 설정되었던 할당자를 이용해서 GetBuffer를 실행한다.

2) GetBuffer를 이용해서 또다른 COM 객체인 미디어샘플을 생성하고 그것의 인터페이스를 얻어온다.

3) 상위필터에서 넘어온 미디어샘플에서 GetPointer를 사용하여 버퍼의 포인터를 얻어낸다.

4) 상위필터에서 넘어온 미디어샘플에서 GetActualDataLength를 사용하여 버퍼의 실제 크기를 얻어낸다.

4) 하위필터와의 버퍼협상에서 설정된 할당자가 새로 마련해준 미디어샘플에서 GetPointer를 사용하여 버퍼의 포

인터를 얻어낸다.

5) 이제 상위필터에서 넘어온 퍼버를 새로 만들어낸 미디어샘플의 버퍼에 그 크기 만큼 복사를 한다.

6) 복사한 새로운 미디어샘플을 통째로 하위필터 Input핀의 Receive함수로 Deliver함수를 사용해서 전달한다.

자, 여기까지 한 다음에 컴파일하고 실행해보도록 하십시오. GraphEdit에서 여러분은 다음과 같이 필

터들을 연결시키면 될 것입니다. 샘플동영상 -> Avi Splitter -> ffdshow video decoder -> BaseTrans

-> Video Renderer. (제가 지금까지 동영상을 직접 로딩하는 방법에 대하여 설명을 드리지 않았지만,

대부분 이것은 기초적인 것이니까 아시고 계실 것이라는 생각에서였습니다. 설명하자면 필터삽입윈도우

의 DirectShow Filters 카테고리에서 File Source(Async.)를 이용하시면 될 것입니다.)

실행을 하시면 여러분은 고개를 갸웃할 것입니다. 화면이 순식각에 지나가고 게다가 동일한 화면이

세개가 동시에 보이기 때문입니다. 자, 이거 이런 현상이 왜 발생할까요? 일단 화면이 순식간에 지나간

것은 여러분이 새로 복사한 미디어샘플에 타임스탬프를 설정하지 않았기 때문에 그렇습니다. 강의 초반

에 제가 SmarT Tee 필터를 설명하면서 이 필터의 PreView에서는 타임스탬프가 제거된채 나온다라고

하였습니다. 이 말의 의미가 바로 현재 여러분이 실습한 바 그대로입니다. 타임스 탬프가 제거되면 나오

는 즉시, 바로바로 랜더링되게 되어 있습니다. 그런데 Cam에서는 스레드에서 원체 화면 하나하나가 만

들어지는 시간이 별도로 있었기 때문에 상식적으로 한꺼번에 슈슈슉 흘러나오지 않았던 것이고요, 동영

Page 185: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 185 -

상 파일의 경우에는 그런것이 없으니까 마치 고속촬영처럼 슈슈슉 흘러가버렸던 것입니다. 이 타임스탬

프를 설정하는 것은 다음과 같습니다.

//Receive 함수에다가 아래와 같은 변수를 추가한다.

var

:

Start, Stop: TReferenceTime;

pStartTime, pEndTime: PReferenceTime;

begin

pSample.GetTime(Start, Stop); // 원본의 미디어 타임을 얻어온다.

result := S_False;

hr := BaseTrans.OutputPin.Allocator.GetBuffer(Out Sample, nil, nil, 0);

if hr <> S_OK then Exit;

OutSample.SetTime(@Start, @Stop); // 새로운 미디어샘플에 얻어온 미디어타임을 설정한다.

:

위와같이 하시면 일단 화면은 정상적인 속도로 랜더링이 될 것입니다. 코드를 보시면 상위필터에서

가져온 미디어샘플에서 일단 미디어타임을 얻어서 새롭게 만들어진 미디어샘플에 그 시간을 설정하고

있습니다. 위의 코드는 간단하기 때문에 더이상 설명을 드리지는 않겠습니다. 이제 나머지 기묘한 화면

의 Display 현상에 대하여 설명드리겠습니다. 아래의 캡쳐한 화면 사진을 보시겠습니다.

위의 현상을 보시고 혹시 세개의 연속된 프레임이 동시에 한 화면에 나왔나보다라고 생각하실 수도

있을 것입니다. 그러나 그렇지는 않고요, 세개의 화면이 만들어진 것은 사실 하나의 화면이 세개로 나누

Page 186: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 186 -

어진 것입니다. 어떤 방식으로 나누어졌는가 하면 각각의 이미지 라인이 하나씩 다음 화면을 만들고

있는 것입니다. 자, 이렇게 생각해 보십시오. 여러분의 OutPut핀은 미디어협상에서 BI_RGB 포멧으로,

그리고 픽셀의 깊이는 24비트로 설정하고 있습니다. 그런데 현재 우리가 받아들이고 있는

MEDIASUBTYPE_YV12 는 Planar형 YUV로서 Y채널의 화면이 크게 하나로 이뤄진게 있고 그 아래에

각각 나머지 U와 V의 화면이 놓여져 있는 구조를 하고 있는 것입니다. 즉 현재의 OutPut 미디어는 픽

셀 하나가 연속된 RGB로서 24바이트를 가지고 있다고 생각하고 있고, 실제로는 1Byte짜리 Y값만 달랑

연속적으로 이어져 있는 것입니다. 따라서 처음부터 1/3지점까지 RGB 방식대로 가면 한 화면의 흑백영

상으로 실제로 완료된고, 거기서 다음 라인이 시작되어 다시 2/3지점까지 다음라인인 두번째 라인이 디

스플레이되고, 마지막 까지 그 다음인 세번짜의 라인이 디스플레이 되는 방식인 것입니다. 그렇게 해서

3개의 화면으로 나뉘어진 것이지, 결코 연속적인 프레임이 한꺼번에 나온 것은 아닙니다. 이제 이것을

수정해 보겠습니다. Output핀 클래스의 GetMediaType함수를 다음과 같이 수정해야만 할 것입니다.

:

pvi.bmiHeader.biCompression := BI_RGB;

pvi.bmiHeader.biBitCount := 8;

pvi.bmiHeader.biSize := SizeOf(TBitmapInfoHeader) ;

pvi.bmiHeader.biWidth := 320 ;

pvi.bmiHeader.biHeight := 240* 3;

:

위에서 수정된 코드를 보시면 픽셀의 깊이인 biBitCount를 8로 설정하고 있습니다. 그리고 이미지의

높이를 3배로 크게 확대해 놓고 있습니다. 이것은 안전을 위해서 최대로 잡은 측면도 있으나, 기본적으

로 320*240의 버퍼 사이즈보다는 크다는 것을 이해하실 것입니다. 즉, 320*240 크기의 1바이트짜리 Y

채널(흑백)로 이뤄진 이미지 아래에 U와 V채널이 연속되어 있을 것이기 때문입니다. 자, 이렇게 수정하

고서 컴파일하고 실행해 보도록 하십시오. 그런데 이번에는 더욱더 크게 고개를 갸웃할 것입니다. 화면

에 아무것도 나오질 않기 때문입니다. (이 부분에서 저도 조금 헤메었습니다.

예전에 만들어둔 소스코드를 또다시 찾을 수가 없어서 팔레트설정이 있다는 것을 또다시 재 확인해야

만 했습니다.) 우리가 비트맵 이미지의 픽셀깊이를 설정할때 8비트, 16비트, 26비트 등을 설정하실 수

가 있으실 것입니다. 그런데 24비트나 16비트등은 시스템 팔레트를 사용하기 때문에 문제가 없으나, 유

독 8비트는 전용 팔레트를 직접 정의하여 만들어줘야 한다는 것입니다. 자, 이제 이것을 정의해 보겟습

니다. Output핀의 GetMediaType함수에는 pvi라는 PVIDEOINFO타입의 변수가 하나 선언되어 있습니

다. 이 PVIDEOINFO타입은 TVideoInfo 레코드의 포인터 형인데요 이것의 멤버들을 보시면 bmiColors

라는 이름으로 256개의 TRGBQuad 레코드로 이뤄진 배열을 보실수가 있습니다. 바로 이것이 8비트 비

Page 187: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 187 -

트맵에서 팔레트를 설정해 놓는 곳입니다. 그렇다면 다시 Output핀의 GetMediaType 함수를 수정해 보

겠습니다.

:

ZeroMemory(pvi, MediaType.cbFormat);

// 그레이 스케일의 팔레트를 직접 설정한다.

for i:= 0 to 255 do

begin

pvi.bmiColors[i].rgbBlue := i;

pvi.bmiColors[i].rgbGreen := i;

pvi.bmiColors[i].rgbRed := i;

pvi.bmiColors[i].rgbReserved := i;

end;

pvi.bmiHeader.biCompression := BI_RGB;

pvi.bmiHeader.biBitCount := 8;

:

위와같이 그레이스케일의 팔레트를 직접 값을 일일이 써넣어서 정의하고 있습니다. 자, 여기까지 한

다음에 한번 실행을 해보시길 바랍니다. 그러면 커다란 한개의 Y채널 화면아래에 작은 U와 V채널이 여

러개 보이실 것입니다. 하지만 이번에도 조금 의문을 가지실 것입니다. 첫째, 화면이 상하뿐만이 아니라

좌우로도 뒤바껴 있다. 둘째, U와 V의 채널이 아래위로 각각, 두개씩 보인다. 자, 이 원인에 대하여는

여러분에게 숙제로 남겨놓겠습니다. (사실 예전에 그 원인에 대하여 알았었는데 까억었습니다. 다시 자

료 찾아보기도 그렇고 해서...) 여러분이 화면에서 보시는 것은 실제 Planar형 YUV의 저장되어 있는 상

태 그대로인 것입니다.

이제 강좌를 마칠 시간이 되었습니다. 여러분이 마지막 강좌에서 YUV의 좀더 구체적인 모습을 실습

을 통하여 체험하셨습니다. 그리고 COPY (복사) 변환필터의 아주 원초적인 구조까지 이해하실 수가 있

었을 것입니다. 그러나 여러분에게는 아직도 미지의 영역이 남아 있습니다. 그것은 바로 Source필터와

Renderer 필터인 것입니다. 이 두개의 필터클래스는 여러분이 반드시 경험해봐야 하는 것들입니다. 저

의 강좌가 필터개발의 가장 기초부분을 염두에 둔 것이라면, 여러분은 이제 나머지 부분을 스스로 연구

하고 노력해서 채워넣으셔야 할 것입니다. Source필터에서는 스레드의 동기화에 대하여 좀 더 깊이있는

지식이 필요하며 Renderer 필터 클래스에서는 비디오카드의 구조와 관련 API함수들을 공부하셔야 할

것입니다. 물론 여러분은 현재 BaseClass.pas 유닛에 선언되어 있는 Source필터와 Renderer필터의 기

Page 188: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 188 -

본 클래스들을 그대로 상속받아서 새로운 필터를 개발하실 수도 있을 것입니다. 저도 일단은 이 방법

을 권유해 드리고 싶습니다. 하지만 각각의 클래스 내부의 정확한 구조를 파악하는 것을 게을리하다가

는 나중에 잡기힘든 버그를 유발해 낼수 있으며, 혹은 변경하기 힘든 불안전한 구조로서 프로젝트를 출

발할 가능성이 높아질 것입니다. (저의 경험으로는 Run, Pause, Stop을 처리함에 있어서 문제가 있었습

니다. 10번 돌렸을때 2~3번 나는 오류는 잡아내기가 어렵지 않습니다. 그러나 100번정도 실행할때 한

두번 오류가 날까말까하면 잡아내기가 여간 힘들지가 않습니다. 스레드의 동기화를 처리함에 있어서

DShow에서 만큼은 Run -> Pause -> Stop 로서 생각하시면 안됩니다. 이런 순서로 불려지지 않고 매

번 Pause가 먼저 불려지고 있습니다. 이 작은 차이는 아주 순간적인 스레드간의 교착상태를 불러 일으

키게 만들수가 있습니다.)

다음은 샘플동영상에서 드리고 싶었던 저의 작별인사를 아래와 같이 이미지로 만들어 보았습니다. 여

러분 건강하시고 행복하시길 바라겠습니다.

Page 189: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 189 -

아... 이제 놀러가야지... 룰루루...

Page 190: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 190 -

Bug ListBug ListBug ListBug List

DSPack 2.3.4 버전에는 버그가 상당부분 있습니다. 강의를 하다가 몇개 찾아낸 것들을 여기다 기록

할 것입니다. 또한 추후에 개발하면서 발견되는 버그 또한 이곳에 기록하도록 하겠습니다. 어느정도 버

그가 바로잡혀지면 별도의 버전으로 올려놓도록 하겠습니다.

(1) ... DSPACK234\Demos\D6-D7\Filters\RGB24 Video Renderer 에 있는 랜더러필터 예제의

버그.

설명) 본 샘플 필터를 생성하여 레지스트리에 등록한후 GraphEdit에서 DShow 기본랜더러를 사용하면 에

러가 발생합니다.

원인) 본 샘플필터의 CLSID가 DShow 기본 랜더러의 CLSID와 중복되어 사용되었기 때문입니다.

해결) 본 샘플필터예제에서 VidRenderer.pas의 가장 아랫부분을 다음과 같이 수정하십시오.

initialization

TBCClassFactory.CreateFilter(TVideoRenderer, '_Delphi Video Renderer' ,

CLSID_DelphiVideoRenderer, CLSID_LegacyAmFilterC ategory, MERIT_DO_NOT_USE, 0,

nil );

예제샘플에서는 CLSID_VideoRenderer 라고 나와있을 것입니다.

이것을 위와같이 CLSID_DelphiVideoRenderer 로 수정하여 사용하십시오.

만약에 모르고 본 샘플필터를 등록하여 사용하다가 DShow의 기본 랜더러필터가 사라지게 된 경우에

당황하지 마시고 regsvr32 -u C:\windows\system32\quartz.dll 로 실행하셨다가 다시 -u를 제거하

고 실행시키십시오. 즉 quartz.dll DShow COM 서버를 제거했다가 다시 등록하여 사용하시면 됩니다.

물론 이때 문제를 발생시킨 델파이 샘플랜더러를 제거한 상태에서 하셔야 할 것입니다.

(2) DSPack의 Filter 샘플이 GraphEdit상에서 Save Graph로 저장되지 못하는 현상.

Page 191: Direct Show in Delphi

델파이로 DirectShow 프로그래밍하기

- 191 -

해설 ) DSPack의 Demo 필터소스를 컴파일하여 윈도우에 Regsvr32로 등록한후 GraphEdt로 사용하는 과정

에서 Save Graph로 저장되지 못하는 현상이 발견되었습니다.

원인 ) DSPack의 BaseClass.pas에 정의되어 있는 기본 클래스에서 IPersist 인터페이스를 얻어오지 못하기

때문에 발생하는 것으로 파악되었습니다.

해결) 현재 델파이의 기본함수 중 하나인 GetInterface가 상속되는 인터페이스를 얻어오지 못하므로 Class

선언에서 상속된 모든 인터페이스를 함께 선언해 놓아야 합니다. 즉, 아래처럼 기본선언을 수정해야

합니다.

수정수정수정수정 전전전전

TBCBaseFilter = class(TBCUnknown, IBaseFilter, IAMovieSetup)

수정수정수정수정 후후후후

TBCBaseFilter = class(TBCUnknown, IPersist, IMediaFilter,

IBaseFilter, IAMovieSetup )

(3) DSPack의 Filter 샘플예제에서 InPlace 필터의 샘플이 현재 C++의 샘플과 다른 작동을 하고 있

습니다. 이것은 예제 샘플 소스의 문제가 아니라 BaseClass.pas에 정의되어 있는 기본클래스가 델파이

로 잘못 포팅되었기 때문에 발생하는 문제로 파악하고 있습니다.

해설 ) 본 샘플예제의 InPlace 필터가 작동하면서 내부적으로 버퍼링을 수행하고 있습니다. C++ 샘플에서는

버퍼링을 하지 않는 것으로 파악되고 있습니다.

원인 ) 몇개의 원인을 발견했으나, 워낙 구조적인 문제이기 때문에 아직도 계속 발견되는 중입니다. 추후 완

성할 예정이나 혹시 그렇지 못할 경우 기록으로 남겨놓겠습니다.

해결 ) ...