-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat(macOS): Capture audio on macOS using Tap API #4209
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
Merged
ReenigneArcher
merged 57 commits into
LizardByte:master
from
ThomVanL:users/thomasvanlaere/feat-macos-ca-taps
Mar 21, 2026
Merged
Changes from all commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
5bd8e54
wip(macos): add system-wide audio tap support
ThomVanL 74c2cf1
feat(macos): some unit tests
ThomVanL 0c2e096
wip(macos): converter creation now queries device to get accurate info
ThomVanL 48ef28d
wip(macos): refactored setupSystemTap and split into methods
ThomVanL 05c76c1
wip(macos): more unit tests to cover refactored system tap methods
ThomVanL d594c06
wip(macos): nullability and cleanup return types
ThomVanL 2b0ba99
wip(macos): NS_ASSUME_NONNULL_BEGIN should be included
ThomVanL 1dc8217
wip(macos): added some info log statements
ThomVanL 1a3bc52
wip(macos): doxygen documentation
ThomVanL cab7da6
wip(macos): added cleanupSystemTapContext instance method to header a…
ThomVanL a729f78
wip(macos): renamed instance method
ThomVanL b29445e
fix(macos): add macOS-specific test files only when building tests fo…
ThomVanL 9762651
fix(cmake): add missing newline
ThomVanL 13bc467
style: format C++ code with clang-format
ThomVanL 5aa03e2
fix(macos): improve nil-safety in av_audio microphone code-path
ThomVanL a847d1f
style(windows): code incorrectly formatted
ThomVanL 6404705
revert: style(windows): code incorrectly formatted
ThomVanL faa1170
wip(macos): refactor ioprocs to c/c++.
ThomVanL fc3609b
style(macos): formatting
ThomVanL b056036
refactor(macos): simplify audio tap to always use stereo configuration
ThomVanL 2c40470
feat(audio): Core Audio tap mute behavior for macOS host audio control.
ThomVanL 1ba9208
fix(audio): mark unused host_audio_enabled parameter as [[maybe_unused]]
ThomVanL 44407a4
Merge branch 'master' into users/thomasvanlaere/feat-macos-ca-taps
ThomVanL 3ce6572
refactor(config): macos_system_wide_audio_tap removed
ThomVanL fe2ef3f
fix(macos): correct minimum macOS version for Core Audio taps from 14…
ThomVanL 186c21c
feat(macos): use system audio tap on macOS 14+ and update related docs
ThomVanL db3d2df
fix(audio): add missing host_audio_enabled parameter to Linux audio.
ThomVanL f667865
fix: gitignore did not end with a newline character.
ThomVanL ee2bc0d
test(macos): remove TCC-dependent integration tests and optimize unit…
ThomVanL 1cf8e9e
Merge remote-tracking branch 'origin/master' into users/thomasvanlaer…
ThomVanL db69ca2
test(macos): fixed typos
ThomVanL f5a6672
tests(macos): clean up and align AVAudioTest naming with project conv…
ThomVanL 6a12b35
refactor(macos/audio): apply review feedback from andygrundman
ThomVanL 418824b
refactor(macos): apply review feedback from ReenigneArcher on PR #4209
ThomVanL 81b60c2
Merge remote-tracking branch 'upstream/master' into users/thomasvanla…
ThomVanL 2813edb
chore(macOS): plist readability
ThomVanL d4fce06
fix(macos/audio): changed microphoneNames to construct autoreleased r…
ThomVanL 8486a52
refactor(macos/audio): merge public/private tap logic into single NSD…
ThomVanL 3831795
fix(macos/audio): fall back to client format when aggregate property …
ThomVanL 3fb5412
Update docs/getting_started.md
ThomVanL 998e1f6
revert(cmake): restore unix compile definitions to ebc41acf
ThomVanL 6563f47
style(macos): removed alignment
ThomVanL 41d4e8e
Merge remote-tracking branch 'upstream/master' into users/thomasvanla…
ThomVanL 74655c2
fix(macos): add macOS audio/screen capture usage strings
ThomVanL 8ede4a2
refactor(macos): remove unused audio buffer constant
ThomVanL 5609dff
Merge branch 'master' into users/thomasvanlaere/feat-macos-ca-taps
ThomVanL 1f79b49
Merge remote-tracking branch 'upstream/master' into users/thomasvanla…
ThomVanL cb83973
fix(macos): clang formatting
ThomVanL 578e3ac
docs(readme): update minimum macOS version to 14.2+
ThomVanL 3d975d3
fix(ci): set macOS deployment target environment variable to 14.2
ThomVanL 614de3f
test(audio): skip gracefully when audio capture is unavailable
ThomVanL cafda16
Merge branch 'master' into users/thomasvanlaere/feat-macos-ca-taps
ReenigneArcher 4dc8c99
style: fix lint issue in Info.plist.in
ReenigneArcher 5a1a80e
Merge branch 'master' into users/thomasvanlaere/feat-macos-ca-taps
ThomVanL 50b2105
feat(ci/homebrew): seed macOS TCC permissions for audio capture in ho…
ThomVanL cafae7f
revert: test(audio): skip gracefully when audio capture is unavailable
ThomVanL 12f9106
Merge branch 'master' into users/thomasvanlaere/feat-macos-ca-taps
ThomVanL File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,168 @@ | ||
| /** | ||
| * @file src/platform/macos/av_audio.h | ||
| * @brief Declarations for audio capture on macOS. | ||
| * @brief Declarations for macOS audio capture with dual input paths. | ||
| * | ||
| * This header defines the AVAudio class which provides distinct audio capture methods: | ||
| * 1. **Microphone capture** - Uses AVFoundation framework to capture from specific microphone devices | ||
| * 2. **System-wide audio tap** - Uses Core Audio taps to capture all system audio output (macOS 14.0+) | ||
| * | ||
| * The system-wide audio tap allows capturing audio from all applications and system sounds, | ||
| * while microphone capture focuses on input from physical or virtual microphone devices. | ||
| */ | ||
| #pragma once | ||
|
|
||
| // platform includes | ||
| #import <AudioToolbox/AudioToolbox.h> | ||
| #import <AVFoundation/AVFoundation.h> | ||
| #import <CoreAudio/AudioHardwareTapping.h> | ||
| #import <CoreAudio/CoreAudio.h> | ||
|
|
||
| // lib includes | ||
| #include "third-party/TPCircularBuffer/TPCircularBuffer.h" | ||
|
|
||
| static const int kBufferLength = 4096; | ||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| // Forward declarations | ||
| @class AVAudio; | ||
| @class CATapDescription; | ||
|
|
||
| namespace platf { | ||
| OSStatus audioConverterComplexInputProc(AudioConverterRef _Nullable inAudioConverter, UInt32 *_Nonnull ioNumberDataPackets, AudioBufferList *_Nonnull ioData, AudioStreamPacketDescription *_Nullable *_Nullable outDataPacketDescription, void *_Nonnull inUserData); | ||
| OSStatus systemAudioIOProc(AudioObjectID inDevice, const AudioTimeStamp *_Nullable inNow, const AudioBufferList *_Nullable inInputData, const AudioTimeStamp *_Nullable inInputTime, AudioBufferList *_Nullable outOutputData, const AudioTimeStamp *_Nullable inOutputTime, void *_Nullable inClientData); | ||
| } // namespace platf | ||
|
|
||
| /** | ||
| * @brief Data structure for AudioConverter input callback. | ||
| * Contains audio data and metadata needed for format conversion during audio processing. | ||
| */ | ||
| struct AudioConverterInputData { | ||
| float *inputData; ///< Pointer to input audio data | ||
| UInt32 inputFrames; ///< Total number of input frames available | ||
| UInt32 framesProvided; ///< Number of frames already provided to converter | ||
| UInt32 deviceChannels; ///< Number of channels in the device audio | ||
| AVAudio *avAudio; ///< Reference to the AVAudio instance | ||
| }; | ||
|
|
||
| /** | ||
| * @brief IOProc client data structure for Core Audio system taps. | ||
| * Contains configuration and conversion data for real-time audio processing. | ||
| */ | ||
| typedef struct { | ||
| AVAudio *avAudio; ///< Reference to AVAudio instance | ||
| UInt32 clientRequestedChannels; ///< Number of channels requested by client | ||
| UInt32 clientRequestedSampleRate; ///< Sample rate requested by client | ||
| UInt32 clientRequestedFrameSize; ///< Frame size requested by client | ||
| UInt32 aggregateDeviceSampleRate; ///< Sample rate of the aggregate device | ||
| UInt32 aggregateDeviceChannels; ///< Number of channels in aggregate device | ||
| AudioConverterRef _Nullable audioConverter; ///< Audio converter for format conversion | ||
| float *_Nullable conversionBuffer; ///< Pre-allocated buffer for audio conversion | ||
| UInt32 conversionBufferSize; ///< Size of the conversion buffer in bytes | ||
| } AVAudioIOProcData; | ||
|
|
||
| /** | ||
| * @brief Core Audio capture class for macOS audio input and system-wide audio tapping. | ||
| * Provides functionality for both microphone capture via AVFoundation and system-wide | ||
| * audio capture via Core Audio taps (requires macOS 14.0+). | ||
| */ | ||
| @interface AVAudio: NSObject <AVCaptureAudioDataOutputSampleBufferDelegate> { | ||
| @public | ||
| TPCircularBuffer audioSampleBuffer; | ||
| TPCircularBuffer audioSampleBuffer; ///< Shared circular buffer for both audio capture paths | ||
| dispatch_semaphore_t audioSemaphore; ///< Real-time safe semaphore for signaling audio sample availability | ||
| @private | ||
| // System-wide audio tap components (Core Audio) | ||
| AudioObjectID tapObjectID; ///< Core Audio tap object identifier for system audio capture | ||
| AudioObjectID aggregateDeviceID; ///< Aggregate device ID for system tap audio routing | ||
| AudioDeviceIOProcID ioProcID; ///< IOProc identifier for real-time audio processing | ||
| AVAudioIOProcData *_Nullable ioProcData; ///< Context data for IOProc callbacks and format conversion | ||
| } | ||
|
|
||
| @property (nonatomic, assign) AVCaptureSession *audioCaptureSession; | ||
| @property (nonatomic, assign) AVCaptureConnection *audioConnection; | ||
| @property (nonatomic, assign) NSCondition *samplesArrivedSignal; | ||
| // AVFoundation microphone capture properties | ||
| @property (nonatomic, assign, nullable) AVCaptureSession *audioCaptureSession; ///< AVFoundation capture session for microphone input | ||
| @property (nonatomic, assign, nullable) AVCaptureConnection *audioConnection; ///< Audio connection within the capture session | ||
| @property (nonatomic, assign) BOOL hostAudioEnabled; ///< Whether host audio playback should be enabled (affects tap mute behavior) | ||
|
|
||
| /** | ||
| * @brief Get all available microphone devices on the system. | ||
| * @return Array of AVCaptureDevice objects representing available microphones | ||
| */ | ||
| + (NSArray<AVCaptureDevice *> *)microphones; | ||
|
|
||
| /** | ||
| * @brief Get names of all available microphone devices. | ||
| * @return Array of NSString objects with microphone device names | ||
| */ | ||
| + (NSArray<NSString *> *)microphoneNames; | ||
|
|
||
| /** | ||
| * @brief Find a specific microphone device by name. | ||
| * @param name The name of the microphone to find (nullable - will return nil if name is nil) | ||
| * @return AVCaptureDevice object if found, nil otherwise | ||
| */ | ||
| + (nullable AVCaptureDevice *)findMicrophone:(nullable NSString *)name; | ||
|
|
||
| /** | ||
| * @brief Sets up microphone capture using AVFoundation framework. | ||
| * @param device The AVCaptureDevice to use for audio input (nullable - will return error if nil) | ||
| * @param sampleRate Target sample rate in Hz | ||
| * @param frameSize Number of frames per buffer | ||
| * @param channels Number of audio channels (1=mono, 2=stereo) | ||
| * @return 0 on success, -1 on failure | ||
| */ | ||
| - (int)setupMicrophone:(nullable AVCaptureDevice *)device sampleRate:(UInt32)sampleRate frameSize:(UInt32)frameSize channels:(UInt8)channels; | ||
|
|
||
| /** | ||
| * @brief Sets up system-wide audio tap for capturing all system audio. | ||
| * Requires macOS 14.0+ and appropriate permissions. | ||
| * @param sampleRate Target sample rate in Hz | ||
| * @param frameSize Number of frames per buffer | ||
| * @param channels Number of audio channels | ||
| * @return 0 on success, -1 on failure | ||
| */ | ||
| - (int)setupSystemTap:(UInt32)sampleRate frameSize:(UInt32)frameSize channels:(UInt8)channels; | ||
|
|
||
| + (NSArray *)microphoneNames; | ||
| + (AVCaptureDevice *)findMicrophone:(NSString *)name; | ||
| // Buffer management methods for testing and internal use | ||
| /** | ||
| * @brief Initializes the circular audio buffer for the specified number of channels. | ||
| * @param channels Number of audio channels to configure the buffer for | ||
| */ | ||
| - (void)initializeAudioBuffer:(UInt8)channels; | ||
|
|
||
| - (int)setupMicrophone:(AVCaptureDevice *)device sampleRate:(UInt32)sampleRate frameSize:(UInt32)frameSize channels:(UInt8)channels; | ||
| /** | ||
| * @brief Cleans up and deallocates the audio buffer resources. | ||
| */ | ||
| - (void)cleanupAudioBuffer; | ||
|
|
||
| /** | ||
| * @brief Cleans up system tap resources in a safe, ordered manner. | ||
| * @param tapDescription Optional tap description object to release (can be nil) | ||
| */ | ||
| - (void)cleanupSystemTapContext:(nullable id)tapDescription; | ||
|
|
||
| /** | ||
| * @brief Initializes the system tap context with specified audio parameters. | ||
| * @param sampleRate Target sample rate in Hz | ||
| * @param frameSize Number of frames per buffer | ||
| * @param channels Number of audio channels | ||
| * @return 0 on success, -1 on failure | ||
| */ | ||
| - (int)initializeSystemTapContext:(UInt32)sampleRate frameSize:(UInt32)frameSize channels:(UInt8)channels; | ||
|
|
||
| /** | ||
| * @brief Creates a Core Audio tap description for system audio capture. | ||
| * @param channels Number of audio channels to configure the tap for | ||
| * @return CATapDescription object on success, nil on failure | ||
| */ | ||
| - (nullable CATapDescription *)createSystemTapDescriptionForChannels:(UInt8)channels; | ||
|
|
||
| /** | ||
| * @brief Creates an aggregate device with the specified tap description and audio parameters. | ||
| * @param tapDescription Core Audio tap description for system audio capture | ||
| * @param sampleRate Target sample rate in Hz | ||
| * @param frameSize Number of frames per buffer | ||
| * @return OSStatus indicating success (noErr) or error code | ||
| */ | ||
| - (OSStatus)createAggregateDeviceWithTapDescription:(CATapDescription *)tapDescription sampleRate:(UInt32)sampleRate frameSize:(UInt32)frameSize; | ||
|
|
||
| @end | ||
|
|
||
| NS_ASSUME_NONNULL_END |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.