WASAPIを使ったPC音録音ソフト
WASAPIを使ったPC音録音ソフト
window7でC言語を使い、PC内の音(スピーカーから出力される音)を取得するソフトを作ろうとしています。
フリーソフトaudacityでWASAPIを使えばサウンドミキサーを使うことなく、PC音を取得できることが分かり、WASAPIの導入を決めたのですが、PC音の取得の方法が分かりません。
WASAPIを使って再生・録音(マイクから)はできました。
何か、方法をご存知の方はヒントでもいいので教えてください。
初の質問版で分かりにくい部分があると思いますが、なにとぞよろしくお願いします。
フリーソフトaudacityでWASAPIを使えばサウンドミキサーを使うことなく、PC音を取得できることが分かり、WASAPIの導入を決めたのですが、PC音の取得の方法が分かりません。
WASAPIを使って再生・録音(マイクから)はできました。
何か、方法をご存知の方はヒントでもいいので教えてください。
初の質問版で分かりにくい部分があると思いますが、なにとぞよろしくお願いします。
Re: WASAPIを使ったPC音録音ソフト
私はAudacityを何年も愛用しています。よくPCの音を録音します。
LAME v3.95もいれています。
http://csi.nisinippon.com/aud.png
コントロールパネルのサウンドがこうなってると思うのでここ
を参考にステレオミキサーを有効にするといいと思います。
https://www18.atwiki.jp/live2ch/pages/227.html
LAME v3.95もいれています。
http://csi.nisinippon.com/aud.png
コントロールパネルのサウンドがこうなってると思うのでここ
を参考にステレオミキサーを有効にするといいと思います。
https://www18.atwiki.jp/live2ch/pages/227.html
Re: WASAPIを使ったPC音録音ソフト
早速の返信ありがとうございます。
audacityにおいてオーディオホストをMMEからWindowWASAPIに変更し、録音デバイスをスピーカーに変更してやるとサウンドミキサーが無効のままでもPC音を取得できます。
これと同じことを自作のプログラムで実装したく試行錯誤してるのですが、とっかかりもつかめません。 もし何かご存知であれば教えてください!
audacityにおいてオーディオホストをMMEからWindowWASAPIに変更し、録音デバイスをスピーカーに変更してやるとサウンドミキサーが無効のままでもPC音を取得できます。
これと同じことを自作のプログラムで実装したく試行錯誤してるのですが、とっかかりもつかめません。 もし何かご存知であれば教えてください!
Re: WASAPIを使ったPC音録音ソフト
ここにhttp://audacity-mp3.xyz/download-install/
[ホスト
「MME」「Windows DirectSound」「Windows WASAPI」の3つの中から選びます。
それぞれ違うのは、動作速度の違いだけです。(動画速度と言うのは、録音した際の遅延の事)
性能の差としては「Windows WASAPI > Windows DirectSound > MME」の順で、「Windows WASAPI」が一番優れています]
とあるけど「ルール」http://dixq.net/board/board.htmlに則って詳しく説明してください。(また、何が目的でなぜそうしたいのか他人にもよくわかるようにしてください。)
[ホスト
「MME」「Windows DirectSound」「Windows WASAPI」の3つの中から選びます。
それぞれ違うのは、動作速度の違いだけです。(動画速度と言うのは、録音した際の遅延の事)
性能の差としては「Windows WASAPI > Windows DirectSound > MME」の順で、「Windows WASAPI」が一番優れています]
とあるけど「ルール」http://dixq.net/board/board.htmlに則って詳しく説明してください。(また、何が目的でなぜそうしたいのか他人にもよくわかるようにしてください。)
Re: WASAPIを使ったPC音録音ソフト
失礼しました。ルールがあったんですね。以下に書き直します。
[1] 質問文
[1.1] 自分が今行いたい事は何か
スピーカーから出力される音をWASAPIを使用して取得すること
[1.2] どのように取り組んだか(プログラムコードがある場合記載)
WASAPIを使用した再生・録音(マイク)プログラムを作成し、これを改良することでスピーカーからの音を取得しようとしている。
スピーカーデバイスをキャプチャーモードでオープンできないかを試行錯誤している。
[1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)
[1.4] 今何がわからないのか、知りたいのか
「スピーカーから出力される音をサウンドミキサーを使用せずに取得することはできない。」と挫折したが、フリーソフト”audacity” ではオーディオホストにWASAPIを指定した場合サウンドミキサーを使用せずに録音できることに気が付き、これと同じようにプログラムを実装したい。しかし、その方法が分からない。
[2] 環境
[2.1] OS : Windows7 64bit
[2.2] コンパイラ名 : VisualStudio2010 Express
[1] 質問文
[1.1] 自分が今行いたい事は何か
スピーカーから出力される音をWASAPIを使用して取得すること
[1.2] どのように取り組んだか(プログラムコードがある場合記載)
WASAPIを使用した再生・録音(マイク)プログラムを作成し、これを改良することでスピーカーからの音を取得しようとしている。
スピーカーデバイスをキャプチャーモードでオープンできないかを試行錯誤している。
[1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)
[1.4] 今何がわからないのか、知りたいのか
「スピーカーから出力される音をサウンドミキサーを使用せずに取得することはできない。」と挫折したが、フリーソフト”audacity” ではオーディオホストにWASAPIを指定した場合サウンドミキサーを使用せずに録音できることに気が付き、これと同じようにプログラムを実装したい。しかし、その方法が分からない。
[2] 環境
[2.1] OS : Windows7 64bit
[2.2] コンパイラ名 : VisualStudio2010 Express
Re: WASAPIを使ったPC音録音ソフト
なんの目的か書かれてない。作っている側は分かって他人への説明にはなってない。レイテンシがすくないとか劣化が気になるのか?WASAPIには排他モードと共有モードの2種類があり、実際にレイテンシが少なくなるのは排他モードの方です。WASAPIはCOMを使ったライブラリです。プログラムをつくろうとするとWASAPI.COMの知識が必要です。
WASAPIを使用した再生・録音(マイク)プログラムを作成とあるがコードを一行も示せないのは大変疑わしい。基本部分を提示するここと。「ルール」をよく読むこと。まず基本設計を他人に分かるように説明する事が肝要です。
WASAPIを使用した再生・録音(マイク)プログラムを作成とあるがコードを一行も示せないのは大変疑わしい。基本部分を提示するここと。「ルール」をよく読むこと。まず基本設計を他人に分かるように説明する事が肝要です。
Re: WASAPIを使ったPC音録音ソフト
Mathさん、ご指摘ありがとうございます。少しは分かりやすくなってるといいのですが…
[目的]
パソコン2台をLANケーブルでつなぎ、一方のパソコンAの音をもう一方のパソコンBに送るプログラムを作成しています。パソコンAにはステレオミキサーが入っていないためWASAPIを使用し、パソコンの音を取得しようと考えています。
[コード]
エラー処理は除いた、WASAPIの初期化部分とイベント処理部分です。
[目的]
パソコン2台をLANケーブルでつなぎ、一方のパソコンAの音をもう一方のパソコンBに送るプログラムを作成しています。パソコンAにはステレオミキサーが入っていないためWASAPIを使用し、パソコンの音を取得しようと考えています。
[コード]
エラー処理は除いた、WASAPIの初期化部分とイベント処理部分です。
#include <Windows.h>
#include <stdio.h>
#include <process.h>
#include <Audioclient.h>
#include <mmdeviceapi.h>
#include <avrt.h>
#pragma comment(lib, "avrt.lib")
const WORD nBitrate = 16;
const WORD nFrequency = 44100;
const WORD nChannel = 2;
IAudioCaptureClient *CaptureClient = NULL;
IAudioClient *AudioClient = NULL;
IAudioRenderClient *RenderClient = NULL;
IMMDevice *device = NULL;
IMMDeviceCollection *deviceCollection = NULL;
IMMDeviceEnumerator *deviceEnumerator = NULL;
HANDLE ShutdownEvent;
HANDLE CaptureThread;
HANDLE AudioSamplesReadyEvent;
HWND hwnd;
WAVEFORMATEXTENSIBLE waveFormat;
CRITICAL_SECTION critical_section;
FILE *fp;
int InitWasapi()
{
HRESULT hr;
/////////////////////
// デバイスの選択
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
hr = deviceEnumerator->EnumAudioEndpoints(eAll, DEVICE_STATE_ACTIVE, &deviceCollection);
deviceCollection->Item(0, &device); // スピーカーは 0
/////////////////////
// イベントの作成
ShutdownEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
AudioSamplesReadyEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
/////////////////////
// AudioClient の準備
hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&AudioClient));
/////////////////////
// デバイスのレイテンシの取得
REFERENCE_TIME DefaultDevicePeriod;
REFERENCE_TIME MinimumDevicePeriod;
hr = AudioClient->GetDevicePeriod(&DefaultDevicePeriod, &MinimumDevicePeriod);
/////////////////////
// waveフォームの作成
waveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
waveFormat.Format.nChannels = nChannel;
waveFormat.Format.nSamplesPerSec = nFrequency;
waveFormat.Format.wBitsPerSample = nBitrate;
waveFormat.Format.nBlockAlign = waveFormat.Format.wBitsPerSample/8 * waveFormat.Format.nChannels;
waveFormat.Format.nAvgBytesPerSec = waveFormat.Format.nSamplesPerSec * waveFormat.Format.nBlockAlign;
waveFormat.Format.cbSize = 22;
waveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
waveFormat.Samples.wValidBitsPerSample = waveFormat.Format.wBitsPerSample;
waveFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
//////////////////////
// AudioClientの初期化
hr = AudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
DefaultDevicePeriod, DefaultDevicePeriod, (WAVEFORMATEX *)&waveFormat, NULL);
UINT32 frame;
if(FAILED(hr)){
if(hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED){
hr = AudioClient->GetBufferSize(&frame);
DefaultDevicePeriod = (REFERENCE_TIME)(10000.0*1000*frame/waveFormat.Format.nSamplesPerSec+0.5);
SafeRelease(&AudioClient);
hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&AudioClient));
if(FAILED(hr)){
MessageBox(hwnd, "AudioClientの2度目の準備に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
hr = AudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
DefaultDevicePeriod, DefaultDevicePeriod, (WAVEFORMATEX *)&waveFormat, NULL);
}
if(FAILED(hr)){
MessageBox(hwnd, "AudioClientの初期化に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
}
//////////////////////
// イベントをセット
hr = AudioClient->SetEventHandle(AudioSamplesReadyEvent);
hr = AudioClient->GetService(IID_PPV_ARGS(&CaptureClient));
/****************************
サービスの取得に失敗する
*****************************/
if(FAILED(hr)){
MessageBox(hwnd, "サービスの取得に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// クリティカルセクション初期化
InitializeCriticalSection(&critical_section);
/////////////////////
// file オープン
fp = fopen("sample.data", "w+b");
if(fp==NULL){
MessageBox(hwnd, "ファイルのオープンに失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// スレッドの作成
CaptureThread = CreateThread( NULL, 0, WASAPICaptureThread, NULL, 0, NULL);
if(CaptureThread == NULL){
MessageBox(hwnd, "スレッドの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// キャプチャの開始
hr = AudioClient->Start();
if(FAILED(hr)){
MessageBox(hwnd, "キャプチャを開始できません", "err", MB_ICONINFORMATION);
return FALSE;
}
return TRUE;
}
DWORD WINAPI WASAPICaptureThread(LPVOID Context)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if(FAILED(hr)){
MessageBox(hwnd, "COM初期化に失敗", "err_thread", MB_ICONINFORMATION);
return hr;
}
//////////////////////
// スレッドの優先順位
DWORD mmcsTaskIndex = 0;
HANDLE mmcsHandle = AvSetMmThreadCharacteristics("Audio", &mmcsTaskIndex);
if(mmcsHandle == NULL){
MessageBox(hwnd, "スレッドの優先順位変更に失敗", "err_thread", MB_ICONINFORMATION);
}
HANDLE waitArray[2] = {ShutdownEvent, AudioSamplesReadyEvent};
bool stillPlaying = true;
while(stillPlaying){
DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, INFINITE);
switch(waitResult)
{
case WAIT_OBJECT_0 + 0: // ShutdownEvent
stillPlaying = false;
case WAIT_OBJECT_0 + 1: // AudioSamplesReadyEvent
DWORD *pData;
DWORD *zeroData;
ULONGLONG adjsData;
UINT32 framesAvailable;
DWORD flags;
hr = CaptureClient->GetBuffer((BYTE **)&pData, &framesAvailable, &flags, NULL, NULL);
if(SUCCEEDED(hr))
{
EnterCriticalSection(&critical_section);
if(flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
zeroData = (DWORD *)calloc(framesAvailable, sizeof(DWORD));
fwrite(zeroData, sizeof(DWORD),framesAvailable, fp);
free(zeroData);
}
else
{
fwrite(pData, sizeof(DWORD),framesAvailable,fp);
}
LeaveCriticalSection(&critical_section);
hr = CaptureClient->ReleaseBuffer(framesAvailable);
if(FAILED(hr)){
MessageBox(hwnd, "リリースに失敗", "err_thread", MB_ICONINFORMATION);
}
}
break;
}
}
return 0;
}
Re: WASAPIを使ったPC音録音ソフト
どうせこんなことだろうと思ってみてたけど10年前のVistaにもOSの機能としてはいってるよ。7にないわけないだろ?だから初めからの投稿をよく読めばわかるのに。
http://pcgenki.com/soft3/vista_rokuon.htm
http://pcgenki.com/soft3/vista_rokuon.htm
Re: WASAPIを使ったPC音録音ソフト
ポニョさん返信ありがとうございます。
録音デバイスのところで 「無効なデバイスを表示する」 としても表示されなかったのですが、最新のデバイスをダウンロードすると表示されるようになりました。拙い説明を読み解いていただきありがとうございました。
ただ、Audacityではステレオミキサーを使用しない録音を実装しているので、その方法も、もう少し考えてみたいと思います。
録音デバイスのところで 「無効なデバイスを表示する」 としても表示されなかったのですが、最新のデバイスをダウンロードすると表示されるようになりました。拙い説明を読み解いていただきありがとうございました。
ただ、Audacityではステレオミキサーを使用しない録音を実装しているので、その方法も、もう少し考えてみたいと思います。
Re: WASAPIを使ったPC音録音ソフト
私のWindows10のマシンではまだ細かい制御ができるようです。http://csi.nisinippon.com/aud2.png富士通に問い合わせたがそこまでは知らないといわれた。このサンプルは20行程のエラーがでる。詳し事はわから無かったが。私が試したなかでエラーがでず正常にビルドできた例は
///////////////////////////////////////////////////////////////////////////////////
// Main : WASAPI再生サンプル v1.00 //
// //
// このソースコードは自由に改変して使用可能です。 //
// また商用利用も可能ですが、すべての環境で正しく動作する保障はありません。 //
// http://www.charatsoft.com/ //
///////////////////////////////////////////////////////////////////////////////////
#include "CWindow.h"
#include "CWav.h"
#define DEBUGMODE // デバッグ出力しない場合はコメント化する
#include "DEBUG.H"
// マクロ
#define SAFE_RELEASE(x) { if( x ) { x->Release(); x=NULL; } }
#define SAFE_FREE(x) { if( x ) { free(x); x=NULL; } }
//////////////////////////////////////////////////////////////////////////////////////
// WASAPI関連
//////////////////////////////////////////////////////////////////////////////////////
// ヘッダ
#include <mmdeviceapi.h>
#include <Audioclient.h>
#include <audiopolicy.h>
#include <endpointvolume.h>
#include <FunctionDiscoveryKeys_devpkey.h>
// ライブラリ
#pragma comment(lib, "Avrt.lib")
#pragma comment(lib, "winmm.lib")
// インターフェースのGUIDの実体(プロジェクト内のCファイルに必ず1つ必要)
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioClock = __uuidof(IAudioClock);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
//////////////////////////////////////////////////////////////////////////////////////
// グローバル変数
//////////////////////////////////////////////////////////////////////////////////////
CWindow win; // ウィンドウ
CWav wav; // WAVファイル
int iAddr = 0; // WAVファイルの再生位置
IMMDeviceEnumerator *pDeviceEnumerator = NULL; // マルチメディアデバイス列挙インターフェース
IMMDevice *pDevice = NULL; // デバイスインターフェース
IAudioClient *pAudioClient = NULL; // オーディオクライアントインターフェース
IAudioRenderClient *pRenderClient = NULL; // レンダークライアントインターフェース
int iFrameSize = 0; // 1サンプル分のバッファサイズ
HANDLE hEvent = NULL; // イベントハンドル
HANDLE hThread = NULL; // スレッドハンドル
BOOL bThreadLoop = FALSE; // スレッド処理中か
//////////////////////////////////////////////////////////////////////////////////////
// 再生スレッド
//////////////////////////////////////////////////////////////////////////////////////
DWORD CALLBACK PlayThread( LPVOID param )
{
DEBUG( "スレッド開始\n" );
while( bThreadLoop ) {
// 次のバッファ取得が必要になるまで待機
DWORD retval = WaitForSingleObject( hEvent,2000 );
if( retval!=WAIT_OBJECT_0 ) {
DEBUG( "タイムアウト\n" );
pAudioClient->Stop();
break;
}
// 今回必要なフレーム数を取得
UINT32 frame_count;
HRESULT ret = pAudioClient->GetBufferSize( &frame_count );
// ODS( "フレーム数 : %d\n",frame_count );
// フレーム数からバッファサイズを算出
int buf_size = frame_count * iFrameSize;
// ODS( "バッファサイズ : %dbyte\n",buf_size );
// 出力バッファのポインタを取得
LPBYTE dst;
ret = pRenderClient->GetBuffer( frame_count,&dst );
if( SUCCEEDED(ret) ) {
// ソースバッファのポインタを取得
LPBYTE src = wav.GetBuffer();
// 現在のカーソルから次に必要なバッファサイズを加算したときにWAVバッファのサイズを超えるか
if( iAddr+buf_size>wav.GetBufferSize() ) {
////////////////////////////////////////////////////////////////////////////////////////
// 超える場合はまず現在のカーソルから最後までをコピーし、次に残った分を先頭から補充する
////////////////////////////////////////////////////////////////////////////////////////
// WAVバッファサイズから現在のカーソル位置を差し引いたものが最後までのバッファサイズ
int last_size = wav.GetBufferSize() - iAddr;
// 現在のカーソルから最後までのバッファを出力バッファの先頭にコピーする
memcpy( &dst[0],&src[iAddr],last_size );
// 今回必要なサイズから今コピーしたサイズを引いたものが先頭から補充するバッファサイズ
int begin_size = buf_size - last_size;
// WAVバッファの先頭から補充サイズ分を出力バッファに追加コピーする
memcpy( &dst[last_size],&src[0],begin_size );
// 補充した分のサイズを現在のカーソルとする
iAddr = begin_size;
ODS( "LOOP OK\n" );
} else {
////////////////////////////////////////////////////////////////////////////////////////
// 超えない場合は現在のカーソルから必要なバッファ分をコピーしてカーソルを進める
////////////////////////////////////////////////////////////////////////////////////////
// WAVバッファの現在のカーソルから出力バッファに指定サイズ分コピーする
memcpy( &dst[0],&src[iAddr],buf_size );
// カーソルをコピーした分だけ進める
iAddr += buf_size;
}
// バッファを開放
pRenderClient->ReleaseBuffer( frame_count,0 );
}
}
DEBUG( "スレッド終了\n" );
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
// WASAPIの初期化
//////////////////////////////////////////////////////////////////////////////////////
BOOL InitWasapi( int latency )
{
HRESULT ret;
// マルチメディアデバイス列挙子
ret = CoCreateInstance( CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator );
if( FAILED(ret) ) {
DEBUG( "CLSID_MMDeviceEnumeratorエラー\n" );
return FALSE;
}
// デフォルトのデバイスを選択
ret = pDeviceEnumerator->GetDefaultAudioEndpoint( eRender,eConsole,&pDevice );
if( FAILED(ret) ) {
DEBUG( "GetDefaultAudioEndpointエラー\n" );
return FALSE;
}
// オーディオクライアント
ret = pDevice->Activate( IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient );
if( FAILED(ret) ) {
DEBUG( "オーディオクライアント取得失敗\n" );
return FALSE;
}
// フォーマットの構築
WAVEFORMATEXTENSIBLE wf;
ZeroMemory( &wf,sizeof(wf) );
wf.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE);
wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wf.Format.nChannels = 2;
wf.Format.nSamplesPerSec = 44100;
wf.Format.wBitsPerSample = 16;
wf.Format.nBlockAlign = wf.Format.nChannels * wf.Format.wBitsPerSample / 8;
wf.Format.nAvgBytesPerSec = wf.Format.nSamplesPerSec * wf.Format.nBlockAlign;
wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample;
wf.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
wf.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
// 1サンプルのサイズを保存(16bitステレオなら4byte)
iFrameSize = wf.Format.nBlockAlign;
// フォーマットのサポートチェック
ret = pAudioClient->IsFormatSupported( AUDCLNT_SHAREMODE_EXCLUSIVE,(WAVEFORMATEX*)&wf,NULL );
if( FAILED(ret) ) {
DEBUG( "未サポートのフォーマット\n" );
return FALSE;
}
// レイテンシ設定
REFERENCE_TIME default_device_period = 0;
REFERENCE_TIME minimum_device_period = 0;
if( latency!=0 ) {
default_device_period = (REFERENCE_TIME)latency * 10000LL; // デフォルトデバイスピリオドとしてセット
DEBUG( "レイテンシ指定 : %I64d (%fミリ秒)\n",default_device_period,default_device_period/10000.0 );
} else {
ret = pAudioClient->GetDevicePeriod( &default_device_period,&minimum_device_period );
DEBUG( "デフォルトデバイスピリオド : %I64d (%fミリ秒)\n",default_device_period,default_device_period/10000.0 );
DEBUG( "最小デバイスピリオド : %I64d (%fミリ秒)\n",minimum_device_period,minimum_device_period/10000.0 );
}
// 初期化
UINT32 frame = 0;
ret = pAudioClient->Initialize( AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
default_device_period, // デフォルトデバイスピリオド値をセット
default_device_period, // デフォルトデバイスピリオド値をセット
(WAVEFORMATEX*)&wf,
NULL );
if( FAILED(ret) ) {
if( ret==AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED ) {
DEBUG( "バッファサイズアライメントエラーのため修正する\n" );
// 修正後のフレーム数を取得
ret = pAudioClient->GetBufferSize( &frame );
DEBUG( "修正後のフレーム数 : %d\n",frame );
default_device_period = (REFERENCE_TIME)( 10000.0 * // (REFERENCE_TIME(100ns) / ms) *
1000 * // (ms / s) *
frame / // frames /
wf.Format.nSamplesPerSec + // (frames / s)
0.5); // 四捨五入?
DEBUG( "修正後のレイテンシ : %I64d (%fミリ秒)\n",default_device_period,default_device_period/10000.0 );
// 一度破棄してオーディオクライアントを再生成
SAFE_RELEASE( pAudioClient );
ret = pDevice->Activate( IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient );
if( FAILED(ret) ) {
DEBUG( "オーディオクライアント再取得失敗\n" );
return FALSE;
}
// 再挑戦
ret = pAudioClient->Initialize( AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
default_device_period,
default_device_period,
(WAVEFORMATEX*)&wf,
NULL );
}
if( FAILED(ret) ) {
DEBUG( "初期化失敗 : 0x%08X\n",ret );
switch( ret )
{
case AUDCLNT_E_NOT_INITIALIZED: DEBUG( "AUDCLNT_E_NOT_INITIALIZED\n" ); break;
case AUDCLNT_E_ALREADY_INITIALIZED: DEBUG( "AUDCLNT_E_ALREADY_INITIALIZED\n" ); break;
case AUDCLNT_E_WRONG_ENDPOINT_TYPE: DEBUG( "AUDCLNT_E_WRONG_ENDPOINT_TYPE\n" ); break;
case AUDCLNT_E_DEVICE_INVALIDATED: DEBUG( "AUDCLNT_E_DEVICE_INVALIDATED\n" ); break;
case AUDCLNT_E_NOT_STOPPED: DEBUG( "AUDCLNT_E_NOT_STOPPED\n" ); break;
case AUDCLNT_E_BUFFER_TOO_LARGE: DEBUG( "AUDCLNT_E_BUFFER_TOO_LARGE\n" ); break;
case AUDCLNT_E_OUT_OF_ORDER: DEBUG( "AUDCLNT_E_OUT_OF_ORDER\n" ); break;
case AUDCLNT_E_UNSUPPORTED_FORMAT: DEBUG( "AUDCLNT_E_UNSUPPORTED_FORMAT\n" ); break;
case AUDCLNT_E_INVALID_SIZE: DEBUG( "AUDCLNT_E_INVALID_SIZE\n" ); break;
case AUDCLNT_E_DEVICE_IN_USE: DEBUG( "AUDCLNT_E_DEVICE_IN_USE\n" ); break;
case AUDCLNT_E_BUFFER_OPERATION_PENDING: DEBUG( "AUDCLNT_E_BUFFER_OPERATION_PENDING\n" ); break;
case AUDCLNT_E_THREAD_NOT_REGISTERED: DEBUG( "AUDCLNT_E_THREAD_NOT_REGISTERED\n" ); break;
// case AUDCLNT_E_NO_SINGLE_PROCESS: DEBUG( "AUDCLNT_E_NO_SINGLE_PROCESS\n" ); break; // VC2010では未定義?
case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: DEBUG( "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED\n" ); break;
case AUDCLNT_E_ENDPOINT_CREATE_FAILED: DEBUG( "AUDCLNT_E_ENDPOINT_CREATE_FAILED\n" ); break;
case AUDCLNT_E_SERVICE_NOT_RUNNING: DEBUG( "AUDCLNT_E_SERVICE_NOT_RUNNING\n" ); break;
case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: DEBUG( "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED\n" ); break;
case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: DEBUG( "AUDCLNT_E_EXCLUSIVE_MODE_ONLY\n" ); break;
case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: DEBUG( "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL\n" ); break;
case AUDCLNT_E_EVENTHANDLE_NOT_SET: DEBUG( "AUDCLNT_E_EVENTHANDLE_NOT_SET\n" ); break;
case AUDCLNT_E_INCORRECT_BUFFER_SIZE: DEBUG( "AUDCLNT_E_INCORRECT_BUFFER_SIZE\n" ); break;
case AUDCLNT_E_BUFFER_SIZE_ERROR: DEBUG( "AUDCLNT_E_BUFFER_SIZE_ERROR\n" ); break;
case AUDCLNT_E_CPUUSAGE_EXCEEDED: DEBUG( "AUDCLNT_E_CPUUSAGE_EXCEEDED\n" ); break;
default: DEBUG( "UNKNOWN\n" ); break;
}
return FALSE;
}
}
// イベント生成
hEvent = CreateEvent( NULL,FALSE,FALSE,NULL );
if( !hEvent ) {
DEBUG( "イベントオブジェクト作成失敗\n" );
return FALSE;
}
// イベントのセット
ret = pAudioClient->SetEventHandle( hEvent );
if( FAILED(ret) ) {
DEBUG( "イベントオブジェクト設定失敗\n" );
return FALSE;
}
// レンダラーの取得
ret = pAudioClient->GetService( IID_IAudioRenderClient,(void**)&pRenderClient );
if( FAILED(ret) ) {
DEBUG( "レンダラー取得エラー\n" );
return FALSE;
}
// WASAPI情報取得
ret = pAudioClient->GetBufferSize( &frame );
DEBUG( "設定されたフレーム数 : %d\n",frame );
UINT32 size = frame * iFrameSize;
DEBUG( "設定されたバッファサイズ : %dbyte\n",size );
DEBUG( "1サンプルの時間 : %f秒\n",(float)size/wf.Format.nSamplesPerSec );
// ゼロクリアをしてイベントをリセット
LPBYTE pData;
ret = pRenderClient->GetBuffer( frame,&pData );
if( SUCCEEDED(ret) ) {
ZeroMemory( pData,size );
pRenderClient->ReleaseBuffer( frame,0 );
}
// スレッドループフラグを立てる
bThreadLoop = TRUE;
// 再生スレッド起動
DWORD dwThread;
hThread = CreateThread( NULL,0,PlayThread,NULL,0,&dwThread );
if( !hThread ) {
// 失敗
return FALSE;
}
// 再生開始
pAudioClient->Start();
DEBUG( "WASAPI初期化完了\n" );
return TRUE;
}
//////////////////////////////////////////////////////////////////////////////////////
// WASAPIの終了
//////////////////////////////////////////////////////////////////////////////////////
void ExitWasapi()
{
// スレッドループフラグを落とす
bThreadLoop = FALSE;
// スレッド終了処理
if( hThread ) {
// スレッドが完全に終了するまで待機
WaitForSingleObject( hThread,INFINITE );
CloseHandle( hThread );
hThread = NULL;
}
// イベントを開放処理
if( hEvent ) {
CloseHandle( hEvent );
hEvent = NULL;
}
// インターフェース開放
SAFE_RELEASE( pRenderClient );
SAFE_RELEASE( pAudioClient );
SAFE_RELEASE( pDevice );
SAFE_RELEASE( pDeviceEnumerator );
DEBUG( "WASAPI終了\n" );
}
//////////////////////////////////////////////////////////////////////////////////////
// メインルーチン
//////////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow )
{
// デバッグ定義
INITDEBUG( "DEBUG.TXT" );
CLEARDEBUG;
// ウィンドウ生成
if( !win.Create(hInstance,L"TestWasapi") ) {
DEBUG( "ウィンドウ生成エラー\n" );
return -1;
}
// 日本語入力の無効化
ImmAssociateContext( win.hWnd, NULL );
// WAVをロード
if( !wav.Load("loop.wav") ) {
win.Delete();
return -1;
}
// COMの初期化
CoInitialize( NULL );
// WASAPI初期化
if( !InitWasapi(0) ) { // 0ならデフォルトデバイスピリオドを使用
// エラーなら終了する
MessageBoxA( win.hWnd,"WASAPI初期化失敗","エラー",MB_OK|MB_ICONHAND );
ExitWasapi();
CoUninitialize();
win.Delete();
return -1;
}
// ウィンドウループ
while(1) {
MSG msg;
int ret = GetMessage( &msg,NULL,0,0 ); // メッセージが登録されるまでブロック
if( ret==0 || ret==-1 ) {
// 終了コードなら抜ける
break;
}
// メッセージを処理する
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// WASAPI終了
ExitWasapi();
// COMの終了
CoUninitialize();
// ウィンドウ削除
win.Delete();
return 0;
}
Re: WASAPIを使ったPC音録音ソフト
久し振りにハードウェアの勉強ができて面白かったです。WASAPIの詳細は不明です。Audacityのforumで質問してみて下さい。http://forum.audacityteam.org/
Re: WASAPIを使ったPC音録音ソフト
WASAPI-sample-全コード[Main.cpp]前に書いたもの
[CWav.cpp]
「CWindow.cpp]
[CWav.cpp]
#pragma warning( disable : 4996 )
#include "CWav.h"
#include <io.h>
#include <fcntl.h>
//#define DEBUGMODE
#include "DEBUG.H"
#define FREE(x) { if(x) free(x); x=NULL; }
CWav::CWav()
{
fp = NULL;
mBuf = NULL;
iBuf = 0;
iStreamPoint = 0;
dwMode = WAVMODE_STATIC;
ZeroMemory( &mHead,sizeof(mHead) );
}
CWav::~CWav()
{
Close();
FREE( mBuf );
}
BOOL CWav::Load( const char *file,DWORD mode )
{
Close();
fp = fopen( file,"rb" );
if( !fp )
return FALSE;
fread( &mHead,sizeof(WAVHEADER),1,fp );
if( strnicmp(mHead.mRiff,"RIFF",4)!=0 ) {
DEBUG( "RIFFフォーマットエラー\n","" );
return FALSE;
}
if( strnicmp(mHead.mWave,"WAVE",4)!=0 ) {
DEBUG( "WAVチャンクが見つからない\n","" );
return FALSE;
}
if( strnicmp(mHead.mFmt,"fmt ",4)!=0 ) {
DEBUG( "フォーマットチャンクが見つからない\n","" );
return FALSE;
}
DEBUG( "フォーマットID = %d\n",mHead.wFormatID );
DEBUG( "チャンクサイズ = %d\n",mHead.dwChuncSize );
DEBUG( "チャンネル数 = %d\n",mHead.wChannel );
DEBUG( "ビット数 = %d\n",mHead.wBitrate );
DEBUG( "サンプリング周波数 = %d\n",mHead.dwSample );
// dataチャンクを探す
DATACHUNC data;
while(1) {
// EOFなら終了
if( feof(fp) ) {
DEBUG( "dataチャンクが見つからなかった\n","" );
fclose( fp );
return FALSE;
}
fread( &data,sizeof(DATACHUNC),1,fp );
// dataチャンクなら抜ける
if( strnicmp(data.mData,"data",4)==0 )
break;
// 次のチャンクへ
if( !fseek( fp,data.dwSize,SEEK_CUR ) ) {
DEBUG( "seekエラー\n","" );
fclose( fp );
return FALSE;
}
}
// データサイズの取得
iBuf = data.dwSize;
DEBUG( "データサイズ = %d\n",iBuf );
// モード記憶
dwMode = mode;
switch( dwMode )
{
case WAVMODE_STATIC:
mBuf = (LPBYTE)realloc( mBuf,iBuf );
if( !mBuf ) {
DEBUG( "メモリ確保エラー\n","" );
return FALSE;
}
fread( mBuf,iBuf,1,fp );
break;
}
return TRUE;
}
BOOL CWav::Close( void )
{
if( fp ) {
fclose( fp );
fp = NULL;
DEBUG( "CLOSE OK\n","" );
}
dwMode = WAVMODE_STATIC;
iStreamPoint = 0;
return TRUE;
}
BOOL CWav::Create( const char *file,int ch,int bit,int samp )
{
Close();
iBuf = 0;
memcpy( &mHead.mRiff,"RIFF",4 );
mHead.dwFileSize = sizeof(WAVHEADER) + sizeof(DATACHUNC);
memcpy( &mHead.mWave,"WAVE",4 );
memcpy( &mHead.mFmt,"fmt ",4 );
mHead.dwChuncSize = 16; // 16固定
mHead.wFormatID = 1;
mHead.wChannel = ch;
mHead.dwSample = samp;
mHead.wBitrate = bit;
mHead.dwBytePerSec = mHead.wChannel * mHead.dwSample * mHead.wBitrate / 8;
mHead.wBlockSize = mHead.wChannel * mHead.wBitrate / 8;
memcpy( &mData.mData,"data",4 );
mData.dwSize = 0;
strcpy( mFile,file );
fp = fopen( mFile,"wb" );
if( !fp )
return FALSE;
fwrite( &mHead,sizeof(WAVHEADER),1,fp );
fwrite( &mData,sizeof(DATACHUNC),1,fp );
return TRUE;
}
BOOL CWav::Write( const LPVOID buf,int size )
{
if( dwMode!=WAVMODE_STATIC )
return FALSE;
/* mBuf = (LPBYTE)realloc( mBuf,iBuf+size );
if( !mBuf )
return FALSE;
memcpy( &mBuf[iBuf],buf,size );/**/
// データ書き込み先
// DEBUG( "書き込み先 %08X\n",sizeof(WAVHEADER) + sizeof(DATACHUNC)+iBuf );
fseek( fp,sizeof(WAVHEADER) + sizeof(DATACHUNC)+iBuf,SEEK_SET );
fwrite( buf,size,1,fp );
// ヘッダの更新
mHead.dwFileSize += size;
mData.dwSize += size;
fseek( fp,0,SEEK_SET );
fwrite( &mHead,sizeof(WAVHEADER),1,fp );
fwrite( &mData,sizeof(DATACHUNC),1,fp );
iBuf += size;
return TRUE;
}
BOOL CWav::Read( char *buf,int *size )
{
if( dwMode!=WAVMODE_STREAM ) {
DEBUG( "ストリームモードではない\n","" );
return FALSE;
}
// ポインタを移動
fseek( fp,sizeof(WAVHEADER) + sizeof(DATACHUNC)+iStreamPoint,SEEK_SET );
// 最大値のチェック
int read = *size;
if( read+iStreamPoint>iBuf )
read = iBuf - iStreamPoint;
*size = read;
iStreamPoint += read;
if( read<1 ) {
DEBUG( "ロード終了\n","" );
return FALSE;
}
// DEBUG( "ロードサイズ %d\n",read );
fread( buf,read,1,fp );
return TRUE;
}
#pragma warning( disable : 4996 )
#include "CWindow.h"
//#define DEBUGMODE
#include "DEBUG.H"
///////////////////////////////////////////////////////////////////////////////////
// スタティック変数
///////////////////////////////////////////////////////////////////////////////////
HINSTANCE CWindow::hInstance = NULL;
HWND CWindow::hWnd = NULL;
BOOL CWindow::bActive = TRUE;
WCHAR CWindow::mName[256] = L"";
const WCHAR* CWindow::cIconID = IDI_APPLICATION; // デフォルトのアイコン
HMENU CWindow::hMenu = NULL;
DWORD CWindow::dwStyle = WS_POPUP|WS_SYSMENU|WS_CAPTION|WS_MINIMIZEBOX;
DWORD CWindow::dwExStyle = 0;
LPONMSG CWindow::mMsg = NULL;
int CWindow::iMsg = 0;
///////////////////////////////////////////////////////////////////////////////////
// コンストラクタ
///////////////////////////////////////////////////////////////////////////////////
CWindow::CWindow(void)
{
}
///////////////////////////////////////////////////////////////////////////////////
// デストラクタ
///////////////////////////////////////////////////////////////////////////////////
CWindow::~CWindow()
{
}
///////////////////////////////////////////////////////////////////////////////////
// メインウインドウのイベントハンドラ
///////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK CWindow::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static CWindow *win = NULL;
switch( uMsg )
{
case WM_CREATE:
win = (CWindow*)lParam;
break;
case WM_ACTIVATE:
bActive = LOWORD(wParam)?TRUE:FALSE; // アクティブ状態変更
break;
case WM_DESTROY: // ALT+F4が押されたら
PostQuitMessage( 0 );
break;
case WM_MOUSEMOVE:
break;
case WM_SYSCOMMAND:
switch( wParam )
{
case SC_CLOSE:
PostQuitMessage(0);
return 0;
}
break;
case WM_IME_NOTIFY:
switch( wParam )
{
case IMN_SETOPENSTATUS:
HIMC hImc = ImmGetContext( hWnd );
ImmSetOpenStatus( hImc,FALSE );
break;
}
break;
}
// 特殊メッセージ処理
int i;
for( i=0;i<iMsg;i++ ) {
if( uMsg==mMsg[i].uiMsg ) {
return mMsg[i].cmdProc( hWnd,wParam,lParam ); // 特殊メッセージ操作完了なら
}
}
return DefWindowProc( hWnd,uMsg,wParam,lParam ); // デフォルトを返す
}
///////////////////////////////////////////////////////////////////////////////////
// ウインドウを生成する
///////////////////////////////////////////////////////////////////////////////////
BOOL CWindow::Create( HINSTANCE hInst,const WCHAR *appName,BOOL show,DWORD w,DWORD h,HWND parent )
{
WNDCLASS wc;
DEVMODE dmMode;
// 画面解像度をチェック
EnumDisplaySettings(NULL,ENUM_CURRENT_SETTINGS,&dmMode);
// 16bit以上の解像度じゃないと起動できない
if( dmMode.dmBitsPerPel<16 ) {
MessageBoxW( GetDesktopWindow(),L"16Bit以上の解像度にしてください",L"起動できません",MB_OK );
return FALSE;
}
// セット
hInstance = hInst;
wcscpy( mName,appName );
// ウインドウクラス登録
wc.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNCLIENT;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(DWORD);
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, cIconID );
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = MAKEINTRESOURCE( hMenu );
wc.lpszClassName = mName;
if ( !RegisterClass(&wc) )
return FALSE;
// ウインドウ生成
hWnd = CreateWindowExW(
dwExStyle,
wc.lpszClassName, // Class
mName, // Title bar
dwStyle, // Style
GetSystemMetrics(SM_CXSCREEN)/2-w/2,
GetSystemMetrics(SM_CYSCREEN)/2-h/2,
w, // Init. x pos
h, // Init. y pos
parent, // Parent window
NULL, // Menu handle
hInstance, // Program handle
this // Create parms
);
if( !hWnd )
return FALSE; // 生成に失敗
// フォントの設定
HDC hdc = GetDC( hWnd );
if( hdc ) {
SetBkMode( hdc,TRANSPARENT );
ReleaseDC( hWnd,hdc );
}
MoveClientWindowCenter( w,h );
// ウインドウを表示
if( show )
::ShowWindow( hWnd,SW_SHOW );
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////////
// 明示的にウィンドウを削除する
///////////////////////////////////////////////////////////////////////////////////
void CWindow::Delete( void )
{
if( hWnd ) {
// 通常のウィンドウなら
::DestroyWindow( hWnd );
// 登録したクラス名を解除
UnregisterClassW( mName,hInstance );
ZeroMemory( &mName,sizeof(mName) );
hWnd = NULL;
hInstance = NULL;
}
}
///////////////////////////////////////////////////////////////////////////////////
// カーソルの表示・非表示
///////////////////////////////////////////////////////////////////////////////////
void CWindow::ShowCursor( BOOL bShow )
{
if( bShow )
while(::ShowCursor(TRUE)<0);
else
while(::ShowCursor(FALSE)>=0);
}
///////////////////////////////////////////////////////////////////////////////////
// ウインドウの表示・非表示
///////////////////////////////////////////////////////////////////////////////////
void CWindow::ShowWindow( BOOL bShow )
{
// ウインドウの表示
if( bShow )
::ShowWindow( hWnd,SW_SHOW );
else
::ShowWindow( hWnd,SW_HIDE );
}
///////////////////////////////////////////////////////////////////////////////////
// アプリケーションのアイコンの変更
///////////////////////////////////////////////////////////////////////////////////
void CWindow::SetIcon( const WCHAR *icon )
{
cIconID = icon;
}
// 特殊メッセージの追加
BOOL CWindow::AddMsgProc( UINT msg,ONCOMMAND proc )
{
int i;
// 既に存在していないかチェック
for( i=0;i<iMsg;i++ ) {
if( mMsg[i].uiMsg==msg ) {
// あれば新しいアドレスに更新
mMsg[i].cmdProc = proc;
return TRUE;
}
}
// 追加
iMsg++;
mMsg = (LPONMSG)realloc( mMsg,sizeof(ONMSG)*iMsg );
ZeroMemory( &mMsg[iMsg-1],sizeof(ONMSG) );
mMsg[iMsg-1].uiMsg = msg;
mMsg[iMsg-1].cmdProc = proc;
return TRUE;
}
// ウインドウスタイルの変更(動的に変更も可能)
BOOL CWindow::SetWindowStyle( DWORD style )
{
dwStyle = style;
if( hWnd ) {
// すでにウインドウが存在する場合は即反映
::SetWindowLong( hWnd,GWL_STYLE,style );
::SetWindowPos( hWnd,0,0,0,0,0,SWP_FRAMECHANGED|SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOSIZE|SWP_NOZORDER );
}
return TRUE;
}
// ウインドウの移動
void CWindow::Move( int x,int y )
{
SetWindowPos( hWnd,0,x,y,0,0,SWP_NOSIZE|SWP_NOZORDER );
}
// ウインドウの移動(幅と高さも同時に変更)
void CWindow::Move( int x,int y,int w,int h )
{
MoveWindow( hWnd,x,y,w,h,TRUE );
}
// 指定サイズがクライアント領域になるようにウインドウを配置
BOOL CWindow::MoveClientWindowCenter( int w,int h )
{
RECT Win,Cli;
GetWindowRect( hWnd,&Win ); // ウインドウの左上を取得
GetClientRect( hWnd,&Cli ); // ウインドウ内のクライアント座標を取得
int frame_w = (Win.right - Win.left) - Cli.right; // フレームの幅
int frame_h = (Win.bottom - Win.top) - Cli.bottom; // フレームの高さ
int scr_w = GetSystemMetrics( SM_CXSCREEN ); // スクリーンの幅
int scr_h = GetSystemMetrics( SM_CYSCREEN ); // スクリーンの高さ
SetWindowPos( hWnd,0,( scr_w - (frame_w/2+w) ) / 2,( scr_h - (frame_h/2+h) ) / 2,w+frame_w,h+frame_h,SWP_NOZORDER );
return TRUE;
}
// メニューアイテムの変更
BOOL CWindow::SetMenuItem(int menuid,BOOL check,BOOL gray )
{
HMENU menu = GetMenu( hWnd );
if( menu ) {
MENUITEMINFO miinfo;
ZeroMemory( &miinfo,sizeof(miinfo) );
miinfo.cbSize = sizeof(miinfo);
miinfo.fMask = MIIM_STATE;
if( check )
miinfo.fState |= MFS_CHECKED;
else
miinfo.fState |= MFS_UNCHECKED;
if( gray )
miinfo.fState |= MFS_GRAYED;
else
miinfo.fState |= MFS_ENABLED;
return SetMenuItemInfo( menu,menuid,FALSE,&miinfo );
}
return TRUE;
}
BOOL CWindow::TextOutW( int x,int y,const WCHAR *str,COLORREF col )
{
HDC hdc = GetDC( hWnd );
if( hdc ) {
SetTextColor( hdc,col );
::TextOutW( hdc,x,y,str,wcslen(str) );
ReleaseDC( hWnd,hdc );
}
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////////
// メニューバーの変更(Createの前に必要)
///////////////////////////////////////////////////////////////////////////////////
void CWindow::SetMenu( HMENU menu )
{
hMenu = menu;
}
Re: WASAPIを使ったPC音録音ソフト
[DEBUG.cpp]
#pragma warning( disable : 4996 )
#include "DEBUG.H"
#include <stdio.h>
// 同期オブジェクト
class CDbgCritialSection {
CRITICAL_SECTION mCS;
public:
CDbgCritialSection() {
InitializeCriticalSection( &mCS );
}
virtual ~CDbgCritialSection() {
DeleteCriticalSection( &mCS );
}
void Enter( void ) {
EnterCriticalSection( &mCS );
}
void Leave( void ) {
LeaveCriticalSection( &mCS );
}
};
static CDbgCritialSection cs;
static BOOL bInit = FALSE;
static char mFileName[MAX_PATH] = "";
// 現在の日時を文字列で返す(同期必須)
const char *GetNow( void )
{
static char mNowStr[256] = "";
SYSTEMTIME st;
GetLocalTime( &st );
sprintf( mNowStr,"%04d/%02d/%02d %02d:%02d:%02d.%03d",st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute,st.wSecond,st.wMilliseconds );
return mNowStr;
}
void dbgInitA( const char *file )
{
cs.Enter();
if( !bInit ) {
// 未初期化なら初期化
strcpy( mFileName,file );
DeleteFileA( mFileName );
bInit = TRUE;
}
cs.Leave();
}
void dbgDebugA( const char *s,... )
{
cs.Enter();
if( bInit ) {
char *str = NULL;
va_list ap;
va_start( ap,s );
int len = _vscprintf( s,ap );
str = (CHAR*)malloc( sizeof(CHAR)*(len+1) );
if( str ) {
vsprintf( str,s,ap );
// ファイル書き出し
FILE *fp;
fp = fopen( mFileName,"a" );
if( fp ) {
fprintf( fp,"[%s] %s",GetNow(),str );
fclose( fp );
}
free( str );
}
va_end( ap );
}
cs.Leave();
}
void dbgClear( void )
{
cs.Enter();
if( bInit ) {
char s[1024];
ZeroMemory( s,sizeof(s) );
sprintf( s,"[%s] << Log Clear >>\n",GetNow() );
// ファイル書き出し
FILE *fp;
fp = fopen( mFileName,"a" );
if( fp ) {
fputs( s,fp );
fclose( fp );
}
}
cs.Leave();
}
void dbgOutputDebugStringA( const char *s,... )
{
cs.Enter();
va_list ap;
va_start( ap,s );
int len = _vscprintf( s,ap );
char *str = (CHAR*)malloc( sizeof(char)*(len+1) );
if( str ) {
vsprintf( str,s,ap );
size_t len2 = strlen(str);
if( len2>=DEBUG_MAXTEXTBUF ) {
str[DEBUG_MAXTEXTBUF-4] = NULL;
strcat( str,"...\n" );
}
OutputDebugStringA( str );
free( str );
}
va_end( ap );
cs.Leave();
}
Re: WASAPIを使ったPC音録音ソフト
[CWav.h]
#ifndef _CWAV_H
#define _CWAV_H
///////////////////////////////////////////////////////////////////////////////////
// CWav : WAV管理クラス v1.01 //
// //
// このソースコードは自由に改変して使用可能です。 //
// また商用利用も可能ですが、すべての環境で正しく動作する保障はありません。 //
// http://www.charatsoft.com/ //
///////////////////////////////////////////////////////////////////////////////////
#include <WinSock2.h>
#include <Windows.h>
#include <stdio.h>
// 読み込みモード
#define WAVMODE_STATIC 0x00000000 // メモリ上にすべてのWAVをロードする(スタティック)
#define WAVMODE_STREAM 0x00000001 // ファイルから任意のサイズを読み取って使用する(ストリーム)
// WAVヘッダ
typedef struct _WAVHEADER {
char mRiff[4]; // 'RIFF'
DWORD dwFileSize; // ファイルサイズ
char mWave[4]; // 'WAVE'
char mFmt[4]; // 'fmt '
DWORD dwChuncSize; // チャンクサイズ(PCMなら16)
WORD wFormatID; // フォーマットID(PCMなら1)
WORD wChannel; // チャンネル数
DWORD dwSample; // サンプリングレート
DWORD dwBytePerSec; // 秒間のバイト数(wChannel*dwSample*wBitrate )
WORD wBlockSize; // 1つのWAVデータサイズ(wChannel*wBitrate)
WORD wBitrate; // サンプリングビット値(8bit/16bit)
} WAVHEADER,*LPWAVHEADER;
// チャンク構造体
typedef struct _DATACHUNC {
char mData[4]; // 'data'
DWORD dwSize; // データサイズ
} DATACHUNC,*LPDATACHUNC;
class CWav {
DWORD dwMode; // ストリームか
WAVHEADER mHead; // WAVヘッダ
DATACHUNC mData; // DATAチャンク
LPBYTE mBuf; // データ部
int iBuf; // データ数
int iStreamPoint; // ストリーム時のデータポインタ
char mFile[MAX_PATH]; // ファイル名
FILE *fp;
public:
CWav();
virtual ~CWav();
BOOL Create( const char *file,int ch=2,int bit=16,int samp=44100 ); // WAVデータの新規定義
BOOL Write( const LPVOID buf,int size ); // ファイルへの書き込み
BOOL Close( void ); // ファイルを閉じる
BOOL Load( const char *file,DWORD mode=WAVMODE_STATIC ); // 読み込みモードを指定してファイルをオープン
BOOL Read( char *buf,int *size ); // ストリームモードでのロード
public:
inline const LPBYTE GetBuffer( void ) { return mBuf; }
inline int GetBufferSize( void ) { return iBuf; }
inline int GetChannel( void ) { return (int)mHead.wChannel; }
inline int GetSampleRate( void ) { return (int)mHead.dwSample; }
inline int GetBitrate( void ) { return (int)mHead.wBitrate; }
};
#endif
Re: WASAPIを使ったPC音録音ソフト
[CWindow.h]
#ifndef __CWINDOW_H
#define __CWINDOW_H
///////////////////////////////////////////////////////////////////////////////////
// CWindow : ウィンドウ管理 v5.00(UNICODE) //
// //
// このソースコードは自由に改変して使用可能です。 //
// また商用利用も可能ですが、すべての環境で正しく動作する保障はありません。 //
// http://www.charatsoft.com/ //
///////////////////////////////////////////////////////////////////////////////////
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib,"imm32.lib")
///////////////////////////////////////////////////////////////////////////////////
// コールバック関数定義
///////////////////////////////////////////////////////////////////////////////////
typedef LRESULT (CALLBACK *ONCOMMAND)( HWND hWnd,WPARAM wParam,LPARAM lParam );
typedef struct _ONMSG {
UINT uiMsg; // メッセージ番号
ONCOMMAND cmdProc; // コールバックアドレス
} ONMSG,*LPONMSG;
///////////////////////////////////////////////////////////////////////////////////
// ウインドウクラス
///////////////////////////////////////////////////////////////////////////////////
class CWindow {
public:
// 使いやすいように外へ出す
static HINSTANCE hInstance; // メインウインドウのインスタンス
static HWND hWnd; // メインウインドウのハンドル
static BOOL bActive; // アクティブか
protected:
// ウインドウ関係
static WCHAR mName[256]; // アプリケーション名(クラス名としても使用)
static const WCHAR *cIconID; // アイコンの種類
static HMENU hMenu; // メニューハンドル
static DWORD dwStyle; // ウインドウスタイル
static DWORD dwExStyle; // 拡張スタイル
static LPONMSG mMsg; // 追加メッセージデータ
static int iMsg; // 追加メッセージの数
private:
// ウインドウメッセージ処理コールバック
static LRESULT CALLBACK WindowProc( HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam );
public:
CWindow();
virtual ~CWindow();
void SetIcon( const WCHAR *icon ); // アイコンの変更(Createの前に必要)
BOOL SetWindowStyle( DWORD style ); // ウインドウのスタイルの変更(動的に変更も可能)
void SetMenu( HMENU menu );
BOOL Create( HINSTANCE hInst,const WCHAR *appName,BOOL show=TRUE,DWORD w=640,DWORD h=480,HWND parent=NULL ); // 内部で管理するウインドウを生成する
void Delete( void ); // 明示的にウィンドウを削除する
BOOL AddMsgProc( UINT msg,ONCOMMAND proc ); // 特殊メッセージの追加
void ShowCursor( BOOL bShow ); // マウスカーソルの表示、非表示
void ShowWindow( BOOL bShow ); // ウインドウの表示、非表示
void Move( int x,int y ); // ウインドウの移動
void Move( int x,int y,int w,int h ); // ウインドウの移動(幅と高さも同時に変更)
BOOL MoveClientWindowCenter( int w,int h ); // 指定サイズがクライアント領域になるようにウインドウを配置
BOOL SetMenuItem( int menuid,BOOL check=FALSE,BOOL gray=FALSE ); // メニューアイテムの状態変更
BOOL TextOutW( int x,int y,const WCHAR *str,COLORREF col=0 ); // 現在のフォントでテキスト表示
};
#endif
Re: WASAPIを使ったPC音録音ソフト
[DEBUG.h]
#ifndef __DEBUG_H
#define __DEBUG_H
///////////////////////////////////////////////////////////////////////////////////
// DEBUG : デバッグ出力ライブラリ v2.00 //
// //
// このソースコードは自由に改変して使用可能です。 //
// また商用利用も可能ですが、すべての環境で正しく動作する保障はありません。 //
// http://www.charatsoft.com/ //
///////////////////////////////////////////////////////////////////////////////////
#include <WinSock2.h>
#include <Windows.h>
//#undef DEBUGMODE // コメントをはずすと全ての出力をキャンセル
#define DEBUG_MAXTEXTBUF 2048 // ODSX()で出力される最大サイズ(これ以上は"..."となって切り捨て)
#undef DEBUG
#ifdef DEBUGMODE
#define INITDEBUG dbgInitA
#define DEBUG(x,...) dbgDebugA(x,__VA_ARGS__)
#define ODS(x,...) dbgOutputDebugStringA(x,__VA_ARGS__)
#define CLEARDEBUG dbgClear()
#else
#define INITDEBUG
#define DEBUG(x,...)
#define ODS(x,...)
#define CLEARDEBUG
#endif
#ifdef __cplusplus
extern "C" {
#endif
// 公開関数
extern void dbgInitA( const char *file="DEBUG.TXT" );
extern void dbgDebugA( const char *s,... );
extern void dbgClear( void );
extern void dbgOutputDebugStringA( const char *s,... );
#ifdef __cplusplus
}
#endif
#endif
Re: WASAPIを使ったPC音録音ソフト
[projectファイル]
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{D95718F7-6F6D-454C-909C-9996D73412AE}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>TestWasapi</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v140</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v140</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(TargetName)D$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<OutputFile>$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="CWav.cpp" />
<ClCompile Include="CWindow.cpp" />
<ClCompile Include="DEBUG.CPP" />
<ClCompile Include="Main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="CWav.h" />
<ClInclude Include="CWindow.h" />
<ClInclude Include="DEBUG.H" />
</ItemGroup>
<ItemGroup>
<None Include="DEBUG.TXT" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
Re: WASAPIを使ったPC音録音ソフト
[DEBUG.TXT]
-End-
[2017/01/07 07:42:02.564] << Log Clear >>
[2017/01/07 07:42:02.646] デフォルトデバイスピリオド : 100000 (10.000000ミリ秒)
[2017/01/07 07:42:02.654] 最小デバイスピリオド : 30000 (3.000000ミリ秒)
[2017/01/07 07:42:03.133] バッファサイズアライメントエラーのため修正する
[2017/01/07 07:42:03.143] 修正後のフレーム数 : 448
[2017/01/07 07:42:03.154] 修正後のレイテンシ : 101587 (10.158700ミリ秒)
[2017/01/07 07:42:03.177] 設定されたフレーム数 : 448
[2017/01/07 07:42:03.182] 設定されたバッファサイズ : 1792byte
[2017/01/07 07:42:03.187] 1サンプルの時間 : 0.040635秒
[2017/01/07 07:42:03.194] スレッド開始
[2017/01/07 07:42:03.200] WASAPI初期化完了
Re: WASAPIを使ったPC音録音ソフト
Mathさん
返信が遅くなりすみません。ためになるプログラムをありがとうございます。
ステレオミキサーを使用することでPC音を取得することができるようになりました。ステレオミキサーを使わない方法はまだ解決していませんが、これは今後の課題としてもっと勉強していこうと思います。
今回は拙い質問にもかかわらず、ご指導いただきありがとうございました。 次回質問版を利用する際には、目的と仕様を相手に分かりやすく説明することを心がけます。本当にありがとうございました。
最後に作成した、プログラムのコードを載せておきます。文字セットにマルチバイト文字セットを使用しています。
返信が遅くなりすみません。ためになるプログラムをありがとうございます。
ステレオミキサーを使用することでPC音を取得することができるようになりました。ステレオミキサーを使わない方法はまだ解決していませんが、これは今後の課題としてもっと勉強していこうと思います。
今回は拙い質問にもかかわらず、ご指導いただきありがとうございました。 次回質問版を利用する際には、目的と仕様を相手に分かりやすく説明することを心がけます。本当にありがとうございました。
最後に作成した、プログラムのコードを載せておきます。文字セットにマルチバイト文字セットを使用しています。
#ifndef STRICT
#define STRICT
#endif
#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>
#include <process.h>
#include <Windows.h>
#include <Audioclient.h>
#include <audiopolicy.h>
#include <Endpointvolume.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys_devpkey.h>
#include <strsafe.h>
#include <avrt.h>
#pragma comment(lib, "avrt.lib")
#pragma comment(lib, "winmm.lib")
#define BUTTON_START 101
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE ,int);
void ExitWasapi();
int InitWasapi();
DWORD WINAPI WASAPICaptureThread(LPVOID Context);
int writeWaveFile(FILE *fp, WORD wBitsPerSample, LPWAVEFORMATEX lpwf);
void makeWaveForm(WAVEFORMATEX *waveForm);
void ChangeButtonState(int id, BOOL state, char *buf);
int StartRec();
int EndRec();
bool GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex, LPWSTR deviceName, size_t size);
char szWindowTitle[128] = "WASAPI録音";
char szClassName[128] = "wasapi_capture";
const WORD nBitrate = 16;
const WORD nFrequency = 44100;
const WORD nChannel = 2;
struct Rect : RECT
{
LONG width() { return right - left; }
LONG height() { return bottom - top; }
};
IAudioCaptureClient *CaptureClient = NULL;
IAudioClient *AudioClient = NULL;
IAudioRenderClient *RenderClient = NULL;
IMMDevice *device = NULL;
IMMDeviceCollection *deviceCollection = NULL;
IMMDeviceEnumerator *deviceEnumerator = NULL;
HANDLE ShutdownEvent;
HANDLE CaptureThread;
HANDLE AudioSamplesReadyEvent;
HWND hwnd;
Rect rcDraw;
HDC hdcDraw;
FILE *fp;
WAVEFORMATEXTENSIBLE waveFormat;
float masterVolume;
BOOL bRec=false;
CRITICAL_SECTION critical_section;
// リングバッファ
int ringbuf_cur = 0;
short *ringbuf;
template <class T> inline void SafeRelease(T *ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
// MAIN関数
int WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
LPSTR lpsCmdLine, int CmdShow)
{
MSG msg;
if(!InitApp(hCurInst))
return FALSE;
if(!InitInstance(hCurInst, CmdShow))
return FALSE;
// COMの初期化
HRESULT hr =CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if(FAILED(hr)){
MessageBox(hwnd, "COMの初期化に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
if(!InitWasapi()){
MessageBox(hwnd, "Wasapi初期化に失敗", "err", MB_ICONINFORMATION);
ExitWasapi();
CoUninitialize();
DestroyWindow(hwnd);
return FALSE;
}
while(GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
SetEvent(ShutdownEvent);
hr = AudioClient->Stop();
if (FAILED(hr))
{
MessageBox(hwnd, "キャプチャを終了できません", "err", MB_ICONINFORMATION);
}
WaitForSingleObject(CaptureThread, INFINITE);
if(fp!=NULL)
fclose(fp);
CloseHandle(CaptureThread);
CloseHandle(ShutdownEvent);
CloseHandle(AudioSamplesReadyEvent);
DeleteCriticalSection(&critical_section);
// WASAPIの終了
ExitWasapi();
// COMの終了
CoUninitialize();
}
// ウェーブフォームの作成
void makeWaveForm(WAVEFORMATEX *waveForm)
{
waveForm->wFormatTag = WAVE_FORMAT_PCM;
waveForm->nChannels = nChannel;
waveForm->nSamplesPerSec = nFrequency;
waveForm->wBitsPerSample = nBitrate;
waveForm->nBlockAlign = waveForm->wBitsPerSample / 8 * waveForm->nChannels;
waveForm->nAvgBytesPerSec =waveForm->nSamplesPerSec * waveForm->nBlockAlign;
}
/*****************************************************************/
//
// name : writeWaveFile
// func : ファイルに保存された波形データをwaveファイルに
// 保存しなおす
//
// param1 : ファイルポインタ
// param2 : WAVEHDR構造体のポインタ
// param3 : WAVEFORMATEX構造体のポインタ
//
int writeWaveFile(FILE *fp, WORD wBitsPerSample, LPWAVEFORMATEX lpwf)
{
const int nPCMBuffSize=256;
LPSTR lpszFileName = TEXT("sample.wav");
HMMIO hmmio;
MMCKINFO mmckRiff;
MMCKINFO mmckFmt;
MMCKINFO mmckData;
int buffer[nPCMBuffSize];
hmmio = mmioOpen(lpszFileName, NULL, MMIO_CREATE | MMIO_WRITE);
if (hmmio == NULL)
return -1;
mmckRiff.fccType = mmioStringToFOURCC(TEXT("WAVE"), 0);
mmioCreateChunk(hmmio, &mmckRiff, MMIO_CREATERIFF);
mmckFmt.ckid = mmioStringToFOURCC(TEXT("fmt "), 0);
mmioCreateChunk(hmmio, &mmckFmt, 0);
mmioWrite(hmmio, (char *)lpwf, sizeof(PCMWAVEFORMAT));
mmioAscend(hmmio, &mmckFmt, 0);
mmckData.ckid = mmioStringToFOURCC(TEXT("data"), 0);
mmioCreateChunk(hmmio, &mmckData, 0);
// 波形データを保存するバッファが大きすぎないように
// nPCMBuffSizeずつ保存していく
// freadはファイルの末尾まで読み込むと読み込んだデータの個数を返す
fseek(fp, 0, SEEK_SET);
unsigned int nBuff=0;
do{
//memset(buffer, 0, nPCMBuffSize);
nBuff = fread(buffer, (wBitsPerSample/8), nPCMBuffSize, fp);
mmioWrite(hmmio, (char*)buffer, nBuff*(wBitsPerSample/8));
}while(nBuff == nPCMBuffSize);
mmioAscend(hmmio, &mmckData, 0);
mmioAscend(hmmio, &mmckRiff, 0);
mmioClose(hmmio, 0);
return 0;
}
//ウィンドウ・クラスの登録
ATOM InitApp(HINSTANCE hInst)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = (LPCSTR)szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
return (RegisterClassEx(&wc));
}
//ウィンドウの生成
BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
hwnd = CreateWindow(szClassName,
szWindowTitle, //タイトルバーにこの名前が表示されます
WS_OVERLAPPEDWINDOW, //ウィンドウの種類
CW_USEDEFAULT, //X座標
CW_USEDEFAULT, //Y座標
CW_USEDEFAULT, //幅
CW_USEDEFAULT, //高さ
NULL, //親ウィンドウのハンドル、親を作るときはNULL
NULL, //メニューハンドル、クラスメニューを使うときはNULL
hInst, //インスタンスハンドル
NULL);
if (!hwnd)
return FALSE;
CreateWindow("BUTTON", "開始", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
100, 100, 100, 50, hwnd, (HMENU)BUTTON_START, hInst, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
PAINTSTRUCT ps;
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
return 0;
case WM_COMMAND:
switch(LOWORD(wp)){
case BUTTON_START:
if(!bRec){
if(!StartRec()){
break;
}
ChangeButtonState(BUTTON_START, true, "停止");
} else {
if(!EndRec()){
break;
}
ChangeButtonState(BUTTON_START, true, "開始");
}
bRec = !bRec;
break;
}
return 0;
case WM_KEYDOWN:
return 0;
case WM_MOUSEMOVE:
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProc(hwnd , msg , wp , lp);
}
void ChangeButtonState(int id, BOOL state, char *buf)
{
HWND hButton;
hButton = GetDlgItem(hwnd, id);
EnableWindow(hButton, state);
SetWindowText(hButton, buf);
return ;
}
DWORD WINAPI WASAPICaptureThread(LPVOID Context)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if(FAILED(hr)){
MessageBox(hwnd, "COM初期化に失敗", "err_thread", MB_ICONINFORMATION);
return hr;
}
//////////////////////
// スレッドの優先順位
DWORD mmcsTaskIndex = 0;
HANDLE mmcsHandle = AvSetMmThreadCharacteristics("Audio", &mmcsTaskIndex);
if(mmcsHandle == NULL){
MessageBox(hwnd, "スレッドの優先順位変更に失敗", "err_thread", MB_ICONINFORMATION);
}
HANDLE waitArray[2] = {ShutdownEvent, AudioSamplesReadyEvent};
bool stillPlaying = true;
while(stillPlaying){
DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, INFINITE);
switch(waitResult)
{
case WAIT_OBJECT_0 + 0: // ShutdownEvent
stillPlaying = false;
case WAIT_OBJECT_0 + 1: // AudioSamplesReadyEvent
DWORD *pData;
DWORD *zeroData;
ULONGLONG adjsData;
UINT32 framesAvailable;
DWORD flags;
hr = CaptureClient->GetBuffer((BYTE **)&pData, &framesAvailable, &flags, NULL, NULL);
if(SUCCEEDED(hr))
{
EnterCriticalSection(&critical_section);
if(flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
zeroData = (DWORD *)calloc(framesAvailable, sizeof(DWORD));
fwrite(zeroData, sizeof(DWORD),framesAvailable, fp);
free(zeroData);
}
else
{
fwrite(pData, sizeof(DWORD),framesAvailable,fp);
}
LeaveCriticalSection(&critical_section);
hr = CaptureClient->ReleaseBuffer(framesAvailable);
if(FAILED(hr)){
MessageBox(hwnd, "リリースに失敗", "err_thread", MB_ICONINFORMATION);
}
}
break;
}
}
return 0;
}
int InitWasapi()
{
HRESULT hr;
/////////////////////
// デバイスの選択
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
if(FAILED(hr)){
MessageBox(hwnd, "インスタンスの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
hr = deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
if(FAILED(hr)){
MessageBox(hwnd, "デバイスの取得に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
UINT deviceCount;
UINT deviceNum;
wchar_t deviceName[128];
hr = deviceCollection->GetCount(&deviceCount);
for(deviceNum=0; deviceNum<deviceCount; deviceNum++){
GetDeviceName(deviceCollection, deviceNum, deviceName, sizeof(deviceName));
if(wcscmp(L"ステレオ ミキサー (Realtek High Definition Audio)", deviceName)==0)
break;
}
if(deviceNum == deviceCount){
MessageBox(hwnd, "ステレオミキサーが使用できません", "err", MB_ICONINFORMATION);
return FALSE;
}
hr = deviceCollection->Item(deviceNum, &device);
if(FAILED(hr)){
MessageBox(hwnd, "ステレオミキサーが使用できません", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// イベントの作成
ShutdownEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if(ShutdownEvent == NULL){
MessageBox(hwnd, "イベントの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
AudioSamplesReadyEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if(AudioSamplesReadyEvent == NULL){
MessageBox(hwnd, "イベントの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// AudioClient の準備
hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&AudioClient));
if(FAILED(hr)){
MessageBox(hwnd, "AudioClientの準備に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
//////////////////////
// マイクの音量設定
IAudioEndpointVolume *MicVolume;
hr = device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&MicVolume));
if(FAILED(hr)){
MessageBox(hwnd, "sesseionManagerの準備に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
MicVolume->SetMasterVolumeLevelScalar(static_cast<float>(100) / 100, nullptr);
SafeRelease(&MicVolume);
/////////////////////
// デバイスのレイテンシの取得
REFERENCE_TIME DefaultDevicePeriod;
REFERENCE_TIME MinimumDevicePeriod;
hr = AudioClient->GetDevicePeriod(&DefaultDevicePeriod, &MinimumDevicePeriod);
/////////////////////
// waveフォームの作成
//WAVEFORMATEXTENSIBLE waveFormat;
waveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
waveFormat.Format.nChannels = nChannel;
waveFormat.Format.nSamplesPerSec = nFrequency;
waveFormat.Format.wBitsPerSample = nBitrate;
waveFormat.Format.nBlockAlign = waveFormat.Format.wBitsPerSample/8 * waveFormat.Format.nChannels;
waveFormat.Format.nAvgBytesPerSec = waveFormat.Format.nSamplesPerSec * waveFormat.Format.nBlockAlign;
waveFormat.Format.cbSize = 22;
waveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
waveFormat.Samples.wValidBitsPerSample = waveFormat.Format.wBitsPerSample;
waveFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
//////////////////////
// AudioClientの初期化
hr = AudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
DefaultDevicePeriod, DefaultDevicePeriod, (WAVEFORMATEX *)&waveFormat, NULL);
UINT32 frame;
if(FAILED(hr)){
if(hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED){
hr = AudioClient->GetBufferSize(&frame);
DefaultDevicePeriod = (REFERENCE_TIME)(10000.0*1000*frame/waveFormat.Format.nSamplesPerSec+0.5);
SafeRelease(&AudioClient);
hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&AudioClient));
if(FAILED(hr)){
MessageBox(hwnd, "AudioClientの2度目の準備に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
hr = AudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
DefaultDevicePeriod, DefaultDevicePeriod, (WAVEFORMATEX *)&waveFormat, NULL);
}
if(FAILED(hr)){
MessageBox(hwnd, "AudioClientの初期化に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
}
//////////////////////
// イベントをセット
hr = AudioClient->SetEventHandle(AudioSamplesReadyEvent);
if(FAILED(hr)){
MessageBox(hwnd, "イベントのセットに失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
hr = AudioClient->GetService(IID_PPV_ARGS(&CaptureClient));
if(FAILED(hr)){
MessageBox(hwnd, "サービスの取得に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// クリティカルセクション初期化
InitializeCriticalSection(&critical_section);
return TRUE;
}
int StartRec()
{
HRESULT hr;
/////////////////////
// file オープン
fp = fopen("sample.data", "w+b");
if(fp==NULL){
MessageBox(hwnd, "ファイルのオープンに失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// スレッドの作成
CaptureThread = CreateThread( NULL, 0, WASAPICaptureThread, NULL, 0, NULL);
if(CaptureThread == NULL){
MessageBox(hwnd, "スレッドの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// キャプチャの開始
hr = AudioClient->Start();
if(FAILED(hr)){
MessageBox(hwnd, "キャプチャを開始できません", "err", MB_ICONINFORMATION);
return FALSE;
}
return TRUE;
}
int EndRec()
{
HRESULT hr;
WAVEFORMATEX mixformat;
makeWaveForm(&mixformat);
writeWaveFile(fp, waveFormat.Format.wBitsPerSample, &mixformat);
fclose(fp);
SetEvent(ShutdownEvent);
hr = AudioClient->Stop();
if (FAILED(hr))
{
MessageBox(hwnd, "キャプチャを終了できません", "err", MB_ICONINFORMATION);
return FALSE;
}
return TRUE;
}
void ExitWasapi()
{
SafeRelease(&CaptureClient);
SafeRelease(&AudioClient);
SafeRelease(&device);
SafeRelease(&deviceCollection);
SafeRelease(&deviceEnumerator);
}
//
// Retrieves the device friendly name for a particular device in a device collection.
//
// The returned string was allocated using malloc() so it should be freed using free();
//
bool GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex, LPWSTR deviceName, size_t size)
{
IMMDevice *device;
HRESULT hr;
hr = DeviceCollection->Item(DeviceIndex, &device);
if (FAILED(hr))
{
MessageBox(hwnd, "デバイスを取得できません", "err", MB_ICONINFORMATION);
return false;
}
IPropertyStore *propertyStore;
hr = device->OpenPropertyStore(STGM_READ, &propertyStore);
SafeRelease(&device);
if (FAILED(hr))
{
MessageBox(hwnd, "デバイスのプロパティを開けません", "err", MB_ICONINFORMATION);
return false;
}
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
SafeRelease(&propertyStore);
if (FAILED(hr))
{
MessageBox(hwnd, "デバイス名を取得できません", "err", MB_ICONINFORMATION);
return false;
}
if(friendlyName.vt != VT_LPWSTR){
swprintf(deviceName, size, L"%s", "Unknown");
} else {
swprintf(deviceName, size, L"%s", friendlyName.pwszVal);
}
PropVariantClear(&friendlyName);
return true;
}
Re: WASAPIを使ったPC音録音ソフト
オフトピック
AudacityでWASAPIを使うとき、再生デバイス名の後ろにloopbackとあります。
『WASAPI loopback』で検索するとMSDNの記事(英語)やMicrosoft開発者ブログ記事(英語、サンプルコードあり)などが見付かりました。
WASAPIには再生(レンダ)デバイスに対するループバックモードという機能があるそうです。
再生したそのままを取り込めるなら、ステレオミキサーでは避けられない劣化も回避できるのですが、はたして…
『WASAPI loopback』で検索するとMSDNの記事(英語)やMicrosoft開発者ブログ記事(英語、サンプルコードあり)などが見付かりました。
WASAPIには再生(レンダ)デバイスに対するループバックモードという機能があるそうです。
再生したそのままを取り込めるなら、ステレオミキサーでは避けられない劣化も回避できるのですが、はたして…
Re: WASAPIを使ったPC音録音ソフト
ISLeさん
情報ありがとうございます! audacityのソースを見つけたので、それと合わせて勉強してみます。
情報ありがとうございます! audacityのソースを見つけたので、それと合わせて勉強してみます。
Re: WASAPIを使ったPC音録音ソフト
オフトピック
せっかくなのでMicrosoft開発者ブログの記事へのリンクをはっておきます。
この記事では再生デバイスをループバックするシンプルなコマンドプログラムを公開しています。
・ブログ記事
Sample – WASAPI loopback capture (record what you hear) – Matthew van Eerde's web log
・ソースファイル
blog/loopback-capture at master · mvaneerde/blog · GitHub
IAudioClientの初期化時にAUDCLNT_STREAMFLAGS_LOOPBACKフラグを指定するところがキモでしょうか。
この記事では再生デバイスをループバックするシンプルなコマンドプログラムを公開しています。
・ブログ記事
Sample – WASAPI loopback capture (record what you hear) – Matthew van Eerde's web log
・ソースファイル
blog/loopback-capture at master · mvaneerde/blog · GitHub
IAudioClientの初期化時にAUDCLNT_STREAMFLAGS_LOOPBACKフラグを指定するところがキモでしょうか。
Re: WASAPIを使ったPC音録音ソフト
ISLeさん
お陰様で、ループバックを使用しサウンドミキサーを通さないアプリを作ることができました。
主な変更点としては、
・IMMDeviceEnumerator::GetDefaultAudioEndpointで第1引数をeCaptureからeRenderに変更
・IAudioClient::Initializeで第1引数をAUDCLNT_SHAREMODE_SHAREDに、第2引数をAUDCLNT_STREAMFLAGS_LOOPBACKに変更
・イベント駆動からタイマー駆動へ変更
の3点でした。
皆様のおかげで疑問を完全に解消することができました。本当にありがとうございました。
最後にループバックを使用したコードも載せておきます。
[コード]
マルチ文字セットを使用しています。スピーカーは16bit,44100Hz,2channelのものに対応しています。
お陰様で、ループバックを使用しサウンドミキサーを通さないアプリを作ることができました。
主な変更点としては、
・IMMDeviceEnumerator::GetDefaultAudioEndpointで第1引数をeCaptureからeRenderに変更
・IAudioClient::Initializeで第1引数をAUDCLNT_SHAREMODE_SHAREDに、第2引数をAUDCLNT_STREAMFLAGS_LOOPBACKに変更
・イベント駆動からタイマー駆動へ変更
の3点でした。
皆様のおかげで疑問を完全に解消することができました。本当にありがとうございました。
最後にループバックを使用したコードも載せておきます。
[コード]
マルチ文字セットを使用しています。スピーカーは16bit,44100Hz,2channelのものに対応しています。
#ifndef STRICT
#define STRICT
#endif
#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>
#include <process.h>
#include <Windows.h>
#include <Audioclient.h>
#include <audiopolicy.h>
#include <Endpointvolume.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys_devpkey.h>
#include <strsafe.h>
#include <avrt.h>
#pragma comment(lib, "avrt.lib")
#pragma comment(lib, "winmm.lib")
#define BUTTON_START 101
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE ,int);
void ExitWasapi();
int InitWasapi();
DWORD WINAPI WASAPICaptureThread(LPVOID Context);
int writeWaveFile(FILE *fp, WORD wBitsPerSample, LPWAVEFORMATEX lpwf);
void makeWaveForm(WAVEFORMATEX *waveForm);
void ChangeButtonState(int id, BOOL state, char *buf);
int StartRec();
int EndRec();
bool GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex, LPWSTR deviceName, size_t size);
void ShowError(HRESULT hr, HWND hwnd);
char szWindowTitle[128] = "WASAPI_LOOPBACK";
char szClassName[128] = "wasapi_capture";
const WORD nBitrate = 16;
const WORD nFrequency = 44100;
const WORD nChannel = 2;
struct Rect : RECT
{
LONG width() { return right - left; }
LONG height() { return bottom - top; }
};
IAudioCaptureClient *CaptureClient = NULL;
IAudioClient *AudioClient = NULL;
IAudioRenderClient *RenderClient = NULL;
IMMDevice *device = NULL;
IMMDeviceCollection *deviceCollection = NULL;
IMMDeviceEnumerator *deviceEnumerator = NULL;
HANDLE ShutdownEvent;
HANDLE CaptureThread;
HANDLE AudioSamplesReadyEvent;
HWND hwnd;
Rect rcDraw;
HDC hdcDraw;
FILE *fp;
WAVEFORMATEXTENSIBLE waveFormat;
float masterVolume;
REFERENCE_TIME DefaultDevicePeriod;
REFERENCE_TIME MinimumDevicePeriod;
BOOL bRec=false;
CRITICAL_SECTION critical_section;
// リングバッファ
int ringbuf_cur = 0;
short *ringbuf;
template <class T> inline void SafeRelease(T *ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
// MAIN関数
int WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
LPSTR lpsCmdLine, int CmdShow)
{
MSG msg;
if(!InitApp(hCurInst))
return FALSE;
if(!InitInstance(hCurInst, CmdShow))
return FALSE;
// COMの初期化
HRESULT hr =CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if(FAILED(hr)){
MessageBox(hwnd, "COMの初期化に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
if(!InitWasapi()){
MessageBox(hwnd, "Wasapi初期化に失敗", "err", MB_ICONINFORMATION);
ExitWasapi();
CoUninitialize();
DestroyWindow(hwnd);
return FALSE;
}
while(GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
SetEvent(ShutdownEvent);
hr = AudioClient->Stop();
if (FAILED(hr))
{
MessageBox(hwnd, "キャプチャを終了できません", "err", MB_ICONINFORMATION);
}
WaitForSingleObject(CaptureThread, INFINITE);
if(fp!=NULL)
fclose(fp);
CloseHandle(CaptureThread);
CloseHandle(ShutdownEvent);
CloseHandle(AudioSamplesReadyEvent);
DeleteCriticalSection(&critical_section);
// WASAPIの終了
ExitWasapi();
// COMの終了
CoUninitialize();
}
// ウェーブフォームの作成
void makeWaveForm(WAVEFORMATEX *waveForm)
{
waveForm->wFormatTag = WAVE_FORMAT_PCM;
waveForm->nChannels = nChannel;
waveForm->nSamplesPerSec = nFrequency;
waveForm->wBitsPerSample = nBitrate;
waveForm->nBlockAlign = waveForm->wBitsPerSample / 8 * waveForm->nChannels;
waveForm->nAvgBytesPerSec =waveForm->nSamplesPerSec * waveForm->nBlockAlign;
}
/*****************************************************************/
//
// name : writeWaveFile
// func : ファイルに保存された波形データをwaveファイルに
// 保存しなおす
//
// param1 : ファイルポインタ
// param2 : WAVEHDR構造体のポインタ
// param3 : WAVEFORMATEX構造体のポインタ
//
int writeWaveFile(FILE *fp, WORD wBitsPerSample, LPWAVEFORMATEX lpwf)
{
const int nPCMBuffSize=256;
LPSTR lpszFileName = TEXT("sample.wav");
HMMIO hmmio;
MMCKINFO mmckRiff;
MMCKINFO mmckFmt;
MMCKINFO mmckData;
int buffer[nPCMBuffSize];
hmmio = mmioOpen(lpszFileName, NULL, MMIO_CREATE | MMIO_WRITE);
if (hmmio == NULL)
return -1;
mmckRiff.fccType = mmioStringToFOURCC(TEXT("WAVE"), 0);
mmioCreateChunk(hmmio, &mmckRiff, MMIO_CREATERIFF);
mmckFmt.ckid = mmioStringToFOURCC(TEXT("fmt "), 0);
mmioCreateChunk(hmmio, &mmckFmt, 0);
mmioWrite(hmmio, (char *)lpwf, sizeof(PCMWAVEFORMAT));
mmioAscend(hmmio, &mmckFmt, 0);
mmckData.ckid = mmioStringToFOURCC(TEXT("data"), 0);
mmioCreateChunk(hmmio, &mmckData, 0);
// 波形データを保存するバッファが大きすぎないように
// nPCMBuffSizeずつ保存していく
// freadはファイルの末尾まで読み込むと読み込んだデータの個数を返す
fseek(fp, 0, SEEK_SET);
unsigned int nBuff=0;
do{
//memset(buffer, 0, nPCMBuffSize);
nBuff = fread(buffer, (wBitsPerSample/8), nPCMBuffSize, fp);
mmioWrite(hmmio, (char*)buffer, nBuff*(wBitsPerSample/8));
}while(nBuff == nPCMBuffSize);
mmioAscend(hmmio, &mmckData, 0);
mmioAscend(hmmio, &mmckRiff, 0);
mmioClose(hmmio, 0);
return 0;
}
//ウィンドウ・クラスの登録
ATOM InitApp(HINSTANCE hInst)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = (LPCSTR)szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
return (RegisterClassEx(&wc));
}
//ウィンドウの生成
BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
hwnd = CreateWindow(szClassName,
szWindowTitle, //タイトルバーにこの名前が表示されます
WS_OVERLAPPEDWINDOW, //ウィンドウの種類
CW_USEDEFAULT, //X座標
CW_USEDEFAULT, //Y座標
CW_USEDEFAULT, //幅
CW_USEDEFAULT, //高さ
NULL, //親ウィンドウのハンドル、親を作るときはNULL
NULL, //メニューハンドル、クラスメニューを使うときはNULL
hInst, //インスタンスハンドル
NULL);
if (!hwnd)
return FALSE;
CreateWindow("BUTTON", "開始", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
100, 100, 100, 50, hwnd, (HMENU)BUTTON_START, hInst, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
PAINTSTRUCT ps;
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
return 0;
case WM_COMMAND:
switch(LOWORD(wp)){
case BUTTON_START:
if(!bRec){
if(!StartRec()){
break;
}
ChangeButtonState(BUTTON_START, true, "停止");
} else {
if(!EndRec()){
break;
}
ChangeButtonState(BUTTON_START, true, "開始");
}
bRec = !bRec;
break;
}
return 0;
case WM_KEYDOWN:
return 0;
case WM_MOUSEMOVE:
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProc(hwnd , msg , wp , lp);
}
void ChangeButtonState(int id, BOOL state, char *buf)
{
HWND hButton;
hButton = GetDlgItem(hwnd, id);
EnableWindow(hButton, state);
SetWindowText(hButton, buf);
return ;
}
DWORD WINAPI WASAPICaptureThread(LPVOID Context)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if(FAILED(hr)){
MessageBox(hwnd, "COM初期化に失敗", "err_thread", MB_ICONINFORMATION);
return hr;
}
//////////////////////
// スレッドの優先順位
DWORD mmcsTaskIndex = 0;
HANDLE mmcsHandle = AvSetMmThreadCharacteristics("Audio", &mmcsTaskIndex);
if(mmcsHandle == NULL){
MessageBox(hwnd, "スレッドの優先順位変更に失敗", "err_thread", MB_ICONINFORMATION);
}
HANDLE waitArray[2] = {ShutdownEvent, AudioSamplesReadyEvent};
bool stillPlaying = true;
while(stillPlaying){
DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, INFINITE);
switch(waitResult)
{
case WAIT_OBJECT_0 + 0: // ShutdownEvent
stillPlaying = false;
case WAIT_OBJECT_0 + 1: // AudioSamplesReadyEvent
DWORD *pData;
DWORD *zeroData;
ULONGLONG adjsData;
UINT32 framesAvailable;
DWORD flags;
hr = CaptureClient->GetBuffer((BYTE **)&pData, &framesAvailable, &flags, NULL, NULL);
if(SUCCEEDED(hr))
{
EnterCriticalSection(&critical_section);
if(flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
zeroData = (DWORD *)calloc(framesAvailable, sizeof(DWORD));
fwrite(zeroData, sizeof(DWORD),framesAvailable, fp);
free(zeroData);
}
else
{
fwrite(pData, sizeof(DWORD),framesAvailable,fp);
}
LeaveCriticalSection(&critical_section);
hr = CaptureClient->ReleaseBuffer(framesAvailable);
if(FAILED(hr)){
MessageBox(hwnd, "リリースに失敗", "err_thread", MB_ICONINFORMATION);
}
}
break;
}
}
return 0;
}
int InitWasapi()
{
HRESULT hr;
/////////////////////
// デバイスの選択
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
if(FAILED(hr)){
MessageBox(hwnd, "インスタンスの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection);
if(FAILED(hr)){
MessageBox(hwnd, "デバイスの取得に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
if(FAILED(hr)){
MessageBox(hwnd, "ステレオミキサーが使用できません", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// イベントの作成
ShutdownEvent = CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if(ShutdownEvent == NULL){
MessageBox(hwnd, "イベントの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
AudioSamplesReadyEvent = CreateWaitableTimer(NULL, FALSE, NULL);
if(AudioSamplesReadyEvent == NULL){
MessageBox(hwnd, "タイマーの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// AudioClient の準備
hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&AudioClient));
if(FAILED(hr)){
MessageBox(hwnd, "AudioClientの準備に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// デバイスのレイテンシの取得
hr = AudioClient->GetDevicePeriod(&DefaultDevicePeriod, &MinimumDevicePeriod);
/////////////////////
// waveフォームの作成
//WAVEFORMATEXTENSIBLE waveFormat;
waveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
waveFormat.Format.nChannels = nChannel;
waveFormat.Format.nSamplesPerSec = nFrequency;
waveFormat.Format.wBitsPerSample = nBitrate;
waveFormat.Format.nBlockAlign = waveFormat.Format.wBitsPerSample/8 * waveFormat.Format.nChannels;
waveFormat.Format.nAvgBytesPerSec = waveFormat.Format.nSamplesPerSec * waveFormat.Format.nBlockAlign;
waveFormat.Format.cbSize = 22;
waveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
waveFormat.Samples.wValidBitsPerSample = waveFormat.Format.wBitsPerSample;
waveFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
//////////////////////
// AudioClientの初期化
hr = AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK,
0, 0, (WAVEFORMATEX *)&waveFormat, NULL);
UINT32 frame;
if(FAILED(hr)){
if(hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED){
hr = AudioClient->GetBufferSize(&frame);
DefaultDevicePeriod = (REFERENCE_TIME)(10000.0*1000*frame/waveFormat.Format.nSamplesPerSec+0.5);
SafeRelease(&AudioClient);
hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&AudioClient));
if(FAILED(hr)){
MessageBox(hwnd, "AudioClientの2度目の準備に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
hr = AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK,
DefaultDevicePeriod, DefaultDevicePeriod, (WAVEFORMATEX *)&waveFormat, NULL);
}
ShowError(hr, hwnd);
if(FAILED(hr)){
MessageBox(hwnd, "AudioClientの初期化に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
}
hr = AudioClient->GetService(IID_PPV_ARGS(&CaptureClient));
if(FAILED(hr)){
MessageBox(hwnd, "サービスの取得に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// クリティカルセクション初期化
InitializeCriticalSection(&critical_section);
return TRUE;
}
int StartRec()
{
HRESULT hr;
/////////////////////
// file オープン
fp = fopen("sample.data", "w+b");
if(fp==NULL){
MessageBox(hwnd, "ファイルのオープンに失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// スレッドの作成
CaptureThread = CreateThread( NULL, 0, WASAPICaptureThread, NULL, 0, NULL);
if(CaptureThread == NULL){
MessageBox(hwnd, "スレッドの作成に失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// タイマーの開始
LARGE_INTEGER liFirstFire;
liFirstFire.QuadPart = -DefaultDevicePeriod / 2;
LONG lTimeBetweenFires = DefaultDevicePeriod / 2 /(1000*10);
BOOL bOK = SetWaitableTimer(AudioSamplesReadyEvent, &liFirstFire, lTimeBetweenFires,
NULL, NULL, FALSE);
if(!bOK){
MessageBox(hwnd, "イベントのセットに失敗", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////
// キャプチャの開始
hr = AudioClient->Start();
if(FAILED(hr)){
MessageBox(hwnd, "キャプチャを開始できません", "err", MB_ICONINFORMATION);
return FALSE;
}
return TRUE;
}
int EndRec()
{
HRESULT hr;
WAVEFORMATEX mixformat;
//////////////////////////
// タイマーの停止
CancelWaitableTimer(AudioSamplesReadyEvent);
//////////////////////////
// 終了イベント
SetEvent(ShutdownEvent);
hr = AudioClient->Stop();
if (FAILED(hr))
{
MessageBox(hwnd, "キャプチャを終了できません", "err", MB_ICONINFORMATION);
return FALSE;
}
/////////////////////////
// waveファイルの作成
makeWaveForm(&mixformat);
writeWaveFile(fp, waveFormat.Format.wBitsPerSample, &mixformat);
fclose(fp);
return TRUE;
}
void ExitWasapi()
{
SafeRelease(&CaptureClient);
SafeRelease(&AudioClient);
SafeRelease(&device);
SafeRelease(&deviceCollection);
SafeRelease(&deviceEnumerator);
}
//
// Retrieves the device friendly name for a particular device in a device collection.
//
// The returned string was allocated using malloc() so it should be freed using free();
//
bool GetDeviceName(IMMDeviceCollection *DeviceCollection, UINT DeviceIndex, LPWSTR deviceName, size_t size)
{
IMMDevice *device;
HRESULT hr;
hr = DeviceCollection->Item(DeviceIndex, &device);
if (FAILED(hr))
{
MessageBox(hwnd, "デバイスを取得できません", "err", MB_ICONINFORMATION);
return false;
}
IPropertyStore *propertyStore;
hr = device->OpenPropertyStore(STGM_READ, &propertyStore);
SafeRelease(&device);
if (FAILED(hr))
{
MessageBox(hwnd, "デバイスのプロパティを開けません", "err", MB_ICONINFORMATION);
return false;
}
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
SafeRelease(&propertyStore);
if (FAILED(hr))
{
MessageBox(hwnd, "デバイス名を取得できません", "err", MB_ICONINFORMATION);
return false;
}
if(friendlyName.vt != VT_LPWSTR){
swprintf(deviceName, size, L"%s", "Unknown");
} else {
swprintf(deviceName, size, L"%s", friendlyName.pwszVal);
}
PropVariantClear(&friendlyName);
return true;
}
void ShowError(HRESULT hr, HWND hwnd)
{
char err[256];
switch(hr)
{
case AUDCLNT_E_ALREADY_INITIALIZED:wsprintf(err, "%s","AUDCLNT_E_ALREADY_INITIALIZED\n");break;
case AUDCLNT_E_WRONG_ENDPOINT_TYPE:wsprintf(err, "%s","AUDCLNT_E_WRONG_ENDPOINT_TYPE\n");break;
case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED:wsprintf(err, "%s","AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED\n");break;
case AUDCLNT_E_BUFFER_SIZE_ERROR:wsprintf(err, "%s","AUDCLNT_E_BUFFER_SIZE_ERROR\n");break;
case AUDCLNT_E_CPUUSAGE_EXCEEDED:wsprintf(err, "%s","AUDCLNT_E_CPUUSAGE_EXCEEDED\n");break;
case AUDCLNT_E_DEVICE_INVALIDATED:wsprintf(err, "%s","AUDCLNT_E_DEVICE_INVALIDATED\n");break;
case AUDCLNT_E_DEVICE_IN_USE:wsprintf(err, "%s","AUDCLNT_E_DEVICE_IN_USE\n");break;
case AUDCLNT_E_ENDPOINT_CREATE_FAILED:wsprintf(err, "%s","AUDCLNT_E_ENDPOINT_CREATE_FAILED\n");break;
case AUDCLNT_E_INVALID_DEVICE_PERIOD:wsprintf(err, "%s","AUDCLNT_E_INVALID_DEVICE_PERIOD\n");break;
case AUDCLNT_E_UNSUPPORTED_FORMAT:wsprintf(err, "%s","AUDCLNT_E_UNSUPPORTED_FORMAT\n");break;
case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED:wsprintf(err, "%s","AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED\n");break;
case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL:wsprintf(err, "%s","AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL\n");break;
case AUDCLNT_E_SERVICE_NOT_RUNNING:wsprintf(err, "%s","AUDCLNT_E_SERVICE_NOT_RUNNING\n");break;
case E_POINTER:wsprintf(err, "%s","E_POINTER\n");break;
case E_INVALIDARG:wsprintf(err, "%s","E_INVALIDARG\n");break;
case E_OUTOFMEMORY:wsprintf(err, "%s","E_OUTOFMEMORY\n");break;
default: wsprintf(err, "s", "unknown"); break;
}
MessageBox(hwnd, err, "err", MB_ICONINFORMATION);
}