통합검색
· 마을서비스란?  · 포럼마을  · 일반마을  · 테마마을  · 마을랭킹  · 활동왕
· 덱스퍼트란?  · TECBOX   · PRSBOX   · 이용안내  
· DEXT제품군  · 솔루션베이  · S/W & ESD 컴포넌트
· 프로그램베이
· LiveSeminar  · LiveConference
Visual C++ 포럼마을 입니다.
  마을등급 Visual C++   이 마을은 포럼마을 입니다이 마을은 자유가입제 마을 입니다 마을소개 페이지로 이동 전입신청
마을촌장촌장 나성훈 주민 34013 since 2006-12-29
우리마을 공지사항
질문&답변
강좌&팁
자유게시판
자료실
앨범
개인게시판
마을 게시판
등록된 마을 게시판이
없습니다.
랑데브 게시판
칼럼 게시판
개발자 고충상담
Dev Talk
자유토론방
벼룩시장
재나미 우스개
구인/프로젝트 정보
사람인 채용 게시판
  고객지원 게시판
마이 데브피아
 나의 e-Money 내역
 활동왕 My Page
 스크랩한 게시글보기
 쪽지관리
 주소록관리

 강좌&팁
 [API] 저수준 제어를 이용한 WAVE 재생 - 03  | Sound 2002-06-12 오후 1:55:11
 lovesgh  lovesgh님께 메시지 보내기lovesgh님을 내 주소록에 추가합니다.lovesgh님의 개인게시판 가기 번호: 4378  / 읽음:6,710

 ------------------------------------------------------------------

 저번 강좌에서는 WAVE파일을 불러다가 정보들을 살펴보고 WAVE 파일의

구조를 알아봤는데, 이제는 실제로 소리를 내보도록 하자. WAVE 파일을

불러다가 실제로 스피커에서 소리가 나오도록 하기위해서는, waveOut..

으로 시작하는 함수들을 사용한다. 이와 반대로 waveIn..으로 시작하는

함수들은 녹음(그냥, 쉬운 표현으로 '녹음'이라 하겠습니다)할 때 사용

하는 것이다. waveOut..에 대한 함수들을 대충 살펴보자면 다음과 같이

 

------------------------------------------------------------------

waveOutClose

waveOutGetDevCaps

waveOutGetErrorText

waveOutGetID

waveOutGetNumDevs

waveOutGetPitch

waveOutGetPitchRate

waveOutGetPosition

waveOutGetVolume

waveOutMessage

waveOutOpen

waveOutPause

waveOutPrepareHeader

waveOutProc

waveOutReset

waveOutRestart

waveOutSetPitch

waveOutSetPlaybackRate

waveOutSetVolume

waveOutUnprepareHeader

waveOutWrite

------------------------------------------------------------------

 

여러개가 있다. 헉.. 너무 많다고? -_-; 그러나, 걱정하지 않아도 된다

사실상 지금 수준에서 (WAVE 재생을 목표로 한) 사용할 함수는 몇 가지

안되기 때문이다. 이 함수들을 이용해서 WAVE 파일들을 열고 재생 하고

멈추고 닫고 .. 이런 일들을 할 것인데, 그보다 먼저 '버퍼'에 대해 짚

고 넘어가야 할 것 같다. 저 함수들 중에서 실제로 소리를 내도록 하는

함수는 waveOutWrite 라는 넘이다. 저넘에 대해서 MSDN 을 뒤져보면 다

음처럼 나온다. (제길.. 한글로는 안 나오나..)

 

------------------------------------------------------------------

[ waveOutWrite ]

 

The waveOutWrite function sends a data block to the given waveform

-audio output device.

 

고수님의 바른 번역 : WaveOutWrite함수는 주어진 웨이브폼으로 데이터

                     블록을 전송한다..

 

 

MMRESULT waveOutWrite (

    

    HWAVEOUT hwo,  

    LPWAVEHDR pwh,

    UINT cbwh      

 

);

 

[Parameters]

hwo  - Handle to the waveform-audio output device.

pwh  - Pointer to a WAVEHDR structure containing information about

       the data block.

cbwh - Size, in bytes, of the WAVEHDR structure.

------------------------------------------------------------------

 

다른 것 볼 필요없이 두번째 인자를 보자.  LPWAVEHDR pwh 라고 나오고

그에 대한 설명으로 아래와 같이 골아픈 영어로 되어 있다. ( 머리야 )

 

" Pointer to a WAVEHDR structure containing information about  the

  data block.  "

 

여기서,유령이 대충 해석해 보자면, 이런 뜻이 아닐까 한다..맞는지 잘

모르겠지만..

 

" 데이타 블록에 대한 정보를 저장하고 있는 WAVEHDR구조체의 포인터 "

 

여기서 데이타블록 이라는 넘은 그냥 다른 생각할 것 없이(머리 아프니

까!) '버퍼'와 같은 거라고 생각하자. (틀릴수도 있지만, 이 강좌는 어

디까지나 처음 시작하는 사람들을 위해 쉬운 개념으로 설명해보자는 취

지 이므로 고수분들의 양해를 바랍니다.) 사운드를 재생 시키기 위해서

는 WAVE 파일을 불러와야 하지만, 불러오는 것으로 끝나는 것이 아니라

실제로 원활한 재생을 위해서는 전체 WAVE를 여러개로 쪼개버리는 것이

좋다. '버퍼'라는 것에 대해서는 이미 다들 개념이 잡혀 있을 것이므로

구구절절한 설명은 하지 않겠다. 잘 몰라도 대충..'뭔가를 담아두는..'

이라고만 생각해도 좋다. 그럼 뭘 담느냐? 바로 잘개 쪼개진 WAVE 파일

들의 조각이다. 왜 쪼개지? ( 길가다가 괜히 실실 쪼개면 싸움 납니다)

쪼개는 이유는 여러가지가 있지만, 가장 맘에 드는 말은 역시.. ' 원활

한 재생을 위해서..' 일 것이다. 그럼 쪼개지 않으면 원활 하게 재생이

되지 않는가??유령넘이 직접 저수준 오디오 제어로 여러개로 쪼개지 않

고 파일 통째로 하나의 버퍼에 담아서 재생해 보았는데..  난리도 아니

었다. 몇 메가 ~ 몇십 메가 까지는 그럭저럭 되는데, 이것이 수백 메가

짜리 WAVE 파일을 던져줬더니만, 컴터가 투쟁을 하기 시작했다. 달래도

보고 협박도 해봤지만, 쉽게 해결이 나지 않길래.. 그러지 않기로 했다

물론.. MCI_ 계열의 고수준 제어를 한다면.. 쪼개고 말고, 더블 버퍼링

기법이고 이딴거 필요없다. RIFF 파일 포맷이 어쩌고, WAVE헤더가 어쩌

고.. 이런것도.. MCI 를 이용해서 제작해 봤는데, 그것들은 별 다른 기

법없이도, 몇백 메가짜리 파일들을 잘도 재생해 냈다.(역시..그래서 고  

수준 이라고 하는 것이다..) 그러나, 우리는 애석하게도 저수준을 지향

하므로 머리를 좀 아프게 해 줄 필요가 있다.일단 대충이라도 여러개로

쪼개야 한다! 라는 사실에는 동의?한 것으로 알겠다. 그 쪼갠 덩어리들

을 담아둘 버퍼를 설정하기 위해서는 [ WAVEHDR ] 라는 넘에 대해서 또

알아야 한다. ( 정말 더럽게 알아야 될 것도 많구만.. - 하지만 이것이

프로그래밍 하는 즐거움 아니겠습니까.. -_-a ..그렇다고 해두죠 머..)

 

------------------------------------------------------------------

[ WAVEHDR ]

 

typedef struct {

 

    LPSTR  lpData;                 // 데이타 버퍼에 대한 포인터

    DWORD  dwBufferLength;         // 데이타 버퍼의 길이

    DWORD  dwBytesRecorded;        // 녹음할 때 사용

    DWORD  dwUser;                 // 프로그램에서 사용

    DWORD  dwFlags;                // 플래그

    DWORD  dwLoops;                // 반복 수

    struct wavehdr_tag * lpNext;   // 예약

    DWORD  reserved;               // 예약

 

} WAVEHDR;

 

참고 : 페졸드의 [프로그래밍 윈도우즈]

------------------------------------------------------------------

 

이 WAVEHDR 구조체에 위에 달린 주석내용대로.. 이것저것 집어 넣어 준

다음에 이 넘으로 waveOutWrite 로 다시 집어 넣어 주면 그제서야 소리

를 내기 시작할 것이다. ( 참, 소리 한번 내기 힘들죠?? )제일 처음 인

자가 "데이타 버퍼에 대한 포인터" 이다.버퍼를 만들어서 그 버퍼에 잘

개 쪼갠 WAVE 덩어리들을 집어 넣은 다음 이 WAVEHDR 구조체에 던져 주

면 되는 것이다. 참고로 본인은 버퍼 선언을 다음과 같이 했다. ( 버퍼

를 전부 6개를 사용한다.. )

 

static PBYTE     pBuffer [6] ;

static PWAVEHDR  pWaveHdr[6] ;

 

이렇게 버퍼를 선언한 다음에는 이 버퍼를 위해 메모리를 할당 해야 한

다. 여기서는 C 에서 자주 애용하던 malloc() 를 이용해서 메모리를 잡

아 줬다.

 

------------------------------------------------------------------

for( int i=0; i < BufferNum; i++)  //여기서 BufferNum은 6으로 설정

{

    pBuffer[i]  = (char*) malloc (read_size);

    pWaveHdr[i] = malloc (sizeof (WAVEHDR)) ;

}

------------------------------------------------------------------

 

자, 여기서 궁금한 것이 있는가? (있어야 하는데..) 다음 한 줄을 살펴

보자.

 

    pBuffer[i]  = (char*) malloc (read_size);

 

read_size 라고 되어 있는데, 이것은 한번에 읽어들일 크기를 저장하고

있는 변수이름이다. (저런 형식으로 지원하는 것이 아니라 제가 임의로

설정한 것입니다.) 버퍼의 크기는 한번에 읽어들일 크기(즉, 잘개 쪼갤

크기만큼)로 설정하는 것인데, read_size는 다음과 같은 계산으로 구해

진다.

 

read_size=((waveFormatEx.nSamplesPerSec * waveFormatEx.nChannels *

           (waveFormatEx.wBitsPerSample / 8 ) / 10 ));

 

헉.. 더럽게 복잡하다고? .. 이 역시 하나하나 풀어보면 별 것 아니다.

일단 지금까지의 강좌를 제대로 이해한 사람이라면, 초당용량이 얼마나

되는지 구하는 공식쯤은 알 것이라고 본다.

 

초당 차지하는 용량 : nSamplesPerSec * nChannels * wBitsPerSample

 

인데, 끝에 나누기 8 은 이것을 Byte로 환산하기 위해서이다.(메모리를

Bit 단위가 아니라 Byte 단위로 잡을 것이기 때문이다.)그리고 또 나누

기 10을 했는데, 이것은 본인이 보다 더! 원활한 재생을 위해서 10분의

1초 (1/10 Sec..) 크기로 또 쪼갠 것이다. 즉, 버퍼 하나에는 WAVE파일

중에 사운드 부분을 1/10 초 단위로 쪼개서 저장한다는 것이다. 얼마를

쪼개던지 그것은 쪼개는 사람의 취향 이므로 별 상관 없겠지만, 일단은

10분의 1초로 쪼갰으니, 그대로 하기로 하자. 이제 버퍼도 설정했고,이

안에 쪼개버린 WAVE 덩어리도 담았고 했으니 남은 일은 이것 들을 아까

위에서 살펴본 [ WAVEHDR ] 이라는 넘에게 넘겨주는 것이다.  위에서는

 

------------------------------------------------------------------

typedef struct {

 

    LPSTR  lpData;                 // 데이타 버퍼에 대한 포인터

    DWORD  dwBufferLength;         // 데이타 버퍼의 길이

    DWORD  dwBytesRecorded;        // 녹음할 때 사용

    DWORD  dwUser;                 // 프로그램에서 사용

    DWORD  dwFlags;                // 플래그

    DWORD  dwLoops;                // 반복 수

    struct wavehdr_tag * lpNext;   // 예약

    DWORD  reserved;               // 예약

 

} WAVEHDR;

------------------------------------------------------------------

 

와 같이 여러개의 멤버가 있는데,사실상 재생을 위해서는 다 쓰지도 않

고 이번 프로젝트?에서는 다음과 같이 3개만 쓰기로 한다. ( 간단하게)

 

------------------------------------------------------------------

for( int i=0; i < BufferNum; i++)  //여기서 BufferNum은 6으로 설정

{

    pWaveHdr[i]->lpData          = pBuffer[i] ;

    pWaveHdr[i]->dwBufferLength  = read_size ;

    pWaveHdr[i]->dwFlags         = WHDR_DONE;

}

------------------------------------------------------------------

 

" 데이타 버퍼에 대한 포인터 , 데이타 버퍼의 길이 , 플래그 " 이렇게

3가지만 설정하고 사용한다.플래그에는 여러가지가 있는데, 플래그들에

대한 설명은 MSDN 참고.. 아래와 같다.

 

------------------------------------------------------------------

dwFlags : Flags supplying information about the buffer.

          The following values are defined:

 

[WHDR_BEGINLOOP ]

This buffer is the first buffer in a loop.  This flag is used only

with output buffers.

 

[WHDR_DONE]

Set by the device driver to indicate that it  is finished with the

buffer and is returning it to the application.

 

[WHDR_ENDLOOP]

This buffer is the last buffer in a loop.  This flag is  used only

with output buffers.

 

[WHDR_INQUEUE]

Set by Windows to indicate that the buffer is queued for playback.

 

[WHDR_PREPARED]

Set by Windows to indicate that the buffer has  been prepared with

the waveInPrepareHeader or waveOutPrepareHeader function.

------------------------------------------------------------------

 

영어 해석하려고 했는데,  별로 어렵지 않은 내용이라 여러분들도 능히

알아볼 수 있을거라고 믿고 싶어서 그냥 원문만 기재했다. 이렇게 모든

설정이 끝났으면.. 다 끝난것이 아니다. ㅠㅠ .. 정말 길고도 험란하기

만 하다.. [waveOutPrepareHeader] 라는 넘을 또 알아야 한다. (언제까

지 알아야만 하는가.. - 올 크리스마스까지는 재생되나? 걱정마시라...

조만간 알아야 할 것도 바닥난다. ) waveOutPrepareHeader라는 넘은 사

운드를 재생시키위해서 waveform-audio data block 을 준비해주는 녀석

이다. 역시 MSDN 의 설명..

 

------------------------------------------------------------------

[ waveOutPrepareHeader ]

The "waveOutPrepareHeader" function prepares a waveform-audio data

block for playback.

 

MMRESULT waveOutPrepareHeader (

 

    HWAVEOUT hwo,  

    LPWAVEHDR pwh,

    UINT cbwh      

 

);

 

[Parameters]

 

hwo :Handle to the waveform-audio output device.

pwh :Pointer to a WAVEHDR structure that identifies the data block

     to be prepared.

cbwh:Size, in bytes, of the WAVEHDR structure.

------------------------------------------------------------------

 

static HWAVEOUT hWaveOut ;

 

waveOutPrepareHeader ( hWaveOut, pWaveHdr[i], sizeof (WAVEHDR) ) ;

 

다음과 같이 설정하고 사용한다. 첫번째 인자로 넘겨주는 " hWaveOut "

은 HWAVEOUT형식(웨이브폼 오디오 출력의 핸들)의 변수로의 포인터이다

 

자.. 이제 길고긴 험난한 여정을 끝으로 waveOutWrite라는 넘을 불러다

가 실제로 소리를 내기 전에.. ( 아직도 알야야 할 것이 많다. )  더블

버퍼링 기법에 대해서 알고 넘어가자. 저렇게 버퍼에 쪼개진 WAVE 조각

을 담아두고 재생시키면 버퍼 재생이 다 끝난후 다시 다음 버퍼를 읽어

들이는 과정에서 음이 끊어지는 현상이 발생 할 수 있다.그렇기 때문에

버퍼를 여러 개 두고 (예를 들어 버퍼가 2개 있다고 가정 할 때)1번 버

퍼의 재생이 끝나면 바로 2번 버퍼의 재생이 들어가고(이때 1번 버퍼에

는 3번째 조각이 들어가게 된다.) 이런식으로 반복 재생을 하는 기법이

소위 "더블 버퍼링" 이라는 기법이다. 간단하게 그림으로 설명하자면..

 

------------------------------------------------------------------

버퍼의 번호 :   1   2   1   2   1   2   1   2   1   2   ...

              +---+---+---+---+---+---+---+---+---+---+

파일의 내용 : | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | . | . | ...

              +---+---+---+---+---+---+---+---+---+---+ ...

------------------------------------------------------------------

 

... 원래는 전국미술대회에도 나가서 상도 타본 넘인데, 이렇게 코드로

만 그리려니까 잘 안되는군.. ㅜ_ㅠ..  어쨋든, 위에 번호는 버퍼순 이

다 버퍼의 재생 진행 순서.. 1 -> 2 -> 1 -> 2 -> 1 -> 2 ...그 아래는

버퍼에 들어갈 쪼개진 덩어리 순서이다. 대충 이해가 갔으리라 믿는다.

아래는 버퍼 설정과 WAVEHDR 설정 , 더블버퍼링 기법이 모두 구현된 함

수의 소스이다.

 

------------------------------------------------------------------

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                              ||

//-- int MM_WOM_OPEN_FUNCTION (HWAVEOUT hWaveOut, int BufferNum)||

//--------------------------------------------------------------||

//                                                              ||

//-- hWaveOut 과 BufferNum을 넘겨 받는다.                       ||

//   - hWaveOut : hWaveOut 핸들                                 ||

//   - BufferNum : 버퍼의 갯수                                  ||

//                                                              ||

//-- waveOutWrite : 버퍼 재생이 끝나면  MM_WOM_DONE: 메세지를   ||

//                  발생시킨다.                                 ||

//                                                              ||

//                                            - 트론의 유령 -   ||

//                                                              ||

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

 

int MM_WOM_OPEN_FUNCTION( HWAVEOUT hWaveOut, int BufferNum )

{

    for ( int i;=0; i < BufferNum; i++ )

    {

 

        pBuffer[i]  = (char*) malloc (read_size);

        pWaveHdr[i] = malloc (sizeof (WAVEHDR)) ;

        pWaveHdr[i]->lpData          = pBuffer[i] ;

        pWaveHdr[i]->dwBufferLength  = read_size ;

        pWaveHdr[i]->dwFlags         = WHDR_DONE;

 

        mmioRead(hMMIO, (char*) pBuffer[i], read_size);

 

        waveOutPrepareHeader ( hWaveOut, pWaveHdr[i],

                               sizeof (WAVEHDR) ) ;

    }

 

    for( i=0; i < BufferNum; i++)

        waveOutWrite (hWaveOut, pWaveHdr[i], sizeof (WAVEHDR)) ;

 

    return TRUE;

}

------------------------------------------------------------------

 

이제 이것으로 끝났으면 좋겠지만, 아직이다.. --; 정말 멀다.. 그러나

누누히 얘기 했지만, 이런 고생끝에 소리가 나면 진짜 소리가 되는것이

다. ( 내 손으로 울리게 하는 소리.. 정말 멋지지 않은가.. )조금만 더

참고 다음회를 기다리자.. (이제 이번 강좌는 여기서 끝이라는 소리다)

 

------------------------------------------------------------------

 

 

 

------------------------------------------------------------------

 

 날씨가 많이 더워졌습니다. 안그래도 짜증나는데,  이 강좌를 보니 더

짜증난다고 투덜대는 분도 많이 계실거라고 봅니다. 이 강좌가 별로 도

움이 안되는 분도 많으실테지만, 제가 바라는 것은 단 한분만이라도 이

런 미디어 재생에 관심이 있어서 자료를 찾던 중 제 강좌가 조금이라도

도움이 되어서 멋진 플레이어를 완성했으면 하는 바램입니다. 다음회에

서는 본격적으로 WINDOWS 프로그래밍쪽에 대해서 알아보겠습니다. 지금

까지는 사실상 원론?적인 내용들이었던 것에 반해 다음회의 강좌부터는

WndProc() 내부에서 펼쳐지는 온갖 잡다한 코드들에 대해서 알아보겠습

니다. 단순히 waveOutWrite( )만 불러온다고 소리가 나는 것이 아니라,

그넘과 관련된 여러메세지를 다루어야 하고 처리해 줘야 합니다.그런것

들에 대해서 알아본다는 것입니다. 강좌가 끝날때쯤에 엉성하기는 하지

만 그래도 소리는 제대로 나는 플레이어의 풀소스를 함께 공개하겠습니

다.그때까지는 혼자서 구현도 해보고 여기저기 정보도 찾아보면서 공부

하시면 정말 큰 도움이 될 것입니다.미숙한 저의 강좌를 그래도 끝까지

봐주신 여러분. 정말 감사드립니다. ( 다음 네번째 강좌는 언제 올릴까

나..)

 

------------------------------------------------------------------

 

코멘트쓰기
  좋음   놀람   궁금   화남   슬픔   최고   침묵   시무룩   부끄럼   난감
* 코멘트는 500자 이내(띄어쓰기 포함)로 적어주세요.
목록 보기   지금 보고 계시는 글을 회원님의 my Mblog >> 스크랩에 넣어두고 다음에 바로 보실 수 있습니다.  
회사소개  |   개인정보취급방침  |  제휴문의  |   광고문의  |   E-Mail 무단수집거부  |   고객지원  |   이용안내  |   세금계산서
사업자등록번호 안내: 220-81-90008 / 통신판매업신고번호 제 2017-서울구로-0055호 / 대표: 홍영준, 서민호
08390, 서울시 구로구 디지털로32길 30, 1211호 / TEL. 02_6719_6200 / FAX. 02-6499-1910
Copyright ⓒ (주) 데브피아. All rights reserved.