WASAPI 排他モードサンプル(タイマモード)

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

WASAPI排他モードサンプル(タイマモード)を作ってみた。

タイマモードはバッファへの書き込みタイミングを自分でコントロールする。 SDKのサンプルではイベントのタイムアウトを使用しているけど、今回はSleepでコントロールすることにした。バッファは大きめ(3ms×4つ分)に確保している。

書き込みはパディングを考慮する必要があるので、イベントモードよりは若干複雑になる。


//
#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>
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "Avrt.lib")
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_;
};
struct timer_period
{
timer_period(uint32_t period) : period_(period)
{
::timeBeginPeriod(period);
}
~timer_period()
{
::timeEndPeriod(period_);
}
private:
const uint32_t period_;
};
struct av_mm_thread_characteristics
{
av_mm_thread_characteristics(std::wstring& str) : task_name_(str)
{
handle_ = ::AvSetMmThreadCharacteristicsW(str.c_str(),(LPDWORD)&task_index_);
}
bool set_priority(AVRT_PRIORITY p){return (::AvSetMmThreadPriority(handle_,p) == TRUE);}
~av_mm_thread_characteristics()
{
::AvRevertMmThreadCharacteristics(handle_);
}
private:
std::wstring task_name_;
boost::uint32_t task_index_;
HANDLE handle_;
};
/// WASAPI処理クラス
struct wasapi
{
wasapi() : com_init_(),period_(1),is_enabled_(false),thread_priority_(wstring(L"Audio"))
{
if(!com_init_.is_initialized())
{
return;
}
try {
thread_priority_.set_priority(AVRT_PRIORITY_NORMAL);
// 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_period =  latency_ms_ /* ms */ * 10000 ;
REFERENCE_TIME buffer_duration = buffer_period * periods_per_buffer_;
THROW_IF_ERR(audio_client_->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_NOPERSIST,
buffer_duration,
buffer_period,
mix_format_,
NULL));
// バッファサイズの取得
THROW_IF_ERR(audio_client_->GetBufferSize(&buffer_size_));
// 再生クライアントの取得
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) * periods_per_buffer_);
const size_t buffer_in_periods = buffer_size_ / periods_per_buffer_;
// サイン波を生成する
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 * periods_per_buffer_;
THROW_IF_ERR(audio_render_client_->ReleaseBuffer(buffer_size_,0));
// 再生開始
THROW_IF_ERR(audio_client_->Start());
// 再生ループ
while(pos < tone_buffer_.size())
{
// レイテンシ時間だけ待つ
Sleep(latency_ms_);
uint32_t padding;
uint32_t frames_available;
// パディングを求める。
THROW_IF_ERR(audio_client_->GetCurrentPadding(&padding));
frames_available = buffer_size_ - padding;
// パディングを除いた部分のバッファを埋める。
// パディングを除いた部分のサイズがbuffer_in_periodsより小さい場合はつぎにまわす。
// パディングを除いた部分を一気に埋めようとしたけどできなかった。。
while(pos < tone_buffer_.size() && (buffer_in_periods <= frames_available) )
{
THROW_IF_ERR(audio_render_client_->GetBuffer(buffer_in_periods,&buffer));
::CopyMemory(buffer,(BYTE*)&(tone_buffer_[pos]),buffer_in_periods *  num_of_frames_);
THROW_IF_ERR(audio_render_client_->ReleaseBuffer(buffer_in_periods,0));
// レイテンシ時間だけ進める
pos += inc;
// パディングを再度求める
THROW_IF_ERR(audio_client_->GetCurrentPadding(&padding));
frames_available = buffer_size_ - padding;
}
}
//再生停止
THROW_IF_ERR(audio_client_->Stop());
}
win32_error_exception* const result() {return exception_holder_.get(); }
private:
// COMの初期化
com_initialize com_init_;
timer_period period_;
IMMDeviceEnumeratorPtr device_enumerator_;
IMMDevicePtr current_device_;
IAudioClientPtr audio_client_;
IAudioRenderClientPtr audio_render_client_;
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_;
av_mm_thread_characteristics thread_priority_;
// 再生レイテンシ
static const uint32_t latency_ms_ = 3;
// バッファ中の区切り数(レイテンシ時間が何個あるか)
static const uint32_t periods_per_buffer_ = 4;
};
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;
}