웨이브(.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
wav 파일 헤더구조  (23) 2012.09.14
  • 감사합니다 2012.11.18 01:41

    wav파일이 깨졌는데 이 글 참고해서 헤더를 씌웠더니 복구됐습니다. 감사합니다 ㅎ

  • unD3R 2012.11.23 15:46 신고

    도움이 되셨다니, 기쁘네요.
    감사합니다 :)

  • nowfeel 2013.06.21 21:55

    임베디드 시스템에서 wav 플레이어를 제작하는데 본 글의 wav 파일 헤더에 대한 설명이 코드 작성, 이해에 큰 도움이 됐습니다. 감사합니다 !!

  • 해인 2014.06.24 10:33

    도움이 엄청 됐어요 감사합니다.

  • 질문 2015.02.02 09:11

    wav 파일을 열어보려면 무슨 프로그램을 써야하나요?

    • unD3R 2015.02.10 11:22 신고

      HEX 를 볼수 있는 에디터면 무엇이든 상관 없습니다.
      제 경우에는 HxD 를 사용했습니다.
      http://mh-nexus.de/en/hxd/

  • linlin 2015.02.21 22:33

    와 신기하네요 잘 읽었어요~~

  • ㅠㅠ 2015.02.22 23:31

    그런데 코드ㅛ
    실행하니 에러가 납니다 fwrite에서요ㅜㅜ 원래이런건지 제가 잘못한건지..

  • 제임스 2015.05.18 12:24

    (오래된 글이라 보실지 모르겠지만…)

    설명을 아주 쉽게 하셔서 이해하는데 도움이 많이 되었습니다.
    감사하고요.

    사소하게 이상한 부분을 알려드린다면
    (short)30000*(sin(2 * 3.141592 * i * SAMPLE_RATE) / SAMPLE_RATE);
    이 부분이 동작을 안하는데요.
    이렇게 바꾸시면 되는 것 같습니다.
    double freq = 1000;
    (short)30000*(sin(2 * 3.141592 * i * freq / SAMPLE_RATE));

    아울러 text 부분에 대한 encoding은 big-endian이란 표현은 없는 것 같습니다.
    short, int의 encoding이 machine에 따라 달라짐이고요.
    text encoding은 모두 똑같거든요.

    훌륭한 포스팅 다시 한번 감사드리고요.
    고맙습니다

    • unD3R 2015.05.18 15:57 신고

      안녕하세요?

      코드 수정 감사드립니다 ^^
      Endian 은 제목을 copy & paste 하고 수정하다보니, 실수해 버렸네요

      다시 한번 코멘트 감사드립니다. :)

  • Park 2015.06.12 20:53

    안녕하세요. 강좌를 참고해서 프로그램을 작성하던 중에 질문이 있습니다.

    현재 UDP 소켓 통신을 통해 WAV file의 Sound data를 전송하려고 하는데요,

    WAV 파일을 읽어서 데이터를 저장하는 버퍼가 192Byte일 때,
    header.Data.ChunkSize가 실제 sound data size이므로
    header.Data.ChunkSize를 192Byte로 나눈 수만큼 반복해서 데이터를 읽으면
    전체 Sound data를 모두 읽어오는 것이 맞는지 궁금합니다.

    감사합니다.

    • unD3R 2015.06.15 16:31 신고

      말씀하신데로 header 를 다 읽은 후에 chunk size 만큼 읽으시면 되겠네요. :)

  • 소리 2015.07.17 11:13

    와 감사합니다!!

  • rokc 2015.12.03 19:16

    파일 생성할 때
    f_out = fopen("D:\\test.wav", "w");가 아니라
    f_out = fopen("D:\\test.wav", "wb");로 만들어야 wav파일이 정상적으로 생성돼요!
    계속 생성되는 소리에 화이트노이즈가 자꾸 끼길래 왜그런가 바이너리 일일이 다 뜯어봤더니 중간중간에 1byte짜리 '0x0D'가 끼어들어서 그렇더라구요.

    http://stackoverflow.com/questions/5537066/strange-0x0d-being-added-to-my-binary-file
    자세한 내용은 여기

    binary타입 파일이라고 명시해줘야 잘 되네요 참고하세요~!

    • unD3R 2016.01.04 11:17 신고

      앗....
      네 맞습니다. 바이너리 모드로 열어줘야 합니다. 실수했네요.
      수정하였습니다.

  • Kang 2017.08.20 19:59

    덕분에 많이 배워갑니다 감사합니다

  • tnckddl7 2019.03.13 22:52

    다른 부분은 정말 이해가 잘되게 설명해주셔서 따라왔어요 감사합니다.
    혹시 아래코드에서 short형 y배열은 무었이고 freq는 주파수 같은데 1000으로 주신 이유가 궁금합니다.
    마지막으로 30000 을 곱해주는 이유와 sin(2 * pi * freq / samplerate) 부분이 어떤 공식인 것인지 궁금하네요 감사합니다
    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);
    }

    • tnckddl7 2019.03.13 23:08

      아 혹시 wav 파일에 집어넣을 특정 주파수 값을 임의로 쓴것인가요??

    • unD3R 2019.03.20 19:22 신고

      네 맞습니다. 주파수는 임의로 정한 값이에요. :)

  • 테슬라 2019.07.04 08:59

    안녕하세요!
    좋은 강의 감사합니다.
    실례가 되지 않는 다면 한가지 여쭤봐도 될까요?

    mp4를 wav로 변환하는데

    wonderfox dvd 라는 프로그램에는
    코덱이 PCM_S16LE 라는게 기본으로 지정되어 있는거 같고..

    사운드 포지에서 MP4를 불러 WAV로 저장할려고 보면
    Description에 Audio: 44.100hz, 16Bit, Stereo.PCM 이렇게 적혀 있는데요

    이것도 역시 위의 값으로 변경된다는 이야기 인가요? ㅎㅎ;;; 제가 잘 몰라서요..

    WAV라는게 원본 그대로 무손실로 변경 시켜주는건지 알았는데 좀 햇갈려요.

    참고로

    같은 29.8mb용량의 mp4파일을 "sound forge pro"에서 wav로 변환을 하면 131mb 용량이 되고,

    맨 처음 Wonderfox라는 프로그램을 사용하여 같은 mp4 파일을 wav로 변환 하면 143mb가 됩니다.
    이 말뜻은 둘중 하나가 또 압축을 했다는 뜻 인거 같은데...
    뭐가 진짜 무손실로 변환을 하는건지 정말 모르겠네요...
    좀 설명해 주시면 안될까요?
    비압축 무손실 wav파일로 변경 하려면 어떻게 해야 하나요..? T.T

    • unD3R 2019.07.04 10:55 신고

      안녕하세요?
      파일의 손실/무손실 압축에 대해서 조금 오해가 있으신 것 같습니다.

      개념만 쉽게 설명을 드리자면,
      000000000010000000000000000000
      이런 이미지 데이터가 있습니다.
      여기서 1은 크게 눈에 띄지도 않으며, 중요하지 않습니다. 이미지의 경우에 말이죠.

      이때 손실압축(jpg gif 등)을 하게되면 1을 0으로 바꿔버립니다. 용량을 줄이기 위해서 말이죠.
      그 뒤에 0이 30개 있다는 의미로 [0]x30 과 같이 바꿔버립니다.
      0 이 30개(30byte) 있던것이 [0]x30 이라는 6byte 로 압축되었습니다.

      이것을 다시 비손실 압축포맷으로 바꾼다고 한들,,
      000000000000000000000000000000
      이 되는것이죠.
      기존에 손실압축을 위해 1을 0으로 바꾸었던 것을 알 방법은 없습니다.

      요약하자면, 손실압축을 통해 소실된 데이터는 그 뒤에 어떤 방법을 써도, 되살릴 방법이 없습니다.

      음성파일의 경우 mp4 는 사람의 가청주파수 20hz ~ 20000hz (맞나?)를 제외한 나머지 음성정보를 날려버립니다. 그리고 그 뒤에 비트레이트라든지 나머지 설정에 맞게 압축을 시도합니다.
      이렇게 날아가버린(삭제된) 나머지 주파수 대역의 음성정보는 영원히 사라지게 됩니다.

      이것을 다시 wave 로 압축한다고 해도, 되 살릴수 없습니다.


      즉,, 애초에 한번이라도 손실압축(mp4) 되었다면 원하시는것처럼 고음질로 복원할 방법은 없습니다.

      감사합니다.

  • 테슬라 2019.07.04 11:25

    답변 감사드립니다.
    mp4에 대한 추가 설명 진심으로 감사드립니다. 많이 배우고 있습니다!! 그런 주파수 대역이 있군요..호...

    아 제가 mp4를 wav로 바꾸는 이유가 고음질로 복원을 위해서가 아니구요 ^^ 죄송합니다.
    제가 질문이 좀 햇갈렸네요 T,T

    개인적인 사정으로 사용함에 있어 mp4 음악들을 wav로 변환을 해야 하는데
    mp3같은걸로 변형을 하면 한번더 압축하는 꼴이 되고 변형이 오기 때문에
    용량이 엄청나와도 더이상 손실이 생기지 않는 비압축 wav를 만들어야 하거든요.

    위에 제가 사용해본 사운드포지나 wonderfox나 둘다 mp4를 wav로 변형해도 비압축으로 변형이 된다고 할 때
    왜 파일 용량이 다를까요? T.T

    용량이 더 적게 나온 사운드포지가 자체 압축을 하는걸까요?

    • unD3R 2019.07.17 14:06 신고

      안녕하세요?
      질문을 요약하자면, 동일 mp4 파일에 대해서 wav 로 변환을 하는데, 툴마다 결과물 wav 파일의 용량 차이가 있다는 말씀이신거죠?
      먼저 확인할 것은 두 경우 모두, 채널 수, 샘플 레이트, 비트레이드 등의 값을 동일하게 설정했는가 입니다.
      확인해 봤는데도 모든 설정이 동일하다면, 그 원인을 찾아야 하는데.
      가장 빠르고 편한 방법은 wav header viewer 같은걸 찾아서 열어보시거나, hex editor 로 열어서 파일 데이터를 직접 비교하는 것 입니다. :)

      아마도 설정을 다르게 하신게 아닌가 싶네요.