ページ 11

WASAPIを使ったPC音録音ソフト

Posted: 2017年1月05日(木) 13:38
by larkpia
window7でC言語を使い、PC内の音(スピーカーから出力される音)を取得するソフトを作ろうとしています。

フリーソフトaudacityでWASAPIを使えばサウンドミキサーを使うことなく、PC音を取得できることが分かり、WASAPIの導入を決めたのですが、PC音の取得の方法が分かりません。
WASAPIを使って再生・録音(マイクから)はできました。

何か、方法をご存知の方はヒントでもいいので教えてください。

初の質問版で分かりにくい部分があると思いますが、なにとぞよろしくお願いします。

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月05日(木) 19:53
by Math
私はAudacityを何年も愛用しています。よくPCの音を録音します。
LAME v3.95もいれています。
http://csi.nisinippon.com/aud.png
コントロールパネルのサウンドがこうなってると思うのでここ
を参考にステレオミキサーを有効にするといいと思います。
https://www18.atwiki.jp/live2ch/pages/227.html

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月05日(木) 20:36
by larkpia
早速の返信ありがとうございます。

audacityにおいてオーディオホストをMMEからWindowWASAPIに変更し、録音デバイスをスピーカーに変更してやるとサウンドミキサーが無効のままでもPC音を取得できます。
これと同じことを自作のプログラムで実装したく試行錯誤してるのですが、とっかかりもつかめません。 もし何かご存知であれば教えてください!

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月05日(木) 21:21
by Math
あ、本当だ。Audacityは無効のまま出来ている。ややこしい問題ですね!

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月05日(木) 21:41
by Math
ここに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に則って詳しく説明してください。(また、何が目的でなぜそうしたいのか他人にもよくわかるようにしてください。)

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月05日(木) 22:15
by larkpia
失礼しました。ルールがあったんですね。以下に書き直します。

[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音録音ソフト

Posted: 2017年1月06日(金) 09:07
by Math
なんの目的か書かれてない。作っている側は分かって他人への説明にはなってない。レイテンシがすくないとか劣化が気になるのか?WASAPIには排他モードと共有モードの2種類があり、実際にレイテンシが少なくなるのは排他モードの方です。WASAPIはCOMを使ったライブラリです。プログラムをつくろうとするとWASAPI.COMの知識が必要です。
WASAPIを使用した再生・録音(マイク)プログラムを作成とあるがコードを一行も示せないのは大変疑わしい。基本部分を提示するここと。「ルール」をよく読むこと。まず基本設計を他人に分かるように説明する事が肝要です。

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月06日(金) 11:18
by larkpia
Mathさん、ご指摘ありがとうございます。少しは分かりやすくなってるといいのですが…

[目的] 
パソコン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音録音ソフト

Posted: 2017年1月06日(金) 13:24
by ポニョ
どうせこんなことだろうと思ってみてたけど10年前のVistaにもOSの機能としてはいってるよ。7にないわけないだろ?だから初めからの投稿をよく読めばわかるのに。
http://pcgenki.com/soft3/vista_rokuon.htm

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月06日(金) 14:38
by larkpia
ポニョさん返信ありがとうございます。
録音デバイスのところで 「無効なデバイスを表示する」 としても表示されなかったのですが、最新のデバイスをダウンロードすると表示されるようになりました。拙い説明を読み解いていただきありがとうございました。

ただ、Audacityではステレオミキサーを使用しない録音を実装しているので、その方法も、もう少し考えてみたいと思います。

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月06日(金) 19:56
by Math
私の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音録音ソフト

Posted: 2017年1月07日(土) 00:04
by Math
久し振りにハードウェアの勉強ができて面白かったです。WASAPIの詳細は不明です。Audacityのforumで質問してみて下さい。http://forum.audacityteam.org/

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月07日(土) 07:57
by Math
WASAPI-sample-全コード[Main.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;
}
「CWindow.cpp]

コード:

#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音録音ソフト

Posted: 2017年1月07日(土) 08:00
by Math
[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音録音ソフト

Posted: 2017年1月07日(土) 08:04
by Math
[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音録音ソフト

Posted: 2017年1月07日(土) 08:06
by Math
[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音録音ソフト

Posted: 2017年1月07日(土) 08:12
by Math
[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音録音ソフト

Posted: 2017年1月07日(土) 08:16
by Math
[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音録音ソフト

Posted: 2017年1月07日(土) 08:19
by Math
[DEBUG.TXT]

コード:

[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初期化完了
-End-

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月07日(土) 10:00
by larkpia
Mathさん
返信が遅くなりすみません。ためになるプログラムをありがとうございます。
ステレオミキサーを使用することで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音録音ソフト

Posted: 2017年1月07日(土) 18:14
by ISLe
オフトピック
AudacityでWASAPIを使うとき、再生デバイス名の後ろにloopbackとあります。
『WASAPI loopback』で検索するとMSDNの記事(英語)やMicrosoft開発者ブログ記事(英語、サンプルコードあり)などが見付かりました。
WASAPIには再生(レンダ)デバイスに対するループバックモードという機能があるそうです。
再生したそのままを取り込めるなら、ステレオミキサーでは避けられない劣化も回避できるのですが、はたして…

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月07日(土) 18:18
by larkpia
ISLeさん
情報ありがとうございます! audacityのソースを見つけたので、それと合わせて勉強してみます。

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月08日(日) 00:38
by ISLe
オフトピック
せっかくなので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フラグを指定するところがキモでしょうか。

Re: WASAPIを使ったPC音録音ソフト

Posted: 2017年1月08日(日) 12:27
by larkpia
ISLeさん

お陰様で、ループバックを使用しサウンドミキサーを通さないアプリを作ることができました。
主な変更点としては、

・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);
}