Skip to content
Draft
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
54 changes: 52 additions & 2 deletions pcsx2-qt/Settings/GraphicsMediaCaptureSettingsTab.ui
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,63 @@
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="2" column="0">
<widget class="QLabel" name="audioCaptureVolumeLabel">
<property name="text">
<string>Volume:</string>
</property>
<property name="buddy">
<cstring>audioCaptureVolume</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="audioCaptureVolumeLayout" stretch="1,0">
<item>
<widget class="QSlider" name="audioCaptureVolume">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>25</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="audioCaptureVolumeValue">
<property name="text">
<string>100%</string>
</property>
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="enableAudioCaptureArguments">
<property name="text">
<string>Extra Arguments</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="4" column="0" colspan="2">
<widget class="QLineEdit" name="audioCaptureArguments"/>
</item>
</layout>
Expand Down Expand Up @@ -401,6 +450,7 @@
<tabstop>enableAudioCapture</tabstop>
<tabstop>audioCaptureCodec</tabstop>
<tabstop>audioCaptureBitrate</tabstop>
<tabstop>audioCaptureVolume</tabstop>
<tabstop>enableAudioCaptureArguments</tabstop>
<tabstop>audioCaptureArguments</tabstop>
</tabstops>
Expand Down
6 changes: 6 additions & 0 deletions pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_capture.enableAudioCapture, "EmuCore/GS", "EnableAudioCapture", true);
SettingWidgetBinder::BindWidgetToIntSetting(
sif, m_capture.audioCaptureBitrate, "EmuCore/GS", "AudioCaptureBitrate", Pcsx2Config::GSOptions::DEFAULT_AUDIO_CAPTURE_BITRATE);
SettingWidgetBinder::BindWidgetAndLabelToIntSetting(
sif, m_capture.audioCaptureVolume, m_capture.audioCaptureVolumeValue, "%", "EmuCore/GS", "AudioCaptureVolume", Pcsx2Config::GSOptions::DEFAULT_AUDIO_CAPTURE_VOLUME);
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, m_capture.enableAudioCaptureArguments, "EmuCore/GS", "EnableAudioCaptureParameters", false);
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_capture.audioCaptureArguments, "EmuCore/GS", "AudioCaptureParameters");
Expand Down Expand Up @@ -813,6 +815,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,

dialog()->registerWidgetHelp(m_capture.audioCaptureBitrate, tr("Audio Bitrate"), tr("192 kbps"), tr("Sets the audio bitrate to be used."));

dialog()->registerWidgetHelp(
m_capture.audioCaptureVolume, tr("Audio Volume"), QStringLiteral("100%"),
tr("Sets the volume level for recorded audio. 100% is full volume, lower values reduce the volume."));

dialog()->registerWidgetHelp(m_capture.enableAudioCaptureArguments, tr("Enable Extra Audio Arguments"), tr("Unchecked"), tr("Allows you to pass arguments to the selected audio codec."));

dialog()->registerWidgetHelp(m_capture.audioCaptureArguments, tr("Extra Audio Arguments"), tr("Leave It Blank"),
Expand Down
2 changes: 2 additions & 0 deletions pcsx2/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ struct Pcsx2Config
static constexpr int DEFAULT_VIDEO_CAPTURE_WIDTH = 640;
static constexpr int DEFAULT_VIDEO_CAPTURE_HEIGHT = 480;
static constexpr int DEFAULT_AUDIO_CAPTURE_BITRATE = 192;
static constexpr int DEFAULT_AUDIO_CAPTURE_VOLUME = 100;
static const char* DEFAULT_CAPTURE_CONTAINER;

static constexpr int DEFAULT_SHADEBOOST_BRIGHTNESS = 50;
Expand Down Expand Up @@ -880,6 +881,7 @@ struct Pcsx2Config
int VideoCaptureWidth = DEFAULT_VIDEO_CAPTURE_WIDTH;
int VideoCaptureHeight = DEFAULT_VIDEO_CAPTURE_HEIGHT;
int AudioCaptureBitrate = DEFAULT_AUDIO_CAPTURE_BITRATE;
int AudioCaptureVolume = DEFAULT_AUDIO_CAPTURE_VOLUME;

std::string Adapter;
std::string HWDumpDirectory;
Expand Down
59 changes: 50 additions & 9 deletions pcsx2/GS/GSCapture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1148,36 +1148,77 @@ bool GSCapture::ProcessAudioPackets(s64 video_pts)
const u32 contig_frames = std::min(pending_frames, AUDIO_BUFFER_SIZE - s_audio_buffer_read_pos);
const u32 this_batch = std::min(s_audio_frame_size - s_audio_frame_pos, contig_frames);

// Apply volume multiplier
const float volume_scale = static_cast<float>(GSConfig.AudioCaptureVolume) / 100.0f;

// Do we need to convert the sample format?
if (!s_swr_context)
{
// No, just copy frames out of staging buffer.
// No, just copy frames out of staging buffer with volume applied.
if (s_audio_frame_planar)
{
// This is slow. Hopefully doesn't happen in too many configurations.
for (u32 i = 0; i < AUDIO_CHANNELS; i++)
{
u8* output = s_converted_audio_frame->data[i] + s_audio_frame_pos * s_audio_frame_bps;
const u8* input = reinterpret_cast<u8*>(&s_audio_buffer[s_audio_buffer_read_pos * AUDIO_CHANNELS + i]);
const s16* input = &s_audio_buffer[s_audio_buffer_read_pos * AUDIO_CHANNELS + i];
for (u32 j = 0; j < this_batch; j++)
{
std::memcpy(output, input, sizeof(s16));
input += sizeof(s16) * AUDIO_CHANNELS;
s16 scaled_input = *input;
if (volume_scale != 1.0f)
{
const float sample = static_cast<float>(scaled_input) * volume_scale;
scaled_input = static_cast<s16>(sample);
}
std::memcpy(output, &scaled_input, sizeof(s16));

input += AUDIO_CHANNELS;
output += s_audio_frame_bps;
}
}
}
else
{
// Direct copy - optimal.
std::memcpy(s_converted_audio_frame->data[0] + s_audio_frame_pos * s_audio_frame_bps * AUDIO_CHANNELS,
&s_audio_buffer[s_audio_buffer_read_pos * AUDIO_CHANNELS], this_batch * sizeof(s16) * AUDIO_CHANNELS);
// Direct copy with volume optimal.
if (volume_scale != 1.0f)
{
const s16* input = &s_audio_buffer[s_audio_buffer_read_pos * AUDIO_CHANNELS];
s16* output = reinterpret_cast<s16*>(s_converted_audio_frame->data[0] + s_audio_frame_pos * s_audio_frame_bps * AUDIO_CHANNELS);
for (u32 i = 0; i < this_batch * AUDIO_CHANNELS; i++)
{
const float sample = static_cast<float>(input[i]) * volume_scale;
output[i] = static_cast<s16>(sample);
}
}
else
{
std::memcpy(s_converted_audio_frame->data[0] + s_audio_frame_pos * s_audio_frame_bps * AUDIO_CHANNELS,
&s_audio_buffer[s_audio_buffer_read_pos * AUDIO_CHANNELS], this_batch * sizeof(s16) * AUDIO_CHANNELS);
}
}
}
else
{
// Use swresample to convert.
const u8* input = reinterpret_cast<u8*>(&s_audio_buffer[s_audio_buffer_read_pos * AUDIO_CHANNELS]);
// Use swresample to convert. Apply volume to source before conversion.
const s16* input_s16 = &s_audio_buffer[s_audio_buffer_read_pos * AUDIO_CHANNELS];
const u8* input;

if (volume_scale != 1.0f)
{
// Apply volume to source samples before conversion
std::vector<s16> volume_buffer;
volume_buffer.resize(this_batch * AUDIO_CHANNELS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For something that's probably going to be running quite frequently, this seems like something you would want to call volume_buffer.reserve(n) for rather than volume_buffer.resize(n). The latter will zero-initialize the values for, in this case, no good reason. (You can also safely declare it inside the volume_scale != 1.0f condition, since it's not used anywhere else.)

I don't assume you're allowed to overwrite the original s_audio_buffer while doing this, as that would arguably be the most efficient way.

Copy link
Member

@chaoticgd chaoticgd Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the code, but I just want to flag up that you need to be really careful with std::vector::reserve since it defeats std::vectors default behaviour of doubling (or similar) the size of the memory buffer every time it needs to grow. Hence it makes it have to reallocate every single time, which can kill performance.

Copy link
Member

@chaoticgd chaoticgd Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading more of the code, it looks like it won't be a problem in this case since it's only called once.

Copy link
Contributor

@TheTechnician27 TheTechnician27 Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the code, but I just want to flag up that you need to be really careful with std::vector::reserve since it defeats std::vectors default behaviour of doubling (or similar) the size of the memory buffer every time it needs to grow. Hence it makes it have to reallocate every single time, which can kill performance.

In this case, the vec is being initialized and then resized to exactly the size it needs to be once per call of ProcessAudioPackets() should this branch be taken. So it should only ever grow one time, and thus it should be the same or faster in all cases.

Edit: Honestly, it's functionally an array, whose allocation might be more efficient.

Copy link
Member

@chaoticgd chaoticgd Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I missed that it was being reallocated for every loop iteration the first time I looked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright so I just pushed changes that should resolve some of the suggestions like the don't clamp mention in the discord for example

for (u32 i = 0; i < this_batch * AUDIO_CHANNELS; i++)
{
const float sample = static_cast<float>(input_s16[i]) * volume_scale;
volume_buffer[i] = static_cast<s16>(sample);
}
input = reinterpret_cast<const u8*>(volume_buffer.data());
}
else
{
input = reinterpret_cast<const u8*>(input_s16);
}

// Might be planar, so offset both buffers.
u8* output[AUDIO_CHANNELS];
Expand Down
1 change: 1 addition & 0 deletions pcsx2/Pcsx2Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitfieldEx(VideoCaptureWidth, "VideoCaptureWidth");
SettingsWrapBitfieldEx(VideoCaptureHeight, "VideoCaptureHeight");
SettingsWrapBitfieldEx(AudioCaptureBitrate, "AudioCaptureBitrate");
SettingsWrapBitfieldEx(AudioCaptureVolume, "AudioCaptureVolume");

SettingsWrapEntry(Adapter);
SettingsWrapEntry(HWDumpDirectory);
Expand Down