레거시 오디오 애플리케이션에 대한 오디오 이벤트

DirectSound, DirectShow 및 waveOutXxx 함수와 같은 레거시 오디오 API를 사용하면 애플리케이션이 오디오 스트림의 볼륨 수준을 가져와서 설정할 수 있습니다. 애플리케이션은 이러한 API의 볼륨 제어 기능을 사용하여 애플리케이션 창에 볼륨 슬라이더를 표시할 수 있습니다.

Windows Vista에서 시스템 볼륨 제어 프로그램인 Sndvol을 사용하면 사용자가 개별 애플리케이션의 오디오 볼륨 수준을 제어할 수 있습니다. 애플리케이션에서 표시하는 볼륨 슬라이더는 Sndvol의 해당 볼륨 슬라이더에 연결되어야 합니다. 사용자가 애플리케이션 창의 볼륨 슬라이더를 통해 애플리케이션 볼륨을 조정하는 경우 Sndvol의 해당 볼륨 슬라이더가 즉시 이동하여 새 볼륨 수준을 나타냅니다. 반대로 사용자가 Sndvol을 통해 애플리케이션 볼륨을 조정하는 경우 애플리케이션 창의 볼륨 슬라이더가 새 볼륨 수준을 나타내도록 이동해야 합니다.

Windows Vista에서 Sndvol은 애플리케이션이 IDirectSoundBuffer::SetVolume 메서드 또는 waveOutSetVolume 함수에 대한 호출을 통해 수행되는 볼륨 변경 내용을 즉시 반영합니다. 그러나 DirectSound 또는 waveOutXxx 함수와 같은 레거시 오디오 API는 사용자가 Sndvol을 통해 애플리케이션 볼륨을 변경할 때 애플리케이션에 알릴 수 있는 수단을 제공하지 않습니다. 애플리케이션이 볼륨 슬라이더를 표시하지만 볼륨 변경에 대한 알림을 받지 못하는 경우 슬라이더는 Sndvol에서 사용자가 변경한 내용을 반영하지 못합니다. 적절한 동작을 구현하려면 애플리케이션 디자이너가 레거시 오디오 API의 알림 부족을 어떻게든 보완해야 합니다.

한 가지 해결 방법은 애플리케이션이 변경되었는지 확인하기 위해 볼륨 수준을 검사 주기적으로 알림을 표시하도록 타이머를 설정하는 것입니다.

더 세련된 솔루션은 애플리케이션이 핵심 오디오 API의 이벤트 알림 기능을 사용하는 것입니다. 특히 애플리케이션은 볼륨이 변경되거나 다른 유형의 오디오 이벤트가 발생할 때 콜백을 수신하도록 IAudioSessionEvents 인터페이스를 등록할 수 있습니다. 볼륨이 변경되면 볼륨 변경 콜백 루틴은 변경 내용을 반영하도록 애플리케이션의 볼륨 슬라이더를 즉시 업데이트할 수 있습니다.

다음 코드 예제에서는 애플리케이션을 등록하여 볼륨 변경 및 기타 오디오 이벤트에 대한 알림을 받는 방법을 보여 줍니다.

//-----------------------------------------------------------
// Register the application to receive notifications when the
// volume level changes on the default process-specific audio
// session (with session GUID value GUID_NULL) on the audio
// endpoint device with the specified data-flow direction
// (eRender or eCapture) and device role.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hr)  \
              if (FAILED(hr)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

class AudioVolumeEvents
{
    HRESULT _hrStatus;
    IAudioSessionManager *_pManager;
    IAudioSessionControl *_pControl;
    IAudioSessionEvents *_pAudioEvents;
public:
    AudioVolumeEvents(EDataFlow, ERole, IAudioSessionEvents*);
    ~AudioVolumeEvents();
    HRESULT GetStatus() { return _hrStatus; };
};

// Constructor
AudioVolumeEvents::AudioVolumeEvents(EDataFlow flow, ERole role,
                                     IAudioSessionEvents *pAudioEvents)
{
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;

    _hrStatus = S_OK;
    _pManager = NULL;
    _pControl = NULL;
    _pAudioEvents = pAudioEvents;

    if (_pAudioEvents == NULL)
    {
        _hrStatus = E_POINTER;
        return;
    }

    _pAudioEvents->AddRef();

    // Get the enumerator for the audio endpoint devices
    // on this system.
    _hrStatus = CoCreateInstance(__uuidof(MMDeviceEnumerator),
                                 NULL, CLSCTX_INPROC_SERVER,
                                 __uuidof(IMMDeviceEnumerator),
                                 (void**)&pEnumerator);
    EXIT_ON_ERROR(_hrStatus)

    // Get the audio endpoint device with the specified data-flow
    // direction (eRender or eCapture) and device role.
    _hrStatus = pEnumerator->GetDefaultAudioEndpoint(flow, role,
                                                     &pDevice);
    EXIT_ON_ERROR(_hrStatus)

    // Get the session manager for the endpoint device.
    _hrStatus = pDevice->Activate(__uuidof(IAudioSessionManager),
                                  CLSCTX_INPROC_SERVER, NULL,
                                  (void**)&_pManager);
    EXIT_ON_ERROR(_hrStatus)

    // Get the control interface for the process-specific audio
    // session with session GUID = GUID_NULL. This is the session
    // that an audio stream for a DirectSound, DirectShow, waveOut,
    // or PlaySound application stream belongs to by default.
    _hrStatus = _pManager->GetAudioSessionControl(NULL, 0, &_pControl);
    EXIT_ON_ERROR(_hrStatus)

    _hrStatus = _pControl->RegisterAudioSessionNotification(_pAudioEvents);
    EXIT_ON_ERROR(_hrStatus)

Exit:
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
}

// Destructor
AudioVolumeEvents::~AudioVolumeEvents()
{
    if (_pControl != NULL)
    {
        _pControl->UnregisterAudioSessionNotification(_pAudioEvents);
    }
    SAFE_RELEASE(_pManager)
    SAFE_RELEASE(_pControl)
    SAFE_RELEASE(_pAudioEvents)
};

앞의 코드 예제에서는 AudioVolumeEvents라는 클래스를 구현합니다. 프로그램 초기화 중에 오디오 애플리케이션은 AudioVolumeEvents 개체를 만들어 오디오 이벤트 알림을 사용하도록 설정합니다. 이 클래스의 생성자는 세 가지 입력 매개 변수를 사용합니다.

생성자는 흐름 및 역할 값을 IMMDeviceEnumerator::GetDefaultAudioEndpoint 메서드에 입력 매개 변수로 제공합니다. 메서드는 지정된 데이터 흐름 방향 및 디바이스 역할로 오디오 엔드포인트 디바이스를 캡슐화하는 IMMDevice 개체를 만듭니다.

애플리케이션은 pAudioEvents가 가리키는 개체를 구현합니다. (구현은 앞의 코드 예제에 표시되지 않습니다. IAudioSessionEvents 인터페이스를 구현하는 코드 예제는 오디오 세션 이벤트를 참조하세요.) 이 인터페이스의 각 메서드는 특정 유형의 오디오 이벤트에 대한 알림을 받습니다. 애플리케이션이 특정 이벤트 형식에 관심이 없는 경우 해당 이벤트 형식에 대한 메서드는 S_OK 반환하는 것 외에도 아무 작업도 수행하지 않아야 합니다.

IAudioSessionEvents::OnSimpleVolumeChanged 메서드는 볼륨 변경에 대한 알림을 받습니다. 일반적으로 이 메서드는 애플리케이션의 볼륨 슬라이더를 업데이트합니다.

앞의 코드 예제에서 AudioVolumeEvents 클래스의 생성자는 세션 GUID 값 GUID_NULL 식별되는 프로세스별 오디오 세션 에 대한 알림을 등록합니다. 기본적으로 DirectSound, DirectShow 및 waveOutXxx 함수와 같은 레거시 오디오 API는 해당 스트림을 이 세션에 할당합니다. 그러나 DirectSound 또는 DirectShow 애플리케이션은 옵션으로 기본 동작을 재정의하고 해당 스트림을 프로세스 간 세션 또는 GUID_NULL 이외의 GUID 값으로 식별되는 세션에 할당할 수 있습니다. (현재 waveOutXxx 애플리케이션에서 유사한 방식으로 기본 동작을 재정의하는 메커니즘이 제공되지 않습니다.) 이 동작을 사용하는 DirectShow 애플리케이션의 코드 예제는 DirectShow 애플리케이션에 대한 디바이스 역할을 참조하세요. 이러한 애플리케이션을 수용하기 위해 이전 코드 예제에서 생성자를 수정하여 세션 GUID와 플래그라는 두 개의 추가 입력 매개 변수를 수락하여 모니터링할 세션이 크로스 프로세스 또는 프로세스별 세션인지 여부를 나타낼 수 있습니다. 생성자의 IAudioSessionManager::GetAudioSessionControl 메서드에 대한 호출에 이러한 매개 변수를 전달합니다.

생성자가 IAudioSessionControl::RegisterAudioSessionNotification 메서드를 호출하여 알림을 등록한 후 애플리케이션은 IAudioSessionControl 또는 IAudioSessionManager 인터페이스가 있는 한 계속 알림을 받습니다. 이전 코드 예제의 AudioVolumeEvents 개체는 소멸자를 호출할 때까지 이러한 인터페이스에 대한 참조를 보유합니다. 이 동작은 애플리케이션이 AudioVolumeEvents 개체의 수명 동안 알림을 계속 받도록 합니다.

DirectSound 또는 레거시 Windows 멀티미디어 애플리케이션은 디바이스 역할에 따라 오디오 디바이스를 암시적으로 선택하는 대신 사용자가 친숙한 이름으로 식별되는 사용 가능한 디바이스 목록에서 디바이스를 명시적으로 선택할 수 있습니다. 이 동작을 지원하려면 선택한 디바이스에 대한 오디오 이벤트 알림을 생성하도록 앞의 코드 예제를 수정해야 합니다. 두 가지 수정이 필요합니다. 먼저 코드 예제의 흐름 및 역할 매개 변수 대신 엔드포인트 ID 문자열 을 입력 매개 변수로 허용하도록 생성자 정의를 변경합니다. 이 문자열은 선택한 DirectSound 또는 레거시 파형 디바이스에 해당하는 오디오 엔드포인트 디바이스를 식별합니다. 둘째, IMMDeviceEnumerator::GetDefaultAudioEndpoint 메서드에 대한 호출을 IMMDeviceEnumerator::GetDevice 메서드에 대한 호출로 바꿉니다. GetDevice 호출은 엔드포인트 ID 문자열을 입력 매개 변수로 사용하고 문자열로 식별되는 엔드포인트 디바이스의 instance 만듭니다.

DirectSound 디바이스 또는 레거시 파형 디바이스에 대한 엔드포인트 ID 문자열을 가져오는 기술은 다음과 같습니다.

먼저 디바이스 열거 중에 DirectSound는 열거된 각 디바이스에 대한 엔드포인트 ID 문자열을 제공합니다. 디바이스 열거를 시작하기 위해 애플리케이션은 콜백 함수 포인터를 입력 매개 변수로 DirectSoundCreate 또는 DirectSoundCaptureCreate 함수에 전달합니다. 콜백 함수의 정의는 다음과 같습니다.

BOOL DSEnumCallback(
  LPGUID  lpGuid,
  LPCSTR  lpcstrDescription,
  LPCSTR  lpcstrModule,
  LPVOID  lpContext
);

Windows Vista에서 lpcstrModule 매개 변수는 엔드포인트 ID 문자열을 가리킵니다. (Windows Server 2003, Windows XP 및 Windows 2000을 비롯한 이전 버전의 Windows에서는 lpcstrModule 매개 변수가 디바이스의 드라이버 모듈 이름을 가리킵니다.) lpcstrDescription 매개 변수는 디바이스의 이름을 포함하는 문자열을 가리킵니다. DirectSound 디바이스 열거형에 대한 자세한 내용은 Windows SDK 설명서를 참조하세요.

둘째, 레거시 파형 디바이스에 대한 엔드포인트 ID 문자열을 가져오려면 waveOutMessage 또는 waveInMessage 함수를 사용하여 파형 디바이스 드라이버에 DRV_QUERYFUNCTIONINSTANCEID 메시지를 보냅니다. 이 메시지의 사용을 보여 주는 코드 예제는 레거시 Windows 멀티미디어 애플리케이션에 대한 디바이스 역할을 참조하세요.

레거시 오디오 API와의 상호 운용성