Retire legacy implementations and flatten managed layout
Build and test / test (macos-latest) (push) Canceled after 0s
Build and test / test (ubuntu-24.04) (push) Canceled after 0s
Build and test / test (windows-latest) (push) Canceled after 0s
Build and test / apple-client (push) Canceled after 0s

This commit is contained in:
2026-09-21 00:11:32 +02:00
parent dd811a0bb8
commit 08e6c5930a
422 changed files with 252 additions and 38242 deletions
@@ -0,0 +1,124 @@
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
#import <Foundation/Foundation.h>
#import <ScreenCaptureKit/ScreenCaptureKit.h>
#import <fcntl.h>
#import <stdatomic.h>
#import <sys/mman.h>
#import <sys/stat.h>
#import <unistd.h>
enum { VCRingHeader = 64, VCRingCapacity = 96000 };
@interface VCScreenCaptureBridge : NSObject <SCContentSharingPickerObserver, SCStreamOutput, SCStreamDelegate>
@property(nonatomic) SCStream *stream;
@property(nonatomic) AVAudioConverter *converter;
@property(nonatomic) AVAudioFormat *inputFormat;
@property(nonatomic) AVAudioFormat *outputFormat;
@property(nonatomic) NSString *ringPath;
@property(nonatomic) int ringFd;
@property(nonatomic) void *ringMap;
@end
@implementation VCScreenCaptureBridge
static SCContentSharingPicker *VCSharedPicker(void) {
Class pickerClass = NSClassFromString(@"SCContentSharingPicker");
return pickerClass ? [pickerClass performSelector:@selector(sharedPicker)] : nil;
}
- (instancetype)init {
if ((self = [super init])) {
_ringFd = -1;
_outputFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatInt16 sampleRate:48000 channels:2 interleaved:YES];
}
return self;
}
- (void)openRing {
if (_ringMap || !_ringPath) return;
[[NSFileManager defaultManager] createDirectoryAtPath:[_ringPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
_ringFd = open(_ringPath.fileSystemRepresentation, O_RDWR | O_CREAT, 0644);
size_t size = VCRingHeader + VCRingCapacity * sizeof(int16_t);
if (_ringFd < 0 || ftruncate(_ringFd, (off_t)size) != 0) return;
_ringMap = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, _ringFd, 0);
if (_ringMap == MAP_FAILED) _ringMap = NULL;
if (_ringMap && *(uint32_t *)_ringMap != 0x56434252) {
memset(_ringMap, 0, VCRingHeader); *(uint32_t *)((uint8_t *)_ringMap + 4) = 1; atomic_thread_fence(memory_order_seq_cst); *(uint32_t *)_ringMap = 0x56434252;
}
}
- (void)setRingActive:(BOOL)active {
[self openRing]; if (!_ringMap) return;
if (active) { *(uint32_t *)((uint8_t *)_ringMap + 8) = 2; *(uint32_t *)((uint8_t *)_ringMap + 12) = 48000; }
atomic_thread_fence(memory_order_seq_cst); *(uint32_t *)((uint8_t *)_ringMap + 16) = active ? 1 : 0;
}
- (void)push:(const int16_t *)samples count:(NSUInteger)count {
if (!_ringMap || count == 0 || count > VCRingCapacity) return;
uint8_t *base = _ringMap; uint64_t write = *(uint64_t *)(base + 24); atomic_thread_fence(memory_order_seq_cst); uint64_t read = *(uint64_t *)(base + 32);
if (VCRingCapacity - (write - read) < count) return;
int16_t *data = (int16_t *)(base + VCRingHeader); NSUInteger index = write % VCRingCapacity;
NSUInteger first = MIN(count, VCRingCapacity - index); memcpy(data + index, samples, first * sizeof(int16_t));
if (first < count) memcpy(data, samples + first, (count - first) * sizeof(int16_t));
atomic_thread_fence(memory_order_seq_cst); *(uint64_t *)(base + 24) = write + count;
}
- (void)present:(NSString *)path API_AVAILABLE(ios(27.0)) {
self.ringPath = path; [self openRing]; SCContentSharingPicker *picker = VCSharedPicker();
Class configurationClass = NSClassFromString(@"SCContentSharingPickerConfiguration");
SCContentSharingPickerConfiguration *configuration = [configurationClass new];
if (!picker || !configuration) return;
configuration.showsMicrophoneControl = NO; configuration.showsCameraControl = NO; picker.defaultConfiguration = configuration;
[picker removeObserver:self]; [picker addObserver:self]; picker.active = YES; [picker presentPickerUsingContentStyle:SCShareableContentStyleDisplay];
}
- (void)stop {
[self.stream stopCaptureWithCompletionHandler:^(__unused NSError *error) {}]; self.stream = nil;
SCContentSharingPicker *picker = VCSharedPicker(); [picker removeObserver:self]; picker.active = NO; [self setRingActive:NO];
}
- (void)contentSharingPicker:(SCContentSharingPicker *)picker didUpdateWithFilter:(SCContentFilter *)filter forStream:(SCStream *)stream API_AVAILABLE(ios(27.0)) {
[self stop]; Class configurationClass = NSClassFromString(@"SCStreamConfiguration"); Class streamClass = NSClassFromString(@"SCStream");
SCStreamConfiguration *configuration = [configurationClass new]; if (!configuration || !streamClass) return; configuration.capturesAudio = YES;
configuration.excludesCurrentProcessAudio = YES; configuration.sampleRate = 48000; configuration.channelCount = 2;
configuration.width = 2; configuration.height = 2;
self.stream = [[streamClass alloc] initWithFilter:filter configuration:configuration delegate:self]; NSError *error = nil;
if (![self.stream addStreamOutput:self type:SCStreamOutputTypeAudio sampleHandlerQueue:dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0) error:&error]) { self.stream = nil; return; }
[self.stream startCaptureWithCompletionHandler:^(NSError *captureError) { [self setRingActive:captureError == nil]; }];
}
- (void)contentSharingPicker:(SCContentSharingPicker *)picker didCancelForStream:(SCStream *)stream API_AVAILABLE(ios(27.0)) { [self stop]; }
- (void)contentSharingPickerStartDidFailWithError:(NSError *)error API_AVAILABLE(ios(27.0)) { [self stop]; }
- (void)stream:(SCStream *)stream didStopWithError:(NSError *)error { [self stop]; }
- (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
if (type != SCStreamOutputTypeAudio || !CMSampleBufferDataIsReady(sampleBuffer)) return;
CMAudioFormatDescriptionRef description = CMSampleBufferGetFormatDescription(sampleBuffer); const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(description);
if (!asbd) return; AVAudioFormat *format = [[AVAudioFormat alloc] initWithStreamDescription:asbd]; AVAudioFrameCount frames = (AVAudioFrameCount)CMSampleBufferGetNumSamples(sampleBuffer);
AVAudioPCMBuffer *input = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:frames]; input.frameLength = frames;
if (CMSampleBufferCopyPCMDataIntoAudioBufferList(sampleBuffer, 0, frames, input.mutableAudioBufferList) != noErr) return;
if (!self.converter || ![self.inputFormat isEqual:format]) { self.inputFormat = format; self.converter = [[AVAudioConverter alloc] initFromFormat:format toFormat:self.outputFormat]; }
AVAudioFrameCount capacity = (AVAudioFrameCount)ceil(frames * 48000.0 / format.sampleRate) + 64; AVAudioPCMBuffer *output = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.outputFormat frameCapacity:capacity];
__block BOOL supplied = NO; NSError *error = nil; AVAudioConverterOutputStatus status = [self.converter convertToBuffer:output error:&error withInputFromBlock:^AVAudioBuffer *(AVAudioPacketCount count, AVAudioConverterInputStatus *inputStatus) { if (supplied) { *inputStatus = AVAudioConverterInputStatus_NoDataNow; return nil; } supplied = YES; *inputStatus = AVAudioConverterInputStatus_HaveData; return input; }];
if (status == AVAudioConverterOutputStatus_Error || output.frameLength == 0 || !output.int16ChannelData) return;
[self push:output.int16ChannelData[0] count:output.frameLength * 2];
}
- (void)dealloc {
[self setRingActive:NO]; if (_ringMap) munmap(_ringMap, VCRingHeader + VCRingCapacity * sizeof(int16_t)); if (_ringFd >= 0) close(_ringFd);
}
@end
static VCScreenCaptureBridge *bridge;
int vc_ios_screen_capture_available(void) {
if (@available(iOS 27.0, *)) return VCSharedPicker().available ? 1 : 0;
return 0;
}
void vc_ios_screen_capture_present(const char *ring_path) {
if (@available(iOS 27.0, *)) { if (!bridge) bridge = [VCScreenCaptureBridge new]; [bridge present:[NSString stringWithUTF8String:ring_path]]; }
}
void vc_ios_screen_capture_stop(void) { [bridge stop]; }
@@ -0,0 +1,3 @@
int vc_ios_screen_capture_available(void) { return 0; }
void vc_ios_screen_capture_present(const char *ring_path) { (void)ring_path; }
void vc_ios_screen_capture_stop(void) {}