123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- // From https://github.com/react-native-webrtc/react-native-webrtc (MIT License)
- // SPDX-FileCopyrightText: 2023 React-Native-WebRTC authors
- // SPDX-License-Identifier: MIT
- #include <mach/mach_time.h>
- #import <ReplayKit/ReplayKit.h>
- #import <WebRTC/RTCCVPixelBuffer.h>
- #import <WebRTC/RTCVideoFrameBuffer.h>
- #import "ScreenCapturer.h"
- #import "SocketConnection.h"
- #import "NextcloudTalk-Swift.h"
- const NSUInteger kMaxReadLength = 10 * 1024;
- @interface Message : NSObject
- @property(nonatomic, assign, readonly) CVImageBufferRef imageBuffer;
- @property(nonatomic, copy, nullable) void (^didComplete)(BOOL succes, Message *message);
- - (NSInteger)appendBytes:(UInt8 *)buffer length:(NSUInteger)length;
- @end
- @interface Message ()
- @property(nonatomic, assign) CVImageBufferRef imageBuffer;
- @property(nonatomic, assign) int imageOrientation;
- @property(nonatomic, assign) CFHTTPMessageRef framedMessage;
- @end
- @implementation Message
- - (instancetype)init {
- self = [super init];
- if (self) {
- self.imageBuffer = NULL;
- }
- return self;
- }
- - (void)dealloc {
- CVPixelBufferRelease(_imageBuffer);
- }
- /** Returns the amount of missing bytes to complete the message, or -1 when not enough bytes were provided to compute
- * the message length */
- - (NSInteger)appendBytes:(UInt8 *)buffer length:(NSUInteger)length {
- if (!_framedMessage) {
- _framedMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false);
- }
- CFHTTPMessageAppendBytes(_framedMessage, buffer, length);
- if (!CFHTTPMessageIsHeaderComplete(_framedMessage)) {
- return -1;
- }
- NSInteger contentLength =
- [CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_framedMessage, (__bridge CFStringRef) @"Content-Length"))
- integerValue];
- NSInteger bodyLength = (NSInteger)[CFBridgingRelease(CFHTTPMessageCopyBody(_framedMessage)) length];
- NSInteger missingBytesCount = contentLength - bodyLength;
- if (missingBytesCount == 0) {
- BOOL success = [self unwrapMessage:self.framedMessage];
- self.didComplete(success, self);
- CFRelease(self.framedMessage);
- self.framedMessage = NULL;
- }
- return missingBytesCount;
- }
- // MARK: Private Methods
- - (CIContext *)imageContext {
- // Initializing a CIContext object is costly, so we use a singleton instead
- static CIContext *imageContext = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- imageContext = [[CIContext alloc] initWithOptions:nil];
- });
- return imageContext;
- }
- - (BOOL)unwrapMessage:(CFHTTPMessageRef)framedMessage {
- size_t width =
- [CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_framedMessage, (__bridge CFStringRef) @"Buffer-Width"))
- integerValue];
- size_t height =
- [CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_framedMessage, (__bridge CFStringRef) @"Buffer-Height"))
- integerValue];
- _imageOrientation = [CFBridgingRelease(
- CFHTTPMessageCopyHeaderFieldValue(_framedMessage, (__bridge CFStringRef) @"Buffer-Orientation")) intValue];
- NSData *messageData = CFBridgingRelease(CFHTTPMessageCopyBody(_framedMessage));
- // Copy the pixel buffer
- CVReturn status =
- CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, NULL, &_imageBuffer);
- if (status != kCVReturnSuccess) {
- NSLog(@"CVPixelBufferCreate failed");
- return false;
- }
- [self copyImageData:messageData toPixelBuffer:&_imageBuffer];
- return true;
- }
- - (void)copyImageData:(NSData *)data toPixelBuffer:(CVPixelBufferRef *)pixelBuffer {
- CVPixelBufferLockBaseAddress(*pixelBuffer, 0);
- CIImage *image = [CIImage imageWithData:data];
- [self.imageContext render:image toCVPixelBuffer:*pixelBuffer];
- CVPixelBufferUnlockBaseAddress(*pixelBuffer, 0);
- }
- @end
- // MARK: -
- @interface ScreenCapturer ()<NSStreamDelegate>
- @property(nonatomic, strong) SocketConnection *connection;
- @property(nonatomic, strong) Message *message;
- @end
- @implementation ScreenCapturer {
- mach_timebase_info_data_t _timebaseInfo;
- NSInteger _readLength;
- int64_t _startTimeStampNs;
- }
- - (instancetype)initWithDelegate:(__weak id<RTCVideoCapturerDelegate>)delegate {
- self = [super initWithDelegate:delegate];
- if (self) {
- mach_timebase_info(&_timebaseInfo);
- }
- return self;
- }
- - (void)setConnection:(SocketConnection *)connection {
- if (_connection != connection) {
- [_connection close];
- _connection = connection;
- }
- }
- - (void)startCaptureWithConnection:(SocketConnection *)connection {
- mach_timebase_info(&_timebaseInfo);
- _startTimeStampNs = -1;
- self.connection = connection;
- self.message = nil;
- [self.connection openWithStreamDelegate:self];
- }
- - (void)stopCapture {
- self.connection = nil;
- }
- // MARK: Private Methods
- - (void)readBytesFromStream:(NSInputStream *)stream {
- if (!stream.hasBytesAvailable) {
- return;
- }
- if (!self.message) {
- self.message = [[Message alloc] init];
- _readLength = kMaxReadLength;
- __weak __typeof__(self) weakSelf = self;
- self.message.didComplete = ^(BOOL success, Message *message) {
- if (success) {
- [weakSelf didCaptureVideoFrame:message.imageBuffer withOrientation:message.imageOrientation];
- }
- weakSelf.message = nil;
- };
- }
- uint8_t buffer[_readLength];
- NSInteger numberOfBytesRead = [stream read:buffer maxLength:_readLength];
- if (numberOfBytesRead < 0) {
- NSLog(@"error reading bytes from stream");
- return;
- }
- _readLength = [self.message appendBytes:buffer length:numberOfBytesRead];
- if (_readLength == -1 || _readLength > kMaxReadLength) {
- _readLength = kMaxReadLength;
- }
- }
- - (void)didCaptureVideoFrame:(CVPixelBufferRef)pixelBuffer withOrientation:(CGImagePropertyOrientation)orientation {
- int64_t currentTime = mach_absolute_time();
- int64_t currentTimeStampNs = currentTime * _timebaseInfo.numer / _timebaseInfo.denom;
- if (_startTimeStampNs < 0) {
- _startTimeStampNs = currentTimeStampNs;
- }
- RTCCVPixelBuffer *rtcPixelBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBuffer];
- int64_t frameTimeStampNs = currentTimeStampNs - _startTimeStampNs;
- RTCVideoRotation rotation;
- switch (orientation) {
- case kCGImagePropertyOrientationLeft:
- rotation = RTCVideoRotation_90;
- break;
- case kCGImagePropertyOrientationDown:
- rotation = RTCVideoRotation_180;
- break;
- case kCGImagePropertyOrientationRight:
- rotation = RTCVideoRotation_270;
- break;
- default:
- rotation = RTCVideoRotation_0;
- break;
- }
- RTCVideoFrame *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuffer
- rotation:rotation
- timeStampNs:frameTimeStampNs];
- [self.delegate capturer:self didCaptureVideoFrame:videoFrame];
- }
- @end
- @implementation ScreenCapturer (NSStreamDelegate)
- - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
- switch (eventCode) {
- case NSStreamEventOpenCompleted:
- NSLog(@"server stream open completed");
- break;
- case NSStreamEventHasBytesAvailable:
- [self readBytesFromStream:(NSInputStream *)aStream];
- break;
- case NSStreamEventEndEncountered:
- NSLog(@"server stream end encountered");
- [self stopCapture];
- [self.eventsDelegate capturerDidEnd:self];
- break;
- case NSStreamEventErrorOccurred:
- NSLog(@"server stream error encountered: %@", aStream.streamError.localizedDescription);
- break;
- default:
- break;
- }
- }
- @end
|