유돌이

calendar

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

Notice

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

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

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

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

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

  FreeMem(Buff);
  FileStm.Free;
end;

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

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

 

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


      y           y             y            .             .              .

           uv          uv

      y           y             y

 
 
      .

 

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

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

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


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


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


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

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

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

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

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

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


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

posted by 유돌이