-
Notifications
You must be signed in to change notification settings - Fork 439
Support loopback recording on macOS #1003
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
base: master
Are you sure you want to change the base?
Support loopback recording on macOS #1003
Conversation
This pr might seem a little bit messy, but most of the new code should be at I also extracted some code from For |
It seems that, in the CI, Well, CI is on macOS 14.7 while I'm on 15.5. Can anyone on macOS 14 try this? |
There are some features not enabled by default. Given the kind of errors produced, I doubt this is a system-specific problem. Make sure you run the proper build commands, run unit tests, and try an example (like "beep"). To properly test this on your system, try using the following commands:
What could have changed that impacted |
@wgibbs-rs Thanks for your suggestions! Both of the following runs flawlessly on my machine:
The wasm error is probably a network issue, it didn't even get to the point to run my code. It failed at "install stable". |
Re-ran the failed tests and they’re OK now. Edit: no you’re right, the macOS test doesn’t finish. I only have an up to date macOS too. Thanks for your contribution! This seems like a worthwhile feature to have. I am away now and should be able to look more into it in a few days. I’ll start by triggering a Copilot review and see what it comes up with. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for loopback recording (recording system audio output) on macOS by implementing aggregate devices with taps. This enables applications to capture audio that is being played through the system's output devices.
Key changes include:
- Added loopback recording capability using macOS Core Audio APIs
- Restructured the device implementation to support both regular and loopback devices
- Enhanced the record_wav example to demonstrate the new functionality
Reviewed Changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
File | Description |
---|---|
src/host/coreaudio/macos/mod.rs | Restructured module to extract device implementation and add loopback support |
src/host/coreaudio/macos/loopback.rs | New module implementing loopback device creation using aggregate devices and taps |
src/host/coreaudio/macos/device.rs | Extracted device implementation with enhanced input stream building for loopback support |
examples/record_wav.rs | Enhanced example to support recording from default output device with configurable duration |
Cargo.toml | Added required Core Foundation and Foundation dependencies for loopback functionality |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Any ideas how we could get it to work on macOS 14? |
I think it might not be very difficult, the only problem is I don't have a macOS 14 machine. When I get time, I could probably try to debug this in a VM. |
Maybe it's because the CI is asking the (non-existent) user whether microphone access is allowed? Maybe have it time out? |
I think that might be the case. But why does recording test pass? It should also ask for microphone access... |
Ok. I just fired up a macOS 14 VM. It does ask for microphone permission. But after granting the permission, it does ran without problems. So, not sure what happened on CI 🤔 |
I'm no expert but maybe the problem is that loopback requires different permissions: screen capture, instead of microphone for the recording test. I'd be OK with skipping such tests conditionally on the CI: #[test]
fn test_foo() {
if std::env::var("CI").is_ok() {
println!("Skipping test_foo in CI environment due to permissions");
return;
}
// ...
} |
Huh. I'm getting some odd behavior with the example. I've spent probably 20+ minutes trying to narrow it down but I'm more confused than I was initially. I'm on macOS 15.6.1, I've swapped between xcode 26 beta 6 and xcode 16.4 via I had it working in a non famous youtube music video and when I play local media. I'm not a huge audio person but the audio quality sounded like "It was under water". I wanted to provide the input recorded audio and the output from the loopback but somehow in the last 15 minutes something changed and now I don't hear anything when I play |
@simlay Thanks for reporting! About the youtube video, I think it might be related to DRM, but I'm not sure. If you could provide its URL I may find out something. About the sound quality, I'm not sure what's happening. Something that comes to my mind:
// Create a tap description for all processes without a specific output.
// Note: I believe there is a bug in the CoreAudio Tap API. If the
// default output device has 2 output channels this works as expected.
// But if you have a device with 4 output channels then the volume of
// the resulting buffer will be halved. You can extrapolate this to any
// number of channels.
// This bug is also present in ScreenCaptureKit if you use it on macOS
// 14.2 or later. The bug is not present in macOS 14.1 or earlier.
// A workaround for this issue could be to increase the volume of the
// audio in the output by the number of channels. Or simply use the
// other API to tap a specific output.
About playing the wav file, well, that's really strange! Hope you would figure it out~ |
In my case (macOS 15.6.1) there are two sections in the screen capture permissions, "Screen capture and system audio" and "system audio only" (I translated from Chinese, might not be word accurate). It should require the "system audio only" permission. However on macOS 14, the OS only asks for the microphone permission when running the tests. I'm personally OK with skipping it on CI, but I'm curious why the microphone tests pass on CI? I didn't see any permission granting thing on the workflow file... |
I don’t know and can only guess that it has something to do with these permissions. Fixing the CI without skipping the test would be great but if we can’t win the fight, then let’s spend our energy elsewhere. |
Because of permissions. Fuck apple.
Agreed. I've now disabled the check on CI and all checks have passed 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for getting the CI to pass. Couple of review comments.
}; | ||
coreaudio::Error::from_os_status(status)?; | ||
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _; | ||
let ranges: &'static [AudioValueRange] = unsafe { slice::from_raw_parts(ranges, n_ranges) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be &'static
or just &
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code isn't really written by me... I just moved it to another file.
But looking at the code, it seems it should be &
as it's a locally-declared Vec
. But maybe it didn't return this pointer so it didn't cause any problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes sure. Will do that will I get back to my computer~
By not checking the return status
This pr adds support for recording system output audio on macOS.
For usage example, please see
src/host/coreaudio/macos/mod.rs
test::test_record_output
.To test this, use
cargo run --example record_wav --device default-output
Testing requirements:
cargo run --example
, e.g. VSCode or Zed.Closes #876