From cad953fd4744cc947d5c80f92766a9d5e713af9e Mon Sep 17 00:00:00 2001 From: spddl Date: Mon, 1 Jul 2024 21:55:30 +0200 Subject: [PATCH] v3.0.0 --- Cargo.lock | 156 ++++++++++++++++++++++++++---- Cargo.toml | 25 ++--- README.md | 44 +++++++-- build.bat | 27 ++++++ run.bat | 1 + src/cpuset.rs | 54 +++++++++++ src/main.rs | 259 ++++++++++++++++++++++++++++++++++++++++++++++---- src/sound.rs | 80 ++++++++++------ 8 files changed, 561 insertions(+), 85 deletions(-) create mode 100644 src/cpuset.rs diff --git a/Cargo.lock b/Cargo.lock index 13f1b66..b324d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,29 +4,141 @@ version = 3 [[package]] name = "LowAudioLatency" -version = "2.0.1" +version = "3.0.0" dependencies = [ + "ntapi", "windows", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" -version = "0.44.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -35,42 +147,48 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index 63a8268..6c200cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "LowAudioLatency" description = "" -version = "2.0.1" +version = "3.0.0" authors = ["spddl"] edition = "2021" @@ -13,24 +13,27 @@ path = "src/main.rs" # This isn't required for development builds, but makes development # build behavior match release builds. To enable unwinding panics # during development, simply remove this line. -panic = "abort" # Abort on panic +panic = "abort" # Abort on panic [profile.release] -opt-level = "z" # Optimize for size. -lto = true # Enable Link Time Optimization -codegen-units = 1 # Reduce number of codegen units to increase optimizations. -panic = "abort" # Abort on panic -strip = true # Automatically strip symbols from the binary. +opt-level = "z" # Optimize for size. +lto = true # Enable Link Time Optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations. +panic = "abort" # Abort on panic +strip = true # Automatically strip symbols from the binary. [dependencies] +ntapi = "0.4.1" [dependencies.windows] -version = "0.44.0" +version = "0.57.0" features = [ - "Win32_System_Com", - "Win32_UI_Shell_PropertiesSystem", "Win32_Foundation", "Win32_Media_Audio", + "Win32_Security", "Win32_System_Com_StructuredStorage", - "Win32_System_Threading" + "Win32_System_SystemInformation", + "Win32_System_Threading", + "Win32_System_Variant", + "Win32_UI_Shell_PropertiesSystem", ] diff --git a/README.md b/README.md index afbda82..4bc1194 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,49 @@ # LowAudioLatency + [![Downloads][1]][2] [![GitHub stars][3]][4] [1]: https://img.shields.io/github/downloads/spddl/LowAudioLatency/total.svg [2]: https://github.com/spddl/LowAudioLatency/releases "Downloads" - [3]: https://img.shields.io/github/stars/spddl/LowAudioLatency.svg [4]: https://github.com/spddl/LowAudioLatency/stargazers "GitHub stars" -only a rewrite of the original [miniant-git/REAL](https://github.com/miniant-git/REAL) +## About This Project + +LowAudioLatency sets the Windows audio buffer to the smallest possible value, similar to [miniant-git/REAL](https://github.com/miniant-git/REAL). LAL not only checks the output devices (headphones, speakers) but also the input devices (microphones). Additionally, it removes the real-time connection to the first CPU thread, as it is not necessary for this function. If the smallest buffer size is already the default buffer size, the program will terminate. + +> [!IMPORTANT] +> Please note that not every audio driver supports this feature, and not all hardware has a driver with this capability. + +## Usage + +Simply run the executable file to minimize the audio latency for the output and input device: + +``` +low_audio_latency.exe +``` -The program checks and sets the smallest possible buffer size for the default output and input device. -If the smallest buffer size is the default buffer size, the program terminates itself. +You can also specify EDataFlow and ERole yourself with an optional buffer value: ->**If a driver supports small buffer sizes (<10ms buffers), will all applications in Windows 10 automatically use small buffers to render and capture audio?** +``` +low_audio_latency.exe eRender,eConsole,336 eCapture,eCommunications,336 +``` + +## Supported parameters + +| Name | Description | Required | Allowed values | +| --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------- | +| [EDataFlow](https://learn.microsoft.com/en-us/windows/win32/api/mmdeviceapi/ne-mmdeviceapi-edataflow#constants) | The EDataFlow enumeration defines constants that indicate the direction in which audio data flows between an audio endpoint device and an application. | Yes | Enumeration ID or `eRender`, `eCapture`, or `eAll` | +| [ERole](https://learn.microsoft.com/en-us/windows/win32/api/mmdeviceapi/ne-mmdeviceapi-erole#constants) | The ERole enumeration defines constants that indicate the role that the system has assigned to an audio endpoint device. | Yes | Enumeration ID or `eConsole`, `eMultimedia`, or `eCommunications` | +| [pMinPeriodInFrames](https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient3-initializesharedaudiostream#parameters) | Periodicity requested by the client. This value must be an integral multiple of the value returned in the pFundamentalPeriodInFrames parameter to IAudioClient3::GetSharedModeEnginePeriod. PeriodInFrames must also be greater than or equal to the value returned in pMinPeriodInFrames and less than or equal to the value returned in pMaxPeriodInFrames. | No | 0 determines the lowest value itself | + +> [!Note] > ->No. By default, all applications in Windows 10 will use 10ms buffers to render and capture audio. If an application needs to use small buffers, then it needs to use the new AudioGraph settings or the WASAPI IAudioClient3 interface, in order to do so. However, if one application in Windows 10 requests the usage of small buffers, then the Audio Engine will start transferring audio using that particular buffer size. In that case, all applications that use the same endpoint and mode will automatically switch to that small buffer size. When the low latency application exits, the Audio Engine will switch to 10ms buffers again. +> ### If a driver supports small buffer sizes, will all applications in Windows 10 and later automatically use small buffers to render and capture audio? +> +> No, by default all applications in Windows 10 and later will use 10-ms buffers to render and capture audio. If an application needs to use small buffers, then it needs to use the new AudioGraph settings or the WASAPI IAudioClient3 interface, in order to do so. However, if one application requests the usage of small buffers, then the audio engine will start transferring audio using that particular buffer size. In that case, all applications that use the same endpoint and mode will automatically switch to that small buffer size. When the low latency application exits, the audio engine will switch to 10-ms buffers again. +> +> quote: [Low-Latency Audio FAQ](https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/low-latency-audio#faq) + +## Requirement -quote: https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/low-latency-audio \ No newline at end of file +Windows 10 for the core task [IAudioClient3](https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nn-audioclient-iaudioclient3) and Windows 11 for [ProcessPowerThrottling](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ne-processthreadsapi-process_information_class) diff --git a/build.bat b/build.bat index 59dada1..33a0fae 100644 --- a/build.bat +++ b/build.bat @@ -1,2 +1,29 @@ +ECHO OFF @REM cargo clean + +:loop +CLS + +IF exist "%~dp0\target\release\low_audio_latency.exe" ( + FOR /F "usebackq" %%A IN ('%~dp0\target\release\low_audio_latency.exe') DO SET /A beforeSize=%%~zA +) ELSE ( + SET /A beforeSize=0 +) + cargo build --release + +FOR /F "usebackq" %%A IN ('%~dp0\target\release\low_audio_latency.exe') DO SET /A size=%%~zA +SET /A diffSize = %size% - %beforeSize% +SET /A size=(%size%/1024)+1 +IF "%diffSize%" EQU "0" ( + ECHO %size% kb +) ELSE ( + IF "%diffSize%" GTR "0" ( + ECHO %size% kb [+%diffSize% b] + ) ELSE ( + ECHO %size% kb [%diffSize% b] + ) +) + +PAUSE +GOTO loop \ No newline at end of file diff --git a/run.bat b/run.bat index 5f4947e..8e29b8c 100644 --- a/run.bat +++ b/run.bat @@ -1,5 +1,6 @@ :loop CLS + cargo run PAUSE diff --git a/src/cpuset.rs b/src/cpuset.rs new file mode 100644 index 0000000..8745b51 --- /dev/null +++ b/src/cpuset.rs @@ -0,0 +1,54 @@ +use ntapi::ntexapi::{NtSetSystemInformation, SystemAllowedCpuSetsInformation}; +use windows::Win32::Foundation::{GetLastError, HANDLE}; +use windows::Win32::System::SystemInformation::{ + GetSystemCpuSetInformation, SYSTEM_CPU_SET_INFORMATION, +}; + +/// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getsystemcpusetinformation +/// GetSystemCpuSetInformation +pub unsafe fn get_system_cpu_set_information( + handle: HANDLE, +) -> Vec> { + let cpu_num: usize = number_of_processors(); + + let buf_len = std::mem::size_of::() * cpu_num; + let mut ret_len: u32 = 0; + let mut infos: Vec> = + Vec::with_capacity(cpu_num); + infos.set_len(cpu_num); + let r = GetSystemCpuSetInformation( + Some(infos.as_ptr() as *mut SYSTEM_CPU_SET_INFORMATION), + buf_len as u32, + &mut ret_len, + handle, + 0, + ); + if !r.as_bool() { + println!("Err GetSystemCpuSetInformation"); + } + + infos +} + +// https://learn.microsoft.com/en-us/windows/win32/sysinfo/ntsetsysteminformation +pub unsafe fn system_allowed_cpu_sets_information(cpusets: Vec) { + let length = (cpusets.len() * std::mem::size_of::()) as u32; + let status = NtSetSystemInformation( + SystemAllowedCpuSetsInformation, + cpusets.as_ptr() as *mut ntapi::winapi::ctypes::c_void, + length, + ); + if status != 0 { + println!( + "Failed to change system CPU set (NTSTATUS: 0x{:08X}, Win32 Error: {:?}).", + status, + GetLastError() + ); + } +} + +pub unsafe fn number_of_processors() -> usize { + let mut info: windows::Win32::System::SystemInformation::SYSTEM_INFO = std::mem::zeroed(); + windows::Win32::System::SystemInformation::GetSystemInfo(&mut info); + return info.dwNumberOfProcessors as usize; +} diff --git a/src/main.rs b/src/main.rs index 13217e1..338ee5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,34 +1,255 @@ -// #![windows_subsystem = "windows"] +// #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use windows::Win32::System::Threading::*; +mod cpuset; mod sound; +use std::sync::mpsc::channel; +use windows::Win32::Foundation::{CloseHandle, GetLastError, FALSE, HANDLE, LUID}; +use windows::Win32::Security::{ + AdjustTokenPrivileges, GetTokenInformation, LookupPrivilegeValueW, TokenElevation, + SE_INC_BASE_PRIORITY_NAME, SE_PRIVILEGE_ENABLED, TOKEN_ADJUST_PRIVILEGES, TOKEN_ELEVATION, + TOKEN_PRIVILEGES, TOKEN_QUERY, +}; +use windows::Win32::System::Com::{CoInitializeEx, COINIT_MULTITHREADED}; +use windows::Win32::System::SystemInformation::{ + SYSTEM_CPU_SET_INFORMATION_ALLOCATED_TO_TARGET_PROCESS, SYSTEM_CPU_SET_INFORMATION_REALTIME, +}; +use windows::Win32::System::Threading::{ + GetCurrentProcess, OpenProcessToken, ProcessPowerThrottling, SetPriorityClass, + SetProcessInformation, IDLE_PRIORITY_CLASS, PROCESS_POWER_THROTTLING_CURRENT_VERSION, + PROCESS_POWER_THROTTLING_EXECUTION_SPEED, PROCESS_POWER_THROTTLING_STATE, +}; + #[cfg(windows)] fn main() { - // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setpriorityclass - unsafe { - SetPriorityClass(GetCurrentProcess(), PROCESS_MODE_BACKGROUND_BEGIN); - } + unsafe { + let current_process = GetCurrentProcess(); + + // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setpriorityclass + SetPriorityClass(current_process, IDLE_PRIORITY_CLASS).expect("Err SetPriorityClass"); + + // ControlMask selects the mechanism and StateMask declares which mechanism should be on or off. + let state = PROCESS_POWER_THROTTLING_STATE { + Version: PROCESS_POWER_THROTTLING_CURRENT_VERSION, + ControlMask: PROCESS_POWER_THROTTLING_EXECUTION_SPEED, + StateMask: PROCESS_POWER_THROTTLING_EXECUTION_SPEED, + }; + + // only works with Windows Build 22000 or higher + let state_pointer: *const std::ffi::c_void = std::ptr::addr_of!(state).cast(); + let _ = SetProcessInformation( + current_process, + ProcessPowerThrottling, + state_pointer, + std::mem::size_of::() as u32, + ); + + // https://learn.microsoft.com/de-de/windows/win32/api/objbase/nf-objbase-coinitialize + let hr = CoInitializeEx(None, COINIT_MULTITHREADED); + if hr.is_err() { + println!("hr: {:?}", hr) + } + } + + let opt = parse_args(std::env::args().skip(1).collect()); + let opt_len = opt.len(); let mut handles = Vec::new(); - for data in [ - ( - windows::Win32::Media::Audio::eRender, - windows::Win32::Media::Audio::eConsole, - ), - ( - windows::Win32::Media::Audio::eCapture, - windows::Win32::Media::Audio::eCommunications, - ), - ] - .iter() - { + let (audio_tx, audio_rx) = channel(); + for data in opt.into_iter() { + let audiothread_tx = audio_tx.clone(); handles.push(std::thread::spawn(move || { - sound::apply_audio_settings(data.0, data.1) + sound::apply_audio_settings(&audiothread_tx, data.0, data.1, data.2); })); } + // Wait for all audio streams to start or fail + for _ in 0..opt_len { + let _ = audio_rx.recv().unwrap(); + } + + unsafe { + let hwnd = GetCurrentProcess(); + let infos = cpuset::get_system_cpu_set_information(hwnd); + let core0 = infos[0].assume_init().Anonymous.CpuSet; // we only need the first thread/core + let flags: u32 = core0.Anonymous1.AllFlags as u32; + + // This process is allocated a core in the CpuSet with realtime flag + if flags & SYSTEM_CPU_SET_INFORMATION_ALLOCATED_TO_TARGET_PROCESS != 0 + && flags & SYSTEM_CPU_SET_INFORMATION_REALTIME != 0 + { + println!("This process is allocated a core in the CpuSet with realtime flag"); + enable_debug_privilege(); + cpuset::get_system_cpu_set_information(hwnd); + + let bitmask: Vec = vec![(1 << cpuset::number_of_processors()) - 1]; + cpuset::system_allowed_cpu_sets_information(bitmask); + cpuset::get_system_cpu_set_information(hwnd); + } + } + + // keeps the parked threads alive for handle in handles { handle.join().unwrap(); } } + +// https://github.com/mstange/samply/blob/eab5ffb44a23d92fa35aa64d1dc0ad31f6a9ae78/samply/src/windows/winutils.rs#L18 +fn is_elevated() -> bool { + unsafe { + let mut handle: HANDLE = Default::default(); + OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle).ok(); + + let mut elevation = TOKEN_ELEVATION::default(); + let mut size = std::mem::size_of::() as u32; + GetTokenInformation( + handle, + TokenElevation, + Some(&mut elevation as *mut _ as *mut std::ffi::c_void), + size, + &mut size, + ) + .ok(); + + elevation.TokenIsElevated != 0 + } +} + +// https://github.com/mstange/samply/blob/eab5ffb44a23d92fa35aa64d1dc0ad31f6a9ae78/samply/src/windows/winutils.rs#L39 +fn enable_debug_privilege() { + if !is_elevated() { + eprintln!( + "You must run samply as an Administrator so that it can enable SeDebugPrivilege. \ + Try using 'sudo' on recent Windows." + ); + std::process::exit(1); + } + + unsafe { + let mut h_token: HANDLE = Default::default(); + let mut tp: TOKEN_PRIVILEGES = std::mem::zeroed(); + + if OpenProcessToken( + GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + &mut h_token, + ) + .is_err() + { + panic!("OpenProcessToken failed. Error: {:?}", GetLastError()); + } + + let mut luid: LUID = std::mem::zeroed(); + + if LookupPrivilegeValueW( + windows::core::PCWSTR::null(), + SE_INC_BASE_PRIORITY_NAME, + &mut luid, + ) + .is_err() + { + panic!("LookupPrivilegeValue failed. Error: {:?}", GetLastError()); + } + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if AdjustTokenPrivileges( + h_token, + FALSE, + Some(&tp), + std::mem::size_of::() as u32, + None, + None, + ) + .is_err() + { + panic!("AdjustTokenPrivileges failed. Error: {:?}", GetLastError()); + } + + if !GetLastError().is_ok() { + eprintln!( + "AdjustTokenPrivileges succeeded, but the error result is failure. Likely \ + the token does not have the specified privilege, which means you are not running \ + as Administrator. GetLastError: {:?}", + GetLastError() + ); + std::process::exit(1); + } + + CloseHandle(h_token).ok(); + } +} + +fn parse_args( + args: Vec, +) -> Vec<( + windows::Win32::Media::Audio::EDataFlow, + windows::Win32::Media::Audio::ERole, + u32, +)> { + use windows::Win32::Media::Audio::*; + if args.len() == 0 { + return vec![ + ( + eRender, // EDataFlow + eConsole, // ERole + 0, // pMinPeriodInFrames + ), + ( + eCapture, // EDataFlow + eCommunications, // ERole + 0, // pMinPeriodInFrames + ), + ]; + } + + let mut result = vec![]; + for data in args.into_iter() { + let k: Vec<&str> = data.split(",").collect(); + + let mut edata_flow: EDataFlow = EDataFlow(0); + let mut erole: ERole = ERole(0); + let mut p_min_period_in_frames: u32 = 0; + for (pos, e) in k.iter().enumerate() { + match pos { + 0 => { + edata_flow = match e.parse::() { + Ok(int) => EDataFlow(int), + Err(_) => match e.to_lowercase().as_str() { + "erender" => EDataFlow(0i32), // eRender + "ecapture" => EDataFlow(1i32), // eCapture + "eall" => EDataFlow(2i32), // eAll + _ => panic!("Error parse EDataFlow"), + }, + }; + } + 1 => { + erole = match e.parse::() { + Ok(int) => ERole(int), + Err(_) => match e.to_lowercase().as_str() { + "econsole" => ERole(0i32), // eConsole + "emultimedia" => ERole(1i32), // eMultimedia + "ecommunications" => ERole(2i32), // eCommunications + _ => panic!("Error parse ERole"), + }, + }; + } + 2 => { + p_min_period_in_frames = match e.parse::() { + Ok(int) => int, + Err(_) => 0, + }; + } + _ => { + println!("Err parse"); + } + } + } + + result.push((edata_flow, erole, p_min_period_in_frames)) + } + + return result; +} diff --git a/src/sound.rs b/src/sound.rs index 9cbbc3b..aa2d329 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -1,15 +1,13 @@ -use windows::core::GUID; use windows::Win32::Media::Audio::{ EDataFlow, ERole, IAudioClient3, IMMDeviceEnumerator, MMDeviceEnumerator, }; -use windows::Win32::System::Com::{ - CoCreateInstance, CoInitialize, StructuredStorage, CLSCTX_ALL, STGM_READ, VT_LPWSTR, -}; +use windows::Win32::System::Com::{CoCreateInstance, StructuredStorage, CLSCTX_ALL, STGM_READ}; +use windows::Win32::System::Variant::{VARENUM, VT_LPWSTR}; use windows::Win32::UI::Shell::PropertiesSystem::{IPropertyStore, PROPERTYKEY}; #[allow(non_upper_case_globals)] const PKEY_Device_FriendlyName: PROPERTYKEY = PROPERTYKEY { - fmtid: GUID::from_values( + fmtid: windows::core::GUID::from_values( 0xa45c254e, 0xdf1c, 0x4efd, @@ -18,32 +16,42 @@ const PKEY_Device_FriendlyName: PROPERTYKEY = PROPERTYKEY { pid: 14, }; -pub fn apply_audio_settings(dataflow: EDataFlow, role: ERole) { +pub fn apply_audio_settings( + audiothread_tx: &std::sync::mpsc::Sender, + edataflow: EDataFlow, + erole: ERole, + p_min_period_in_frames: u32, +) { unsafe { - CoInitialize(None).expect("CoInitialize Failed"); - let device_enumerator: IMMDeviceEnumerator = CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL) .expect("CoCreateInstance Failed"); - let default_audio_endpoint = device_enumerator.GetDefaultAudioEndpoint(dataflow, role); + // https://learn.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdeviceenumerator-getdefaultaudioendpoint + let default_audio_endpoint = device_enumerator.GetDefaultAudioEndpoint(edataflow, erole); if default_audio_endpoint.is_err() { - println!("GetDefaultAudioEndpoint Failed: {:?}", default_audio_endpoint); + println!( + "GetDefaultAudioEndpoint Failed: {:?}", + default_audio_endpoint.unwrap_err() + ); + audiothread_tx.send(false).unwrap(); return; } let endpoint = default_audio_endpoint.unwrap(); - let property_store = endpoint - .OpenPropertyStore(STGM_READ) - .expect("OpenPropertyStore Failed"); - let p_audio_client: IAudioClient3 = endpoint .Activate(CLSCTX_ALL, None) .expect("Activate Failed"); + // https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-getmixformat let p_format = p_audio_client.GetMixFormat().unwrap(); + // https://learn.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdevice-openpropertystore + let property_store = endpoint + .OpenPropertyStore(STGM_READ) + .expect("OpenPropertyStore Failed"); + let friendly_name = get_property_vt_lpwstr(&property_store, &PKEY_Device_FriendlyName); let n_channels = (*p_format).nChannels; @@ -55,6 +63,7 @@ pub fn apply_audio_settings(dataflow: EDataFlow, role: ERole) { let mut pfundamentalperiodinframes: u32 = 0; let mut pminperiodinframes: u32 = 0; let mut pmaxperiodinframes: u32 = 0; + // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient3-getsharedmodeengineperiod p_audio_client .GetSharedModeEnginePeriod( @@ -89,36 +98,49 @@ pub fn apply_audio_settings(dataflow: EDataFlow, role: ERole) { pmaxperiodinframes as f64 / n_samples_per_sec_float * 1000.0 ); - if pminperiodinframes >= pdefaultperiodinframes { - println!("no change necessary, exit"); - return; + if p_min_period_in_frames == 0 { + if pminperiodinframes >= pdefaultperiodinframes { + println!("no change necessary, exit"); + audiothread_tx.send(false).unwrap(); + return; + } + } else { + pminperiodinframes = p_min_period_in_frames; + println!( + "Buffer new (min) size{:.>6} samples (about {} milliseconds)", + pminperiodinframes, + pminperiodinframes as f64 / n_samples_per_sec_float * 1000.0 + ); } - const NULL_GUID: GUID = GUID { - data1: 0, - data2: 0, - data3: 0, - data4: [0, 0, 0, 0, 0, 0, 0, 0], - }; + // https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient3-initializesharedaudiostream p_audio_client - .InitializeSharedAudioStream(0, pminperiodinframes, p_format, Some(&NULL_GUID)) - .expect("InitializeSharedAudioStream Failed"); + .InitializeSharedAudioStream(0, pminperiodinframes, p_format, None) + .expect("p_audio_client.InitializeSharedAudioStream Failed"); + + // https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-start + p_audio_client.Start().expect("p_audio_client.Start Failed"); - p_audio_client.Start().expect("Start Failed"); + // Thread is ready + audiothread_tx.send(true).unwrap(); std::thread::park(); + + // unreachable code + // https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-stop + p_audio_client.Stop().expect("Stop Failed"); } } fn get_property_vt_lpwstr(store: &IPropertyStore, props_key: &PROPERTYKEY) -> String { #[allow(unused_assignments)] let mut result = String::from(""); + unsafe { let mut property_value = store.GetValue(props_key as *const _ as *const _).unwrap(); + let prop_variant = property_value.as_raw().Anonymous.Anonymous; - let prop_variant = &property_value.Anonymous.Anonymous; - - if prop_variant.vt != VT_LPWSTR { + if !VT_LPWSTR.eq(&VARENUM(prop_variant.vt)) { println!( "property store produced invalid data: {:?}", prop_variant.vt