웨이브(.wav) 파일 분석




오늘은 음성파일인 .wav 에 대해 살펴보려 합니다.

이에 앞서 정확한 의미부터 짚고 넘어가도록 하겠습니다.


WAV 또는 WAVE는 웨이브폼 오디오 포맷(웨이브 오디오 포맷, Waveform audio file format)의 준말로 개인용 컴퓨터에서 오디오를 재생하는 마이크로소프트와 IBM 오디오 파일 포맷 표준이다. 덩어리째로 데이터를 저장하기 위한 RIFF 비트스트림 포맷 방식에서 변화한 것으로, 아미가에 쓰이는 IFFF와 매킨토시 컴퓨터에 쓰이는 AIFF 포맷에 매우 가깝다. 가공되지 않은 오디오를 위한 윈도 시스템에 쓰이는 기본 포맷이다. 윈도 상에서는 WAV가 비압축 형식만 있는 것이 아니고 여러 압축 코덱을 이용할 수 있다. 그러나 윈도 비스타부터는 기본 지원 포맷이 WMA으로 바뀌었다.

WAV는 비압축 오디오 포맷으로, 프로그램 구동음이나 일반 수준의 녹음용으로 사용되지만 전문 녹음용으로는 WAV가 아닌 다른 비압축 포맷이 더 많이 쓰이기도 한다. 참고로 일부 라디오 방송국에서는 MP2이라는 압축 오디오 포맷 대신 비압축 WAV 포맷을 사용하는 경우도 있다.

[출처: wikipedia.org]



요약하자면, WAV 는 Waveform Audio File Format 의 약자입니다. 확장자가 .wav 이기 때문에 흔히들 wav 라고 부르는 것이지요. 내용에 나와 있듯이 wav 는 기본적으로 비압축 포맷입니다. 무슨말인가 하면, 소리나는 그대로 값들을 저장해둔 파일이라고 생각하시면 됩니다.



마치 오르골과 비슷합니다.

오르골은 원통 모양의 몸체에 볼록한 돌기들이 있습니다.

그리고 원통 바로 옆에 소리를 내는 쇳조각이 근접해 있죠.

원통이 돌아가면 돌기들이 쇳조각을 건드리면서 소리가 발생합니다.


어떻게 보면 wav 파일과 흡사합니다.

어떤 압축도 없이 그냥 소리 그대로를 원통에 찍어낸 것이죠.

wav 파일도 이처럼 소리값을 그대로 파일에 저장해 둔 것입니다.


물론 조금 차이가 있는 것은, wave 파일도 header 가 있다는 거겠죠 :)



이제 가장 중요한 wave 파일의 header 에 대해서 살펴보겠습니다.



보시는 것처럼 크게 3개의 파트로 구성되어 있습니다.

가장 위에 존재하는것이 파일의 형식을 나타내는 chunk 입니다.

그 다음이 음성정보를 담고 있는 Chunk 고, 이어서 Low Data 들이 위치합니다.


이해를 돕기 위해서 실제 wave 파일을 열어서 살펴보겠습니다.



하나씩 좀더 상세히 살펴보도록 하겠습니다.





1. Chunk ID (4byte)

 - 이 부분에 'RIFF' 라는 문자가 ASCII 값으로 들어갑니다. wave 파일에 대한 고정값입니다.

 - 주의할 점은 \0(NULL) 로 끝나는 문자열이 아니라는 것입니다. 프로그래밍할 때, 주의해야 합니다.


2. Chunk Size (4byte) Little Endian

 - 이 부분에는 나머지 부분에 대한 Size 가 들어갑니다.

 - 파일 전체 사이즈에서, 위에 'Chunk ID' 와 자기자신인 'Chunk Size' 를 제외한 값이라고 보시면 됩니다.

 - (전체 파일크기 - 8 byte) 라고 생각하시면 됩니다.

 - Little Endian 값이니 파일 사이즈가 0x00000010 인 경우, 메모리 값으로는 10 00 00 00 으로 저장되어 있습니다.


3. Format (4byte)

 - 파일 형식을 나타냅니다.

 - wave 파일인 경우, 'WAVE' 라는 문자가 ASCII 값으로 들어갑니다. wave 파일에 대한 고정값입니다.





1. Chunk ID (4byte)

 - 'fmt ' 라는 고정값이 들어갑니다.

 - 'fmt' 가 아니라 'fmt ' 라는 것에 주의하시기 바랍니다. ftm + (space) 입니다.


2. Chunk Size (4byte) Little Endian

 - 현재 Header 에서 뒤에 이어지는 값들의 사이즈입니다.

 - 즉, 현재 주황색 헤더의 전체 크기가 24 byte 인데, 다음에 이어지는 부분의 크기는 16 이죠.

 - 고정값이라고 생각하시면 됩니다.


3. Audio Format (2byte) Little Endian

 - 고정값으로 1 이 사용된다고 생각하시면 됩니다.

 - 엄밀히 말하면, PCM 인 경우인데, 대부분 wave 파일은 PCM 입니다.

 - Little Endian 이므로 00 01 이 아닌, 01 00 입니다.


4. Number Of Channel (2byte) Little Endian

 - 음성파일의 채널 수 입니다.

 - mono 라면 1 이고, stereo 라면 2가 됩니다.

 - 아래 표를 참고하시기 바랍니다.


 NumChannel 값

의미 

data 구성 

1

mono 

[data][data][data]... 

stereo 

[left][right][left][right]... 

3 channel 

[left][right][center][left][right][center]... 

quad 

[front left][front right][rear left][rear right]... 

5

4 channel 

[left][center][right][surround]... 

6 channel 

[left center][left][center][right center][right][surround]...


5. Sample Rate (4byte) Little Endian

 - Number of Samples Per Second 입니다. Hz 단위로 표시합니다.

 - 쉽게 설명하자면, 1초 동안의 소리를 몇 개의 조각으로 쪼개서 저장(분석)하는가 입니다.

 - 10 Hz 라고 하면, 0.1 초 단위로 정보를 저장한다는 거겠지요. 당연히 숫자값이 커질수록 음질이 좋아집니다.


6. Byte Rate (4byte) Little Endian

 - Average Bytes Per Second 입니다. 1초 동안 소리를 내는데 필요한 byte 수라고 생각하시면 됩니다.

 - 일단 위에 Sample Rate 가 441000 이고, mono 채널이라고 가정해 봅시다.

    그럼 기본적으로 1초 재생에 필요한 byte 수는 아래와 같습니다.

    (Sample 1개가 차지하는 byte) x (1초당 Sample 수) x (채널 수)

    = (Sample 1개가 차지하는 byte) x 441000 x 1

 - 그렇다면 (Sample 1개가 차지하는 byte) 가 무엇일까요? 

   가장 마지막에 나오는 field 인 'Bits Per Sample' 이 바로 그것입니다. 그때 설명하겠습니다.


7. Block Align (2byte) Little Endian

 - Sample Frame 의 크기입니다. Sample 1개의 크기가 아니라, Sample Frame 의 크리란 말입니다.

 - mono 면 'sample 크기 x 1' 이고, stereo 면 'sample 크기 x 2' 가 되겠습니다.


8. Bit Per Sample (2byte) Little Endian

 - Sample 한 개를 몇 bit 로 나타낼 것이냐 입니다.

 - 다른말로 Bit Depth 라고도 말합니다.

 - 쉽게 말해서, '도레미파솔라시도' 는 총 음이 8가지 입니다. 이 경우는 2의 3승(8)이고 비트로 나타내면 3 이 됩니다.

 - 이 값이 8 이라면, 2의 8승인 256 이 되겠네요. 순간의 소리를 256 레벨로 표현한다는 것입니다.

 - 이 값이 16 이라면, 2의 16 승개의 레벨로 순간의 음을 나타내겠다는 것이고요.

 - 당연히 이 값이 클수록 음질이 좋아집니다.





1. Chunk ID (4byte)

 - 이 값은 고정값으로서 'data' 라는 문자가 ASCII 로 들어가 있습니다.


2. Chunk Size (4byte) Little Endian

 - 뒤이어 나올 data 의 size 입니다.

 - 즉, 소리 정보가 들어있는 data 의 실제 size 라고 생각하시면 됩니다.

 - 파일 전체크기에서 header 를 제외한 크기겠지요.





Sample Rate 니, Bit Rate 니 하는게 헷갈리고 잘 이해가 안가실 수 있습니다.

그래서 그림으로 표현해 보도록 하겠습니다.


아래는 다음을 나타낸 그림입니다.


1. Channel 은 2 채널

2. Sample Rate 는 10.

3. Byte Rate 는 20 bytes

4. Block Align 은 2 bytes

5. Bit Per Sample 은 8 bits





하나씩 천천히 보시면, 이해가 가실거라고 생각합니다.


※ 데이터가 들어가는 순서는,

   위 그림처럼 2채널인 경우, 'LEFT CHANNEL 의 1개 샘플{Bit Per Sample}', 'RIGHT CHANNEL 의 1개 샘플{Bit Per Sample}' 이 순차적으로 들어갑니다.

  즉, 위 그림의 경우 '00101101 00011000 10010000 0001011 ...' 이런 순서입니다. 채널의 샘플 크기만큼 순차적으로 반복되어 들어갑니다.



header 를 만들고 임의의 wave 파일을 만드는 코드를 표현하면 다음과 같습니다.

참고로 일정한 고주파음이 들립니다 ㅎ.

(원하는 음을 만드는 부분은 공부해 봐야 겠네요)



    [WaveHeader.h]


#pragma once


#define WAVE_FORMAT_UNKNOWN      0X0000;
#define WAVE_FORMAT_PCM          0X0001;
#define WAVE_FORMAT_MS_ADPCM     0X0002;
#define WAVE_FORMAT_IEEE_FLOAT   0X0003;
#define WAVE_FORMAT_ALAW         0X0006;
#define WAVE_FORMAT_MULAW        0X0007;
#define WAVE_FORMAT_IMA_ADPCM    0X0011;
#define WAVE_FORMAT_YAMAHA_ADPCM 0X0016;
#define WAVE_FORMAT_GSM          0X0031;
#define WAVE_FORMAT_ITU_ADPCM    0X0040;
#define WAVE_FORMAT_MPEG         0X0050;
#define WAVE_FORMAT_EXTENSIBLE   0XFFFE;



typedef struct
{
	unsigned char ChunkID[4];    // Contains the letters "RIFF" in ASCII form
	unsigned int ChunkSize;      // This is the size of the rest of the chunk following this number
	unsigned char Format[4];     // Contains the letters "WAVE" in ASCII form
} RIFF;

//-------------------------------------------
// [Channel]
// - streo     : [left][right]
// - 3 channel : [left][right][center]
// - quad      : [front left][front right][rear left][reat right]
// - 4 channel : [left][center][right][surround]
// - 6 channel : [left center][left][center][right center][right][surround]
//-------------------------------------------
typedef struct
{
	unsigned char  ChunkID[4];    // Contains the letters "fmt " in ASCII form
	unsigned int   ChunkSize;     // 16 for PCM.  This is the size of the rest of the Subchunk which follows this number.
	unsigned short AudioFormat;   // PCM = 1
	unsigned short NumChannels;   // Mono = 1, Stereo = 2, etc.
	unsigned int   SampleRate;    // 8000, 44100, etc.
	unsigned int   AvgByteRate;   // SampleRate * NumChannels * BitsPerSample/8
	unsigned short BlockAlign;    // NumChannels * BitsPerSample/8
	unsigned short BitPerSample;  // 8 bits = 8, 16 bits = 16, etc
} FMT;


typedef struct
{
	char          ChunkID[4];    // Contains the letters "data" in ASCII form
	unsigned int  ChunkSize;     // NumSamples * NumChannels * BitsPerSample/8
} DATA;


typedef struct
{
	RIFF Riff;
	FMT	 Fmt;
	DATA Data;
} WAVE_HEADER;





    [Test.cpp]


#include <iostream>
#include "WaveHeader.h"


#define DURATION 10
#define SAMPLE_RATE 44100
#define CHANNEL 1
#define BIT_RATE 32

int main() {
	FILE * f_out;
    f_out = fopen("D:\\test.wav", "wb");

    WAVE_HEADER header;
    memcpy(header.Riff.ChunkID, "RIFF", 4);
    header.Riff.ChunkSize = DURATION * SAMPLE_RATE * CHANNEL * BIT_RATE / 8 + 36;
    memcpy(header.Riff.Format, "WAVE", 4);

    memcpy(header.Fmt.ChunkID, "fmt ", 4);
    header.Fmt.ChunkSize = 0x10;
    header.Fmt.AudioFormat = WAVE_FORMAT_PCM;
    header.Fmt.NumChannels = CHANNEL;
    header.Fmt.SampleRate = SAMPLE_RATE;
    header.Fmt.AvgByteRate = SAMPLE_RATE * CHANNEL * BIT_RATE / 8;
    header.Fmt.BlockAlign = CHANNEL * BIT_RATE / 8;
    header.Fmt.BitPerSample = BIT_RATE;

    memcpy(header.Data.ChunkID, "data", 4);
    header.Data.ChunkSize = DURATION * SAMPLE_RATE * CHANNEL * BIT_RATE / 8;

    fwrite(&header, sizeof(header), 1, f_out);

    short y[1];
    double freq = 1000;
    for(int i = 0; i < SAMPLE_RATE * DURATION * CHANNEL * BIT_RATE / 8; i++) {
        y[0] = (short)30000*sin(2 * 3.141592 * i * freq / SAMPLE_RATE); // 제임스님 코멘트에 따른 수정
        fwrite(&y[0], sizeof(short), 1, f_out);
    }

    fclose(f_out);

    

	return 0;
}





다음에 기회가 생긴다면, 퓨리에 시리즈 등을 이용하여 원하는 소리를 발생시키는 wave 파일을 만들어 보도록 하겠습니다.





'개발관련 > Specification' 카테고리의 다른 글

PSD File Format (번역)  (0) 2013.01.02