Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Version.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<CaptureKitVersion>1.0.3</CaptureKitVersion>
<CaptureKitVersion>1.0.5</CaptureKitVersion>
<Version>$(CaptureKitVersion)</Version>
</PropertyGroup>
</Project>
1 change: 1 addition & 0 deletions src/CaptureKit.Abstractions/IVideoCaptureService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public interface IVideoCaptureSession : IDisposable
void Pause();
void Resume();
void SetAudioCaptureEnabled(bool enabled);
void SetSystemAudioVolume(int volumePercentage);
void SetAudioInputSource(string? sourceId);
void SetAudioInputVolume(int volumePercentage);
}
3 changes: 2 additions & 1 deletion src/CaptureKit.Abstractions/VideoCaptureOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ public sealed record VideoCaptureOptions(
uint VideoBitrate = 5_000_000,
uint AudioBitrate = 128_000,
string? AudioInputSourceId = null,
int AudioInputVolumePercentage = 100);
int AudioInputVolumePercentage = 100,
int SystemAudioVolumePercentage = 100);
58 changes: 37 additions & 21 deletions src/CaptureKit.Windows.Native.Core/CaptureSessionConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ struct CaptureSessionConfig
/// </summary>
uint32_t audioInputVolumePercentage;

/// <summary>
/// System/desktop audio volume as a percentage. 100 preserves the captured signal.
/// </summary>
uint32_t systemAudioVolumePercentage;

/// <summary>
/// Target video frame rate (FPS). Default is 30.
/// </summary>
Expand All @@ -132,7 +137,8 @@ struct CaptureSessionConfig
uint32_t vidBitrate = 5'000'000,
uint32_t audBitrate = 128'000,
std::wstring audioSourceId = L"",
uint32_t audioVolumePercentage = 100)
uint32_t audioVolumePercentage = 100,
uint32_t systemVolumePercentage = 100)
: targetType(CaptureTargetType::Monitor)
, hMonitor(monitor)
, hwnd(nullptr)
Expand All @@ -147,6 +153,7 @@ struct CaptureSessionConfig
, audioBitrate(audBitrate)
, audioInputSourceId(std::move(audioSourceId))
, audioInputVolumePercentage(audioVolumePercentage)
, systemAudioVolumePercentage(systemVolumePercentage)
{
}

Expand All @@ -161,7 +168,8 @@ struct CaptureSessionConfig
uint32_t vidBitrate = 5'000'000,
uint32_t audBitrate = 128'000,
std::wstring audioSourceId = L"",
uint32_t audioVolumePercentage = 100)
uint32_t audioVolumePercentage = 100,
uint32_t systemVolumePercentage = 100)
: targetType(CaptureTargetType::Monitor)
, hMonitor(monitor)
, hwnd(nullptr)
Expand All @@ -176,6 +184,7 @@ struct CaptureSessionConfig
, audioBitrate(audBitrate)
, audioInputSourceId(std::move(audioSourceId))
, audioInputVolumePercentage(audioVolumePercentage)
, systemAudioVolumePercentage(systemVolumePercentage)
{
}

Expand All @@ -197,6 +206,7 @@ struct CaptureSessionConfig
, audioBitrate(128'000)
, audioInputSourceId(L"")
, audioInputVolumePercentage(100)
, systemAudioVolumePercentage(100)
{
}

Expand All @@ -208,9 +218,10 @@ struct CaptureSessionConfig
uint32_t vidBitrate = 5'000'000,
uint32_t audBitrate = 128'000,
std::wstring audioSourceId = L"",
uint32_t audioVolumePercentage = 100)
uint32_t audioVolumePercentage = 100,
uint32_t systemVolumePercentage = 100)
{
CaptureSessionConfig config(monitor, std::move(path), audio, fps, vidBitrate, audBitrate, std::move(audioSourceId), audioVolumePercentage);
CaptureSessionConfig config(monitor, std::move(path), audio, fps, vidBitrate, audBitrate, std::move(audioSourceId), audioVolumePercentage, systemVolumePercentage);
config.targetType = CaptureTargetType::Monitor;
return config;
}
Expand All @@ -223,9 +234,10 @@ struct CaptureSessionConfig
uint32_t vidBitrate = 5'000'000,
uint32_t audBitrate = 128'000,
std::wstring audioSourceId = L"",
uint32_t audioVolumePercentage = 100)
uint32_t audioVolumePercentage = 100,
uint32_t systemVolumePercentage = 100)
{
CaptureSessionConfig config(nullptr, std::move(path), audio, fps, vidBitrate, audBitrate, std::move(audioSourceId), audioVolumePercentage);
CaptureSessionConfig config(nullptr, std::move(path), audio, fps, vidBitrate, audBitrate, std::move(audioSourceId), audioVolumePercentage, systemVolumePercentage);
config.targetType = CaptureTargetType::Window;
config.hwnd = window;
return config;
Expand All @@ -243,9 +255,10 @@ struct CaptureSessionConfig
uint32_t vidBitrate = 5'000'000,
uint32_t audBitrate = 128'000,
std::wstring audioSourceId = L"",
uint32_t audioVolumePercentage = 100)
uint32_t audioVolumePercentage = 100,
uint32_t systemVolumePercentage = 100)
{
CaptureSessionConfig config(monitor, std::move(path), audio, fps, vidBitrate, audBitrate, std::move(audioSourceId), audioVolumePercentage);
CaptureSessionConfig config(monitor, std::move(path), audio, fps, vidBitrate, audBitrate, std::move(audioSourceId), audioVolumePercentage, systemVolumePercentage);
config.targetType = CaptureTargetType::Rectangle;
config.sourceLeft = left;
config.sourceTop = top;
Expand Down Expand Up @@ -335,21 +348,24 @@ struct CaptureSessionConfig
constexpr uint32_t MIN_AUDIO_BITRATE = 32'000; // 32 kbps
constexpr uint32_t MAX_AUDIO_BITRATE = 320'000; // 320 kbps

if (audioEnabled)
if (audioBitrate < MIN_AUDIO_BITRATE || audioBitrate > MAX_AUDIO_BITRATE)
{
if (audioBitrate < MIN_AUDIO_BITRATE || audioBitrate > MAX_AUDIO_BITRATE)
{
result.AddError("audioBitrate must be between " +
std::to_string(MIN_AUDIO_BITRATE) + " and " +
std::to_string(MAX_AUDIO_BITRATE) + " (got " +
std::to_string(audioBitrate) + ")");
}
result.AddError("audioBitrate must be between " +
std::to_string(MIN_AUDIO_BITRATE) + " and " +
std::to_string(MAX_AUDIO_BITRATE) + " (got " +
std::to_string(audioBitrate) + ")");
}

if (audioInputVolumePercentage > 100)
{
result.AddError("audioInputVolumePercentage must be between 0 and 100 (got " +
std::to_string(audioInputVolumePercentage) + ")");
}
if (audioInputVolumePercentage > 100)
{
result.AddError("audioInputVolumePercentage must be between 0 and 100 (got " +
std::to_string(audioInputVolumePercentage) + ")");
}

if (systemAudioVolumePercentage > 100)
{
result.AddError("systemAudioVolumePercentage must be between 0 and 100 (got " +
std::to_string(systemAudioVolumePercentage) + ")");
}

return result;
Expand Down
11 changes: 11 additions & 0 deletions src/CaptureKit.Windows.Native.Core/CompositeAudioCaptureSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,17 @@ void CompositeAudioCaptureSource::SetVolume(uint32_t volumePercentage)
}
}

void CompositeAudioCaptureSource::SetSystemVolume(uint32_t volumePercentage)
{
uint32_t clampedVolume = std::min<uint32_t>(volumePercentage, 100);

std::lock_guard<std::mutex> lock(m_sourceMutex);
if (m_loopbackSource)
{
m_loopbackSource->SetVolume(clampedVolume);
}
}

bool CompositeAudioCaptureSource::IsRunning() const
{
return m_isRunning.load();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class CompositeAudioCaptureSource final : public IAudioCaptureSource
void SetEnabled(bool enabled) override;
bool IsEnabled() const override;
void SetVolume(uint32_t volumePercentage) override;
void SetSystemVolume(uint32_t volumePercentage) override;
bool IsRunning() const override;
bool SetInputDeviceId(const wchar_t* sourceId, HRESULT* outHr = nullptr) override;
void SetClockWriter(IMediaClockWriter* clockWriter) override;
Expand Down
9 changes: 9 additions & 0 deletions src/CaptureKit.Windows.Native.Core/IAudioCaptureSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ class IAudioCaptureSource : public IMediaClockAdvancer
/// </summary>
virtual void SetVolume(uint32_t volumePercentage) = 0;

/// <summary>
/// Set the system/desktop volume independently from microphone/input volume.
/// Standalone sources treat this as their normal volume control.
/// </summary>
virtual void SetSystemVolume(uint32_t volumePercentage)
{
SetVolume(volumePercentage);
}

/// <summary>
/// Check if the audio input source is currently running.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/CaptureKit.Windows.Native.Core/ICaptureSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ class ICaptureSession
/// <param name="enabled">True to enable audio, false to mute.</param>
virtual void ToggleAudioCapture(bool enabled) = 0;

/// <summary>
/// Set the system/desktop audio volume percentage for the active recording.
/// </summary>
virtual void SetSystemAudioVolume(uint32_t volumePercentage) = 0;

/// <summary>
/// Change the audio input source for the active recording.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/CaptureKit.Windows.Native.Core/ScreenRecorderImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ bool ScreenRecorderImpl::SetAudioCaptureEnabled(bool enabled)
return true;
}

bool ScreenRecorderImpl::SetSystemAudioVolume(uint32_t volumePercentage)
{
if (!HasActiveSession() || !m_captureSession->IsActive())
{
return false;
}

m_captureSession->SetSystemAudioVolume(volumePercentage);
return true;
}

bool ScreenRecorderImpl::SetAudioInputSource(const wchar_t* sourceId)
{
if (!HasActiveSession() || !m_captureSession->IsActive())
Expand Down
5 changes: 5 additions & 0 deletions src/CaptureKit.Windows.Native.Core/ScreenRecorderImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ class ScreenRecorderImpl
/// <param name="enabled">True to enable audio, false to mute.</param>
bool SetAudioCaptureEnabled(bool enabled);

/// <summary>
/// Set system/desktop audio volume during recording.
/// </summary>
bool SetSystemAudioVolume(uint32_t volumePercentage);

/// <summary>
/// Change the audio input source during recording.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ WindowsGraphicsCaptureSession::~WindowsGraphicsCaptureSession()
bool WindowsGraphicsCaptureSession::Initialize(HRESULT* outHr)
{
HRESULT hr = S_OK;
const bool audioRequested = m_config.audioEnabled || !m_config.audioInputSourceId.empty();

// Validate state - must be in Created state to initialize
if (m_stateMachine.GetState() != CaptureSessionState::Created)
Expand All @@ -114,10 +113,8 @@ bool WindowsGraphicsCaptureSession::Initialize(HRESULT* outHr)
}

// Validate dependencies
if (!m_mediaClock || !m_videoCaptureSource || !m_sinkWriter || (audioRequested && !m_audioCaptureSource))
if (!m_mediaClock || !m_audioCaptureSource || !m_videoCaptureSource || !m_sinkWriter)
{
// Audio is intentionally optional for a video-only session. All other dependencies,
// plus audio when requested, must be supplied by the factory.
[[maybe_unused]] bool transitioned = m_stateMachine.TryTransitionTo(CaptureSessionState::Failed);
// Transition should always succeed from Created to Failed
assert(transitioned && "Transition to Failed should always succeed from Created state");
Expand All @@ -135,23 +132,20 @@ bool WindowsGraphicsCaptureSession::Initialize(HRESULT* outHr)
}

m_audioAvailable = false;
if (audioRequested)
{
// Audio is the preferred media-clock advancer when it was explicitly requested.
// A video-only session must not touch WASAPI or add an AAC stream.
m_mediaClock->SetClockAdvancer(m_audioCaptureSource.get());
// Every video session prepares audio so mute, unmute, source, and volume controls
// remain live for the entire recording. audioEnabled is only the initial mute state.
m_mediaClock->SetClockAdvancer(m_audioCaptureSource.get());

if (m_audioCaptureSource->Initialize(&hr) && m_audioCaptureSource->GetFormat())
{
m_audioAvailable = true;
}
else
{
OutputDebugStringW(L"[CaptureInterop V1] Audio source initialization failed; continuing with video-only timing fallback.\r\n");
m_audioCaptureSource->SetAudioSampleReadyCallback(nullptr);
m_audioCaptureSource->Stop();
m_audioCaptureSource.reset();
}
if (m_audioCaptureSource->Initialize(&hr) && m_audioCaptureSource->GetFormat())
{
m_audioAvailable = true;
}
else
{
OutputDebugStringW(L"[CaptureInterop V1] Audio source initialization failed; continuing with video-only timing fallback.\r\n");
m_audioCaptureSource->SetAudioSampleReadyCallback(nullptr);
m_audioCaptureSource->Stop();
m_audioCaptureSource.reset();
}

// Initialize sink writer
Expand Down Expand Up @@ -426,6 +420,7 @@ bool WindowsGraphicsCaptureSession::StartAudioCapture(HRESULT* outHr)

// Apply audio enabled setting
m_audioCaptureSource->SetEnabled(m_config.audioEnabled);
m_audioCaptureSource->SetSystemVolume(m_config.systemAudioVolumePercentage);
m_audioCaptureSource->SetVolume(m_config.audioInputVolumePercentage);

// Start audio
Expand Down Expand Up @@ -563,6 +558,14 @@ void WindowsGraphicsCaptureSession::ToggleAudioCapture(bool enabled)
}
}

void WindowsGraphicsCaptureSession::SetSystemAudioVolume(uint32_t volumePercentage)
{
if (m_audioCaptureSource)
{
m_audioCaptureSource->SetSystemVolume(volumePercentage);
}
}

bool WindowsGraphicsCaptureSession::SetAudioInputSource(const wchar_t* sourceId)
{
if (!m_audioCaptureSource)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class WindowsGraphicsCaptureSession : public ICaptureSession
void Pause() override;
void Resume() override;
void ToggleAudioCapture(bool enabled) override;
void SetSystemAudioVolume(uint32_t volumePercentage) override;
bool SetAudioInputSource(const wchar_t* sourceId) override;
void SetAudioInputVolume(uint32_t volumePercentage) override;
bool IsActive() const override { return m_stateMachine.IsActive(); }
Expand Down
17 changes: 14 additions & 3 deletions src/CaptureKit.Windows.Native.Recording/ScreenRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ extern "C"
options->videoBitrate,
options->audioBitrate,
options->audioInputSourceId ? options->audioInputSourceId : L"",
options->audioInputVolumePercentage);
options->audioInputVolumePercentage,
options->systemAudioVolumePercentage);
break;

case CaptureRecordingTargetKind::Window:
Expand All @@ -79,7 +80,8 @@ extern "C"
options->videoBitrate,
options->audioBitrate,
options->audioInputSourceId ? options->audioInputSourceId : L"",
options->audioInputVolumePercentage);
options->audioInputVolumePercentage,
options->systemAudioVolumePercentage);
break;

case CaptureRecordingTargetKind::Rectangle:
Expand All @@ -99,7 +101,8 @@ extern "C"
options->videoBitrate,
options->audioBitrate,
options->audioInputSourceId ? options->audioInputSourceId : L"",
options->audioInputVolumePercentage);
options->audioInputVolumePercentage,
options->systemAudioVolumePercentage);
break;

default:
Expand Down Expand Up @@ -154,6 +157,14 @@ extern "C"
});
}

__declspec(dllexport) CaptureRecorderResult SetScreenRecordingSystemAudioVolume(uint32_t volumePercentage)
{
return GuardRecorderCall(L"SetScreenRecordingSystemAudioVolume", CaptureRecorderStatus::InvalidState, [volumePercentage] {
std::lock_guard<std::mutex> lock(g_recorderMutex);
return GetRecorder().SetSystemAudioVolume(volumePercentage) ? Success() : NoActiveSession();
});
}

__declspec(dllexport) CaptureRecorderResult SetScreenRecordingAudioInputSource(const wchar_t* sourceId)
{
return GuardRecorderCall(L"SetScreenRecordingAudioInputSource", CaptureRecorderStatus::InvalidState, [sourceId] {
Expand Down
2 changes: 2 additions & 0 deletions src/CaptureKit.Windows.Native.Recording/ScreenRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct CaptureRecordingOptions
uint32_t audioBitrate;
const wchar_t* audioInputSourceId;
uint32_t audioInputVolumePercentage;
uint32_t systemAudioVolumePercentage;
};

extern "C"
Expand All @@ -50,6 +51,7 @@ extern "C"
__declspec(dllexport) CaptureRecorderResult ResumeScreenRecording();
__declspec(dllexport) CaptureRecorderResult StopScreenRecording();
__declspec(dllexport) CaptureRecorderResult SetScreenRecordingAudioEnabled(uint32_t enabled);
__declspec(dllexport) CaptureRecorderResult SetScreenRecordingSystemAudioVolume(uint32_t volumePercentage);
__declspec(dllexport) CaptureRecorderResult SetScreenRecordingAudioInputSource(const wchar_t* sourceId);
__declspec(dllexport) CaptureRecorderResult SetScreenRecordingAudioInputVolume(uint32_t volumePercentage);

Expand Down
Loading
Loading