・IAudioClient::Initializeで第1引数をAUDCLNT_SHAREMODE_SHAREDに、第2引数をAUDCLNT_STREAMFLAGS_LOOPBACKに変更
コード:
#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);
}