WASAPI 排他モードサンプル(イベントモード)

公開:2010-11-10 22:30
更新:2020-02-15 04:36
カテゴリ:windows,wasapi,audio,windows api

WASAPIの備忘録がわりに排他モードのサンプルプログラムを書いてみた。 SDKサンプルを参考に、スレッドを使わずシンプルな内容にしてみた。


//
#include <SDKDDKVer.h>
#include "targetver.h"
#include <iostream>
#include <tchar.h>
#include "objbase.h"
#include <comdef.h>
#include "avrt.h"
#include "mmsystem.h"
#include <mmdeviceapi.h>
#include <AudioClient.h>
#include <audiopolicy.h>
#include <string>
#include <vector>
#include <boost/cstdint.hpp>
#include <boost/format.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/assign.hpp>
#include <boost/assign/ptr_list_of.hpp>
#include <boost/assign/ptr_list_inserter.hpp>
#include <boost/foreach.hpp>
#define _USE_MATH_DEFINES
#include <math.h>
#include <limits.h>
using namespace boost;
using namespace std;
// COM Pointer 定義
_COM_SMARTPTR_TYPEDEF(IMMDeviceEnumerator,__uuidof(IMMDeviceEnumerator));
_COM_SMARTPTR_TYPEDEF(IMMDevice,__uuidof(IMMDevice));
_COM_SMARTPTR_TYPEDEF(IAudioClient,__uuidof(IAudioClient));
_COM_SMARTPTR_TYPEDEF(IAudioRenderClient,__uuidof(IAudioRenderClient));
// HRESULT エラーが起きたら例外を投げる
#define THROW_IF_ERR(hres) \
if (FAILED(hres)) { throw sf::win32_error_exception(hres); }
#define SAFE_RELEASE(x) if(x) x.Release();
namespace sf
{
///Exceptionクラス
class exception : public std::exception
{
public:
explicit exception(const std::wstring& reason)
{
m_reason = reason;
};
const wchar_t * what() {return m_reason.c_str();};
const std::wstring& what_str() { return m_reason;};
protected:
std::wstring m_reason;
};
/// Win32エラー例外クラス
class win32_error_exception : std::exception
{
public:
win32_error_exception(boost::uint32_t hr);
win32_error_exception();
virtual ~win32_error_exception() {};
boost::uint32_t hresult() {return hresult_;}
const std::wstring& error() {return error_;}
private:
boost::uint32_t hresult_;
std::wstring error_;
};
/// COMの初期化を行う
struct com_initialize
{
/// コンストタクタでCOMの初期化を行う
com_initialize(DWORD init = COINIT_MULTITHREADED )
{
hr_ = ::CoInitializeEx(0,init);
}
/// デストラクタでCOMの終了処理を行う
~com_initialize()
{
if(hr_ == S_OK){
::CoUninitialize();
}
}
bool is_initialized() { return hr_ == S_OK;}
private:
HRESULT hr_;
};
// メモリ管理クラス どこかの掲示板より拝借
// policy class
struct heap_memory_free_policy
{
template< typename T >
void operator()( const T* AMemory ) const
{
if( NULL != AMemory )
::HeapFree( ::GetProcessHeap(), 0, AMemory );
}
};
// policy class
struct local_memory_free_policy
{
template< typename T >
void operator()( const T* AMemory ) const
{
if( NULL != AMemory )
::LocalFree( AMemory );
}
};
// policy class
struct co_task_memory_free_policy
{
template< typename T >
void operator()( const T* AMemory ) const
{
if( NULL != AMemory )
::CoTaskMemFree( AMemory );
}
};
// base guard class
template< typename T,class TFreePolicy >
class base_memory
{
private:
T *FMemory;
public:
base_memory( T* AMemory = NULL )
: FMemory( AMemory ) {}
virtual ~base_memory( void )
{ reset(); }
T* release( void )
{
T *tmp = FMemory;
FMemory = NULL;
return tmp;
}
void reset( T* AMemory = NULL )
{
if( AMemory != FMemory )
{
if( NULL != FMemory )
TFreePolicy( FMemory );
FMemory = AMemory;
}
}
operator T* ()
{ return FMemory; }
T* get() {return FMemory;}
T* operator ->() {return FMemory;}
T** operator&( void )
{ return &FMemory; }
};
template< typename T >
class heap_memory : public base_memory< T,
heap_memory_free_policy >
{
public:
heap_memory( T* AMemory = NULL )
: base_memory< T, heap_memory_free_policy >( AMemory )
{ }
};
template< typename T >
class local_memory : public base_memory< T,
local_memory_free_policy >
{
public:
local_memory( T* AMemory = NULL )
: base_memory< T, local_memory_free_policy >( AMemory )
{ }
};
template< typename T >
class co_task_memory : public base_memory< T,
co_task_memory_free_policy >
{
public:
co_task_memory( T* AMemory = NULL )
: base_memory< T, co_task_memory_free_policy >( AMemory )
{ }
};
// ハンドル管理クラス
 struct handle_holder
{
handle_holder(HANDLE handle) : handle_(handle) {}
~handle_holder() { ::CloseHandle(handle_);}
operator HANDLE () {return handle_; };
private:
HANDLE handle_;
};
/// WASAPI処理クラス
struct wasapi
{
wasapi() : com_init_(),is_enabled_(false),buffer_control_event_(::CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE))
{
if(!com_init_.is_initialized() || buffer_control_event_ == NULL)
{
return;
}
try {
// WASAPIの初期化処理
// IMMDeviceEnumeratorの取得
THROW_IF_ERR(
CoCreateInstance(
__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&device_enumerator_)));
// デフォルトのオーディオデバイスを取得する
THROW_IF_ERR(
device_enumerator_
->GetDefaultAudioEndpoint(eRender,eMultimedia,&current_device_)
);
// オーディオクライアントを取得
THROW_IF_ERR(
current_device_
->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
NULL, reinterpret_cast<void **>(&audio_client_))
);
// フォーマット定義
audio_client_->GetMixFormat(&mix_format_);
WAVEFORMATEXTENSIBLE *waveFormatExtensible = reinterpret_cast<WAVEFORMATEXTENSIBLE *>((WAVEFORMATEX*)mix_format_);
waveFormatExtensible->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
waveFormatExtensible->Format.wBitsPerSample = 16;
waveFormatExtensible->Format.nBlockAlign = (mix_format_->wBitsPerSample / 8) * mix_format_->nChannels;
waveFormatExtensible->Format.nAvgBytesPerSec = waveFormatExtensible->Format.nSamplesPerSec*waveFormatExtensible->Format.nBlockAlign;
waveFormatExtensible->Samples.wValidBitsPerSample = 16;
//mix_format_->wFormatTag = WAVE_FORMAT_PCM;
//mix_format_->wBitsPerSample = 16;
//mix_format_->nBlockAlign = (mix_format_->wBitsPerSample / 8) * mix_format_->nChannels;
//mix_format_->nAvgBytesPerSec = mix_format_->nSamplesPerSec*mix_format_->nBlockAlign;
THROW_IF_ERR(
audio_client_->IsFormatSupported(
AUDCLNT_SHAREMODE_EXCLUSIVE,mix_format_, NULL));
// 再生クライアントの初期化
REFERENCE_TIME buffer_duration = 10 /* ms */ * 10000;
THROW_IF_ERR(audio_client_->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
buffer_duration,
buffer_duration,
mix_format_,
NULL));
// バッファサイズの取得
THROW_IF_ERR(audio_client_->GetBufferSize(&buffer_size_));
// イベントハンドルの設定
THROW_IF_ERR(audio_client_->SetEventHandle(buffer_control_event_));
// 再生クライアントの取得
THROW_IF_ERR(audio_client_->GetService(IID_PPV_ARGS(&audio_render_client_)));
//
num_of_frames_ = mix_format_->nBlockAlign;
is_enabled_ = true;
} catch (win32_error_exception& e)
{
exception_holder_.reset(new win32_error_exception(e.hresult()));
is_enabled_ = false;
} catch(...) {
is_enabled_ = false;
}
}
~wasapi()
{
// WASAPIの終了処理
if(audio_client_)
{
audio_client_->Stop();
audio_client_.Release();
}
}
bool is_enabled () const {return is_enabled_;}
void create_wave_data()
{
// サイン波の生成
boost::uint32_t buffer_size_in_bytes = buffer_size_ * mix_format_->nBlockAlign;
size_t render_data_length = mix_format_->nSamplesPerSec * 10 /* 秒 */ * mix_format_->nBlockAlign / sizeof(short);
tone_buffer_.reserve(render_data_length);
double sampleIncrement = (440 /* Hz */ * (M_PI * 2.0)) / (double)mix_format_->nSamplesPerSec;
double theta = 0.0;
for (size_t i = 0 ; i < render_data_length ; i += mix_format_->nChannels)
{
double sinValue = sin( theta );
for(size_t j = 0 ;j < mix_format_->nChannels; j++)
{
tone_buffer_.push_back((short)(sinValue * _I16_MAX));
}
theta += sampleIncrement;
}
}
// サウンド再生処理
void play()
{
BYTE* buffer;
boost::uint32_t pos = 0;
const size_t inc  = buffer_size_ * num_of_frames_ / sizeof(short);
// サイン波を生成する
create_wave_data();
// 最初にバッファを埋める
THROW_IF_ERR(audio_render_client_->GetBuffer(buffer_size_,&buffer));
::CopyMemory(buffer,(BYTE*)&(tone_buffer_[pos]),buffer_size_ * num_of_frames_);
pos += inc;
THROW_IF_ERR(audio_render_client_->ReleaseBuffer(buffer_size_,0));
// 再生開始
THROW_IF_ERR(audio_client_->Start());
// 再生ループ
while(pos < tone_buffer_.size())
{
::WaitForSingleObject(buffer_control_event_,INFINITE);
THROW_IF_ERR(audio_render_client_->GetBuffer(buffer_size_,&buffer));
::CopyMemory(buffer,(BYTE*)&(tone_buffer_[pos]),buffer_size_ *  num_of_frames_);
pos += inc;
THROW_IF_ERR(audio_render_client_->ReleaseBuffer(buffer_size_,0));
}
//再生停止
THROW_IF_ERR(audio_client_->Stop());
}
win32_error_exception* const result() {return exception_holder_.get(); }
private:
// COMの初期化
com_initialize com_init_;
IMMDeviceEnumeratorPtr device_enumerator_;
IMMDevicePtr current_device_;
IAudioClientPtr audio_client_;
IAudioRenderClientPtr audio_render_client_;
handle_holder buffer_control_event_;
co_task_memory<WAVEFORMATEX> mix_format_;
bool is_enabled_;
boost::shared_ptr<win32_error_exception> exception_holder_;
boost::uint32_t num_of_frames_;
boost::uint32_t buffer_size_;
std::vector<short> tone_buffer_;
};
typedef boost::shared_ptr<sf::wasapi> wasapi_ptr_type;
std::map<HRESULT,std::wstring> com_error_  = boost::assign::list_of<std::pair<HRESULT,std::wstring> >
(E_POINTER,L"E_POINTER")
(E_INVALIDARG,L"E_INVALIDARG")
(AUDCLNT_E_NOT_INITIALIZED,L"AUDCLNT_E_NOT_INITIALIZED")
(AUDCLNT_E_ALREADY_INITIALIZED,L"AUDCLNT_E_ALREADY_INITIALIZED")
(AUDCLNT_E_WRONG_ENDPOINT_TYPE,L"AUDCLNT_E_WRONG_ENDPOINT_TYPE")
(AUDCLNT_E_DEVICE_INVALIDATED,L"AUDCLNT_E_DEVICE_INVALIDATED")
(AUDCLNT_E_NOT_STOPPED,L"AUDCLNT_E_NOT_STOPPED")
(AUDCLNT_E_BUFFER_TOO_LARGE,L"AUDCLNT_E_BUFFER_TOO_LARGE")
(AUDCLNT_E_OUT_OF_ORDER,L"AUDCLNT_E_OUT_OF_ORDER")
(AUDCLNT_E_UNSUPPORTED_FORMAT,L"AUDCLNT_E_UNSUPPORTED_FORMAT")
(AUDCLNT_E_INVALID_SIZE,L"AUDCLNT_E_INVALID_SIZE")
(AUDCLNT_E_DEVICE_IN_USE,L"AUDCLNT_E_DEVICE_IN_USE")
(AUDCLNT_E_BUFFER_OPERATION_PENDING,L"AUDCLNT_E_BUFFER_OPERATION_PENDING")
(AUDCLNT_E_THREAD_NOT_REGISTERED,L"AUDCLNT_E_THREAD_NOT_REGISTERED")
(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED,L"AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED")
(AUDCLNT_E_ENDPOINT_CREATE_FAILED,L"AUDCLNT_E_ENDPOINT_CREATE_FAILED")
(AUDCLNT_E_SERVICE_NOT_RUNNING,L"AUDCLNT_E_SERVICE_NOT_RUNNING")
(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED,L"AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED")
(AUDCLNT_E_EXCLUSIVE_MODE_ONLY,L"AUDCLNT_E_EXCLUSIVE_MODE_ONLY")
(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL,L"AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL")
(AUDCLNT_E_EVENTHANDLE_NOT_SET,L"AUDCLNT_E_EVENTHANDLE_NOT_SET")
(AUDCLNT_E_INCORRECT_BUFFER_SIZE,L"AUDCLNT_E_INCORRECT_BUFFER_SIZE")
(AUDCLNT_E_BUFFER_SIZE_ERROR,L"AUDCLNT_E_BUFFER_SIZE_ERROR")
(AUDCLNT_S_BUFFER_EMPTY,L"AUDCLNT_S_BUFFER_EMPTY")
(AUDCLNT_S_THREAD_ALREADY_REGISTERED,L"AUDCLNT_S_THREAD_ALREADY_REGISTERED");
win32_error_exception::win32_error_exception(boost::uint32_t hr)
: std::exception("HRESULT ERROR"),hresult_(hr)
{
local_memory<wchar_t> mem;
DWORD result = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,0,hr,0,(LPWSTR)&mem,0,0);
if(result != 0){
error_ = mem;
} else {
std::map<HRESULT,std::wstring>::iterator it = com_error_.find(hr);
if(it != com_error_.end())
{
error_ = it->second;
} else {
error_ = (boost::wformat(L"0x%x 不明なCOMエラー") % hr).str();
}
}
};
win32_error_exception::win32_error_exception()
{
hresult_ = ::GetLastError();
local_memory<wchar_t> mem;
DWORD rv =  FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,0,hresult_,0,(LPWSTR)&mem,0,0);
error_ = mem;
//Logger::outputDebugPrintf(L"Win32 Error %x %s",hresult_,mem.Get() );
};
}
using namespace sf;
/// アプリケーションのエントリポイント
int _tmain(int argc, _TCHAR* argv[])
{
wcout.imbue(locale("japanese"));
// インストールデバイスを取得する
{
wasapi_ptr_type wasapi_audio(new sf::wasapi());
if(wasapi_audio->is_enabled())
{
try {
wasapi_audio->play();
} catch (win32_error_exception& e)
{
wcout << L"ERROR:" << e.error() << endl;
}
} else {
if(wasapi_audio->result() != 0)
{
wstring error = wasapi_audio->result()->error();
wcout << L"ERROR:" << error << endl;
}
}
}
return 0;
}