반응형

RIFF (Resource Interchange File Format)의 저장방식

 

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

"WAVE FILE"  (부모청크)

  전체크기 10메가

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

블록사이즈 : 1024 스테레오 (자식청크1)

  샘플링  : 11kHz 비트 16비트

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

1024 바이트 음성 (블록 1) (데이터청크1)

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

블록 사이즈 : 1024 모노 (자식청크2)

  샘플링 : 22kHz 비트 16비트

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

1024바이트 음성 (블록1) (데이터청크2)

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

 

위에 그림에서 한 블록 하나 하나가 모두 청크(Chunk)라 합니다.

처음에 나오는 "WAVE FILE" 이라는 헤더가 있는 블록을 부모청크라 하며, 그 하단부에 청크를 자식청크라 합니다.

위와 같은 데이터 저장 방식을 RIFF 라 하며, WAVE파일이나 AVI 파일이 모두 RIFF 파일입니다.

 

 

 

WAVE 파일의 구조 :

 

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

데이터형     크기          내용                        설명                              

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

Char           4          "RIFF"                      RIFF 파일이라는 지정.           

DWORD      4          "DATASIZE"              현재부터 마지막까지의 파일크기, 즉 파일크기 -4

Char           4          "WAVE"                    웨이브 파일.

Char           4          "fmt"                        자식청크 시작.

DWORD      4           16                           웨이브포맷사이즈.

short          2           1                             PCM WAVE포맷.

short          2           1                             채널수.

DWORD      4           SAMPLING                샘플링의 수.

DWORD      4          AVE SAMPLING       초당 샘플 바이트.

short          2           1                             샘플당 바이트.

short          2          비트                          웨이브 비트.

Char          4           "data"                      데이터 청크 시작.

UINT          4           DATA SIZE                데이터의 크기.

                               데이터                       데이터

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

 

부모청크 1개, 자식청크 1개, 데이터청크 1개로 구성되어 있으며,

부모 청크에는

"RIFF" -데이터 크기(4바이트) -"WAVE"

로 설정되어있습니다.

여기서 "RIFF"란 웨이브 파일이 RIFF 파일이란 의미이며, 현재 4바이트이고, 그 다음 파일 끝까지는 몇 바이트라는것이 데이터 크기에 DWORD형으로 저장되어있습니다. 그 다음에 WAVE 파일이라는 "WAVE"가 기록되있습니다.

 

다음 자식 청크에는

"fmt" -현재 포인터 -WAVEFORMAT -채널(스테레오, 모노) -샘플링 -초당 샘플바이트 -샘플당바이트 -비트(8비트, 16비트등)

이 기록 됩니다.

초당 샘플 바이트란 샘플링X채널X(비트/8)입니다. 즉, 1초당 필요한 바이트를 말합니다.

만약 8Bit 스테레오면 두개의 샘플이 필요하므로 11kHz이면 22Kbytes가 필요하고,

16비트라면 한 개의 데이터를 2바이트로 설정하므로 44Kbytes가 필요하게 됩니다.

 

다음 데이터 청크에는

"data" -데이터 크기 -데이터

가 기록되어있습니다.

WAVEFORMAT이란 웨이브 형태를 의미합니다. PCM_WAVE 형태의 값은 1입니다.

현재 포인터란 현재 위치를 말하는데, 청크의 cksize라고도 하며, 위의 구조대로 계산하면 다음과 같습니다

부모 청크 = 4+4+4 = 12

"현재포인터" 이전까지 자식 청크 = 4

위의 값을 모두 더하면 12+4 =16 이므로 16 값을 넣습니다.

사실은 wave 파일 구조에서는 위 값에 변함이 없습니다.

변함이 없는 값은 WAVEFORMAT과 현재 포인터 두개입니다.

WAVEFORMAT에는 1을, 현재 포인터에는 16을 넣어주면 됩니다.

 

 

RIFF 이용 함수 :

RIFF 파일을 읽기 위해서 사용되는 파일 입출력 함수는 mmio_ 계열 함수입니다.

 

mmioOpen() : RIFF 파일을 연다.

mmioClose() : 파일을 닫는다.

mmioSeek() : 파일의 특정 위치를 찾는다.

mmioRead() : 파일에서 데이터를 읽는다.

mmioWrite() : 데이터를 쓴다.

mmioDescent() : 현재 위치에서 하위 청크로 내려간다.

mmioAscent() : 현재 위치에서 상위 청크로 올라간다.

mmioCreateChunk() : 새로운 청크를 만든다.

mmioFOURCC() : 청크 이름을 등록한다.

mmioStringToFOURCC() : 문자열을 청크 이름으로 등록시킨다.

 

 

 

 

웨이브 파일 읽기 :

웨이브 파일을 읽고 쓰는 방법에는 mmio 방법과 그냥 일반적인 방법 두가지가 있습니다.

먼저 mmio를 이용하여 파일을 읽고 표준 함수를 이용하여 파일에 저장하는 방법입니다.

이때 mmioOpen()함수를 사용합니다.

 

HMMIO hmmio = mmioOpen((LPSTR)"test.wav", NULL, MMIO_READ);

 

표준 입출력 파일형은 HFILE이지만 mmio 함수에서 사용하는 파일 핸들러형은 HMMIO입니다.

mmioOpen함수로 열기에 성공하면 hmmio에 핸들을 넘겨주고, 아닐때는 NULL을 리턴합니다.

NULL은 바로 MMCKINFO형인 청크 정보 구조체를 말합니다.

파일을 열때는 청크 정보 구조체가 없으므로 NULL을 설정합니다.

MMIO_READ란 읽기 위해 파일을 개방한다는 의미입니다.

읽고 쓰기 위해서는 MMIO_READWRITE를 사용하면 됩니다.

파일을 열면 먼저, 부모 청크를 찾아줍니다.

 

MMCKINFO MMCkInfoParent; //MMCKINF인 청크 구조체로 부모 청크형 선언.

MMCkInfoParent.fccType = mmioFOURCC ('W', 'A', 'V', 'E'); //청크형에 "WAVE"를 기록.

mmioDescend ( hmmio, &MMCkInfoParent, NULL, MMIO_FINDRIFF ); //부모 청크를 찾아라.

 

mmioDescend는 성공하면 0을, 실패하면 error code를 리턴 합니다.

부모 청크를 찾은 후에는 자식 청크를 찾아야 합니다.

자식 청크의 처음은 "fmt"이니 이것을 찾으면 됩니다.

 

MMCKINFO MMCkInfoChild; //MMCKINF인 청크 구조체로 자식 청크형 선언.

MMCkInfoChild.ckid = mmioFOURCC ('f', 'm', 't', ' '); //자식 청크를 찾기 위해 fmt를 ckid에 저장.

    //(반드시 4번째 빈공간 확보해야합니다.)

mmioDescend ( hmmio, &MMCkInfoChild, &MMCkInfoParent, MMIO_FINDCHUNK ); //자식 청크를 찾아라.


"fmt" 다음부터는 웨이브 파일의 구조가 들어있습니다. 다음은 정보를 읽어 오는 방법입니다.


PCMWAVEFORMAT WaveRecord;

mmioRead ( hmmio, (LPSTR)&WaveRecord, MMCkInfoChild.cksize ); //WaveRecord에 현재 정보 저장.



//참고 자료

PCMWAVEFORMAT 구조체 :

typedef struct pcmwaveformat_tag {
    WAVEFORMAT  wf;  //웨이브 레코드 포맷
    WORD        wBitsPerSample;  //샘플당 비트 ( 8비트, 16비트, 32비트 등 )
} PCMWAVEFORMAT;


//WAVEFORMAT 구조체 :

typedef struct {
    WORD  wFormatTag; //PCMWAVEFORMAT 값 = 1
    WORD  nChannels; //채널
    DWORD nSamplesPerSec; //샘플링 값
    DWORD nAvgBytesPerSec; //초당 샘플링 바이트
    WORD  nBlockAlign; //1개의 샘플링 바이트
} WAVEFORMAT;



만약, 22kHz 샘플링에 8비트 사운드 데이터이고 스테레오라면 위의 WaveRecord는 다음처럼 바뀝니다.

WaveRecord.wf.wFormatTag = 1;

WaveRecord.wf.nChannels = 2; //스테레오

WaveRecord.wf.nSamplesPerSec = 22050; //22kHz

WaveRecord.wf.nAvgBytesPerSec = 22050* nChannels*nBlockAlign = 44100

WaveRecord.wf.nBlockAlign = 1;

WaveRecord.wBitsPerSample = 8; //8비트


그러므로 "fmt" 청크를 찾아서 웨이브 데이터 포맷을 알고 난후에는 다시 부모 청크로 이동합니다.


mmioAscend ( hmmio, &MMCkInfoChild, 0 ); //부모 청크로 상위 이동

MMCkInfoChild.ckid = mmioFOURCC ('d', 'a', 't', 'a');

mmioDescend ( hmmio, &MMCkInfoChild, &MMCkInfoParent, MMIO_FINDCHUNK); //데이터 청크를 찾아라


위의 함수를 실행하면 MMCkInfoChild.cksize 안에 데이터의 크기가 설정 되어 있으므로, 데이터 크기만큼 데이터를 읽으면됨.


DWORD IDatasize; //데이터 크기 저장할 변수 선언

IDatasize = MMCkInfoChild.cksize; //데이터 크기를 얻는다

LPSTR pWave;

LPSTR WaveDataBlock;

WaveDataBlock = ::GlobalAlloc ( GMEM_MOVEABLE ); //datasize 만큼 메모리 설정

pWave = (LPBYTE)::GlobalLock( WaveDataBlock ); //메모리를 pWave에 연결

mmioRead ( hmmio, (LPSTR)pWave, IDatasize );


여기까지가 데이테를 pWave에 설정하고, WaveRecord안에 웨이브 포맷을 적재하는 방법입니다.



웨이브 데이터 출력 :


디바이스를 열어 웨이브 데이터를 출력합니다.


HWAVEIN hWaveInn; //녹음 하기 위한 디바이스

WaveInOpen ( (LPHWAVEIN)&hWaveInn, WAVE_MAPPER, (tWAVEFORMATEX*)&WaveRecord, 0L, 0L, 0L );


위에 WaveRecord에 저장한 포맷 형태로 디바이스를 열고, 출력을 위해 헤더를 보냅니다.

이때 웨이브 헤더는 미리 준비 해야 합니다.


WAVEHDR WaveHeader; //웨이브 데이터 헤더

WaveHeader.lpData = (LPSTR)pWave; //음성 데이터 설정

WaveHeader.dwBufferLength = IDatasize; //데이터 크기 설정

WaveHeader.dwFlags = 0L; //현재는 데이터의 처음이므로 0

WaveHeader.dwLoops = 0L; //루프 플래그

WaveHeader.dwBytesRecorded = IDatasize; //데이터 크기 설정


waveOutPrepareHeader ( hWaveOut, &WaveHeader, sizeof(WaveHeader) ); //헤더를 준비

int m_nPassTime; //시간 변수


다음은 데이터에서 10초 후의 데이터를 출력시키는 방법


m_nPassTime = 10; //현재 출력할 위치를 10초후의 데이터로 설정


//포인터를 pWave에서 10초후의 포인터로 이동

WaveHeader.lpData = (LPSTR)pWave+((m_nPassTime)*WaveRecord.wf.nAvgBytesPerSec);


//데이터 크기 변환

WaveHeader.dwBurfferLength = IDatasize -((m_nPassTime)*WaveRecord.wf.nAvgBytesPerSec);

WaveHeader.dwFlags = 0L;

WaveHeader.dwLoops = 0L;


위처럼 하는 이유는 현재 데이터에서 앞으로 전진 혹은 후진할때 사용하기 위해서입니다.

만약 FORWIND 버튼으로 10초 앞으로 움직이게 하려면


m_nPassTime = m_nPass + 10;


처럼 설정하고 waveOutPrePareHeader();를 실행시키고,

10초 뒤로 이동 시키려면


m_nPassTime = m_nPass - 10;


으로 한뒤 waveOutPrePareHeader(); 함수를 실행 시키면 됩니다.

그리고, 출력 시키는 함수는 다음과 같습니다.


waveOutWrite( hWaveOut, &WaveHeader, sizeof(WaveHeader) );



웨이브 데이터 멈춤 :


waveOutPause( hWaveOut );

waveOutReset( hWaveOut ); //웨이브 디바이스를 다시 세팅

waveOutUnprepareHeader ( hWaveOut, &WaveHeader, sizeof(WaveHeader) ); //헤더를UnPrepare시킴

waveOutClose ( hWaveOut ); //디바이스를 닫음




-출처 : 도서 : VC++ Bible 6.x-

반응형
Posted by Real_G