154 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			154 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "low_latency_client.h"
 | |
| #include "util/logging.h"
 | |
| 
 | |
| #ifdef _MSC_VER
 | |
| DEFINE_GUID(CLSID_MMDeviceEnumerator,
 | |
|     0xBCDE0395, 0xE52F, 0x467C,
 | |
|     0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
 | |
| #endif
 | |
| 
 | |
| #define PRINT_FAILED_RESULT(name, ret) \
 | |
|     log_warning("audio::lowlatency", "{} failed, hr={}", name, FMT_HRESULT(ret))
 | |
| 
 | |
| namespace hooks::audio {
 | |
|     bool LOW_LATENCY_SHARED_WASAPI = false;
 | |
|     static bool COM_INITIALIZED = false;
 | |
|     static LowLatencyAudioClient *LOW_LATENCY_CLIENT = nullptr;
 | |
| 
 | |
|     void init_low_latency() {
 | |
|         if (!LOW_LATENCY_SHARED_WASAPI) {
 | |
|             return;
 | |
|         }
 | |
|         log_info("audio::lowlatency", "initializing");
 | |
| 
 | |
|         HRESULT hr;
 | |
| 
 | |
|         // initialize COM
 | |
|         COM_INITIALIZED = true;
 | |
|         hr = CoInitialize(NULL);
 | |
|         if (FAILED(hr)) {
 | |
|             PRINT_FAILED_RESULT("CoInitialize", hr);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // initialize device enumerator
 | |
|         IMMDeviceEnumerator* enumerator;
 | |
|         hr = CoCreateInstance(
 | |
|                 CLSID_MMDeviceEnumerator,
 | |
|                 NULL,
 | |
|                 CLSCTX_ALL,
 | |
|                 IID_IMMDeviceEnumerator,
 | |
|                 reinterpret_cast<void**>(&enumerator));
 | |
|         if (FAILED(hr)) {
 | |
|             PRINT_FAILED_RESULT("CoCreateInstance(CLSID_MMDeviceEnumerator)", hr);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // get default audio endpoint from enumerator
 | |
|         IMMDevice* device;
 | |
|         hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
 | |
|         if (FAILED(hr)) {
 | |
|             PRINT_FAILED_RESULT("GetDefaultAudioEndpoint", hr);
 | |
|             return;
 | |
|         }
 | |
|         enumerator->Release();
 | |
| 
 | |
|         // start the client using the default audio endpoint
 | |
|         LOW_LATENCY_CLIENT = hooks::audio::LowLatencyAudioClient::Create(device);
 | |
|         log_info("audio::lowlatency", "initialized");
 | |
|     }
 | |
| 
 | |
|     void stop_low_latency() {
 | |
|         if (!LOW_LATENCY_SHARED_WASAPI) {
 | |
|             return;
 | |
|         }
 | |
|         log_info("audio::lowlatency", "stopping");
 | |
|         if (LOW_LATENCY_CLIENT) {
 | |
|             delete LOW_LATENCY_CLIENT;
 | |
|             LOW_LATENCY_CLIENT = nullptr;
 | |
|         }
 | |
|         if (COM_INITIALIZED) {
 | |
|             COM_INITIALIZED = false;
 | |
|             CoUninitialize();
 | |
|         }
 | |
|         log_info("audio::lowlatency", "stopped");
 | |
|     }
 | |
| }
 | |
| 
 | |
| using namespace hooks::audio;
 | |
| 
 | |
| LowLatencyAudioClient::LowLatencyAudioClient(IAudioClient3* audioClient) : audioClient(audioClient) {}
 | |
| 
 | |
| LowLatencyAudioClient::~LowLatencyAudioClient() {
 | |
|     if (this->audioClient) {
 | |
|         HRESULT ret;
 | |
|         ret = this->audioClient->Stop();
 | |
|         if (FAILED(ret)) {
 | |
|             PRINT_FAILED_RESULT("IAudioClient3::Stop", ret);
 | |
|         }
 | |
|         this->audioClient->Release();
 | |
|         this->audioClient = nullptr;
 | |
|     }
 | |
| }
 | |
| 
 | |
| LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
 | |
|     HRESULT ret;
 | |
|     UINT32 minPeriod;
 | |
|     UINT32 defaultPeriod;
 | |
|     UINT32 fundamentalPeriod;
 | |
|     UINT32 maxPeriod;
 | |
|     PWAVEFORMATEX pFormat;
 | |
|     IAudioClient3* audioClient;
 | |
| 
 | |
|     ret = device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL, NULL, reinterpret_cast<void**>(&audioClient));
 | |
|     device->Release();
 | |
|     if (FAILED(ret)) {
 | |
|         PRINT_FAILED_RESULT("IMMDevice::Activate(IID_IAudioClient3...)", ret);
 | |
|         log_warning("audio::lowlatency", "note that only Windows 10 and above supports IAudioClient3");
 | |
|         return nullptr;
 | |
|     }
 | |
| 
 | |
|     ret = audioClient->GetMixFormat(&pFormat);
 | |
|     if (FAILED(ret)) {
 | |
|         PRINT_FAILED_RESULT("IAudioClient3::GetMixFormat", ret);
 | |
|         audioClient->Release();
 | |
|         audioClient = nullptr;
 | |
|         return nullptr;
 | |
|     }
 | |
| 
 | |
|     ret = audioClient->GetSharedModeEnginePeriod(pFormat, &defaultPeriod, &fundamentalPeriod, &minPeriod, &maxPeriod);
 | |
|     if (FAILED(ret)) {
 | |
|         PRINT_FAILED_RESULT("IAudioClient3::GetSharedModeEnginePeriod", ret);
 | |
|         audioClient->Release();
 | |
|         audioClient = nullptr;
 | |
|         return nullptr;
 | |
|     }
 | |
| 
 | |
|     ret = audioClient->InitializeSharedAudioStream(0, minPeriod, pFormat, NULL);
 | |
|     if (FAILED(ret)) {
 | |
|         PRINT_FAILED_RESULT("IAudioClient3::InitializeSharedAudioStream", ret);
 | |
|         audioClient->Release();
 | |
|         audioClient = nullptr;
 | |
|         return nullptr;
 | |
|     }
 | |
| 
 | |
|     ret = audioClient->Start();
 | |
|     if (FAILED(ret)) {
 | |
|         PRINT_FAILED_RESULT("IAudioClient3::Start", ret);
 | |
|         audioClient->Release();
 | |
|         audioClient = nullptr;
 | |
|         return nullptr;
 | |
|     }
 | |
| 
 | |
|     log_info("audio::lowlatency", "low latency shared mode audio client initialized successfully.");
 | |
|     log_info("audio::lowlatency", "this is NOT used to output sound...");
 | |
|     log_info("audio::lowlatency", "but rather to reduce buffer sizes when the game requests an audio client at a later point");
 | |
|     log_info("audio::lowlatency", "has no effect if the game uses exclusive WASAPI or ASIO!");
 | |
|     log_info("audio::lowlatency", "... sample rate         : {} Hz", pFormat->nSamplesPerSec);
 | |
|     log_info("audio::lowlatency", "... min buffer size     : {} samples ({} ms)", minPeriod, 1000.0f * minPeriod / pFormat->nSamplesPerSec);
 | |
|     log_info("audio::lowlatency", "... max buffer size     : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec);
 | |
|     log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec);
 | |
|     log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on");
 | |
|     return new LowLatencyAudioClient(audioClient);
 | |
| }
 |