You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SampleUploader.m 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /*
  2. * Copyright @ 2021-present 8x8, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <MessageUI/MessageUI.h>
  17. #import <ReplayKit/ReplayKit.h>
  18. #import "SampleUploader.h"
  19. #import "SocketConnection.h"
  20. static const NSInteger kBufferMaxLenght = 10 * 1024;
  21. @interface SampleUploader ()
  22. @property (nonatomic, assign) BOOL isReady;
  23. @property (nonatomic, strong) dispatch_queue_t serialQueue;
  24. @property (nonatomic, strong) SocketConnection *connection;
  25. @property (nonatomic, strong) CIContext *imageContext;
  26. @property (nonatomic, strong) NSData *dataToSend;
  27. @property (nonatomic, assign) NSUInteger byteIndex;
  28. @end
  29. @implementation SampleUploader
  30. - (instancetype)initWithConnection:(SocketConnection *)connection {
  31. self = [super init];
  32. if (self) {
  33. self.serialQueue = dispatch_queue_create("org.jitsi.meet.broadcast.sampleUploader", DISPATCH_QUEUE_SERIAL);
  34. self.connection = connection;
  35. [self setupConnection];
  36. self.imageContext = [[CIContext alloc] initWithOptions:nil];
  37. self.isReady = false;
  38. }
  39. return self;
  40. }
  41. - (void)sendSample:(CMSampleBufferRef)sampleBuffer {
  42. self.isReady = false;
  43. self.dataToSend = [self prepareSample:sampleBuffer];
  44. self.byteIndex = 0;
  45. dispatch_async(self.serialQueue, ^{
  46. [self sendData];
  47. });
  48. }
  49. // MARK: Private Methods
  50. - (void)setupConnection {
  51. __weak __typeof(self) weakSelf = self;
  52. self.connection.didOpen = ^{
  53. weakSelf.isReady = true;
  54. };
  55. self.connection.streamHasSpaceAvailable = ^{
  56. dispatch_async(weakSelf.serialQueue, ^{
  57. weakSelf.isReady = ![weakSelf sendData];
  58. });
  59. };
  60. }
  61. /**
  62. This function downscales and converts to jpeg the provided sample buffer, then wraps the resulted image data into a CFHTTPMessageRef. Returns the serialized CFHTTPMessageRef.
  63. */
  64. - (NSData *)prepareSample:(CMSampleBufferRef)sampleBuffer {
  65. CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  66. CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
  67. CGFloat scaleFactor = 2;
  68. size_t width = CVPixelBufferGetWidth(imageBuffer)/scaleFactor;
  69. size_t height = CVPixelBufferGetHeight(imageBuffer)/scaleFactor;
  70. CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1/scaleFactor, 1/scaleFactor);
  71. NSData *bufferData = [self jpegDataFromPixelBuffer:imageBuffer withScaling:scaleTransform];
  72. CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
  73. if (bufferData) {
  74. CFHTTPMessageRef httpResponse = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
  75. CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Content-Length", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", bufferData.length]);
  76. CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Width", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", width]);
  77. CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Height", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", height]);
  78. CFHTTPMessageSetBody(httpResponse, (__bridge CFDataRef)bufferData);
  79. CFDataRef serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse);
  80. CFRelease(httpResponse);
  81. return CFBridgingRelease(serializedMessage);
  82. }
  83. return nil;
  84. }
  85. - (BOOL)sendData {
  86. if (!self.dataToSend) {
  87. NSLog(@"no data to send");
  88. return false;
  89. }
  90. NSUInteger bytesLeft = self.dataToSend.length - self.byteIndex;
  91. NSInteger length = bytesLeft > kBufferMaxLenght ? kBufferMaxLenght : bytesLeft;
  92. uint8_t buffer[length];
  93. [self.dataToSend getBytes:&buffer range:NSMakeRange(self.byteIndex, length)];
  94. length = [self.connection writeBufferToStream:buffer maxLength:length];
  95. if (length > 0) {
  96. self.byteIndex += length;
  97. bytesLeft -= length;
  98. if (bytesLeft == 0) {
  99. NSLog(@"video sample processed successfully");
  100. self.dataToSend = nil;
  101. self.byteIndex = 0;
  102. }
  103. }
  104. else {
  105. NSLog(@"writeBufferToStream failure");
  106. }
  107. return true;
  108. }
  109. - (NSData *)jpegDataFromPixelBuffer:(CVPixelBufferRef)pixelBuffer withScaling:(CGAffineTransform)scaleTransform {
  110. CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer];
  111. image = [image imageByApplyingTransform:scaleTransform];
  112. NSDictionary *options = @{(NSString *)kCGImageDestinationLossyCompressionQuality: [NSNumber numberWithFloat:1.0]};
  113. NSData *imageData = [self.imageContext JPEGRepresentationOfImage:image
  114. colorSpace:image.colorSpace
  115. options:options];
  116. return imageData;
  117. }
  118. @end