정보

오디오 프로그래밍에 대해서 – 4

오디오 플레이어, 오디오 디코더 구현해보면서 공부한 것 정리.

1. 오디오 데이터의 형태

오디오 데이터는 PCM 또는 IEEE 754 부동소수점 등의 방식으로 저장할 수 있다. 이 데이터를 샘플이라고 부르며, 아날로그 오디오를 디지털화 하는 것을 샘플링이라고 한다.

샘플의 데이터 크기를 결정짓는 것은 채널 갯수(Channels), 샘플당 비트 수(Bits per Sample), 샘플링 주기(Sample Rate 또는 Samples per Second)의 세 가지인데, 한 채널에는 1초 길이당 샘플링 주기 크기만큼의 데이터가 만들어진다. 이 샘플 데이터의 총 바이트 길이는 샘플링 주기 * ( 샘플당 비트 수 / 2 )이고, 1초 길이의 총 샘플의 바이트 크기는 결국 채널 갯수 * (샘플당 비트 수 / 8 ) * 샘플링 주기가 된다.

각 샘플은 채널에 따라 따로 존재하는 것이 아니라 번갈아 가면서 있는데, 이 때문에 한 샘플 블럭 정렬(Block Alignment)의 크기는 ( 샘플당 비트 수 / 8 ) * 채널 갯수가 된다(단, MP3 파일은 채널에 따라 데이터를 따로 보관하고 있어 디코딩할 때 합쳐야 한다).

PCM 샘플 데이터를 IEEE 754 부동소수점 샘플 데이터로 변환하는 방법은 간단한데, 각각의 샘플을 2^샘플당 비트 수-1한 크기로 나눈 부동소수점 숫자가 샘플이 된다. 반대로 IEEE 754 부동소수점 샘플 데이터를 PCM 샘플 데이터로 변환하려면 각각의 샘플을 2^샘플당 비트 수-1한 크기로 곱하면 된다.

샘플당 비트 수는 8, 16, 24, 32가 허용된다. 일반적으로 MP3, Ogg Vorbis, AAC 등의 음원은 16비트를 사용하지만 WAV, FLAC 등의 음원은 다른 비트 수도 사용할 수 있다.

어떤 음원이 2채널, 16비트, 44100Hz 샘플링 주기를 가지고, 총 3분 20초 길이라면 이 음원의 블록 크기는 2 * (16 / 8 ) = 4바이트, 초당 바이트 길이(Bytes per Second 또는 Byte Rate)는 2 * (16 / 8 ) * 44100 = 176400바이트,  PCM 총 길이는 176400 * 200 = 35280000바이트 = 약 33.64MB가 된다.

1-1. WAVEFORMATEX

위에서 말한 채널 갯수, 샘플당 비트 수, 샘플링 주기, 블럭 정렬, 초당 바이트 길이, 그리고 각 샘플의 형태를 담는 구조체가 Windows API에서 기본 제공되는데, 이 구조체의 이름은 WAVEFORMATEX이다.

typedef struct {
WORD  wFormatTag;
WORD  nChannels;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD  nBlockAlign;
WORD  wBitsPerSample;
WORD  cbSize;
} WAVEFORMATEX;

wFormatTag에는 이 오디오 데이터가 PCM 데이터냐 IEEE 부동소수점이냐를 담고 있는데, 이 구조체가 확장 구조체인가를 담기도 하며, 이 경우 PCM 또는 IEE 부동소수점 데이터인지에 대한 정보는 해당당 확장 구조체를 뜯어야 알 수 있다.

그 외에 nChannels = 채널 갯수, nSamplesPerSec = 샘플링 주기, nAvgBytesPerSec = 초당 바이트 길이, nBlockAlign = 블럭 정렬, wBitsPerSample = 샘플당 비트 수, cbSize = 구조체 총 크기를 뜻한다.

이 구조체가 확장 구조체인 경우 WAVEFORMATEXTENSIBLE로 변환하면 뜯어볼 수 있다. 단, WAVEFORMATEX 구조체 변수가 포인터 변수였을 때에 한정하여 변환할 수 있다.

typedef struct WAVEFORMATEXTENSIBLE {
WAVEFORMATEX Format;
union {
WORD wValidBitsPerSample;
WORD wSamplesPerBlock;
WORD wReserved;
} Samples;
DWORD        dwChannelMask;
GUID         SubFormat;
}  *PWAVEFORMATEXTENSIBLE;

이 구조체 정보에는 위의 WAVEFORMATEX 구조체와 함께 채널 마스크와 서브 포맷을 담고 있는데, 채널 마스크는 멀티채널 오디오일 때 각 채널의 출력 스피커 번호를 명시할 수 있는 플래그를 담는다. 서브 포맷은 이 오디오가 PCM이냐 IEEE 부동소수점이냐를 담고 있다.

 

2. 오디오 코덱과 컨테이너

샘플링 주기가 크면 클 수록, 그리고 샘플당 비트 수가 크면 클 수록 정밀도가 높은 오디오 음원을 보관할 수 있기 때문에 일반적으로 16비트에 44100Hz 주기 이상을 사용하고 있는데, 이 경우 3~5분 내외의 음원을 보관할 때 매우 큰 파일이 생성된다.

이러한 큰 파일을 좀 더 작게 보관하기 위해 인코더를 이용해 압축하는데, 이 때 데이터 처리 방식에 따라 비손실 압축 코덱과 손실 압축 방식으로 나뉘어진다.

비손실 압축은 원본 데이터를 모두 살려서 보관하는 대신 압축률이 작다. 경우에 따라 음원을 압축하지 않은 WAV 파일과 비교했을 때 음원에 따라 무의미한 크기로 압축되기도 한다.

손실 압축은 시간 도메인의 데이터(PCM 또는 IEEE 754 부동소수점 샘플 데이터)를 푸리에 변환(DFT)이나 이산 코사인 변환(DCT) 등을 통해 주파수 도메인 데이터로 변환 후 필요 없는 주파수 대역의 데이터를 양자화를 이용해 모두 삭제하여 남은 데이터를 비손실 압축 방식으로 압축한다. 보통 이 주파수 대역을 결정하는 데에는 비트레이트를 이용하는데, 비트레이트가 작으면 작을 수록 손실되는 주파수 대역도 많아진다.

압축이 됐든 되지 않았든 이러한 오디오 데이터를 그냥 나열한다고 파일이 만들어지는 것은 아니고, 컨테이너 포맷을 이용해 재구성을 해야 하는데, 일반적으로는 AVI 또는 WAV에 사용되는 RIFF 컨테이너, MP4 또는 3GP 등에 사용되는 ISOBMFF, MKA 또는 WEBM 등에 사용되는 Matroska, Vorbis 또는 Opus 등에 사용되는 Ogg 등이 주로 사용된다.

MP3는 컨테이너가 따로 사용되지 않는다. 각 블록을 그냥 시간 순서대로 나열해놓은 원시적인 형태이지만 이 덕분에 ID3 태그 등의 비표준 블록을 추가하더라도 큰 문제가 없다. 또한 각 블럭의 초기 4바이트가 해당 블럭의 오디오 정보를 모두 담고 있기 때문에 따로 컨테이너가 필요 없기도 하다. 물론 MP3 데이터를 MP4 ISOBMFF 등의 다른 컨테이너에 보관하는 것도 가능하다.

 

3. 오디오 API

오디오 데이터를 스피커나 헤드폰 등으로 출력하려면 오디오 API를 사용해야 한다. 보통 이런 API들은 HAL(Hardware Abstraction Layer) 구조를 이용해 HAL의 하위 레이어인 드라이버만 잘 구현하면 이용할 수 있도록 되어 있다.

오디오 API의 종류로는 Windows Audio Session API(WASAPI), DirectSound 8, XAudio 2, OpenAL 등이 존재한다. 모두 저수준 API이며, 때문에 디코딩을 알아서 해주진 않기 때문에 압축된 데이터는 디코더를 이용해 PCM 또는 IEEE 754 부동소수점 데이터로 변환해주어야 한다.

물론 압축을 해제하면 MB 단위의 큰 데이터가 나오기 때문에 메모리를 아끼기 위해 조금씩 쪼개서 출력하기도 하는데 이런 방식을 스트리밍이라고 한다. 메모리 관리 문제도 있지만 사실 디코딩 과정이 오래 걸리는 문제도 있어서 초기 재생 딜레이를 줄이기 위한 방법이기도 하다.

이런 복잡한 구조를 쉽게 사용할 수 있도록 만들어진 고수준 API도 존재하는데, 일반적으로는 FMOD, BASS, NAudio, CSCore 등이 사용된다. 이런 API들은 파일명 입력해주고 재생 함수만 딱 호출해주면 알아서 재생해준다. 다만 API에 따라 다른 파일 포맷은 지원을 못하는 경우도 있음.

광고

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중