본문 바로가기

Programming/DirectX

[DirectSound] 4. DirectSound Device 생성

반응형

DirectSound를 통해서 재생하기 위해서는 장치 생성을 먼저 해야 합니다.

DirectX는 COM(Component Object Model)을 기반으로 되어 있습니다.

COM은 ActiveX 등에도 사용되는 기반 기술로 그 내용이 상당히 방대한 편입니다.

간단하게 COM은 인터페이스를 사용하며 순수 가상 함수만을 갖는 클래스로 표현됩니다.

관련 내용은 아래의 링크를 참조하면 확인이 가능합니다.

2014/12/03 - [Programming/C&C++] - 다형성과 가상함수

vptr이라는 포인터가 동적으로 실제 호출해야 하는 함수의 테이블을 가리키고 사용하게 합니다.

COM은 3 종류로 구분되며 DirectSound는 DLL(Inproc-server) 형태로 제공됩니다.

그리고 참조 카운팅을 통해서 해제 시점을 스스로 관리합니다.

인터페이스의 객체에 대한 포인터를 획득해서 해당 인터페이스가 제공하는 기능을 사용합니다.

먼저 DirectSound를 사용하기 위해 헤더와 라이브러리를 추가합니다.

dsound.h와 dsound.lib입니다.

DirectSound는 기본적으로 한 프로그램에 하나의 장치(Device)Primary Buffer를 가집니다.

그리고 각각의 소리(효과음, 배경음 등)는 Secondary Buffer로 표현됩니다.

가장 먼저 Device를 생성하는 작업을 진행해야 합니다.

IDirectSound8라는 인터페이스는 Device를 지칭하는 인터페이스입니다.

인터페이스는 간단하게는 클래스로 생각하면 됩니다.

실제로도 struct(class와 기본 액세스 지정자를 제외하고 동일)로 정의되어 있습니다.

먼저 IDirectSound8 인터페이스의 객체를 생성합니다.

COM은 일반 객체 생성과 다르게 일반적으로 CoCreateInstance()로 객체를 생성합니다.

다만 IDrectSound8은 DirectSoundCreate8()이라는 전용의 함수가 제공됩니다.

함수의 원형은 다음과 같습니다.

HRESULT DirectSoundCreate8(
         LPCGUID lpcGuidDevice,
         LPDIRECTSOUND8 * ppDS8,
         LPUNKNOWN pUnkOuter
)

인자는 3개를 전달하도록 되어 있습니다.

첫 번째 인자는 장치 열거에서 획득한 장치의 GUID값을 전달합니다.

NULL이나 DSDEVID_DefaultPlayback을 지정하면 기본 장치를 사용하게 됩니다.

두 번째는 생성된 객체의 포인터를 리턴받기 위해서 전달합니다.

IDrectSound8** 타입으로 내부에서 메모리를 할당해서 다시 넘겨줘야하기 때문에 이런 타입을 사용하게 됩니다.

세 번째는 COM aggregation과 관련된 것으로 반드시 NULL을 전달해야 합니다.

다음과 같이 호출하면 기본 재생 장치를 사용하는 Device가 생성됩니다.

DirectSoundCreate8(NULL, &IDirectSound8포인터, NULL);

COM 인터페이스가 제공하는 함수들은 HRESULT 타입의 결과를 리턴합니다.

함수 호출이 성공했는지 실패했는지는 SUCCEEDED, FAILED 매크로를 통해서 확인이 가능합니다.

리턴된 값을 해당 매크로에 넣어보면 성공인지 실패인지 확인이 가능합니다.

그리고 DirectSound에서는 협력 레벨(Cooperative Levels)을 사용합니다.

일반적으로 장치의 수는 한정되어 있기 때문에 한 번에 여러 응용프로그램이 장치를 함께 사용하게 됩니다.

이럴 때 잘못된 방법으로 잘못된 타이밍에 접근할 소지가 있기 때문에 이것을 막기 위해서 협력 레벨을 사용합니다.

SetCooperativeLevel() 메소드를 호출해서 설정이 가능합니다.

첫 번째로 전달하는 인자는 윈도우 핸들로 메인 윈도우의 핸들을 전달하면 됩니다.

두 번째는 보통 DSSCL_PRIORITY를 전달합니다.

DSSCL_NORMAL, DSSCL_PRIORITY, DSSCL_WRITEPRIMARY 중 하나를 선택해서 사용할 수 있습니다.

각각의 차이점은 주로 Primary Buffer의 제어와 관련된 차이점입니다.

다음과 같은 형식으로 호출하면 됩니다.

pDirectSound8->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);

pDirectSound8은 DirectSoundCreate8() 가 리턴하는 두 번째 인자입니다.

이렇게 두 번만 호출하면 기본적으로 Device 객체가 생성이 됩니다.

마지막으로 주의할 점은 객체를 전부 사용한 이후에는 반드시 Release()를 호출해야 한다는 점입니다.

다만 CComPtr, CComQIPtr 등을 사용해서 자동적으로 처리가 되도록 할 수 있습니다.

포인터를 CComPtr 등으로 관리하면 Release()를 호출하지 않아도 자동적으로 메모리가 해제됩니다.

전체 코드는 다음과 같습니다.

CComPtr<IDirectSound8> pDirectSound;
HRESULT hr = DirectSoundCreate8(nullptr, &pDirectSound, nullptr);
if (FAILED(hr))
{
	return FALSE;
}
hr = pDirectSound->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);
if (FAILED(hr))
{
	return FALSE;
}

간단하게 Device 생성이 완료되고 이후에 버퍼를 생성하고 재생하는 것을 진행하면 됩니다.

반응형