[App 개발] 맥마메 분석 - 5 사운드 출력
본문
개인적으로 가장 관심있는 부분으로 접어들었습니다. 오랫동안 피씨 프로그래밍만 해오다 보니 매킨토시의 사운드 출력이 어떻게 이루어지는지를 궁금해 해 왔습니다. 최근에는 맥과 피씨가 유사한 칩셋들을 많이 공유하고 있어서 특성들이 많이 비슷해졌습니다만, 옛날 맥 II 시절만 해도 피씨는 따라오지 못할 정도로 막강한 그래픽/사운드 성능을 갖고 있었는데 말이지요. 그 때부터 쌓아온 노하우가 어떻게 활용되고 있는지 프로그래밍을 통해서 살펴보는 계기가 되겠습니다.
맥마메의 사운드 함수들은 macsound.c 에 선언되어 있습니다.
1) osd_start_audio_stream 함수
osd 함수들은 마메에서 OS Dependent 코드를 호출하는 인터페이스 함수들이라고 생각됩니다. 마메에서 osd_... 함수를 호출하는 코드부터 시작해서 어떻게 사운드 기능이 초기화되고 호출되고 종료되는지 살펴보면 되겠습니다.
먼저 osd_start_audio_stream() 함수를 보겠습니다. 전달인수는 아마도 mono/stereo 선택을 하는 변수인 것 같군요. 초반부에는 변수들을 초기화하는 루틴들이고, 사운드 버퍼도 초기화가 이루어지고 있군요. 그 다음에 상당히 흥미로워보이는 함수 호출이 있습니다. InitializeSoundBuffers() 라고 되어 있군요. 한 번 들어가보죠.
if (InitializeSoundBuffers())
{
sInitFailed = TRUE; // R. Nabet 001212 : we must not play anything
logerror("InitializeSoundBuffers routine failed (too little memory, or sound manager internal error?)
");
return 1;
}
버퍼를 초기화하고 파라미터를 선택한 다음 SndPlayDoubleBuffer() 함수를 호출하는군요.
헉, 그런데 이 함수는 카본에서 더 이상 지원하지 않는다고 테크노트에 써 있군요. (TN 1198) 그렇다면 이게 어떻게 된 일인가?
아, 그렇군요. ^^; 이것은 Double Buffer Version 이라고 표시가 되어 있군요. 테크 노트에서는 이제 카본에서 더 이상 더블 버퍼 사운드 출력을 지원하지 않는다는 뜻인가봅니다.
그래서 다시 찾아가 보았더니 밑에 Sound Command Version 이 있군요. 이것을 살펴보겠습니다.
여기서는 SndDoCommand() 함수가 호출되고 있습니다. 예상대로 퀵타임 함수였습니다. ^^ 파라미터로서는 SndChannelPtr, SndCommand, 그리고 wait status 를 전달하는군요. 맥마메 소스는 low level 매킨토시 사운드 출력 기능의 견본을 보는 것 같습니다. 거의 그대로 갖다 베껴 쓰고 싶은 충동이… ㅡㅡ;;;
err = SndDoCommand(sSoundChannel, &sSoundBufferCmd[i], true);
if (err != noErr)
return true;
2) osd_stop_audio_stream 함수
이름 그대로 스트림을 정지하는 함수이겠지요? 재미있게도 버퍼를 클리어하기 전에 잠시 딜레이를 주고 있군요. 사운드 시스템으로 하여금 셧다운을 할 수 있는 시간적 여유를 주는 딜레이라고 친절하게 코멘트가 달려 있고요.
그리고는 FreeSoundBuffers() 함수가 호출됩니다. 내용은 별반 다를 것은 없고요, 위의 InitializeSoundBuffers() 함수에서 설정된 버퍼 메모리를 해제하는 역할만 수행됩니다.
3) osd_update_audio_stream 함수
인수 buffer 는 INT16 타입의 포인터입니다. 보아하니 스테레오 채널인 경우 한 프레임에서 재생할 데이터 청크를 따로 분리해서 저장하고 있습니다. 인터리빙이 아니고요. 요렇다는 말이죠.
| Left Channel | Right Channel |
인터리빙이라면
LRLRLRLRLRLRLRLRLR…
요렇게 되어야 맞겠지요. 채널에 따라서 청크를 따로 구성하는 것이 더 유리한 것 같습니다. 왜냐하면 sSoundStream 구조체에 새 프레임 데이터를 추가할 때, sSoundStream 은 채널별로 따로 선언되어 있기 때문에, 그 갯수만큼 memcpy() 함수를 호출하면 간편하고 속도도 더 빠르기 때문입니다.
나머지는 모노/스테레오 처리, 프레임 크기와 현재 남아있는 데이터의 크기에 따라서 스트림의 어디에서부터 채워나갈 것인지를 계산하는 루틴입니다.
while (sFramesThisVideoFrame > 0)
{
// determine how many frames we can copy
framesToCopy = sSoundStreamFrames - soundIn;
if (framesToCopy > sFramesThisVideoFrame)
framesToCopy = sFramesThisVideoFrame;
// copy and count the samples
memcpy(&sSoundStream[soundIn << sSoundIsStereo], buffer, framesToCopy << (1 + sSoundIsStereo));
sFramesThisVideoFrame -= framesToCopy;
buffer += framesToCopy << sSoundIsStereo;
// adjust the output pointer
soundIn += framesToCopy;
sSoundInTotalFrames += framesToCopy;
if (soundIn >= sSoundStreamFrames)
soundIn -= sSoundStreamFrames;
}
4) Callback 함수
콜백 함수는 시스템에서 우리 모듈을 호출한다는 의미를 갖고 있죠. 멀티 태스킹 시스템에서 OS 가 시스템 리소스의 주도권을 잡고 있을 때 사용하는 방법 되겠습니다.
맥에서도 사운드 버퍼 갱신을 콜백 함수를 이용하는 것 같습니다. 먼저 UPP 를 만들고요, 그 다음 SndNewChannel() 함수를 호출해서 콜백 함수를 등록해 주는군요.
// allocate a UPP callback
sSoundCallback = NewSndCallBackUPP(SoundCallback);
// now allocate the channel
sSoundChannel = NULL;
err = SndNewChannel(&sSoundChannel, sampledSynth, (sSoundIsStereo ? initStereo : initMono) + initNoInterp + initNoDrop, sSoundCallback);
return (err != noErr);
함수의 자세한 사항들은 퀵타임 래퍼런스를 참조하시면 되겠습니다. 저도 래퍼런스 PDF 파일을 받긴 했는데, 총 1143페이지짜리 문서를 인쇄하고픈 맘은 들지 않는군요. ㅡㅡ;
사운드 콜백 함수에서 맨 처음 하는 일은 sSoundBuffer 배열의 sampleArea 멤버에 저장된 포인터에 sSoundStream 배열의 샘플을 복사하는 FillSoundBuffer() 함수를 호출합니다.
그리고 SndDoCommand() 함수를 호출합니다. 특이하게도 함수를 두 번 호출하는데요, 첫 번째는 bufferCmd 를 이용하는 호출이고, 두 번째는 callBackCmd 를 이용하는 호출입니다. 설정하는 파라미터가 약간 다릅니다.
먼저 bufferCmd 를 이용하는 출력에서는 param1 은 사용하지 않고, param2 를 사용하는데, 사운드 데이터의 헤더와 사운드 버퍼가 같이 들어있습니다. 그리고 callBackCmd 에서는 param1 이 사용되고 있습니다. 이것은 다음번 콜백 함수의 호출시 전달되는 파라미터 SndCommand 가 됩니다. param1은 user-defined 값이고, 사용자 마음대로 값을 선언할 수 있는데요, 예를 들어 AIFF 파일을 재생하는 중이었다… 한다면 파일을 다 읽어들이고 더 이상 재생할 것이 없을 때 다음번 콜백 함수에 1을 전송하고, 데이터가 아직 남았다 할 때에는 0을 전송해서 콜백 함수가 파일 재생중에 계속 호출될 수 있도록 로직을 구성할 수도 있겠지요.
맥마메에서는 callBackCmd 의 param1 에 현재까지 재생된 샘플 버퍼의 최종 위치를 전송함으로써 그 다음에 어디서부터 버퍼를 복사해야 할지를 알려주는 역할을 하고 있습니다. 하긴 맥마메는 프로그램 종료시까지 사운드 스트림이 끊임이 없으니까요.
5) 정리
맥마메의 사운드 출력은 퀵타임을 이용하고 있습니다. 출력 주파수는 CD 음질인 44.1KHz 를 사용하고 있고 스테레오 채널을 적용하고 있습니다. 물론 이 값은 변경 가능합니다.
사운드 출력 파라미터를 변경하게 되면 AIFF 헤더 파일의 내용을 변경해서 다시 사운드를 초기화해준 다음 콜백 함수 루프를 다시 돌리게 되겠습니다.
마메는 상당한 수의 FM 음향장비들을 에뮬레이션하고 있습니다. 고전 중의 고전인 3중 화음 PSG (AY-3-8910) 부터 시작해서 게임 PCB 에서 지원하는 야마하 칩셋들을 골고루 지원합니다. 그리고 코나미의 커스텀 칩 외에도 각 회사 고유의 칩셋과 PCM 장비들을 에뮬레이션하고 있죠.
그러나 출력 채널은 단 하나, 스테레오 44.1KHz PCM 출력이죠. 그리고 맥마메에서는 그것을 퀵타임 오디오 더블 버퍼 콜백 방식을 이용해서 재생하고 있습니다.
애플 사이트를 찾아 보시면 퀵타임 스트리밍 예제들도 있던데, 참조해서 보시면 더 좋을 것 같습니다.
최신글이 없습니다.
최신글이 없습니다.
댓글목록 0