WASAPI 共有モードのサンプルコード(タイマモード)

公開:2011-01-02 16:57
更新:2020-02-15 04:36
カテゴリ:windows,wasapi,audio,windows api

WASAPIの共有モードのタイマモードで440Hzのサイン波を10秒ほど再生するサンプルを書いてみた。

排他モードのサンプルを2・3行書き換えるだけで共有モードに変更できた。
異なるところは、初期化の部分。
IAudioClient::IsFormatSupported()とIAudioClient::Initialize()の1つ目の引数をAUDCLNT_SHAREMODE_EXCLUSIVE -> AUDCLNT_SHAREMODE_SHAREDに変えるくらい。
気を付けないといけないのは、IAudioClient::IsFormatSupported()の第3引数のポインタ(WAVEFORMATEX**)はNULLではなくポインタへのアドレスをセットすること。指定しないとエラーになる(排他モードの場合はエラーにならない)。
共有モードの場合、第2引数で指定したフォーマットをサポートしない場合それに近いフォーマットを第3引数で指定したポインタに格納してくれる。その場合戻り値にはS_FALSEが格納される。


//
#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;
sf::co_task_memory<WAVEFORMATEX>  alt_format;
THROW_IF_ERR(
audio_client_->IsFormatSupported(
AUDCLNT_SHAREMODE_SHARED,mix_format_,&alt_format));
// 再生クライアントの初期化
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_SHARED,
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_ = 50;/* ms */
// バッファ中の区切り数(レイテンシ時間が何個あるか)
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;
}