본문 바로가기

Programming/DirectX

[DirectSound] 6. DirectSound 버퍼의 재생

반응형

버퍼의 생성까지 완료되면 이제 버퍼에 실제 데이터를 입력해서 재생하면 됩니다.

버퍼를 생성하면 약간의 코드만 추가하면 기본적인 재생이 가능합니다.

이번에 사용하는 방법은 비교적 짧은 길이의 소리를 한 번에 재생하는 방법입니다.

이후에 긴 길이의 소리나 스트리밍을 재생하는 방법인 버퍼의 통지를 구현할 예정입니다.

재생은 IDirectSoundBuffer8Play() 메소드를 통해서 합니다.

각각의 소리는 Secondary Buffer로 표현이 됩니다.

이 소리들을 Primary Buffer에서 섞어주기 때문에 동시에 다양한 소리를 듣는 것이 가능합니다.

Primary Buffer는 각 응용프로그램에 하나씩 자동으로 생성되기 때문에 따로 생성하지 않아도 됩니다.

재생은 다음과 같은 순서로 진행됩니다.

먼저 IDirectSoundBuffer8 인터페이스의 Lock() 메소드를 호출해서 사용가능한 버퍼의 포인터를 받습니다.

그리고 할당받은 포인터에 PCM 데이터를 기록(복사)합니다.

기록이 완료되면 Unlock() 메소드를 통해서 해제하면 됩니다.

일반적인 Lock을 통한 메모리 복사와 동일한 방법입니다.

재생할 때는 SetCurrentPosition() 메소드를 호출해서 위치를 지정한 이후에 Play()를 호출하면 됩니다.

소스 코드는 다음과 같습니다.

// Create Device Instance
// Create Secondary Buffer Instance

LPVOID lpWrite;
DWORD dwLength;

// Get Lock
if (DS_OK != ppDsb8->Lock(0, 0, &lpWrite, &dwLength, NULL, NULL, DSBLOCK_ENTIREBUFFER))
{
	return -1;
}
	
// Copy PCM Data
memcpy_s(lpWrite, dwLength, PCM데이터주소, dwLength);

// Release Lock
ppDsb8->Unlock(lpWrite, dwLength, NULL, 0);

// Set Position to 0
ppDsb8->SetCurrentPosition(0);

// Play
if (FAILED(ppDsb8->Play(0, 0, 0)))
{
	return -2;
}

간단하게 구현이 됩니다.

먼저 Lock()을 호출해서 메모리를 지정받습니다.

첫 번째 값은 Offset이고 두 번째 값은 할당받을 사이즈입니다.

마지막의 플래그에 따라서 두 값 중 하나는 무시되거나 모두 사용되거나 합니다.

3, 5번째 인자에서 실제 기록이 가능한 메모리의 주소를 리턴받고 4, 6번째에서는 메모리의 크기를 받습니다.

3, 4번째와 5, 6번째가 한 쌍입니다.

여기서 5, 6 번째 인자를 사용하지 않은 이유는 다음과 같습니다.

버퍼의 크기가 100바이트이고 현재 기록 커서(Write Cursor)의 위치가 50의 위치에 있다고 가정합니다.

이 상황에서 70바이트를 요구하면 버퍼의 끝까지 할당해서 50바이트가 최대 할당 가능한 크기입니다.

그래서 버퍼를 원통형(순환)의 버퍼로 보고 0~19 만큼의 크기를 할당해서 각각 5, 6번째 인자에 결과를 줍니다.

종이의 양쪽 끝 부분을 말면 원통이 되면서 계속 이어지는 것과 같은 원리입니다.

ppDsb8->Lock(0, 0, &lpWrite1, &dwLength1, &lpWrite2, &dwLength2, DSBLOCK_ENTIREBUFFER);

위와 같이 호출해서 lpWrite1에는 50의 위치를 가리키는 포인터, dwLength1에는 길이 50을 리턴합니다.

lpWrite2에는 0을 가리키는 포인터, dwLength2에는 20을 리턴하는 것입니다.

이렇게 하면 70의 크기를 부여받고 사용이 가능합니다.

인자를 전부 넘겨줘도 lpWrite2와 dwLength2가 필요 없는 경우(작게 요청한 경우)에는 lpWrite2가 NULL입니다.

NULL 체크를 통해서 안전하게 코딩을 하면 됩니다.

플래그는 DSBLOCK_ENTIREBUFFER를 전달했기 때문에 두 번째 인자가 무시됩니다.

DSBLOCK_FROMWRITECURSOR를 사용하면 첫 번째 인자가 무시됩니다.

정상적으로 획득했으면 PCM 데이터를 memcpy 등으로 복사하면 됩니다.

Unlock()을 호출할 때는 Lock()을 호출할 때 3, 4, 5, 6번에 전달한 인자를 그대로 전달하면 됩니다.

마지막으로 SetCurrentPosition(0)을 호출해서 가장 처음 부분으로 커서를 이동시켰습니다.

그리고 Play()를 호출하면 재생이 됩니다.

Play() 메소드의 첫 번째 인자는 예약된 값이기 때문에 0을 입력합니다.

두 번째는 우선 순위로 보통 0을 입력하며 0xFFFFFFFF까지 입력이 가능합니다.

세 번째는 플래그인데 DSBPLAY_LOOPING를 전달하면 명시적으로 Stop()을 호출하기 전까지 반복 재생하게 됩니다.

여기서는 작은 파일을 한 번만 재생하기 위해서 플래그를 사용하지 않았습니다.

만약 게임 등의 배경음을 반복적으로 재생하고 싶다면 해당 플래그를 사용하면 됩니다.

또한 대용량의 PCM이나 스트리밍을 재생하는데 사용이 가능합니다.

위와 같이 간단한 방법을 통해서 버퍼를 재생할 수 있습니다.

반응형