時間分解能が不十分かもしれないので、low latencyとされるWASAPIを使ったコードを検討しておく
Gemini君にお伺いを立てると、
WinMM (Windows Multimedia API)概要:
古くから存在するWindowsの標準的なマルチメディアAPIです。波形オーディオの再生・録音(waveOut、waveIn)などの機能を提供します。
特徴:扱いやすく、古い規格のソフトウェアやゲームで広く使われています。Windowsのオーディオミキサーや各種エフェクトを経由するため、若干の遅延(レイテンシー)が発生します。現在のWindowsでは、内部的に後述のWASAPI上で動作するようになっています。
WASAPI (Windows Audio Session API)概要:
Windows Vista以降に導入された、Windowsのモダンなオーディオアーキテクチャ(Core Audio APIs)の核となるAPIです。特徴:OSのミキサーやシステム音をバイパスして直接オーディオデバイスにデータを送る排他モード(Exclusive Mode)を備えており、ビットパーフェクトな高音質再生(ハイレゾ音源など)が可能です。他のアプリの音とミックスして再生する共有モード(Shared Mode)も用意されています。WinMMと比較して非常に低遅延で音声処理を行えます。
どちらを選ぶべきか?
WinMM: 互換性重視の古いアプリケーションの保守や、システム音の再生、遅延がシビアではない一般的な用途に向いています。
WASAPI: DAW(音楽制作ソフト)での低遅延モニタリングや、オーディオプレイヤーでのハイレゾ・高音質再生など、音質やレスポンスが求められるモダンなアプリケーションの開発に適しています。
と言ってきました。Copilot君による”完全コード”とかもいくらでも出てきますけど、ことごとく動きませんでした。なんとも時間の無駄でしたね。かって筆者は、システム音のレベルの表示プログラムは、このWASAPIを使って書いていました。
ですね。今回はmicの入力レベルを捉えればいいんですが、ほぼCopilot君によるコードは動きません。レベルを捉えられません。したがって“AI”君達のsuggestは無視して、動くコードを求めて徘徊し、ようやく下記のコマンドラインプログラムを得ました。ただしそのままではエラーでコンパイルできませんから、適切に修正しました。出所は、
コードは、
#include <stdio.h>
#include <Windows.h>
#include <initguid.h>
#include <dshow.h>
#include <Audioclient.h>
#include <mmdeviceapi.h>
#include <assert.h>
int main() {
HRESULT hr;
IMMDeviceEnumerator* enumerator = NULL;
IMMDevice* recorder = NULL;
IMMDevice* renderer = NULL;
IAudioClient* recorderClient = NULL;
IAudioClient* renderClient = NULL;
IAudioRenderClient* renderService = NULL;
IAudioCaptureClient* captureService = NULL;
WAVEFORMATEX* format = NULL;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
assert(SUCCEEDED(hr));
hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator),
NULL,
CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator),
(void**)&enumerator
);
assert(SUCCEEDED(hr));
hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &recorder);
assert(SUCCEEDED(hr));
hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &renderer);
assert(SUCCEEDED(hr));
hr = enumerator->Release();
assert(SUCCEEDED(hr));
hr = recorder->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&recorderClient);
assert(SUCCEEDED(hr));
hr = renderer->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&renderClient);
assert(SUCCEEDED(hr));
hr = recorderClient->GetMixFormat(&format);
assert(SUCCEEDED(hr));
printf("Mix format:\n");
printf(" Frame size : %d\n", format->nBlockAlign);
printf(" Channels : %d\n", format->nChannels);
printf(" Bits per second: %d\n", format->wBitsPerSample);
printf(" Sample rate: : %d\n", format->nSamplesPerSec);
hr = recorderClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 10000000, 0, format, NULL);
assert(SUCCEEDED(hr));
hr = renderClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 10000000, 0, format, NULL);
assert(SUCCEEDED(hr));
hr = renderClient->GetService(__uuidof(IAudioRenderClient), (void**)&renderService);
assert(SUCCEEDED(hr));
hr = recorderClient->GetService(__uuidof(IAudioCaptureClient), (void**)&captureService);
assert(SUCCEEDED(hr));
UINT32 nFrames;
DWORD flags;
BYTE* captureBuffer;
BYTE* renderBuffer;
hr = recorderClient->Start();
assert(SUCCEEDED(hr));
hr = renderClient->Start();
assert(SUCCEEDED(hr));
while (true) {
hr = captureService->GetBuffer(&captureBuffer, &nFrames, &flags, NULL, NULL);
assert(SUCCEEDED(hr));
hr = captureService->ReleaseBuffer(nFrames);
assert(SUCCEEDED(hr));
hr = renderService->GetBuffer(nFrames, &renderBuffer);
assert(SUCCEEDED(hr));
memcpy(renderBuffer, captureBuffer, format->nBlockAlign * nFrames);
hr = renderService->ReleaseBuffer(nFrames, 0);
assert(SUCCEEDED(hr));
}
// This code won't be reached but if the loop condition changes
// you should always clear the resources
recorderClient->Stop();
renderClient->Stop();
captureService->Release();
renderService->Release();
recorderClient->Release();
renderClient->Release();
recorder->Release();
renderer->Release();
CoUninitialize();
}
ループは、
while (true) {
hr = captureService->GetBuffer(&captureBuffer, &nFrames, &flags, NULL, NULL);
assert(SUCCEEDED(hr));
hr = captureService->ReleaseBuffer(nFrames);
assert(SUCCEEDED(hr));
hr = renderService->GetBuffer(nFrames, &renderBuffer);
assert(SUCCEEDED(hr));
memcpy(renderBuffer, captureBuffer, format->nBlockAlign * nFrames);
hr = renderService->ReleaseBuffer(nFrames, 0);
assert(SUCCEEDED(hr));
}
なる永久ループなので、captureしてrenderするという繰り返しですね。これが一応動きます。動作している動画は、
hr = recorderClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 10000000, 0, format, NULL);
ここが多分キーなのだと思いますが、マジックナンバー連発で意味不明なので、かろうじて日本語コメント入りの行を重ねておきます。
// 共有モード、イベント駆動なし(タイマー駆動)で初期化
// 基準間隔を100ミリ秒(1000000 100ns単位)に設定
hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 1000000, 0, pwfx, NULL);
if (FAILED(hr)) goto Exit;
ちなみにこちらはGemini君の情報です。共有モードだけは見てわかりますね。



コメント