Add an option for mono mic capture in ZoomIt

This commit is contained in:
foxmsft
2026-02-05 01:05:33 +01:00
committed by Alex Mihaiuc
parent bb4c548a4b
commit 450d6db343
12 changed files with 71 additions and 6 deletions

View File

@@ -34,13 +34,15 @@ namespace winrt
using namespace Windows::Devices::Enumeration; using namespace Windows::Devices::Enumeration;
} }
AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio) AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio, bool micMonoMix)
: m_captureMicrophone(captureMicrophone) : m_captureMicrophone(captureMicrophone)
, m_captureSystemAudio(captureSystemAudio) , m_captureSystemAudio(captureSystemAudio)
, m_micMonoMix(micMonoMix)
{ {
OutputDebugStringA(("AudioSampleGenerator created, captureMicrophone=" + OutputDebugStringA(("AudioSampleGenerator created, captureMicrophone=" +
std::string(captureMicrophone ? "true" : "false") + 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_audioEvent.create(wil::EventOptions::ManualReset);
m_endEvent.create(wil::EventOptions::ManualReset); m_endEvent.create(wil::EventOptions::ManualReset);
m_startEvent.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 expectedSamplesPerQuantum = (m_graphSampleRate / 100) * m_graphChannels;
uint32_t numMicSamples = audioBuffer.Length() / sizeof(float); 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<float*>(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<float>(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 // Drain loopback samples regardless of whether we have mic audio
if (m_loopbackCapture) if (m_loopbackCapture)
{ {

View File

@@ -5,7 +5,7 @@
class AudioSampleGenerator class AudioSampleGenerator
{ {
public: public:
AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true); AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true, bool micMonoMix = false);
~AudioSampleGenerator(); ~AudioSampleGenerator();
winrt::Windows::Foundation::IAsyncAction InitializeAsync(); winrt::Windows::Foundation::IAsyncAction InitializeAsync();
@@ -70,4 +70,5 @@ private:
std::atomic<bool> m_started = false; std::atomic<bool> m_started = false;
bool m_captureMicrophone = true; bool m_captureMicrophone = true;
bool m_captureSystemAudio = true; bool m_captureSystemAudio = true;
bool m_micMonoMix = false;
}; };

View File

@@ -861,6 +861,7 @@ VideoRecordingSession::VideoRecordingSession(
uint32_t frameRate, uint32_t frameRate,
bool captureAudio, bool captureAudio,
bool captureSystemAudio, bool captureSystemAudio,
bool micMonoMix,
winrt::Streams::IRandomAccessStream const& stream) winrt::Streams::IRandomAccessStream const& stream)
{ {
m_device = device; m_device = device;
@@ -964,7 +965,7 @@ VideoRecordingSession::VideoRecordingSession(
winrt::check_hresult(m_d3dDevice->CreateRenderTargetView(backBuffer.get(), nullptr, m_renderTargetView.put())); winrt::check_hresult(m_d3dDevice->CreateRenderTargetView(backBuffer.get(), nullptr, m_renderTargetView.put()));
// Always create audio generator for loopback capture; captureAudio controls microphone // Always create audio generator for loopback capture; captureAudio controls microphone
m_audioGenerator = std::make_unique<AudioSampleGenerator>(captureAudio, captureSystemAudio); m_audioGenerator = std::make_unique<AudioSampleGenerator>(captureAudio, captureSystemAudio, micMonoMix);
} }
@@ -1112,9 +1113,10 @@ std::shared_ptr<VideoRecordingSession> VideoRecordingSession::Create(
uint32_t frameRate, uint32_t frameRate,
bool captureAudio, bool captureAudio,
bool captureSystemAudio, bool captureSystemAudio,
bool micMonoMix,
winrt::Streams::IRandomAccessStream const& stream) winrt::Streams::IRandomAccessStream const& stream)
{ {
return std::shared_ptr<VideoRecordingSession>(new VideoRecordingSession(device, item, crop, frameRate, captureAudio, captureSystemAudio, stream)); return std::shared_ptr<VideoRecordingSession>(new VideoRecordingSession(device, item, crop, frameRate, captureAudio, captureSystemAudio, micMonoMix, stream));
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------

View File

@@ -28,6 +28,7 @@ public:
uint32_t frameRate, uint32_t frameRate,
bool captureAudio, bool captureAudio,
bool captureSystemAudio, bool captureSystemAudio,
bool micMonoMix,
winrt::Streams::IRandomAccessStream const& stream); winrt::Streams::IRandomAccessStream const& stream);
~VideoRecordingSession(); ~VideoRecordingSession();
@@ -188,6 +189,7 @@ private:
uint32_t frameRate, uint32_t frameRate,
bool captureAudio, bool captureAudio,
bool captureSystemAudio, bool captureSystemAudio,
bool micMonoMix,
winrt::Streams::IRandomAccessStream const& stream); winrt::Streams::IRandomAccessStream const& stream);
void CloseInternal(); void CloseInternal();

View File

@@ -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 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 &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 "&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 COMBOBOX IDC_MICROPHONE,81,176,152,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Microphone:",IDC_MICROPHONE_LABEL,32,178,47,8 LTEXT "Microphone:",IDC_MICROPHONE_LABEL,32,178,47,8
END END

View File

@@ -51,6 +51,7 @@ DWORD g_RecordScalingMP4 = 100;
RecordingFormat g_RecordingFormat = RecordingFormat::MP4; RecordingFormat g_RecordingFormat = RecordingFormat::MP4;
BOOLEAN g_CaptureSystemAudio = TRUE; BOOLEAN g_CaptureSystemAudio = TRUE;
BOOLEAN g_CaptureAudio = FALSE; BOOLEAN g_CaptureAudio = FALSE;
BOOLEAN g_MicMonoMix = FALSE;
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0}; TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
TCHAR g_RecordingSaveLocationBuffer[MAX_PATH] = {0}; TCHAR g_RecordingSaveLocationBuffer[MAX_PATH] = {0};
TCHAR g_ScreenshotSaveLocationBuffer[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<DOUBLE>(g_RecordScalingMP4) }, { L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast<DOUBLE>(g_RecordScalingMP4) },
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) }, { L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
{ L"CaptureSystemAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureSystemAudio, static_cast<DOUBLE>(g_CaptureSystemAudio) }, { L"CaptureSystemAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureSystemAudio, static_cast<DOUBLE>(g_CaptureSystemAudio) },
{ L"MicMonoMix", SETTING_TYPE_BOOLEAN, 0, &g_MicMonoMix, static_cast<DOUBLE>(g_MicMonoMix) },
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) }, { L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) },
{ L"RecordingSaveLocation", SETTING_TYPE_STRING, sizeof(g_RecordingSaveLocationBuffer), g_RecordingSaveLocationBuffer, static_cast<DOUBLE>(0) }, { L"RecordingSaveLocation", SETTING_TYPE_STRING, sizeof(g_RecordingSaveLocationBuffer), g_RecordingSaveLocationBuffer, static_cast<DOUBLE>(0) },
{ L"ScreenshotSaveLocation", SETTING_TYPE_STRING, sizeof(g_ScreenshotSaveLocationBuffer), g_ScreenshotSaveLocationBuffer, static_cast<DOUBLE>(0) }, { L"ScreenshotSaveLocation", SETTING_TYPE_STRING, sizeof(g_ScreenshotSaveLocationBuffer), g_ScreenshotSaveLocationBuffer, static_cast<DOUBLE>(0) },

View File

@@ -3840,6 +3840,9 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO, CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED ); 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) // 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_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_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_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 ); GetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text, 3 );
text[2] = 0; text[2] = 0;
newTimeout = _tstoi( text ); newTimeout = _tstoi( text );
@@ -5605,6 +5609,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
g_RecordFrameRate, g_RecordFrameRate,
g_CaptureAudio, g_CaptureAudio,
g_CaptureSystemAudio, g_CaptureSystemAudio,
g_MicMonoMix,
stream ); stream );
recordingStarted = (g_RecordingSession != nullptr); recordingStarted = (g_RecordingSession != nullptr);

View File

@@ -111,6 +111,7 @@
#define IDC_SMOOTH_IMAGE 1107 #define IDC_SMOOTH_IMAGE 1107
#define IDC_CAPTURE_SYSTEM_AUDIO 1108 #define IDC_CAPTURE_SYSTEM_AUDIO 1108
#define IDC_MICROPHONE_LABEL 1109 #define IDC_MICROPHONE_LABEL 1109
#define IDC_MIC_MONO_MIX 1110
#define IDC_SAVE 40002 #define IDC_SAVE 40002
#define IDC_COPY 40004 #define IDC_COPY 40004
#define IDC_RECORD 40006 #define IDC_RECORD 40006

View File

@@ -93,6 +93,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public BoolProperty CaptureAudio { get; set; } public BoolProperty CaptureAudio { get; set; }
public BoolProperty MicMonoMix { get; set; }
public StringProperty MicrophoneDeviceId { get; set; } public StringProperty MicrophoneDeviceId { get; set; }
} }
} }

View File

@@ -1,4 +1,4 @@
<local:NavigablePage <local:NavigablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.ZoomItPage" x:Class="Microsoft.PowerToys.Settings.UI.Views.ZoomItPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -281,6 +281,12 @@
<tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" ContentAlignment="Left"> <tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" ContentAlignment="Left">
<CheckBox x:Uid="ZoomIt_Record_CaptureAudio" IsChecked="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" /> <CheckBox x:Uid="ZoomIt_Record_CaptureAudio" IsChecked="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" />
</tkcontrols:SettingsCard> </tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="ZoomItRecordMicMonoMix" ContentAlignment="Left">
<CheckBox
x:Uid="ZoomIt_Record_MicMonoMix"
IsChecked="{x:Bind ViewModel.RecordMicMonoMix, Mode=TwoWay}"
Visibility="{x:Bind ViewModel.RecordCaptureAudio, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard <tkcontrols:SettingsCard
Name="ZoomItRecordMicrophone" Name="ZoomItRecordMicrophone"
x:Uid="ZoomIt_Record_Microphone" x:Uid="ZoomIt_Record_Microphone"

View File

@@ -4718,6 +4718,9 @@ The break timer font matches the text font.</value>
<data name="ZoomIt_Record_CaptureAudio.Content" xml:space="preserve"> <data name="ZoomIt_Record_CaptureAudio.Content" xml:space="preserve">
<value>Capture audio input</value> <value>Capture audio input</value>
</data> </data>
<data name="ZoomIt_Record_MicMonoMix.Content" xml:space="preserve">
<value>Mono</value>
</data>
<data name="ZoomIt_Record_Microphone.Header" xml:space="preserve"> <data name="ZoomIt_Record_Microphone.Header" xml:space="preserve">
<value>Microphone</value> <value>Microphone</value>
</data> </data>

View File

@@ -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 public string RecordMicrophoneDeviceId
{ {
get => _zoomItSettings.Properties.MicrophoneDeviceId.Value; get => _zoomItSettings.Properties.MicrophoneDeviceId.Value;