Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate audio pipeline to float from 16-bit integer #2873

Merged
merged 7 commits into from
Jul 26, 2024

Conversation

ns6089
Copy link
Contributor

@ns6089 ns6089 commented Jul 16, 2024

Description

Float is the native format of opus codec and most if not all capture backends.

Implementation progress:

  • Windows
  • MacOS
  • Linux

Screenshot

Issues Fixed or Closed

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Dependency update (updates to dependencies)
  • Documentation update (changes to documentation)
  • Repository update (changes to repository files, e.g. .github/...)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated the in code docstring/documentation-blocks for new or existing methods/components

Branch Updates

LizardByte requires that branches be up-to-date before merging. This means that after any PR is merged, this branch
must be updated before it can be merged. You must also
Allow edits from maintainers.

  • I want maintainers to keep my branch updated

cgutman
cgutman previously approved these changes Jul 18, 2024
Copy link
Collaborator

@cgutman cgutman left a comment

Choose a reason for hiding this comment

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

Successfully tested locally on macOS and Linux

@ns6089 ns6089 marked this pull request as ready for review July 18, 2024 23:47
@cgutman cgutman enabled auto-merge (squash) July 19, 2024 04:10
Copy link

codecov bot commented Jul 19, 2024

Codecov Report

Attention: Patch coverage is 34.64912% with 149 lines in your changes missing coverage. Please review.

Project coverage is 9.57%. Comparing base (aa2cf8e) to head (6dbb08f).
Report is 137 commits behind head on master.

Files with missing lines Patch % Lines
src/platform/windows/audio.cpp 38.53% 120 Missing and 6 partials ⚠️
src/logging.cpp 0.00% 8 Missing ⚠️
src/platform/linux/audio.cpp 0.00% 6 Missing ⚠️
src/platform/macos/microphone.mm 0.00% 5 Missing ⚠️
src/audio.cpp 0.00% 2 Missing ⚠️
src/platform/macos/av_audio.m 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##           master   #2873      +/-   ##
=========================================
+ Coverage    9.19%   9.57%   +0.37%     
=========================================
  Files          97      97              
  Lines       17523   17586      +63     
  Branches     8331    8336       +5     
=========================================
+ Hits         1611    1683      +72     
- Misses      13086   15099    +2013     
+ Partials     2826     804    -2022     
Flag Coverage Δ
Linux 6.95% <0.00%> (-0.01%) ⬇️
Windows 5.08% <36.74%> (+0.56%) ⬆️
macOS-12 10.29% <0.00%> (-0.01%) ⬇️
macOS-13 10.21% <0.00%> (+<0.01%) ⬆️
macOS-14 10.52% <0.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/logging.h 32.35% <ø> (ø)
src/platform/common.h 34.44% <ø> (ø)
src/audio.cpp 28.57% <0.00%> (ø)
src/platform/macos/av_audio.m 18.03% <0.00%> (ø)
src/platform/macos/microphone.mm 37.77% <0.00%> (ø)
src/platform/linux/audio.cpp 9.64% <0.00%> (-0.05%) ⬇️
src/logging.cpp 77.98% <0.00%> (-6.18%) ⬇️
src/platform/windows/audio.cpp 26.38% <38.53%> (+12.31%) ⬆️

... and 28 files with indirect coverage changes

src/platform/linux/audio.cpp Fixed Show resolved Hide resolved
@@ -432,7 +432,7 @@
}

// *2 --> needs to fit double
sample_buf = util::buffer_t<std::int16_t> { std::max(frames, frame_size) * 2 * channels_out };
sample_buf = util::buffer_t<float> { std::max(frames, frame_size) * 2 * channels_out };

Check failure

Code scanning / CodeQL

Multiplication result converted to larger type High

Multiplication result may overflow 'unsigned int' before it is converted to 'size_t'.
@psyke83
Copy link
Collaborator

psyke83 commented Jul 19, 2024

This seems to break audio on my system by default.

Log is here; relevant lines:

[2024:07:19:19:51:47]: Error: Couldn't set Wave Format [0x88890008]
[2024:07:19:19:51:47]: Error: Unable to initialize audio capture. The stream will not have audio.

I can make audio work with your PR by disabling (or rather, disallowing audio playback via the Sound settings) the Steam Streaming Speaker and Microphone devices and reconnecting the session; simply setting a real (non-Steam) endpoint to the default doesn't fix the issue, as the Steam virtual devices are always set to default when a new session is started.

@ns6089
Copy link
Contributor Author

ns6089 commented Jul 20, 2024

Can reproduce, let's see what did I do wrong...

@ns6089 ns6089 disabled auto-merge July 20, 2024 07:35
@ns6089 ns6089 marked this pull request as draft July 20, 2024 07:35
@ns6089
Copy link
Contributor Author

ns6089 commented Jul 20, 2024

Can reproduce, let's see what did I do wrong...

Messed with the function picks format for steam virtual speakers device.

@psyke83 Should work now, good catch

@ns6089 ns6089 marked this pull request as ready for review July 20, 2024 08:24
@ns6089
Copy link
Contributor Author

ns6089 commented Jul 20, 2024

2024-07-20 11_33_08-Window

Wasn't me by the way, github did it automatically.
No new changes to Linux or MacOS code, the review is still valid.

@ReenigneArcher
Copy link
Member

ReenigneArcher commented Jul 20, 2024

2024-07-20 11_33_08-Window

Wasn't me by the way, github did it automatically. No new changes to Linux or MacOS code, the review is still valid.

Reviews get dismissed when the code changes. That is a branch protection rule I have enabled.

@psyke83
Copy link
Collaborator

psyke83 commented Jul 20, 2024

Can reproduce, let's see what did I do wrong...

Messed with the function picks format for steam virtual speakers device.

@psyke83 Should work now, good catch

Confirmed working via Steam endpoints with latest changes on your PR branch - thanks.


return wave_format;
}

int
set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
wave_format->nSamplesPerSec = SAMPLE_RATE;
wave_format->wBitsPerSample = 16;
wave_format->wBitsPerSample = 24;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm concerned we might need some additional logic for this. I don't know if it's guaranteed that all devices will support 24-bit PCM for all channel configurations. I think it would be best if we tried them in order: 32-bit -> 24-bit -> 16-bit

That would ensure we don't possibly regress any audio devices that are 16-bit only, and also provide the highest quality on devices with 32-bit support.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's used only for "virtual" sinks, which I thought only consisted of steam virtual speakers. Am I wrong in this assumption?

/**
 * If the requested sink is a virtual sink, meaning no speakers attached to
 * the host, then we can seamlessly set the format to stereo and surround sound.
 *
 * Any virtual sink detected will be prefixed by:
 *    virtual-(format name)
 * If it doesn't contain that prefix, then the format will not be changed
 */
std::optional<std::wstring>
set_format(const std::string &sink) {
  auto sink_info = get_sink_info(sink);

  // If the sink isn't a device name, we'll assume it's a device ID
  auto wstring_device_id = find_device_id_by_name(sink).value_or(from_utf8(sink_info.second.data()));

  if (sink_info.first == format_t::none) {
    // wstring_device_id does not contain virtual-(format name)
    // It's a simple deviceId, just pass it back
    return std::make_optional(std::move(wstring_device_id));
  }

  wave_format_t wave_format;
  auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format);
  if (FAILED(status)) {
    BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';

    return std::nullopt;
  }

  set_wave_format(wave_format, formats[(int) sink_info.first - 1]);

  WAVEFORMATEXTENSIBLE p {};
  status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *) &p);

  // Surround 5.1 might contain side-{left, right} instead of speaker in the back
  // Try again with different speaker mask.
  if (status == 0x88890008 && sink_info.first == format_t::surr51) {
    set_wave_format(wave_format, surround_51_side_speakers);
    status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *) &p);
  }
  if (FAILED(status)) {
    BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']';

    return std::nullopt;
  }

  return std::make_optional(std::move(wstring_device_id));
}

Copy link
Member

Choose a reason for hiding this comment

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

It's used only for "virtual" sinks, which I thought only consisted of steam virtual speakers. Am I wrong in this assumption?

Other virtual speakers can be used, such as virtual audio cable https://vb-audio.com/Cable/

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 sunshine can have default or custom audio sink, and default or custom virtual audio sink.
Will check how it can be handled.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it could just be as simple as a loop that tries SetDeviceFormat() until it succeeds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought it would be a simple loop too, but then ended up with a nasty memory corruption. The rest of the code had accumulated quite a bit of technical dept. Either way it should be ready soon.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pushed and tested what I could.

auto adapter_name = no_null((LPWSTR) adapter_friendly_name.prop.pszVal);
auto device_name = no_null((LPWSTR) device_friendly_name.prop.pszVal);
auto device_description = no_null((LPWSTR) device_desc.prop.pszVal);
auto adapter_name = (LPWSTR) adapter_friendly_name.prop.pszVal;

Check failure

Code scanning / CodeQL

Cast from char* to wchar_t*

Conversion from LPSTR to LPWSTR. Use of invalid string can lead to undefined behavior.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll commit a fix for these and merge

auto device_name = no_null((LPWSTR) device_friendly_name.prop.pszVal);
auto device_description = no_null((LPWSTR) device_desc.prop.pszVal);
auto adapter_name = (LPWSTR) adapter_friendly_name.prop.pszVal;
auto device_name = (LPWSTR) device_friendly_name.prop.pszVal;

Check failure

Code scanning / CodeQL

Cast from char* to wchar_t*

Conversion from LPSTR to LPWSTR. Use of invalid string can lead to undefined behavior.
auto device_description = no_null((LPWSTR) device_desc.prop.pszVal);
auto adapter_name = (LPWSTR) adapter_friendly_name.prop.pszVal;
auto device_name = (LPWSTR) device_friendly_name.prop.pszVal;
auto device_description = (LPWSTR) device_desc.prop.pszVal;

Check failure

Code scanning / CodeQL

Cast from char* to wchar_t*

Conversion from LPSTR to LPWSTR. Use of invalid string can lead to undefined behavior.
cgutman
cgutman previously approved these changes Jul 25, 2024
@cgutman cgutman enabled auto-merge (squash) July 25, 2024 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants