2010년 3월 29일 월요일

MPEG1 Audio Layer3 (mp3 파일구조) AAU

MPEG1 Audio Layer3

mp3 파일은 여러개의 AAU(Audio Access Uint)들과 1개의 AudioTag로 이루어져 있다.

AAU AAU AAU ... Audio Tag

 

각각의 AAU들은 다음과 같은 구조를 가진다.

Header CRC SideInfo Main Data

 

AAU의 사이즈는 다음과 같다.

144*BitRate/샘플링주파수+Padding 

mp3는 프레임당 1152 비트가 할당되기 때문에 바이트 계산하면 144바이트가 된다. BitRate와 샘플링주파수는 헤더정보에서 얻을 수 있다. 파일 Size가 3985814 바이트이고 샘플링 주파수가 44.1khz, BitRate가 128kbit인 mp3파일의 프레임 Size는 144*128000 / 441000 로 약 417바이트가 되며 프레임당 약 9558바이트의 Size가된다.

 

1. Header

mp3 파일에는 각각의 AAU마다 헤더가 따로 존재한다. 그러므로 각 AAU들은 독립적으로 decode될 수 있다. 그러나 대부분의 mp3에서 모든 헤더들은 동일하므로, 전체를 디코딩할 시에는 첫 번째 AAU의 헤더만을 읽는다.

Header는 32비트의 길이를 갖고 13개의 항목으로 구성되어 있다.

 

길이설명
11 동기패턴모두 1로 설정
2 MPEG Audio Version ID00 - MPEG Version 2.5
01 - reserved
10 - MPEG Version 2
11 - MPEG Version 1(mp3의 경우)
2 Layer 정의00 - reserved
01 - Layer III
10 - Layer II
11 - Layer I
1 Checksum 설정유무0 - Error 체크 있음
1 - Error 체크 없음
4 BitRate
MPEG1
Layer1
MPEG1
Layer2
MPEG1
Layer3
MPEG2
Layer1
MPEG2
Layer2,3
0000 free free free free free
0001 32 32 32 32 8
0010 64 48 40 48 16
0011 96 56 48 56 24
0100 128 64 56 64 32
0101 160 80 64 80 40
0110 192 96 80 96 48
0111 224 112 96 112 56
1000 256 128 112 128 64
1001 288 160 128 144 80
1010 320 192 160 160 96
1011 352 224 192 176 112
1100 384 256 224 192 128
1101 416 320 256 224 144
1110 448 384 320 256 160
1111 bad bad bad bad bad
2 샘플링 주파수
MPEG1 MPEG2 MPEG2.5
00 44100 22050 11025
01 48000 24000 12000
10 32000 16000 8000
11 reserved reserved reserved
1 Padding bit0 - frame is not padded
1 - frame is padded with one extra bit
1 개별용도비트특별히 사용되지 않음
2 Channel Mode 00 - Stereo
01 - Joint Stereo
10 - Dual Channel
11 - Single Channel
2 확장 Mode(Joint Stereo에서만 사용)
Layer 1,2Layer 3
Intensity
stereo
MS
stereo
00 subbands 4 to 31 off off
01 subbnads 8 to 31 on off
10 subbnads 12 to 31 off on
11 subbnads 16 to 31 on on
1 Copyright0 - Audio is not copyrighted
1 - Audio is copyrighted
1 Original0 - 복사본
1 - 원본
2 Emphasis  

2. CRC

CRC Check는 프레임의 오류를 검사하기 위한 정보를 가진다.

 

3. SideInfo

mp3에서 주요하게 사용되는 압축 알고리즘에는 Scalefactor, Huffman 부호화, 양자화 등이 있다. 실제 각 프레임의 메인데이터를 decoding 하는 데 필요한 정보들을 담고 있다.

 

길이사이드인포 요소 설명
9 main_data_end mp3 Player 프로그램의 버퍼상에서 비트스트림의 위치
3 Private_bits  
1 Scfsi Scalefactor 디코딩에 사용
12 Part2_3_length 허프만 디코딩에 사용
9 Big_values 허프만 디코딩에 사용
8 Global_gain 역양자화에 사용
4 Scalefac_compress Scalefactor Encoding시에 사용
1 Blocksplit_flag  
2 Block_type  
1 Switch_point  
5 Table_select 허프만 디코딩에 사용
3 Subblock_gain 역양자화에 사용
4 Region_address1 허프만 디코딩에 사용
3 Region_address2 허프만 디코딩에 사용
1 Preflag 역양자화에 사용
1 Scalefac_scale Scalefactor 디코딩에 사용, 역양자화에 사용
1 Count1table_select 허프만 코딩에 사용

 

다음에 이 정보를 저장하기 위한 자료구조가 있다.

typedef struct { 
unsigned long part2_3_length;
unsigned long big_values;
unsigned long global_gain;
unsigned long scalefac_compress;
unsigned long window_switching_flag;
unsigned long block_type;
unsigned long mixed_block_flag;
unsigned long table_select[3];
unsigned long subblock_gain[3];
unsigned long region0_count;
unsigned long region1_count;
unsigned long preflag;
unsigned long scalefac_scale;
unsigned long count1table_select;
}gr_info_s;


typedef struct {
unsigned long main_data_begin;
unsigned long private_bits; 
struct {
unsigned long scfsi[4]; 
gr_info_s gr[2]; // 메인데이터는 2개의 granule로 구성
}ch[2]; 
}III_side_info_t;

사이드인포의 크기는 채널에 따라 그 크기가 달라지는데 Mono일 경우 136bits, Stereo일 경우 256bits이다.(메인데이터는 2개의 granule로 나누어지고 각각의 granule은 채널로 나뉘어짐) 사이드인포 정보 중에서 granule별 채널별로 갖고 있는 정보가 있기 때문에 채널에 따라서 그 크기가 다른 것이다.

다음은 사이드 인포 정보를 얻기 위한 소스 코드이다.

// Mono : 136 bits (= 17 bytes)
// Stereo : 256 bits (= 32 bytes) 
BOOL get_side_info(uunsigned long channels, III_side_info_t *side_info) {
int ch, gr;
side_info->main_data_begin = get_bits(9);
if (channels == 1) 
side_info->private_bits = get_bits(5);
else 
side_info->private_bits = get_bits(3);
for (ch=0; chch[ch].scfsi[0] = get_bits(1);
side_info->ch[ch].scfsi[1] = get_bits(1);
side_info->ch[ch].scfsi[2] = get_bits(1);
side_info->ch[ch].scfsi[3] = get_bits(1);
}
for (gr=0; gr<2; gr++) {
for (ch=0; chch[ch].gr[gr].part2_3_length = get_bits(12);
  side_info->ch[ch].gr[gr].big_values = get_bits(9);
  side_info->ch[ch].gr[gr].global_gain = get_bits(8);
  side_info->ch[ch].gr[gr].scalefac_compress = get_bits(4);
  side_info->ch[ch].gr[gr].window_switching_flag = get_bits(1);
  // window_switching_flag 는 blocksplit_flag와 같다. 
  // mixed_block_flag 는 switch_point와 같다. 
  if (side_info->ch[ch].gr[gr].window_switching_flag) // 22 bits 
  { 
   side_info->ch[ch].gr[gr].block_type = get_bits(2);
   side_info->ch[ch].gr[gr].mixed_block_flag = get_bits(1);
   side_info->ch[ch].gr[gr].table_select[0] = get_bits(5);
   side_info->ch[ch].gr[gr].table_select[1] = get_bits(5);
   side_info->ch[ch].gr[gr].subblock_gain[0] = get_bits(3);
   side_info->ch[ch].gr[gr].subblock_gain[1] = get_bits(3);
   side_info->ch[ch].gr[gr].subblock_gain[2] = get_bits(3);
   if (side_info->ch[ch].gr[gr].block_type == 0)
    return(FALSE);
   else if (side_info->ch[ch].gr[gr].block_type == 2 && side_info->ch[ch].gr[gr].mixed_block_flag == 0)
    side_info->ch[ch].gr[gr].region0_count = 8;
   else 
    side_info->ch[ch].gr[gr].region0_count = 7;
   side_info->ch[ch].gr[gr].region1_count = 20 - side_info->ch[ch].gr[gr].region0_count;
  } 
  else // 22 bits 
  { 
   side_info->ch[ch].gr[gr].table_select[0] = get_bits(5);
   side_info->ch[ch].gr[gr].table_select[1] = get_bits(5);
   side_info->ch[ch].gr[gr].table_select[2] = get_bits(5);
   side_info->ch[ch].gr[gr].region0_count = get_bits(4);
   side_info->ch[ch].gr[gr].region1_count = get_bits(3);
   side_info->ch[ch].gr[gr].block_type = 0;
  }
  side_info->ch[ch].gr[gr].preflag = get_bits(1);
  side_info->ch[ch].gr[gr].scalefac_scale = get_bits(1);
  side_info->ch[ch].gr[gr].count1table_select = get_bits(1);
} 
}
return(TRUE);
}

4. Main Data

메인데이터는 실질적인 오디오 데이터가 실려있는 영역을 말한다. mp3의 메인데이터는 한 프레임이 1152개의 Sample 데이터를 갖는다.  

granule은 메인데이터가 처리되는 기본단위다. mp3 부호화 방법중 주파수 등분할 방식인 32 서브밴드 방법이 있다. 각 서브밴드는 18개의 sample 데이터를 갖는다. 32*18 = 576 이라는 크기값이 나오며 이것을 한 개의 granule단위로 처리하게된다.  

mp3는 2개의 granule로 구성되어 1152개의 Sample 데이터를 갖게된다. 각 granule안에서는 채널별로 decoding된다.

 

5. 오디오 태그 (Audio Tag)

오디오 태그는 AAU의 집합 끝 부분 즉 mp3파일의 마지막 부분 128바이트를 말한다. Artist정보, 곡의 제목, 앨범 Title, 출판년도 등의 mp3파일 전체의 부가정보를 담고 있다. 장르에 대한 인덱스는 생략한다.

길이(byte)설명
3 Tag ID
30 Title
30 Artist
30 Album
4 Year
30 Comment
1 장르

 

6. mp3 데이터 복원

사이드 인포 디코드 (Side Information Decode)

SideInfo는 mp3의 실제 음향 데이터인 MainData 부분 앞에 위치하며 압축데이터를 복원할 때 필요한 정보들의 집합체이다. SideInfo 정보를 읽어 각 알고리즘 ②,③,④,⑤,⑥ 에서 사용할 수 있게 재배치한다.

스케일 펙터 디코드 (Scalfactors Decode)

스케일 펙터는 각 밴드의 샘플 데이터 부호화의 한 과정이다. 하나의 밴드 내의 36개 샘플 데이터(Layer III 의 경우 1152/32 = 36 즉 각 서브밴드 당 36 샘플)는 파형과 배율로 분리된다. 파형을 최대 진폭이 1.0이 되도록 정규화 하는데, 그때의 배율이 스케일 펙터로서 부호화 된다. 이 과정을 거치면 Band의 Sample Data 들은 비슷한 값들끼리 모이게 되고, 양자화잡음의 발생을 제한할 수 있기 때문에 청각심리 효과가 작용하여 이들 잡음이 감지되지 않게 된다.

적응 비트 할당이란 각 프레임, 각 서브밴드 마다 비트를 조정하는 것을 말한다. 스케일 펙터와 조합해서 크리티컬 밴드(임계대역폭)를 고려한 마스킹 레벨 한도 내에서 양자화를 함으로써 마스킹 효과를 더욱 효과적으로 이용 할 수 있다. 즉 마스킹의 효과로 인하여 인식되지 않는 주파수 대역에 대해서는 비트를 할당하지 않는다.

허프만 디코드 (Huffman Decode)

허프만 부호화 테이블을 이용하여 복호화 한다. 이때 사용되는 허프만 테이블은 신호의 통계적 편중을 주파수 대역별로 최적화 하여 만들어진 표준화 테이블이라고 한다. 이는 무손실 압축 방법으로 MPEG, JPEG 등에 주로 사용되는 압축기법이므로 자료를 찾는데 무리가 없을 것이므로 여기서는 그냥 넘어가도록 하겠다.

역양자화 (Dequantization)

역양자화는 양자화를 복원하는 방법이다. 양자화는 데이터의 손실이 오더라도 사람이 감각적으로 감지하기 힘들게 된다면 어느정도의 데이터에 손실을 가하여 압축률을 높이는 방법이다. 역시 허프만 부호화와 함께 주로 사용되는 기법이다.

IMDCT(Inverse Modified Discrete Cosine Transform)

MDCT는 시간 영역 데이터를 주파수 영역으로 변환하는 것이므로, IMDCT는 역으로 주파수 영역 데이터를 시간영역 데이터로 복호화 한다. 디코딩과정에서 IMDCT는 전체 처리과정에서 CPU점유율 30%이상 많은 연산을 필요로 한다.

32서브밴드 통합 필터뱅크(32 Subband Synthesis Filter Bank)

 

크리티컬 밴드 등의 청각특성을 효율적으로 이용하기 위해서는 우선 신호를 주파수 성분으로 나누는 것이 필요하다. 이 때문에 Encoder에서는 전 대역을 32개의 밴드로 등간격 주파수폭으로 세분하여 서브밴드 부호화 하므로 Decoder에서 다시 이를 복호화 한다.

그러나 통상의 필터로 1/32의 주파수대역을 취하는 경우 이상적인 필터가 아니기 때문에 부 표본화의 시점에서 앨리어싱을 일으킨다. 앨리어싱 잡음을 소거하기 위해 폴리페이즈 필터뱅크라고 불리는 필터를 사용하며 이 방법은 주로 Layer I, II 에서 사용된다. Layer III 에서는 MDCT(Modified Discrete Cosine Transform : 변형이산 여현변화)를 복합적으로 사용하고 있다.

 

7. 참고 자료



크리에이티브 커먼즈 라이선스
Creative Commons License

리눅스 사운드 프로그래밍

음악이 생겨라 (Let There Be Music)

By Shuveb Hussain 
한글번역 전정호 
이 글은 한글번역판입니다. 원문은 여기에서 볼 수 있습니다.

리눅스 멀티미디어에는 여러 활동이 있어 왔다. 많은 사람들이 리눅스를 멋진 멀티미디어 플래폼으로 만들기위해 노력해왔다. 현재도 많은 리눅스 멀티미디어 프로젝트들이 진행중이다. 여러 리눅스용 사운드 도구에 대한 소개가 필요하다면, 훌륭한 이전 Linux Gazette 기사를 참고하라.

나는 새로 작성할 프로그램에 음악을 포함하고 싶어서 웹을 검색했다. Ogg Vorbis나 MP3와 같은 압축 음악파일 형식을 변환할 수 있는 라이브러리가 많았다. 스피커로 음을 재생하는 것도 어려운 일이 아니였다. 이 글은 간단한 프로그램을 작성하여 Ogg Vorbis와 MP3 파일을 연주하는 방법을 설명한다. 이 글은 초보자를 염두에 두고 썼다. 그러니 리눅스 고수에게는 간단한 것을 너무 길게 설명하여 미안하다.

사운드카드 프로그래밍

사운드카드 프로그래밍을 살펴보기 전에 어떻게 컴퓨터가 음악 자료를 녹음, 저장, 재생하는지 알아보자.

샘플링(sampling)

음을 녹음할때 음질을 선택한다. 음질을 결정하는 중요한 요소중 하나가 샘플링 빈도(sampling rate)이다. 음은 시간에 따라 진동수가 바뀌는 파장일 뿐이다. 음을 디지털로 녹음하려면 일정 간격으로 음의 진동수를 기록한다. 이 간격을 샘플링 빈도라고 한다. 샘플링 빈도가 높을수록 음질이 좋아진다. 예를 들어, CD의 샘플링 빈도는 초당 44100번, 즉 44100 Hz이다. PCM(Pulse Code Modulation, 펄스 코드 변조)이라고 하는 이 작업은 ADC(Analogue to Digital Converter, 아날로그-디지털 변환기)가 한다.

비트와 채널(channel)

음을 샘플링할때, 샘플당 일정 수의 비트를 사용하고, 이런 채널을 여러개 사용한다. 샘플링하는 자료의 비트개수를 샘플링 정밀도(resolution)라고 한다. 가장 흔한 형식은 8 비트 양수 바이트 혹은 16 비트이다. 채널별로 자료를 분리한다. 예를 들어, CD 음악에는 왼쪽, 오른쪽 (스테레오) 두 채널이 있다. 8 비트 정밀도로 CD 음질을 녹음할때 초당 필요한 크기를 계산해보면:

		2 x 44100 = 88200 bytes

	 (채널 개수 x 샘플링 빈도)

이제 이론은 충분하니 실제 프로그래밍을 시작하자. 사운드카드를 프로그래밍하는 방법이 몇가지 있다. 초기 리눅스 드라이버가 만들어지던때 두 그룹이 있었다. Open Sound System (OSS)은 많은 장치드라이버를 작성한 그룹으로 심지어 하드웨어 사양을 공개하지 않거나 비밀유지동의(non-disclusure agreement)를 하지않은 개발자에게 사양을 공개하지않는 사운드카드 제조사의 이진모듈도 포함하였다. 하드웨어 제조사가 개인에게 사양을 알려주려고하지 않았기때문에 OSS는 4Front Technologies란 회사를 만들었다. 다른 그룹인 ALSA (Advanced Linux Sound Architecture)는 제조사가 사양을 공개한 사운드카드 드라이버만을 작성했다. ALSA는 2.6-test 커널에 포함되있다.

ALSA는 OSS 에뮬레이션계층을 제공하기때문에 OSS로 작성한 프로그램은 OSS와 ALSA 시스템에서 모두 동작한다. 간단하고 대다수의 시스템에 설치되있기때문에 이 글은 OSS 프로그래밍을 다룰 것이다.

유닉스/리눅스는 모든 장치드라이버마다 (보통 /dev 디렉토리에) 파일시스템 인터페이스가 있다. 이 장치파일을 open,read,write,lseek,close 같은 파일관련 시스템호출에 사용한다. 장치파일을 장치드라이버가 요청을 기다리는 파일시스템상의 훅(hook)이라고 보면 된다. 예를 들어, /dev/hda1 파일은 primary master IDE 하드디스크의 첫번째 파티션에 대한 인터페이스이다. open 시스템호출을 사용하여 파일을 열면 일반파일과 같이 파일핸들(file handle)을 얻을 수 있다. 파일핸들을 읽으면 실제로 파티션의 첫번째 섹터에 있는 자료를 읽게 된다! 또, 앞으로 lseek하면 파일포인터(file pointer)를 앞으로 이동한다. 섹터를 건너뛰으려면 섹터 크기만큼 앞으로 lseek한다. 위험하므로 하드디스크 장치파일을 수정하지는 마라. 파티션에 값을 쓰면 파티션이 망가질 수 있다. 하드웨어 장치는 보통 root만 직접 접근할 수 있다.

이제 코드로 넘어가자. 이 코드는 root가 아니여도 문제없이 실행되야 한다. 장치파일을 접근하는데 문제가 있다면 root로 su하고 chmod하여 접근권한을 푼다. 물론 리눅스에서 작동하는 사운드카드를 사용해야 한다. 직접 코드를 입력하지않고 여기에서 다운받을 수 있다. demo.pcm 파일도 같이 다운받아야 한다.

/*
	oss.c는 사운드카드에서 raw PCM 22KHz 샘플음을 낸다
	
	중요 - 프로그램을 실행하기전에 현재 디렉토리에
	demo.pcm 파일이 있는지 확인하라.
*/

#include < sys/types.h >
#include < sys/stat.h >
#include < sys/soundcard.h >
#include < sys/ioctl.h >
#include < unistd.h >
#include < fcntl.h >
#include < errno.h >
#include < stdlib.h >

#define SECONDS 5 //재생 시간 (초)

int main() 
{
	int fd;
	int handle = -1;
	int channels = 1;         // 0=모노 1=스테레오
	int format = AFMT_U8;
	int rate = 22000;
	unsigned char* data;
	
/* 사운드카드에 해당하는 파일에 쓰기위해(write) 파일을 연다(open). DSP = Digital Signal Processor */
	if ( (handle = open("/dev/dsp",O_WRONLY)) == -1 )
	{
		perror("open /dev/dsp");
		return -1;
	}
/* 재생하려는 음이 스테레오라고 사운드카드에 알린다. 0=모노 1=스테레오 */

	if ( ioctl(handle, SNDCTL_DSP_STEREO,&channels) == -1 )
	{
		perror("ioctl stereo");
		return errno;
	}

	/* 자료 형식을 사운드카드에게 알린다 */
	if ( ioctl(handle, SNDCTL_DSP_SETFMT,&format) == -1 )
	{
		perror("ioctl format");
		return errno;
	}
	
	/* DSP 재생율(playback rate), 즉 raw PCM 음의 샘플링 빈도를 지정한다. */
	if (ioctl(handle, SNDCTL_DSP_SPEED,&rate) == -1 )
	{
		perror("ioctl sample rate");
		return errno;
	}
   
	// 빈도 * 5 초 * 두 채널
	data = malloc(rate*SECONDS*(channels+1));
	
	if((fd=open("demo.pcm",O_RDONLY))==-1)
	{
		perror("open file");
		exit(-1);
	}

	/* demo 파일에 저장된 정보를 읽어서 할당한 메모리에 저장한다 */
	read(fd,data,rate*SECONDS*(channels+1));
	close(fd);
	
	/* 읽은 내용을 사운드카드에 쓴다(write)! 그러면 재생이 된다. */

	write(handle, data, rate*SECONDS*(channels+1));

	if (ioctl(handle, SNDCTL_DSP_SYNC) == -1)
	{
		perror("ioctl sync");
		return errno;
    	}

	free(data); //좋다. 마무리.
	close(handle);
	printf("
Done
");
	return 0;
}

이 프로그램은 raw PCM, 22 KHz, 스테레오 자료파일을 5초간 연주한다. 프로그램은 크게 세가지 작업을 한다:

a. sound 장치 열기(open)
b. 재생을 위한 파라미터 설정
c. 장치에 자료 쓰기(write)

open()/write()/close() 시스템호출은 일반파일과 동일하다. ioctl() (Input/Ouput ConTroL) 시스템호출을 사용하여 재생에 사용할 파라미터를 설정한다. ioctl() 시스템호출은 입출력 장치와 통신하거나 파라미터를 설정할때 사용한다. 또, 시스템호출의 개수를 줄이기위한 편법으로 사용되기도 한다. 그래서 흔히 프로그래머의 다용도칼이라고 부른다. ioctl() 시스템호출의 함수형은:

int ioctl(int fd, int command, ...)

첫번째 파라미터는 장치의 파일기술자(file descriptor)이고, 두번째 파라미터는 장치에 대한 요청/명령, 나머지 파라미터들은 두번째 파라미터에 따라 다르다 [명령 목록은 ioctl_list manpage를 참고]. 다음을 보자:

ioctl(handle, SNDCTL_DSP_SPEED,&rate)

이 ioctl() 호출은 DSP 재생율을 설정한다. 세번째 파라미터는 장치드라이버로 전달되는 실제 재생율이다.

예제 프로그램에는 세가지 ioctl() 호출이 있다. 첫번째는 재생에 사용할 채널개수를 설정하고, 두번째는 PCM 형식을, 세번째는 재생율을 지정한다. 이것으로 장치에 필요한 정보는 충분하고, 다음은 장치파일에 PCM 자료를 직접 쓰면(write) 재생이 된다. 마지막 ioctl()은 장치를 비우고(flush), 그 다음 PCM 자료를 저장하기위해 할당한 메모리를 해제한다. 이게 끝이다.

음악 저장 형식

방금 전에 5초짜리 22KHz 음악을 재생하기위해 raw PCM 자료 220 KB가 필요했다.

	
	22,000 x 5 x 2 = 220,000
	(샘플 x 초 x 채널)
	
	CD 음질로 1분을 재생하기위해 필요한 크기는:

	44,100 x 60 x 2 = 5292000 바이트, 약 5 MB!

음악CD는 raw PCM 형식으로 자료를 저장한다. 그러나 컴퓨터는 음악을 효율적으로 저장하고 (인터넷으로) 전송하기위해 음악을 압축하여 크기를 줄이고 재생시 다시 압축을 푼다. 어떤 음악이던 관계없이 샘플링률은 고정되있다. 아무 소리가 없는 몇초간을 샘플링했다고 생각해보자. 같은 시간의 시끄러운 락음악과 동일한 크기가 필요하다. raw 음악파일은 자료반복이 매우 적기때문에 일반적인 압축방법으로는 잘 압축되지 않는다. 음악은 다른 기술을 사용하여 압축한다. 가장 일반적인 방법은 사람이 귀로 들을 수 없는 부분을 제거하거나 음악 특성에 따라 압축한다. 음악을 좋아하는 사람들에게 가장 대중적인 형식은 분명 MP3다. 현재 MP3에는 특허문제가 걸려있기때문에, 오픈소스 공동체에서는 Ogg Vorbis 형식을 반긴다. 두 형식 모두 크기가 비슷하게 줄어들며 음질의 손실을 최소화한다.

사운드카드가 어떻게 raw 음악자료를 재생하는지 알았으니, Ogg Vorbis 파일을 재생하는 방법을 알아보자. Ogg Vorbis 파일을 재생하려면 먼저 복호화(decode), 즉 raw PCM 자료로 변환해야 한다. libvorbisfile이 이 작업을 한다. 이 라이브러리는 매우 낮은 수준을 다루지만 더 세밀한 조절이 가능한 libvorbis 라이브러리보다 높은 수준에서 변환을 한다. 이제 문제가 간단해진다: Ogg Vorbis 파일에서 읽은 자료를 라이브러리로 넘기고, 라이브러리가 변환한 raw PCM 자료를 사운드카드로 직접 보낸다. 소스는 여기에

#include < sys/types.h >
#include < sys/stat.h >
#include < sys/soundcard.h >
#include < sys/ioctl.h >
#include < unistd.h >
#include < fcntl.h >
#include < errno.h >
#include < stdlib.h >

#include "vorbis/codec.h"
#include "vorbisfile.h"

int setup_dsp(int fd,int rate, int channels);


char pcmout[4096]; // 변환한 PCM 자료를 저장할 4KB 버퍼
int dev_fd;

int main(int argc, char **argv)
{
	OggVorbis_File vf;
	int eof=0;
	int current_section;
	FILE *infile,*outfile;

	if(argc<2)
	{
		printf("supply file arguement
");
		exit(0);
	}

	if ( (dev_fd = open("/dev/dsp",O_WRONLY)) == -1 ) 
	{
		perror("open /dev/dsp");
		return -1;
	}

	infile=fopen(argv[1],"r");
	
	if(infile==NULL)
	{
		perror("fopen");
		exit(-1);
	}   

	if(ov_open(infile, &vf, NULL, 0) < 0) 
	{
		fprintf(stderr,"Input does not appear to be an Ogg bitstream.
");
		exit(1);
	}
	
	char **ptr=ov_comment(&vf,-1)->user_comments;
	vorbis_info *vi=ov_info(&vf,-1);
	while(*ptr)
	{
		fprintf(stderr,"%s
",*ptr);
		++ptr;
	}

	fprintf(stderr,"
Bitstream is %d channel, %ldHz
",vi->channels,vi->rate);
	fprintf(stderr,"
Decoded length: %ld samples
",(long)ov_pcm_total(&vf,-1));
	fprintf(stderr,"Encoded by: %s

",ov_comment(&vf,-1)->vendor);
  
	if(setup_dsp(dev_fd,vi->rate,vi->channels-1))
	{
		printf("dsp setup error.aborting
");
		exit(-1);
	}

	int count=0;
	
	while(!eof)
	{
		long ret=ov_read(&vf,pcmout,sizeof(pcmout),0,2,1,&current_section);
		if (ret == 0)
		{
			/* EOF */
			eof=1;
		}
		else if (ret < 0)
		{
	      		/* 자료에 오류가 있다.  (프로그램에) 중요하다면 보고하지만,
			이 프로그램은 하지 않는다. */
		}
		else 
		{
			printf("Writing %d bytes for the %d time.
",ret,++count);
			write(dev_fd,pcmout,ret);
		}
	}

	ov_clear(&vf);
	fclose(infile);
	
	if (ioctl(dev_fd, SNDCTL_DSP_SYNC) == -1) 
	{
    		perror("ioctl sync");
		return errno;
	}
	close(dev_fd);
	fprintf(stderr,"Done.
");
	return(0);
}

int setup_dsp(int handle,int rate, int channels)
{
	int format;

	if ( ioctl(handle, SNDCTL_DSP_STEREO,&channels) == -1 ) 
	{
		perror("ioctl stereo");
		return errno;
	}
	
	format=AFMT_U8;
	if ( ioctl(handle, SNDCTL_DSP_SETFMT,&format) == -1 )
	{
		perror("ioctl format");
		return errno;
	}
	
	if ( ioctl(handle, SNDCTL_DSP_SPEED,&rate) == -1 )
	{
		perror("ioctl sample rate");
		return errno;
	}
	
	return 0;
}

다음과 같이 프로그램을 컴파일한다
gcc oggplay.c -oggplay -lvorbisfile -I/path/to/vorbis/header/files -L/path/to/vorbis/lib/files

라이브러리파일과 헤더파일이 /usr/lib 이나 /usr/include 같은 일반적인 위치에 있다면 -l 과 -L 명령행 옵션이 필요없다. 모든 대중적인 배포본에 vorbisfile 라이브러리가 포함되있다. 만약 시스템에 설치가 안되있다면 여기에서 다운받을 수 있다. 또, Ogg Vorbis 사이트는 여기다. 프로그램을 컴파일하려면 (개발용) 헤더파일이 필요하다.

setup_dsp 함수는 dsp 장치를 열고(open), 세가지 중요한 파라미터인 채널, 형식, 재생율을 지정한다. 음악 재생을 위해 가장 중요한 세가지 파라미터를 기억하라. main 함수는 오류검사외에, 쉘 아규먼트로 준 Ogg Vorbis 파일을 열고, 파일에 저장된 설명과 파일정보를 출력한다. ov_open() 라이브러리함수가 채우는 구조체에 저장된 샘플링 빈도와 채널 정보를 setup_dsp 함수에 넘긴다. 그후 프로그램은 Ogg Vorbis 파일 내용을 변환하여 버퍼에 넣는 반복문을 실행한다. 반복문은 버퍼에 저장한 raw PCM 자료를 앞에서 설정한 dsp 장치에 쓴다. 파일이 끝날때까지 반복한 후, 마지막으로 정리하고 종료한다. 간단하지 않는가? 쉽고 직관적으로 변환해준 libvorbisfile에게 감사하다.

Ogg Vorbis 파일을 변환하려면 libvorbisfile을 사용하면 된다. MP3 파일을 변환하여 재생하고 싶다면 여러 라이브러리를 선택할 수 있다. 많이 사용하는 라이브러리중에 영상과 음악을 모두 변환할 수 있는 smpeg 라이브러리가 있다. 또, libmad 라이브러리도 찾았다. 나는 smpeg의 음악 변환 기능만 사용하기로 했다. 관심이 있다면 smpeg의 MPEG 영상 변환 기능도 살펴보길 바란다. 라이브러리 사용법을 설명하는 샘플 프로그램 plaympeg도 포함되있다. 영상을 보여주기위해 smpeg은 매우 사용하기 쉽고 기능이 많은 그래픽 프로그래밍 라이브러리인 SDL을 사용한다. 이외에도 MP3를 변환하는 라이브러리는 많지만, 많은 리눅스 배포본에 smpeg이 포함되있기때문에 smpeg을 선택했다. 많이 사용하는 gtv MPEG 영상플레이어도 smpeg 패키지에 포함되있다.

다음은 MP3 파일을 연주하는 예제 프로그램이다. smpeg 라이브러리가 변환한 MP3 자료를 직접 사운드카드로 보낸다. 직접 PCM을 다룰 필요가 없다. 소스 다운로드

#include < stdio.h >
#include < stdlib.h >
#include < string.h >
#include < signal.h >
#include < unistd.h >
#include < errno.h >
#include < sys/types.h >
#include < sys/stat.h >
#include < sys/ioctl.h >
#include < sys/time.h >

#include "smpeg.h"

void usage(char *argv0)
{
	printf("

Hi,

%s 

This is the normal useage.
",argv0);
}

int main(int argc, char *argv[])
{
    int volume;
    SMPEG *mpeg;
    SMPEG_Info info;
   
    volume = 100; //Volume level
    
    /* 아규먼트가 없으면 사용법만 출력한다 */
	if (argc == 1) 
	{
	        usage(argv[0]);
	        return(0);
	}

	mpeg = SMPEG_new(argv[1], &info;, 1);

        if ( SMPEG_error(mpeg) ) 
	{
            fprintf(stderr, "%s: %s
", argv[1], SMPEG_error(mpeg));
            SMPEG_delete(mpeg);
        }
	
        SMPEG_enableaudio(mpeg, 1);
        SMPEG_setvolume(mpeg, volume);

        /* 음악에 대한 정보를 출력한다 */
        if ( info.has_audio ) 
	{
            printf("%s: MPEG audio stream
", argv[1]);
            
	        if ( info.has_audio ) 
		    printf("	Audio %s
", info.audio_string);

        	if ( info.total_size ) 
			printf("	Size: %d
", info.total_size);
	        
		if ( info.total_time )
			printf("	Total time: %f
", info.total_time);

		/* 재생하고 재생이 끌날때까지 기다린다 */
		SMPEG_play(mpeg);
        
		while(SMPEG_status(mpeg)==SMPEG_PLAYING);
        	SMPEG_delete(mpeg); //구조체 반환
	}
	return(0);
}

여기에 있는 smpeg을 설치한후 프로그램을 컴파일한다. smpeg을 설치하기위해서 미리 SDL을 설치해야 한다. 컴파일할때 헤더파일과 라이브러리파일 경로를 컴파일러에게 알려줘야 한다. 쉽게 하려고 smpeg 패키지는 smpeg-config라는 간단한 도구를 제공한다. 다음과 같이 프로그램을 컴파일한다:

gcc playmp3.c `smpeg-config --cflags --libs` -I/usr/include/SDL

smpeg-config 명령어를 감싸는 따옴표문자를 주의하라. 보통 backqoute라고 부르며, US식 키보드에서 틸데(~) 문자와 같이 있다. 쉘에서 이 문자는 안에 있는 명령어의 출력을 그 자리에 대신한다. 그냥 smpeg-config 명령만 실행해보면 내가 무슨 말을 하는지 알 것이다. -I/usr/include/SDL 명령행 옵션을 -I < 실제 SDL 경로 >로 수정하라. SDL 함수를 사용하지 않아도, smpeg.h가 내부적으로 SDL 헤더파일을 사용하므로 SDL 헤더파일 경로를 지정해야 한다. RPM 사용자는 프로그램을 컴파일하기위해 smpeg과 smpeg-devel 패키지를 설치해야 한다.

SMPEG_new 라이브러리함수는 필요한 내부 객체를 할당하고, MP3 파일에 대한 정보를 SMPEG_info 구조체에 채운다. 프로그램은 이 정보를 출력하고, SMPEG_play를 호출하여 재생을 시작한다. 함수는 즉시 반환되기때문에 다른 작업을 하면서 MP3를 연주하는 프로그램에 유용하다. 이 프로그램에서는 따로 할 일이 없기때문에 재생이 끝날 때까지 기다린다. SMPEG_delete는 그동안 라이브러리가 할당한 모든 메모리를 반환하고, 마친다.

이 글이 여러분의 프로그래밍 작업에 조금이나마 도움이 되길 바란다. 의견을 듣고 싶다. 잘못된 점을 알려주거나 제안을 바란다. shuveb@shuvebhussain.org로 내게 연락할 수 있다.


참고로 테스트를 해봤는데 PCM RAW파일로만 가능한다. ^^ 형식이 그렇네. mp3나 다른 형식의 파일을 받아서 cool edit로 변환해 주었다. 쿠울~~~ 에디터 ㄱㄱ 그리고 역시나 속도 그런거 잘 맞춰줘야 한다는 ^^

Sound Programming

Sound Programming

[ Audio-1 ]      [Audio-2]      [ OSS ]  
[Play Programing,Recording Programing]


1.OSS

Open Sound System(OSS)은 UNIX, LINUX 등 여러 운영체제의 사운드 카드 드라이버이다.
OSS는 저수준의 API를 제공하며 이를 이용하여 OSS devices 그리고 mixer,audio,MIDI,raw
musix,Virtual Mixer,SoftOSS device를 프로그래밍 할 수 있다. 아울러 이문서는
http://www.opensound.com 의 "Open Sound System Programmer's Guide ver 1.11"중
에서 playback 과 recording을 위한 Audio 부분만을 골라 편집하여 번역한 것이다  

2.Device Files Supported by OSS

*OSS API는 soundcard.h 에 선언되어 있다

*/dev/mixer
*/dev/sndstat
*/dev/dsp 그리고 /dev/audio
*/dev/sequencer
*/dev/music(/dev/sequencer)
*/dev/midi
*/dev/dmfm
*/dev/dmmidi
 
2-1. /dev/dsp and /dev/audio

/dev/dsp 그리고 /dev/audio 는 음성을 기계화(digitized)시키는 주된 장치화일이다. 어떤
음성 데이터도 사운드 카드의 DAC/PCM/DSP 와 /dsv/dsp 그리고 /dev/audio 에 읽거나
써서 재생 또는 녹음을 한다 .
 dev/dsp 와 /dev/audio 는 매우 유사하나 dsv/audio는 loigarithmic mu-law encoding을
사용하며 dsv/dsp는 8-bit unsigned linear encoding을 사용한다. 이 화일들은 ioctl 프로
시저를 이용하여 제어할수 있다 (-dev/audio는 SunOS에서 사용하기에 적합하다.)
 

 

Audio programing

1.introduction

*기본적인 programming 방법

1. 장치들를 열고 8kHz mono mode를 작동 시킨다

2. encoding 은 ioctl 프로시저를 호출 함으로 서 바꾸는 것이 가능 해진다 ,후에 모든 장치
   화일들 은 유사하게 행동한다.

그러나 나중에 정의된 ioctl 프로시저를 호출 하는것에 의하여 장치들의 많은 파라 미터를
바꾸는 것이 가능하다. 모든 codec 장치들은 기록, 재생 능력을 가지고 있으나 전혀 기록(
레코딩),재생 기능을 가지고 있지 않은 장치들도 있다. 그러나 대부분의 audio 장치는 이런
기능을 가지고 있다.
동시에 레코딩 재생을 하는 장치들을 전이중 (full duplex)장치라고 부른다. 가장일반적인 사운드 데이타로 레코딩,재생 하는 방법은 UNIX,LINUX 명령어 (dd,cat )를 사용하는 것이다.예를들면

*녹음 : cat  /dev/dsp > xyz : (오디오 장치에서 디스크화일로 명령어가 kill할때 까지 저장
                                         한다.)

*재생 : cat  xyz > /dev/dsp  

오디오 장치는 항상 exclusively 하게 열린다. 만약 하나의 프로그램이 그것 이 이미 open상태일떄 장치를 열려고 한다면 사운드 드라이버는 즉시 에러 (EBUSY)를 를 RETURN 할것이다.

 

1. Declarations for an Audio Program

OSS API를 사용하는 프로그램은 <soundcard.h> C언어 헤더 를 포함해야 한다. 그리고 그
밖의 화일을 다루기 위한 헤더를 포함한다.

/****Listing1 - Definitions for an Audio Program*****/

/********* Standard includes************/

#include <ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>

/********* Mandatory variables.**********/

#define BUF_SIZE 4096
int audio_fd;
unsigned char audio_buffer[BUF_SIZE];

*BUF_SIZE 매크로는 audio의 data를 위하여 할당해진 버퍼의 크기를 정의한다.(-그러나 짧은 버퍼가 결과를 더잘 recording 해준다.버퍼사이즈는 1024(linux) 4096사이에서 의 선택이 좋다)

 

2. Selecting and Opening the Sound Device

audio장치는 사용되기 전에 open해야한다. 만약 프로그램에 encoding format 이 장착되지
않았다면 올바른 장치 화일을 open하는것이 중요하다. 이창치 화일들은 실제 디바이스화일의
심볼릭 링크이다
예를들면 /dev/dsp 는 /dev/dsp0를 가르킨다(이것은 오디오장치가 시스템에서 처음 탐색
하는 화일이다)장치 화일을 열었다면 오직 O_RDONRY 와 O_WRONRY만을 쓸것을 권장한다.
O_RDWR는 Full duplex(동시에 재생과 녹음을 한다.)에서 사용되어야 한다

/********Listing 2 - Opening a Device File***********/

if ((audio_fd = open(DEVICE_NAME, open_mode, 0)) == -1)
{
        /* Open of device failed */
 
       perror(DEVICE_NAME);
        exit(1);
}

 

*error 중 유일하게  EBUSY (Device busy )는 장치가 결코 이용할 수 있게 될것이라는 것이 보장되지 않을 지라도
프로그램이 다시한번 같은시간에 장치화일을 열려고 노력한다

3. A Simple Recording Application

/***********Listing 3 - Sound Recording**********/

int len;
if ((len = read(audio_fd, audio_buffer, count)) == -1)  
{
        perror("audio read");
        exit(1);
}

위 예에서 count는 프로그램이 장치로부터 읽기를 원한 바이트들의 수를 정의합니다. 그것은
적은  audio buffer의 크기와 같다. 덧붙이자면 그것은 또한 항상 sample크기의 정수형 배
수이다 그리고 그것은 2의 정수형을 사용한다 (2,4,8,16,32)그리고 이것은 내적으로 드라이
버에 의하여 사용될때 최고로 작동된다.
장치로부터 기록된 바이트들의 수가 정확하게 시간을 재는데 사용될수있다. 오디오 데이터
속도는 (data rate) (bytes per secound)는 샘플 스피드와 크기 그리고 채널의 수에 의존한다.

4. Simple Playback Application

재생 프로그램 역시 레코딩 프로그램과 같다 . 차이점은 재생 프로그램은 write 프로시저를
호출 한다는 점이다.

5. Setting Sampling Parameters

*음질에 영향을 미치는 세 파라미터들

1.sample format (때떄로 number of bits 라고 불린다)

2,number of channels (mono or stereo),

3,sampling rate(속도)

디바이스 는 새로운 샘풀링 파라미터를 받아들이기 전에 SNDCTL_DSP_RESET 으로 reset 을
한다.

6. Selecting Audio Format

샘플 포맷은 오디오 데이타의 질에 영향을 끼치는 중요한 요소이다. OSS API는 여러 다른
샘풀 포맷을 지원한다
<soundcard.h> 헤더 화일이 다음의 견본 포맷 identifiers의 경계를 정한다.

Name

Description

AFMT_QUERY

오디오 포맷이 아니다 그러나 현재오디오 포맷의 포맷에 대해 질문을 할때 사용되는 indentifier이다.

AFMT_MU_LAW

 Logarithmic mu-law audio encoding

AFMT_A_LAW

Logarithmic A-law audio encoding (rarely used)

AFMT_IMA_ADPCM

 4:1 로 압축된 현재 16-bit 오디오로 대표되는  평균 4bit per sample의  포맷이다. 이것은 여러 다른 ADPCM 포맷이있다 . IMA ( interactive Multimedia Association,대화형 멀티 협회)에 의하여 하나가 정의 되고. 또한 Creative ADPCM  format은 SoundBlaster16에 의하여 사용된 ADPCM 포맷이 이것을 가지고 부합하지 않습니다.AFMT_U8 : The standard unsigned 8-bit audio encoding used in PC soundcards.

AFMT_S16_LE

The standard 16-bit signed little-endian (Intel) sample format used in PC soundcards

AFMT_S16_BE

Big-endian (M68K, PowerPC, SPARC, etc.) variant of the 16-bit signed format.

AFMT_S16_NE

16-bit signed format in machine's native endian convention.

AFMT_S8

Signed 8-bit audio format.

AFMT_S32_LE

Signed little-endian 32-bit format. Used for 24-bit audio data where the data is stored in the 24 most significant bits and the least significant 8 bits are not used(should be set to 0).

AFMT_S32_BE

Signed big-endian 32-bit format. Used for 24-bit audio data where the data is

AFMT_U16_LE

Unsigned little-endian 16-bit format.

AFMT_U16_BE

Unsigned big-endian 16-bit format.

AFMT_MPEG MPEG MP2/MP3

audio format (currently not supported).

                <Table 1 - Sound Sample Formats>

하드웨어 레벨에서 대부분의 장치들이  8-bit unsigned format ( AFMT U8 ) 을 지지한다는
것은 중요한 사실이다( 비록 그것들이 16-bit format의 high-end device를 제공하더라도 ).
다른 일반적인  format은 AFMT_S16_LE 와 AFMT_MU_LAW이다. 많은 장치들을 가지고
AFMT_MU_LAW 는 소프트웨어에서 기본 번역기로 제공하는 mu-law와 8-bit encoding 와
경쟁한다. 이것은 오직 8bit를 사용 할때 보다 낮은 음질이 생기는 원인이 된다. 어플리 케이
션은  장치에 의하여 그들이 요구하는 견본 포맷이 지지된다는 것을 검사해야 한다. 지원 되지
않는 포맷의 작업은 데이터를 다른 포맷으로 바꾸어 주어야 한다. (보통 AFMT_U8). 양자
택일적으로 , 프로그램은 그것이 전환을 할 수 없다면 중단해야 한다.
 

AFMT_S32_XX FORMAT은 애플리케이션이 16-bit 샘플사이즈 이상을 요구하였을 때 사용
하기 위하여 만들어졌다. 하나의 샘플을 32-bit로 저장 할당 할때 int 는 최고의 아키텍쳐이다.
24 bit 의 데이터는 3개의 중요한 bytes로 저장 되어야 한다 (가장 중요한 바이트는 0으로
쎄팅 되어야한다).

the number of bits required to store a sample is:

* 4 bits for the IMA ADPCM format,

* 8 bits for 8-bit formats, mu-law and A-law,

* 16 bits for the 16-bit formats

* 32 bits for the 24/32 bit formats.

* MPEG audio format.

샘플 포맷은 ioctl ,SNDCTL_DSP_SETFM을 사용하여 설치할수있다. 다음의 코드는 오디오
포맷을 AFMT_S16_LE(다른 포맷과 유사한)로 설치 한것이다

/***************Listing 4 - Setting Sample Format****************/

int format;
format = AFMT_S16_LE;

if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format) == -1)
{
        /* fatal error */
        perror("SNDCTL_DSP_SETFMT");
        exit(1);
 }

if (format != AFMT_S16_LE)
        /* The device doesn't support the requested audio format. The
        program should use another format (for example the one returned
        in "format") or alternatively it must display an error message
        and to abort. */

The SNDCTL_DSP_SETFMT ioctl call simply returns the currently used format if
AFMT_QUERY is passed as the argument.

만약 AFMT_QUERY argument 가 설정되어있다면 SNDCTL_DSP_SETFMT iocl 호출은 단
순하게 현제 사용되고 있는 format을 return 한다. ioc시 호출로 현재 사용하는 format을 체
크하는 것은 아주 중요한 일이다. 만약 사용할 특별한 포맷이 하드웨어에서 제공되지 않는
다면 다른 시스템 콜을 거절하고 다른 포맷을 제공한다.
 

/***********Listing 5 - Checking for Supported Formats************/

int mask;
if (ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &mask) == -1)
        /* Handle fatal error ... */

if (mask & AFMT_MPEG)
        /* The device supports MPEG format ... */

*NOTE*

SNDCTL_DSP_GETFMTS 는 실제로 하드웨어에서 제공되는 포맷만을 리턴 한다. 그것은
드라이버에서 제공되는 포맷이 더 많은 종류의 소프트웨어 변화를 사용 가능 하게 만
든다.(signed to unsigned, big-endian to little-endian or 8bit to 16-bit). 이러한 포맷들의
우열은 ioctl에 의해서 리포트 되지는 않는다 그러나 SNDCL_DSP_SETFMT는 그것들을 받
아들인다.
FMT_MU_LAW는 거의 모든 장치에서 지원대는 데이터 포맷이다. OSS 버전 3.6 에서는 항상
위의 포맷이  NDCTL_DSP_GETFMTS  으로 제공 되었다. 버젼 3.6 그리고 나중의 버전에
서는  장치가 하드웨어안에서 mu-law 포맷을 지한다. 이 엔코딩은 오직 어프리케이션 그리고
시스템이 mu-law 엔코딩을 사용하는 오디오 파일에서 사용된다

7. Selecting the Number of Channels (Mono/Stereo)
기본  모드는 mono이지만 거의 대부분의 장치는 stereo를 지원한다. 어플리케이션은 ioctl
호출로 채널의 수를 선택한다. SNDCTLl_DSP_CHANNELS은 argument와 함께 채널의 수를
지정할수있다.
대부분의 디바이스 장치는 16개의 채널을 지원한다. 미래의 장치는 점점더 지원하는 채널
수가 늘어날 것다

Listing  6. Setting Number of Channels

int channels = 2; /********* 1=mono, 2=stereo ***********/
if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels) == -1)
{
        /* Fatal error */
        perror("SNDCTL_DSP_CHANNELS");
        exit(1);
}
if (channels != 2)
        /* The device doesn't support stereo mode ... */


*NOTE

 어플리 케이션은 sampling speed를 선택 하기전에 채널의수와 비트의 수를 꼭 선택 하여
야만 한다.  장치들은  모노와 스테레오의 최대 스피드를 가지고 있다.카드의 최대 스피드모
드로  장착한 후에  프로그램이 부정확하게 채널의 수를 바꾼다면 프로그램은 부정확하게
행동할것이다.  스피드는 처음 장치에서 읽거나 쓰기전에 선택하여야만 한다.

8. Selecting Sampling Rate (speed)

샘플링 속도는 오디오의 음질을 결정하는 중요한  매개 변수이다. OSS API는 1HZ에서 2GHz
 까지의 주파수의 선택을 허가한다. 그러나 실제로는 오디오 장치에 존재 하고 사용하는 것은
제한되어 있다 . 최대 주파수가 넓게 변화되는 동안 최소의 주파수는 보통 5kHz 이다몆개의
오래된 사운드 카드는 재생시에는 22.05kHz 레코딩시에는 11.025kHz를 이용하고 다음의
세대는 44.1kHz (모노) 혹은 22.05kHz(스테레오)를 제공한다  
최대의 사운드 카드는 최대 96kHz(DVD 음질)을 제공한다 그러나 일반적으로 제공되는 것은
44.1kHz(CD음질)이다.
기본 샘풀링 속도는 8kHz이다.그러나 어플리케이션은 그런것에 의존하지 않는다 디바이스
장치의 제공이높으면 높게 제공이 된다. 코덱장치는 보통 높은주파수의  crystal oscillator
스피드 눈금 에 의해 샘풀링 눈금을 만들어 낸다. 다음은 샘플링 스피드를 설정하는 프로그램
코드이다 .

/************Listing 7 - Setting Sampling Rate*********/

int speed = 11025;
if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed)==-1)
{
        /* Fatal error */
        perror("SNDCTL_DSP_SPEED");
        exit(Error code);
}
if ( /* returned speed differs significantly from the requested one... */ )         /* The device doesn't support the requested speed... */

NOTE

어플리케이션은 샘풀링 스피드를 설정하기 전에 채널의 수와 비트의 수를 선택해야 한다 .
장치들마다 다른 모노와 스테레오의 스피드를 가지고 있다 . 만약 카드에 채널의 수가 에 높은
스피드 모드가 설정된이후에 바뀐다면 프로그램은 부정확한 행동을 할 것이다. 샘플링 스피
드느 처음 읽거나 쓰기전에 장체에 설정 되어야 한다.

9. Other Commonly Used ioctl Calls

ioctl 프로시저 호출을 하지 않고  오디오 처리 프로그램을 하는 것은 가능 하다. 그것은 다른
3가지가 이미 초기에 기술 되어있기 때문이다.
눈에 띄는 delay나 pause없이 어플리케이션에서 이미 paramenters를 호출하여 장치를 열어
 잇따라 연속적으로 읽거나 쓴후에 파일을 닫는 것은 것은 가능하다 이러한 종류의 어플리
케이션은 stream이나 batch apllication으로 described 되어있다 이러한 추가의 3가지 호출은
아마도 약간은 더 까다로운 프로그래밍을 요구할것이다. 모든 그러한 것들은 argument를
요구하거나 리턴 하지않는다 (just use an argument of 0)

ioctl SNDCTL_DSP_SYNC 는 어플리케이션이 장치에 played시에 마지막 바이트를 쓸데까지
기다리기를(wait)원할 때   사용한다 (그것은 recoding 모드에서는 되지않는다) 그러한 경
우엔 시스템 호출은 장치를 reset(stop)한다 그리고 호출한 프로그램으로 돌아가 리턴한다.
이러한 호출은 아마도 몇몇의 두 번째 수행의 버퍼의 데이터에 의존한다.  은연중에 어떤 사
운드 장치 호출도 SNDCTL_DSP_SYNC 로 종료한다. SNDC시_DSP_SYNC 는 장치를 close
하거나 reopen 할시에  사용하는 것이 좋다. ioctl SNDCTL_DSP_RESET는 즉시 장치를 멈
추고 그리고 그것이 새로운 파라미터들을 받아들일 수 있는 상태로 그것을 돌려줍니다 . 장
치를 openning 한후에 호출 하였을때 불리한 효과가 있다 이호출은 오직 레코딩중에
 playback이 abort를 필요로 할때만 요구한다
 .

반적으로 장치의 열고 닫는 것은 SNDCTL_DSP_RESET를 사용한 후에 할 것을 추천 한다

<ioctl SNDCTL_DSP_POST는 SNDCTL_DSP_SYNC의 lightweight 버전이다. 그것은 단지 드라이버가 출력중의 정
  지 일것같다고 말한다 이것은 장치 좀더 괜찮은 정지를 핸들링이 가능하게 해준다>


NOTE

그들 모드는클릭 혹은 출력중의 불필요한 정지로  ioctl을 호출한다. 프로그래머는 단지 절
대적으로 요구할때만 사용 하여야 한다. 프로그래머는 자신의 프로그램에서 오디오 데이타가
비교적 오랜시간 출력이 멈추었을때SNDCTL_DSP_POST를 사용 하여야한다.

-예를 들면 이런 종류의 상태는 다음과 같다:

*재생 후에 즉시 효과가 나타나지 않을 때

*사용자의 입력을 기다리는 적용시간들 전에
 
*그러한 큰 메모리 파일이 로딩시에 오래 작동 하기 전에

프로시저  SNDCTL_DSP_RESET 혹은 SNDCTL_DSP_SYNC 는 어플리 케이션이 샘플 파라
미터(스피드 채널의 수 혹은 비트의수)를  호출한다. 파라메터의 변화에 순간에 장치를 열고
닫는 것이 더 신뢰성이있다. 어플리케이션은 레코딩이나 재생을 바꿔주기 전
에SNDCTL_DSP_SYNC 혹은SNDCTL_DSP_RESET을 호출한다 (혹은 이런경우엔 장치를
열고 닫는 것을 더 추천 한다 ).
10. Interpreting Audio Data

오디오 데이터의 엔코딩은 샘플의 포맷에 의존한다

11. Mu-law (Logarithmic Encoding)

하이 포맷은 디지털 전화 기술에서 기원 되었다. 각 샘플은 본래는 16비트 값인데 8비트로 압축하여 묘사한다. 만기된 logaruthmic 엔코딩은 그 포맷이 사용되기 전에 선형의 포맷으로 바뀐다.(two mu-law encoded values cannot simply be add)
-가능하다면 mu-law를 피하라 그리고 그 대신 8 , 또한 16-bit 선의 포맷들을 사용해라.

*8-bit Unsigned

이것은 일반적으로 모든 사운드 카드(S/B)의 포맷이다 또한 거의 모든 하드웨어에 제공된다. 각각의 샘플은 8비트로 저장된다.  0은 제일작은 레벨을 나타내고 255는 최고의 레벨을 나타낸다. 자연스러운 레벨은 128이다(0x80 (16진법)) 그러나  이것은 조용한 가운데에서 레코딩을 진행해도 약간의 소음이있다 그래서 바이트값 127(0x7f)에서 129(0x81)사이에 변화를 준다. C언어에서 의 데이터 값으로는 unsigned char형을 사용한다 . signed 8-bit 포맷에서 unsigned 바꾸기 위하여 128의 값을 가감하여 바꾼다.

*16-bit Signed

16비트 포맷으로 작업을 해야 할땐 주의해야 할점이있다. 16비트 데이터는cpu와 오디오 장치의 설계에 의존하여 이식할수 없다. 상황이 정상적인 soundcard를 가지고 little-endian을 사용하는  x86 CPU를 사용할 때에는  간결하다. 이러한 경우는 cpu와 사운드카드가 같은 16bit 엔코딩을 사용할때이다 그러나 위와 같지 않다면 그렇지는 않다.
16 비트 엔코딩은 일반적으로 little-endian(byte ordering)같은 사운드 하드웨어에서 AFMT_S16_LE로 사용한다. 그러나  다른 기계들은 오직 오디오 칩에 big-endian만을 제공한다.
16비트 signed 데이터는 c언어에서 사용될시에는 signed short로 사용 되는 것이 제일 적합하다 그러나 이러한 사실은 오직 little-endian 머신에서만 적합되는 말이다. 덧붙여서 C  표준에서는 데이터 타입을 정의 하지 않았다 그래서 short가 16비트의 머신에 적합한지는 아무도 보증을 해줄수 없다. 이러한 이유로 unsigned short 형의 array를 사용할시에는 신중해야 한다.
적당한 방법은 array of unsigned char를 사용하는 것이다 그리고   드라이버에 pass 하기전에 직접 assemble/disassemble the buffer해버리는 것이다 .

NOTE
*참고로 byte ordering을 해주는 함수로는
*Unsigned short interger
   htons() : host -to- network 바이트변환
   ntohs() : network -to- host
*Unsigned long interger 변환
   htonl() :host -to-network
   nyohl() :network -to-host

/*****************Listing 9 - Handling 16-bit Data ****************/

unsigned char devbuf[4096];//8bit,apllication의 2배가있어야 받아들이수있다.
int applicbuf[2048];//16bit
int i, p=0;

/********* Place 2048 16-bit samples into applicbuf[] here **********/

for (i=0; i<2048; i+=2)
{
        /* first send the low byte then the high byte */
        devbuf[p++] = (unsigned char)(applicbuf[i] & 0xff);
        devbuf[p++] = (unsigned char)((applicbuf[i] >> 8) & 0xff);
}

/********** Write the data to the device*********/

       < 풀   이 >

char  => 8 bit =>unsigned char devbuf[p]
int     =>16 bit =>int applicbuf[p]

11110111 00110010
00000000 11111111
_________________ &  
         00110010 devbuf[0]=00110010

11111111 11110111            
00000000 11111111 &
_________________
         11110111 devbuf[1]=111100111    

 AFMT_S16_NE 포맷은 프로그램에서  encoding 이나 decoding을 원할 때 사용하는forma
t이다.그것은 자동적으로 올바른 cpu구조의 포맷을 찾아내어준다. 이러한 방법은 보통 단순
하게 signed short format 샘플을 저장하는 것을 가능하게 해준다

24 and 32 bit signet formats

AFMT_S32_S32_LE ,AFMT_S32_BE, 그리고 AFMT_S32_NE 포맷은 32bit-signed포맷이다
 데이터는 왼쪽부터 저장하며 사용하지 않는 비트는 0으로 설정한다

13. Multiple channels

OSS는 전문적인 멀티 오디오 장치 채널을 제공한다  디바이스는 2개의 다른 다중채널의 핸
들링에 의존한다.
이 방법안에(서) 복합적인 채널들을 지지하는 단지 하나의 장치 파일(/dev/dsp)이 있다. 어플리 케이션은 단순하게  SNDLC_DSP_CHANNELS  ioctl을 사용하여 multiple channels(2,3,4,...,N)을 요구합니다.

*Multiple audio device method

여러 장치 파일들(dev/dspM ,dev/dspM+N   : M=처음 채널의 장치 넘버  N= 채널의 넘버 ) 이방법은 같은  같은 애플리케이션 혹은 여러 부분들의 애플리케이션에서 채널을 개별적으로Open 할 것이다 그것은 또한 여러 장치 파일들이 stereo pairs처럼 조직화 되는 것이 가능하다(/dev/dspM = channels0/1, dev/dspM+1=channels2/3,etc)  

*Mixed multi device and interleaved method

디바이스들은 독립된 복합적인 디바이스 화일을 준다.  거의 무한한 유연성을 제공하는 3번째 방법을 사용하기에 가능합니다.    

이 방법은 하나의 어플리케이션이 하나의 파일( /dev/dsp0)을 열수있게 한다. 그리고 그것은 스테레오 (2 channel)모드(이것은 /dev/dsp1의 보류된 채널을 장착한다 ) 이다. 다른 어플리케이션은 아마도 /dev/dsp2를 열것이고 그리고 그것은 4채널모드일 것이다 (그래서 채널은 /den/dsp3./dev/dsp4 그리고 /dev/dsp5을 할당해 얻을 것이다.) 최후의 세번쨰그리고 네번째 어플리케이션은 /dev/dsp6 그리고 /dev/dsp7을 open할 것이다 그리고 그것들은 mono 모드를 사용할 것이다 . 모든 가능한 채널은 제한이 복합적이다

14. Changing mixer settings for an audio device

일반적으로 오디오 어플리케니션이 mixer 세팅을 건드리지 않을 것을 권장한다. 사용자는 믹서 전용 프로그램으로 믹서 쎄팅하기 때문이다. 만약 그렇지 않다면 프로그램들은 서로 충돌하게 될것이다. 적어도 어플리케이션이  위의 문제를 염두에 두어야 믹서의 적용부분을 깨지않는다. 믹서의 제공이 깨지기 때문에 실패한 애플리 케이션은 너무나 많다.

만약 프로그래머가 그래도 믹서를 변화시키겠다면 아래의 방법을 따라라

1) 어떤 /dev/mixer# 장치도 열지 않고 대신 mixer ioctl(OSS 문서 중 믹서 프로그램 부분을 참조 바람 )을 오디오 장치 파일에서 직접 호출 해라 프로그래머는 믹서 접근을 위하여 다시 장치를 재개하려고 노력 하지 말아야 한다.

2)오직 PCM(PCM은 아날로그 데이터 전송을 위한 디지털 설계이다. ) 볼륨과 레코딩 디바이스 세팅을 바꿀수 있다. 그것들은 같은 방법으로 모든 디바이스를 콘트롤 한다  만약 같은 믹서 제어가 실패해도 그것은 에러 상태가 아니다.

3)만약 너가 믹서를 접근할시 어떤 종류의 에러를 만났을때 그것을 무시하거나 혹은 멈추하하거나 게다가 믹서를 작동 시킨다면  당신이 할수 잇는 가장큰 실수는 믹서 때문에 어플리 케이션을 중단 시키는 것이다.

 위에 나와있는 말들은 OSS 의 오디오 어플리 케이션의 프로그래밍을 할때 기초가 되는 부분 들이다 그러나 그것들은 게임과 같은  실시간 오디오 어플리케이션 ,음성 회의 시스템 ,음성분석툴 , 다른 많은 처리 들을 하기위해서는  더많은 기술을 요구로 한다 이러한 것들 고급 프로그래밍 부분(Advanced Programming Topics)에서 다루고 있다 . 그러나 위의 부분 들을 잘 이해 하고 있다면 많은 오디오 관련 프로그래밍을 할때에 큰 힘이 될것이다.


출처

OSS(Open Sound System) 오디오 프로그래밍

제목: OSS 오디오 프로그래밍
날짜: 2004년 2월 26일 목요일
소속: 상명리눅스연구회 ELF (http://elf.smu.ac.kr/)
작성: 김보람 (isom@smu.ac.kr)

본 문서는 레코딩 소스를 선택하고 볼륨을 조절하는 믹서(Mixer)
프로그래밍, 레코딩 소스로 부터 녹음하여 파일로 저장하고 재생할 수 있는
오디오(Audio) 프로그래밍을 위한 기본 단계를 OSS(Open Sound System)
API에 기반하여 설명한다.
_______________________________________________________________________

Contents
---------

1. OSS (Open Sound System)
2. Devices
3. Header Files
4. Mixer Programming
  4.1 Open the OSS Mixer Device
  4.2 Get Available Mixer Channels (optional)
  4.3 Get Available Recording Devices (optional)
  4.4 Select Recording Source
  4.5 Get Currently Active Recording Sources (optional)
  4.6 Set Volume
  4.7 Get Volume (optional)
  4.8 Close the Mixer Device
5. Audio Programming
  5.1 Open the OSS Audio Device
  5.2 Select Audio Format (Number of Bits)
  5.3 Select the Number of Channels (Mono/Stereo)
  5.4 Select Sampling Rate (Speed)
  5.5 Record or Play
  5.6 Synchronize
  5.7 Close the OSS Audio Device
6. References
7. License
8. Revision History

_______________________________________________________________________


1. OSS (Open Sound System)
---------------------------

리눅스 커널에서는 기본적으로 사운드 디바이스 드라이버를
제공한다. 하지만, 리눅스 커널에 기본적으로 포함되어 있는 드라이버는
수가 적어 다양한 사운드 디바이스를 사용할 수 없다. 따라서, 몇가지의
대안이 존재한다.
리눅스의 커널에 기본적으로 포함된 사운드 디바이스 드라이버를
OSS/Free[1]라고 한다. OSS/Free는 4Front Technologies[2]에서 개발한
OSS[3]를 따르는 공개 드라이버로 4Front Technologies의 OSS 개발자가
대부분을 개발하였다. 그러나 다양한 드라이버가 OSS/Free에는 존재하지
않고, 그 외의 많은 드라이버는 4Front Technologies에 의해 상용으로
개발된다.  따라서, 리눅스 진영에서 무료로 사용할 수 있는 드라이버는
그리 많지 않게 된다. 이러한 현실을 극복하기 위해서 SuSE[4] 커뮤니티로
부터 ALSA(Advanced Linux Sound Architecture) Project[5]가
시작되었다. ALSA는 오픈소스 프로젝트로 수 많은 드라이버들이 계속
개발되고 무료로 공개되고 있다.
기본적으로 리눅스에서의 오디오 프로그래밍은 위 세가지 종류의 디바이스
드라이버에 의존적이다. 하지만, OSS/Free[1]와 OSS[3]는 동일한 OSS
API[6]를 사용하고, ALSA[5] 또한 OSS API를 에뮬레이션[7] 하기 때문에
리눅스의 오디오 프로그래밍은 OSS API만으로 충분히 구현 가능하다. 물론,
보다 진보된 기능을 사용하고 싶다면 ALSA Library API[8]를 사용하여야 할
것이다.
OSS 드라이버의 설치 또는 ALSA 드라이버의 설치는 본 문서의 범위를
벗어나기 때문에 필요하다면 Installing Open Sound System[9] 또는 ALSA
Sound Card Matrix & INSTALL documentation[10]을 참조하기 바란다.


2. Devices
-----------

OSS[3]에는 다양한 디바이스가 존재한다. 기본적으로 유닉스 기반의
시스템은 모든 디바이스를 파일과 동일하게 처리하기 때문에, OSS에
있어서도 예외는 아니다. OSS에 의해서 지원되는 디바이스 파일은 다음과
같다:

- /dev/mixer
  사운드 카드의 믹서에 접근하기 위한 디바이스 파일
- /dev/sequencer
  전자 음악(Electronic Music)을 목적으로 하는 디바이스 파일
- /dev/midi
  Raw 모드로 동작하는 MIDI버스 포트의 인터페이스로 TTY(캐릭터
  터미널)과 유사하게 동작하는 디바이스 파일
- /dev/dsp
  디지탈 오디오 어플리케이션(Audio Application)의 주요 디바이스 파일로
  기본적으로 8-비트의 부호없는(unsigned) 선형(linear) 인코딩을
  사용한다.
- /dev/audio
  /dev/dsp와 동일한 디바이스 파일이지만, 기본 인코딩은 Mu-Law 인코딩
  이다.
- /dev/dspW
  /dev/dsp와 동일한 디바이스 파일이지만, 기본 인코딩은 16-비트의
  부호있는(signed) 리틀-엔디안(little-endian)을 사용한다.
- /dev/sndstat
  다른 디바이스 파일과 달리 사운드 카드의 진단(Diagnostic)을 위한
  디바이스 파일로 사람이 읽을 수 있는(Human readable format) 정보를
  제공한다. 따라서, 'cat /dev/sndstat'과 같은 명령으로 정보를 볼 수
  있다.
- /dev/dmfm
  FM 신디사이저(Synthesizers)를 위한 Raw 인터페이스(Interface) 이다.
- /dev/music
  /dev/sequencer와 매우 유사한 디바이스 파일로 MIDI 디바이스와
  신디사이저(Synthesizers)를 동일한 방법으로 다룬다.

위와 같이 OSS에서는 다양한 사운드 디바이스를 지원하지만, 본 문서에서
실제로 사용될 디바이스는 /dev/mixer와 /dev/dsp(또는 /dev/audio나
/dev/dspW)와 같다. /dev/mixer는 각 채널별 음량(Volume)이나
녹음(Recording)의 대상이 되는 디바이스를 설정할 수 있으며, /dev/dsp는
설정된 녹음 디바이스로 부터 데이타를 읽거나 오디오를 출력하기 위해
사용된다.
OSS에서 대부분의 디바이스는 하나 이상 존재하는 것이 가능하다. 따라서
위의 디바이스 파일뒤에 숫자를 붙여 구분하게 되며, /dev/mixer 또는
/dev/dsp와 같은 파일은 심볼릭 링크(Symbolic Links)로
존재한다. 기본적으로 각 심볼릭 링크는 첫번째 디바이스를
가리킨다(그러나 이를 보장할 수는 없다).
또한 리눅스의 경우 OSS는 모든 디바이스의 메이져 번호(Major Number)를
14로 고정하고, 0에서 8 사이의 번호를 마이너 번호(Minor Number)의 하위
4-비트에 위치시켜 디바이스의 종류를 구분한다. 또한 마이너 번호에서
분류 번호 다음의 4-비트에 디바이스의 번호를 더하여 최종 디바이스의
마이너 번호가 결정된다(예: /dev/dsp1의 마이너 번호는
19(16+3:00010011)). 따라서, 실제로 다음과 같은 디바이스 파일이
존재하게 된다:

Major    Minor    Name
-----------------------------------------------------------------------
14       0        /dev/mixer0
14       1        /dev/sequencer
14       2        /dev/midi00
14       3        /dev/dsp0
14       4        /dev/audio0
14       5        /dev/dspW0
14       6        /dev/sndstat
14       7        /dev/dmfm0
14       8        /dev/music
14       16       /dev/mixer1
14       19       /dev/dsp1
14       35       /dev/dsp2

본 문서에는 /dev/mixer와 /dev/dsp(또는 /dev/audio나 /dev/dspW) 이외의
디바이스에 대한 내용은 다루지 않는다. 자세한 내용이 알고 싶다면 OSS
API[6]을 참조하기 바란다.


3. Header Files
----------------

리눅스에서 OSS API[6]를 이용한 오디오 프로그래밍을 하기 위해서는 우선
오디오 디바이스와의 통신을 위해 필요한 다양한 함수와 구조체, 상수등을
담고 있는 헤더 파일(Header File)을 include하여야 한다. OSS API에서는
단지 하나의 C언어 헤더 파일 <sys/soundcard.h>만이
요구된다(<linux/soundcard.h>를 사용할 수 있지만, 이는 표준이
아니다). 그러나, OSS API를 사용하기 위한 ioctl, read, 또는 write와
같은 표준 함수나 시스템 콜(System Call)을 사용하기 때문에 다음의 헤더
파일(Header Files)들을 필요로 한다.

-----------------------------------------------------------------------
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/soundcard.h>
-----------------------------------------------------------------------


4. Mixer Programming
---------------------

믹서(Mixer) 프로그래밍은 각종 사운드 채널(Channel)의 음량(Volume)을
설정하고, 녹음(Recording)을 위한 디바이스를 선택하는 방법을
제공한다. 이와 같은 프로그램은 기본적으로 다음과 같은 순서에 따라
실행된다:

1. OSS 믹서 디바이스 열기
2. 사용 가능한 믹서 채널 얻기 (옵션)
3. 사용 가능한 레코딩 디바이스 얻기 (옵션)
4. 레코딩 소스 선택하기
5. 현재의 레코딩 소스 얻기 (옵션)
6. 음량 설정하기
7. 음량 구하기 (옵션)
8. OSS 믹서 디바이스 닫기

이제 위의 각 단계에 대해 자세히 살펴 보겠다.

4.1 Open the OSS Mixer Device

첫번째 단계는 매우 비교적 매우 간단하다. open 함수를 이용하여
믹서(Mixer) 디바이스를 열고 파일 기술자(File Descriptor)를 얻어오면
된다. open 함수를 이용한 코드는 다음과 같다:

-----------------------------------------------------------------------
int mixer;
char *device = "/dev/mixer";
if ( (mixer = open(device, O_RDWR)) < 0 )
{
  // Could not open device
}
-----------------------------------------------------------------------

디바이스 이름 device는 기본적으로 /dev/mixer 또는 사용자 입력을
사용하는 것이 좋다. 이는 프로그램의 이식성(Portability)를 높이기 위한
방법으로 프로그래머가 /dev/mixer0과 같은 장치로 값을 고정한다면,
프로그램은 특정 하드웨어에서만 작동하거나 오동작을 할 수
있다. /dev/mixer를 사용할 경우는 심볼릭 링크로 사용자가 변경할 수 있기
때문에 큰 문제가 되지 않는다.
open 호출은 성공하였을 경우 디바이스의 파일 기술자를 mixer로 반환하고,
실패하였을 경우 -1로 설정한다.

4.2 Get Available Mixer Channels (optional)

이번 단계는 생략이 가능하다. 하지만, 모든 사운드 디바이스에서 사용할
수 있는 채널이 같은 것은 아니다. 예를 들어 USB 카메라(Camera)에 내장된
마이크(Mic.)와 같은 사운드 디바이스는 오직 mic만이 가능한 채널이지만,
보편적인 사운드 카드는 보다 많은 채널을 지원한다. 따라서, 범용적인
프로그램의 작성을 위해서는 현재 디바이스의 채널을 알아보아야 할
것이다. 이를 위해 ioctl을 통한 쿼리를 이용하게 된다. 다음의 코드는
사용가능한 믹서 채널을 알아보기 위해 쿼리를 보낸다:

-----------------------------------------------------------------------
int devmask = 0;
if ( -1 == ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &devmask) )
{
  // Query failed
}
-----------------------------------------------------------------------

ioctl 호출은 성공하였을 경우 0이 아닌 수를 반환하며, 실패하였을 경우
-1을 반환한다.
위에서 사용된 SOUND_MIXER_READ_DEVMASK ioctl 호출은 성공하였을 경우
devmask에 사용가능한 채널들의 마스크(Mask)를 담는다. 기본적으로 각
채널은 고유의 상수를 갖는다. 각 상수는 0부터 상수
SOUND_MIXER_NRDEVICES 사이의 정수이다. 따라서, 마스크에 특정 채널의
비트를 확인하기 위해서는 다음의 식을 사용하여야 한다:

-----------------------------------------------------------------------
if ( (1 << SOUND_MIXER_MIC) & devmask )
{
  // SOUND_MIXER_MIC is available.
}
else
{
  // SOUND_MIXER_MIC is not available.
}
-----------------------------------------------------------------------

각 채널에 해당하는 상수는 헤더 파일 <sys/soundcard.h>와 OSS API[6]를
참조하기 바란다.

4.3 Get Available Recording Devices (optional)

이번 단계 또한 생략할 수 있지만, 위의 믹서(Mixer) 채널(Channels)과
동일한 이유로 이식성(Portability)을 위해 사용하는 것이 좋다. 이번
단계에서는 녹음(Recording)을 위해 사용될 수 있는 디바이스를 알아보기
위해 사용된다. 이 또한 ioctl 호출을 이용하여 쿼리를 보내게 되는데 이는
다음의 코드를 사용한다:

-----------------------------------------------------------------------
int recmask = 0;
if ( -1 == ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) )
{
  // Query failed
}
-----------------------------------------------------------------------

SOUND_MIXER_READ_RECMASK ioctl도 recmask에 마스크(Mask)를
담는다. 따라서 위와 동일한 방법으로 다음과 같이 테스트 할 수 있다.

-----------------------------------------------------------------------
if ( (1 << SOUND_MIXER_MIC) & recmask )
{
  // SOUND_MIXER_MIC is available.
}
else
{
  // SOUND_MIXER_MIC is not available.
}
-----------------------------------------------------------------------

4.4 Select Recording Source

녹음(Recording)에 사용되는 소스(Sources)는 디바이스에 따라 다르지만,
보편적으로 한가지만 사용 가능하다. 녹음을 위한 소스를 선택하기
위해서는 다음의 ioctl 호출을 사용한다:

-----------------------------------------------------------------------
int srcmask = 0;
int src = SOUND_MIXER_MIC;
srcmask = srcmask | (1 << src);
if ( -1 == ioctl(mixer, SOUND_MIXER_WRITE_RECSRC, &srcmask) )
{
  // Set failed
}
-----------------------------------------------------------------------

위의 호출에서 주의할 점은 여러가지 소스를 선택하는 것 또한 가능하기
때문에, srcmask는 마스크(Mask)가 되어야 한다는 것이다. 따라서,
srcmask에 선택된 소스의 비트를 추가하기 위해 srcmask = srcmask | (1 <<
src) 와 같은 식이 사용된다.
기본적으로 사용자가 각 소스에 해당하는 상수를 인지하기는
어렵다. 따라서, 각 소스에 할당된 고유의 이름이 존재하는데 이는
제공되는 배열 SOUND_DEVICE_LABELS를 통해 다음과 같이 구할 수 있다:

-----------------------------------------------------------------------
int index;
char *src = "mic";
char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
for (index = 0; index < SOUND_MIXER_NRDEVICES; index++)
{
  if ( 0 == strcmp(names[index], src) )
    srcmask = srcmask | (1 << index);
}
-----------------------------------------------------------------------

4.5 Get Currently Active Recording Sources (optional)

이번 단계에서는 앞서 설정한 녹음(Recording) 소스를 확인하기 위한
방법을 설명한다. 이번 단계 또한 생략이 가능하지만, 정확한 설정이
이루어졌는지 확인하기 위해서 사용하는 것이 좋다. 이번 단계 또한 다음의
ioctl 호출을 사용하여 수행하며, 현재 녹음(Recording) 소스(Source)의
마스크(Mask)를 반환하게 된다:

-----------------------------------------------------------------------
int index;
int recsrc = 0;
if ( -1 == ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) )
{
  // Query failed
}
for (index = 0; index < SOUND_MIXER_NRDEVICES; index++)
{
  if ( (1 << index) & recsrc )
    // Current recording source
}
-----------------------------------------------------------------------

4.6 Set Volume

이번 단계는 음량을 조절하기 위한 단계이다. 이번 단계에서 음량을
조절하기 위해서는 사운드 카드가 확실하게 지원하는 채널(Channels)을
사용하여야 한다. 채널을 바꾸는 동작 역시 ioctl 호출을 사용하며,
MIXER_WRITE() 매크로(Macro)를 사용하여 음량을 조절할 채널을 선택하고
ioctl의 파라미터(Parameter)로 0과 100사이의 음량을 지정하면 된다. 단,
음량은 0과 100사이에서 요구한 볼륨이 정확하게 설정되지 않을 수
있다. 따라서 실제로 설정된 음량이 호출이 완료된 후 파라미터를 통해
반환된다.
만일 채널이 스테레오(Stereo)를 지원할 경우에는 왼쪽과 오른쪽의 음량을
각각 설정할 수 있는데 이 경우에 왼쪽의 음량은 하위 8비트에 오른쪽의
음량은 그 위의 8비트에 저장되게 된다. 또한 모노(Mono)의 경우에는
왼쪽의 음량만을 설정한 채 나머지는 0으로 채우게 된다. 따라서 음량을
설정하기 위해서는 다음의 코드를 이용여야 한다:

-----------------------------------------------------------------------
int stereomask = 0;
int volume = 70;
if ( -1 == ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereomask) )
{
  // Query failed
}

if ( (1 << SOUND_MIXER_VOLUME) & stereomask )
  volume = volume | (volume << 8);

if ( -1 == ioctl(mixer, MIXER_WRITE(SOUND_MIXER_VOLUME), &volume) )
{
  // Set failed
}
// Print actual volume
-----------------------------------------------------------------------

4.7 Get Volume (optional)

이번 단계는 앞서 설정한 음량이 올바르게 설정되었는지 확인하기 위해서,
사용되는 단계로 생략이 가능하지만, 올바르게 음량이 설정되었는지 또는
현재 설정된 음량을 확인하기 위하여 사용한다. 사용법은 앞의 4.6 Set
Volume장에서와 비슷하며, 다음과 같다:

-----------------------------------------------------------------------
if ( -1 == ioctl(mixer, MIXER_READ(SOUND_MIXER_VOLUME), &volume) )
{
  // Query failed
}
if ( (1 << SOUND_MIXER_VOLUME) & stereomask )
{
  printf("Current left master volume level is %d\n", (0xFF & volume) );
  printf("Current right master volume level is %d\n", (0x00FF & volume) );
}
else
{
  printf("Current master volume level is %d\n", (0xFF & volume) );
}
-----------------------------------------------------------------------

4.8 Close the Mixer Device

마지막 단계는 매우 간단하다. 단순히 앞서 open한 믹서(Mixer) 디바이스를
다음의 호출로 닫아주면 된다.

-----------------------------------------------------------------------
close(mixer);
-----------------------------------------------------------------------


5. Audio Programming
---------------------
오디오(Audio) 프로그래밍은 선택된 녹음(Recording) 디바이스로 부터
데이타를 읽어와 저장하고, 저장된 데이타를 출력하기 위한 방법을
제공한다. 이와 같은 프로그램은 기본적으로 다음과 같은 순서에 따라
실행된다:

1. OSS 오디오 디바이스 열기
2. 오디오 포멧 (비트수) 설정
3. 채널의 수(모노/스테레오) 설정
4. 샘플링 레이트(Sampling Rate) 설정
5. 녹음(Recording) 및 재생(Playing)
6. 동기화(Synchronize)
7. OSS 오디오 디바이스 닫기

오디오 프로그래밍에 있어서는 위의 단계를 지키는 것이 매우
중요하다. 순서가 뒤바뀔 경우 프로그래머가 원하지 않은 동작을 할 수
있다. 따라서 반드시 위의 순서에 따라 프로그램을 작성하기
바란다. 자세한 이유는 OSS API[6]를 참조하기 바란다.
이제 위의 각 단계에 대해 자세히 살펴 보겠다.

5.1 Open the OSS Audio Device

오디오(Audio) 프로그래밍의 첫번째 단계는 단지 디바이스의 이름이
변경되는 것을 제외하면 믹서(Mixer) 프로그래밍의 첫번째 단계와
동일하다. 또한, 같은 이유로 특정한 디바이스 파일 보다는 /dev/dsp와
같은 보편적인 이름을 사용하거나 사용자 입력을 사용할 것을
추천한다. 또한 녹음(Recording)을 위해 디바이스를 사용하려 한다면
O_RDONLY로, 재생(Playing)을 위해서라면 O_WRONLY로 열어야 한다. 물론
디바이스에 따라 O_RDWR로 디바이스 파일을 오픈할 수 있지만, 이는 Full
Duplex를 지원하는 디바이스로 제한되고 별도의 설정을 필요로
한다. 따라서, Full Duplex와 관련된 내용은 본 문서에서 다루지 않으며,
보다 보편적인 Half Duplex를 기준으로 설명하겠다. Full Duplex와 관련된
자세한 내용은 OSS API[6]을 참조하기 바란다.

-----------------------------------------------------------------------
int audio;
char *device = "/dev/dsp";
if ( (audio = open(device, O_RDONLY)) < 0 ) or
if ( (audio = open(device, O_WRONLY)) < 0 )
{
  // Could not open device
}
-----------------------------------------------------------------------

5.2 Select Audio Format (Number of Bits)

이번 단계는 오디오 포멧을 설정하는 단계이다. 오디오 포멧은 사운드
데이타의 최소 단위인 샘플(Sample)을 표현하기 위한 방법을
말한다. 오디오 포멧 각각의 포멧에 대응하는 상수가 존재하며, 다음과
같은 오디오 포멧이 OSS에서 제공된다:

Name            Description
-----------------------------------------------------------------------
AFMT_QUERY      현재의 오디오 포멧을 쿼리한다.
AFMT_MU_LAW     Mu-Law
AFMT_A_LAW      A-Law
AFMT_IMA_ADPCM  16-비트 오디오 시퀀스(Sequence)를 A 4:1로 압축
AFMT_U8         부호없는(unsigned) 8-비트
AFMT_S16_LE     부호있는(signed) 16-비트 리틀-엔디안(Little-Endian)
AFMT_S16_BE     부호있는(signed) 16-비트 빅-엔디안(Big-Endian)
AFMT_S16_NE     부호있는(signed) 16-비트 (시스템 의존적)
AFMT_S8         부호있는(signed) 8-비트
AFMT_S32_LE     부호있는(signed) 32-비트 리틀-엔디안(Little-Endian)
AFMT_S32_BE     부호있는(signed) 32-비트 빅-엔디안(Big-Endian)
AFMT_U16_LE     부호없는(unsigned) 16-비트 리틀-엔디안(Little-Endian)
AFMT_U16_BE     부호없는(unsigned) 16-비트 빅-엔디안(Big-Endian)
AFMT_MPEG       MPEG MP2/MP3 포멧 (현재 지원되지 않음)

대부분의 사운드 카드는 AFMT_U8 포멧을 지원하며, 요즘 대부분의 사운드
카드에서는 AFMT_S16_LE 포멧을 사용하고 있다. 하지만 부호있는(signed)
16-비트의 경우에는 시스템에 따라 정확한 작동을 보장하지 못할 수
있다. 이에 관한 자세한 정보는 OSS API[6]을 참조하기 바란다. 본
문서에서는 리틀-엔디안 방식의 시스템을 사용한다는 가정아래 가장
보편적인 AFMT_S16_LE를 사용하도록 하겠다. 오디오 포멧을 설정하기
위해서는 다음의 ioctl 호출을 사용한다:

-----------------------------------------------------------------------
int format = AFMT_S16_LE;
if ( -1 == ioctl(audio, SNDCTL_DSP_SETFMT, &format) )
{
  // Set failed
}
if (AFMT_S16_LE != format)
{
  // Not support
}
-----------------------------------------------------------------------

오디오 포멧의 설정 또한 믹서(Mixer) 프로그래밍에서의 음량(Volume)
설정과 같이 요청한 오디오 포멧을 지원하지 않는다면, 근접한 오디오
포멧으로 설정하고 파라미터(Parameter)를 통해 그 값을 반환한다. 따라서,
설정 후 적용된 값을 검사하여야 할 필요가 있다.

5.3 Select the Number of Channels (Mono/Stereo)

이번 단계는 우리가 녹음(Recording)하기 위해 선택한 소스의 채널의 수를
설정하는 단계이다. 반드시 이번 단계는 샘플링 레이트(Sampling Rate)의
설정보다 앞서 이루어져야 한다. 자세한 이유는 OSS API[6]를 참조하기
바란다. 채널의 설정 또한 지원하지 않는 채널의 수라면 근사치로 설정하게
되므로 ioctl의 파라미터(Parameter)를 통한 반환된 값을 확인하여야
한다. 채널을 설정하기 위한 ioctl 호출은 다음과 같다:

-----------------------------------------------------------------------
int channels = 2;
if ( -1 == ioctl(audio, SNDCTL_DSP_CHANNELS, &channels) )
{
  // Set Failed
}
if (2 != channels)
{
  // Not Support
}
-----------------------------------------------------------------------

5.4 Select Sampling Rate (Speed)

이번 단계는 샘플링 레이트(Sampling Rate)를 결정하는 단계이다. 샘플링
레이트는 소리의 질을 결정하는 중요한 요소 중 하나이다. OSS API[6]는
1Hz에서 2GHz의 대역폭을 지원한다. 기본적으로 지원되는 샘플링 레이트는
8kHz로 전화기 수준의 음질을 뜻하며, CD 수준은 44.1kHz, DVD 수준은
96kHz의 값을 갖는다. 우리는 기본적인 8kHz의 값으로 설정 할 것이며,
디바이스에 따라 지원하는 샘플링 레이트의 값이 서로 다르다. 따라서,
샘플링 레이트의 값 또한 요청한 값에 근사한 값으로 설정될 수
있다. 그러므로, 역시 ioctl 호출 후의 파라미터(Parameter)를 조사하여야
한다. 샘플링 레이트를 설정하기 위한 ioctl 호출은 다음과 같다:

-----------------------------------------------------------------------
int speed = 8000;
if ( -1 == ioctl(audio, SNDCTL_DSP_SPEED, &speed) )
{
  // Set Failed
}
if (8000 != speed)
{
  // Not Support
}
-----------------------------------------------------------------------

5.5 Record or Play

이제 주요 작업인 녹음(Recording)와 재생(Playing)을 하는 단계 이다. 이
단계에서는 우선 오디오(Audio) 데이타를 저장할 버퍼(Buffer)를
생성하여야 하는데, 버퍼의 자료형(Data Type)은 오디오 포멧에 의존적으로
결정된다. 예를 들면, 리틀 엔디안(Little-Endian) 형태의 데이타를 빅
엔디안(Big-Endian) 머신(Machine)에서 사용할 경우 별도의 처리를 위해
데이타 타입이 요구될 수 있다. 또한, 버퍼의 크기는 샘플의 크기의 배수로
설정 되어야 한다.
만약 앞에서 디바이스 파일을 읽기전용으로 열었다면, 프로그래머는
데이타를 읽어 저장하기 위한 녹음(Recording)을 수행하여야 한다. 녹음은
단순히 디바이스 데이타에서 read 호출을 사용하여 정해진 바이트 만큼을
읽어와 파일로 저장하는 작업을 뜻한다. 단, 오디오 디바이스로 부터의
read는 EOF(End of File)가 존재하지 않기 때문에 프로그래머가 정확한
녹음의 끝을 알려야 한다. read 호출을 이용하여 오디오 데이타를 저장하는
방법은 다음과 같다:

-----------------------------------------------------------------------
int index;
int length = 640;
int count = 640;
char *file = "file_name";
FILE *fin;
int flength;
unsigned char buffer[2048];
if ( NULL == (fin = fopen(file, "w")) )
{
  // Could not open device
}
for (index = 0; (index < 1000) && (length > 0) && (flength > 0); index++)
{
  if ( -1 == (length = read(audio, buffer, count)))
  {
    // Read failed
  }
  flength = fwrite(buffer, 1, count, fin);
}
fclose(fin);
-----------------------------------------------------------------------

녹음된 사운드를 재생(Playing)하는 방법은 위와 거의 비슷한 방법으로
write 호출을 사용한다. write 호출의 사용은 다음과 같다.

-----------------------------------------------------------------------
int index;
int length = 640;
int count = 640;
char *file = "file_name";
FILE *fout;
int flength;
unsigned char buffer[2048];
if ( NULL == (fout = fopen(file, "r")) )
{
  // Could not open device
}
flength = fread(buffer, 1, count, fout);
while ( (flength > 0) && (length > 0) )
{
  if ( -1 == (length = write(audio, buffer, count)))
  {
    // Write failed
  }
  flength = fread(buffer, 1, count, fout);
}
fclose(fout);
-----------------------------------------------------------------------

5.6 Synchronize

이번 단계는 녹음(Recording) 또는 재생(Playing)을 완료한 후 현재
버퍼(Buffer)에 남아있는 내용을 모두 출력 또는 입력하기 위해서 동기화를
시도하는 단계이다. 이 또한 ioctl 호출에 의해서 이루어지는데, 다음의
호출이 버퍼의 내용이 모두 비워질 때까지 프로그램의 동작을
지연(Delay)시키게 된다:

-----------------------------------------------------------------------
if ( -1 == ioctl(audio, SNDCTL_DSP_SYNC, 0) )
{
  // Sync failed
}
-----------------------------------------------------------------------

5.7 Close the OSS Audio Device

마지막으로 믹서(Mixer) 프로그래밍의 경우와 같이 open된 디바이스 파일을
닫기 위해 다음의 코드를 수행하여야 한다:

-----------------------------------------------------------------------
close(audio);
-----------------------------------------------------------------------


6. References
--------------

[1]  OSS(Open Sound System)/Free,
     http://www.opensound.com/ossfree/index.html.
[2]  4 Front Technologies, http://www.4front-tech.com/.
[3]  OSS(Open Sound System), http://www.opensound.com/.
[4]  SuSE, http://www.suse.com/.
[5]  ALSA(Advanced Linux Sound Architecture),
     http://www.alsa-project.org/.
[6]  Jeff Tranter, Open Sound System Programmer's Guide, 4 Front
     Technologies, http://www.opensound.com/pguide/oss.pdf, 2000.
[7]  Takashi Iwai, NOTES ON KERNEL OSS-EMULATION, ALSA Project,
     http://www.alsa-project.org/~iwai/OSS-Emulation.html, 2003.
[8]  Jaroslav Kysela, Abramo Bagnara, Takashi Iwai, and Frank van de
     Pol, ALSA Library API on-line documentation, ALSA Project,
     http://www.alsa-project.org/alsa-doc/alsa-lib/, 2001.
[9]  Installing Open Sound System,
     http://www.4front-tech.com/install_gzipped.html.
[10] ALSA Sound Card Matrix & Install documentation,
     http://www.alsa-project.org/alsa-doc/.


7. License
-----------

Copyright (c) 2004 James G. Kim. Permission to distribute and modify
this document is granted under the GNU Free Documentation License. An
on-line copy is available at http://www.gnu.org/licenses/fdl.html


8. Revision History
--------------------

2004년 02월 26일  김보람  최초 작성


끝.

저작자 표시 비영리 변경 금지
크리에이티브 커먼즈 라이선스
Creative Commons License