From 450d6db3439529f05418dd1d2d21d95112f6c3ad Mon Sep 17 00:00:00 2001 From: foxmsft Date: Thu, 5 Feb 2026 01:05:33 +0100 Subject: [PATCH] Add an option for mono mic capture in ZoomIt --- .../ZoomIt/ZoomIt/AudioSampleGenerator.cpp | 30 +++++++++++++++++-- .../ZoomIt/ZoomIt/AudioSampleGenerator.h | 3 +- .../ZoomIt/ZoomIt/VideoRecordingSession.cpp | 6 ++-- .../ZoomIt/ZoomIt/VideoRecordingSession.h | 2 ++ src/modules/ZoomIt/ZoomIt/ZoomIt.rc | 1 + src/modules/ZoomIt/ZoomIt/ZoomItSettings.h | 2 ++ src/modules/ZoomIt/ZoomIt/Zoomit.cpp | 5 ++++ src/modules/ZoomIt/ZoomIt/resource.h | 1 + .../Settings.UI.Library/ZoomItProperties.cs | 2 ++ .../SettingsXAML/Views/ZoomItPage.xaml | 8 ++++- .../Settings.UI/Strings/en-us/Resources.resw | 3 ++ .../Settings.UI/ViewModels/ZoomItViewModel.cs | 14 +++++++++ 12 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp index b3b0ed373f..5ebdf4992a 100644 --- a/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp +++ b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp @@ -34,13 +34,15 @@ namespace winrt using namespace Windows::Devices::Enumeration; } -AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio) +AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio, bool micMonoMix) : m_captureMicrophone(captureMicrophone) , m_captureSystemAudio(captureSystemAudio) + , m_micMonoMix(micMonoMix) { OutputDebugStringA(("AudioSampleGenerator created, captureMicrophone=" + std::string(captureMicrophone ? "true" : "false") + - ", captureSystemAudio=" + std::string(captureSystemAudio ? "true" : "false") + "\n").c_str()); + ", captureSystemAudio=" + std::string(captureSystemAudio ? "true" : "false") + + ", micMonoMix=" + std::string(micMonoMix ? "true" : "false") + "\n").c_str()); m_audioEvent.create(wil::EventOptions::ManualReset); m_endEvent.create(wil::EventOptions::ManualReset); m_startEvent.create(wil::EventOptions::ManualReset); @@ -631,6 +633,30 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender uint32_t expectedSamplesPerQuantum = (m_graphSampleRate / 100) * m_graphChannels; uint32_t numMicSamples = audioBuffer.Length() / sizeof(float); + // Apply mono mixing to microphone audio if enabled + // This converts stereo mic input (with same signal on both channels) to true mono + // by averaging the channels and writing the result to both channels + if (m_micMonoMix && m_captureMicrophone && numMicSamples > 0 && m_graphChannels >= 2) + { + float* micData = reinterpret_cast(sampleBuffer.data()); + uint32_t numFrames = numMicSamples / m_graphChannels; + for (uint32_t i = 0; i < numFrames; i++) + { + // Sum all channels for this frame + float sum = 0.0f; + for (uint32_t ch = 0; ch < m_graphChannels; ch++) + { + sum += micData[i * m_graphChannels + ch]; + } + // Power-preserving mix: divide by sqrt(N) to maintain perceived loudness + float mono = sum / std::sqrt(static_cast(m_graphChannels)); + for (uint32_t ch = 0; ch < m_graphChannels; ch++) + { + micData[i * m_graphChannels + ch] = mono; + } + } + } + // Drain loopback samples regardless of whether we have mic audio if (m_loopbackCapture) { diff --git a/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.h b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.h index 7ffe1438b7..d5197f7e39 100644 --- a/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.h +++ b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.h @@ -5,7 +5,7 @@ class AudioSampleGenerator { public: - AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true); + AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true, bool micMonoMix = false); ~AudioSampleGenerator(); winrt::Windows::Foundation::IAsyncAction InitializeAsync(); @@ -70,4 +70,5 @@ private: std::atomic m_started = false; bool m_captureMicrophone = true; bool m_captureSystemAudio = true; + bool m_micMonoMix = false; }; \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.cpp b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.cpp index d8c465bea4..367c732dc6 100644 --- a/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.cpp +++ b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.cpp @@ -861,6 +861,7 @@ VideoRecordingSession::VideoRecordingSession( uint32_t frameRate, bool captureAudio, bool captureSystemAudio, + bool micMonoMix, winrt::Streams::IRandomAccessStream const& stream) { m_device = device; @@ -964,7 +965,7 @@ VideoRecordingSession::VideoRecordingSession( winrt::check_hresult(m_d3dDevice->CreateRenderTargetView(backBuffer.get(), nullptr, m_renderTargetView.put())); // Always create audio generator for loopback capture; captureAudio controls microphone - m_audioGenerator = std::make_unique(captureAudio, captureSystemAudio); + m_audioGenerator = std::make_unique(captureAudio, captureSystemAudio, micMonoMix); } @@ -1112,9 +1113,10 @@ std::shared_ptr VideoRecordingSession::Create( uint32_t frameRate, bool captureAudio, bool captureSystemAudio, + bool micMonoMix, winrt::Streams::IRandomAccessStream const& stream) { - return std::shared_ptr(new VideoRecordingSession(device, item, crop, frameRate, captureAudio, captureSystemAudio, stream)); + return std::shared_ptr(new VideoRecordingSession(device, item, crop, frameRate, captureAudio, captureSystemAudio, micMonoMix, stream)); } //---------------------------------------------------------------------------- diff --git a/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.h b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.h index c199e9d4b9..8d9c2ec808 100644 --- a/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.h +++ b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.h @@ -28,6 +28,7 @@ public: uint32_t frameRate, bool captureAudio, bool captureSystemAudio, + bool micMonoMix, winrt::Streams::IRandomAccessStream const& stream); ~VideoRecordingSession(); @@ -188,6 +189,7 @@ private: uint32_t frameRate, bool captureAudio, bool captureSystemAudio, + bool micMonoMix, winrt::Streams::IRandomAccessStream const& stream); void CloseInternal(); diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc index d3c5210744..a8e2c70c9d 100644 --- a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc @@ -279,6 +279,7 @@ BEGIN LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,55,251,19 CONTROL "Capture &system audio",IDC_CAPTURE_SYSTEM_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,149,83,10 CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,161,83,10 + CONTROL "Mono",IDC_MIC_MONO_MIX,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,98,161,30,10 COMBOBOX IDC_MICROPHONE,81,176,152,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Microphone:",IDC_MICROPHONE_LABEL,32,178,47,8 END diff --git a/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h b/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h index e7176e6ec8..483df0bb70 100644 --- a/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h +++ b/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h @@ -51,6 +51,7 @@ DWORD g_RecordScalingMP4 = 100; RecordingFormat g_RecordingFormat = RecordingFormat::MP4; BOOLEAN g_CaptureSystemAudio = TRUE; BOOLEAN g_CaptureAudio = FALSE; +BOOLEAN g_MicMonoMix = FALSE; TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0}; TCHAR g_RecordingSaveLocationBuffer[MAX_PATH] = {0}; TCHAR g_ScreenshotSaveLocationBuffer[MAX_PATH] = {0}; @@ -99,6 +100,7 @@ REG_SETTING RegSettings[] = { { L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast(g_RecordScalingMP4) }, { L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast(g_CaptureAudio) }, { L"CaptureSystemAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureSystemAudio, static_cast(g_CaptureSystemAudio) }, + { L"MicMonoMix", SETTING_TYPE_BOOLEAN, 0, &g_MicMonoMix, static_cast(g_MicMonoMix) }, { L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast(0) }, { L"RecordingSaveLocation", SETTING_TYPE_STRING, sizeof(g_RecordingSaveLocationBuffer), g_RecordingSaveLocationBuffer, static_cast(0) }, { L"ScreenshotSaveLocation", SETTING_TYPE_STRING, sizeof(g_ScreenshotSaveLocationBuffer), g_ScreenshotSaveLocationBuffer, static_cast(0) }, diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp index b3f736fd43..9e1a64d814 100644 --- a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp +++ b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp @@ -3840,6 +3840,9 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message, CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO, g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED ); + CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MIC_MONO_MIX, + g_MicMonoMix ? BST_CHECKED: BST_UNCHECKED ); + // // The framerate drop down list is not used in the current version (might be added in the future) // @@ -4260,6 +4263,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message, g_ShowExpiredTime = IsDlgButtonChecked( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED ) == BST_CHECKED; g_CaptureSystemAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO) == BST_CHECKED; g_CaptureAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO) == BST_CHECKED; + g_MicMonoMix = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MIC_MONO_MIX) == BST_CHECKED; GetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text, 3 ); text[2] = 0; newTimeout = _tstoi( text ); @@ -5605,6 +5609,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR g_RecordFrameRate, g_CaptureAudio, g_CaptureSystemAudio, + g_MicMonoMix, stream ); recordingStarted = (g_RecordingSession != nullptr); diff --git a/src/modules/ZoomIt/ZoomIt/resource.h b/src/modules/ZoomIt/ZoomIt/resource.h index c3cffd6d7b..d2a09a6d8b 100644 --- a/src/modules/ZoomIt/ZoomIt/resource.h +++ b/src/modules/ZoomIt/ZoomIt/resource.h @@ -111,6 +111,7 @@ #define IDC_SMOOTH_IMAGE 1107 #define IDC_CAPTURE_SYSTEM_AUDIO 1108 #define IDC_MICROPHONE_LABEL 1109 +#define IDC_MIC_MONO_MIX 1110 #define IDC_SAVE 40002 #define IDC_COPY 40004 #define IDC_RECORD 40006 diff --git a/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs b/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs index 6c6535801a..f83d3ba6cb 100644 --- a/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs +++ b/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs @@ -93,6 +93,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library public BoolProperty CaptureAudio { get; set; } + public BoolProperty MicMonoMix { get; set; } + public StringProperty MicrophoneDeviceId { get; set; } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml index 88496afab6..51b6cf776b 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml @@ -1,4 +1,4 @@ - + + + Capture audio input + + Mono + Microphone diff --git a/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs index ccadf776ff..24319c8fd8 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs @@ -878,6 +878,20 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool RecordMicMonoMix + { + get => _zoomItSettings.Properties.MicMonoMix.Value; + set + { + if (_zoomItSettings.Properties.MicMonoMix.Value != value) + { + _zoomItSettings.Properties.MicMonoMix.Value = value; + OnPropertyChanged(nameof(RecordMicMonoMix)); + NotifySettingsChanged(); + } + } + } + public string RecordMicrophoneDeviceId { get => _zoomItSettings.Properties.MicrophoneDeviceId.Value;