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.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. CGImagePropertyOrientation orientation = ((__bridge NSNumber*)CMGetAttachment(sampleBuffer, (__bridge CFStringRef)RPVideoSampleOrientationKey , NULL)).unsignedIntValue;
  71. CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1/scaleFactor, 1/scaleFactor);
  72. NSData *bufferData = [self jpegDataFromPixelBuffer:imageBuffer withScaling:scaleTransform];
  73. CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
  74. if (bufferData) {
  75. CFHTTPMessageRef httpResponse = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
  76. CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Content-Length", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", bufferData.length]);
  77. CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Width", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", width]);
  78. CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Height", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", height]);
  79. CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Orientation", (__bridge CFStringRef)[NSString stringWithFormat:@"%u", orientation]);
  80. CFHTTPMessageSetBody(httpResponse, (__bridge CFDataRef)bufferData);
  81. CFDataRef serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse);
  82. CFRelease(httpResponse);
  83. return CFBridgingRelease(serializedMessage);
  84. }
  85. return nil;
  86. }
  87. - (BOOL)sendData {
  88. if (!self.dataToSend) {
  89. NSLog(@"no data to send");
  90. return false;
  91. }
  92. NSUInteger bytesLeft = self.dataToSend.length - self.byteIndex;
  93. NSInteger length = bytesLeft > kBufferMaxLenght ? kBufferMaxLenght : bytesLeft;
  94. uint8_t buffer[length];
  95. [self.dataToSend getBytes:&buffer range:NSMakeRange(self.byteIndex, length)];
  96. length = [self.connection writeBufferToStream:buffer maxLength:length];
  97. if (length > 0) {
  98. self.byteIndex += length;
  99. bytesLeft -= length;
  100. if (bytesLeft == 0) {
  101. NSLog(@"video sample processed successfully");
  102. self.dataToSend = nil;
  103. self.byteIndex = 0;
  104. }
  105. }
  106. else {
  107. NSLog(@"writeBufferToStream failure");
  108. }
  109. return true;
  110. }
  111. - (NSData *)jpegDataFromPixelBuffer:(CVPixelBufferRef)pixelBuffer withScaling:(CGAffineTransform)scaleTransform {
  112. CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer];
  113. image = [image imageByApplyingTransform:scaleTransform];
  114. NSDictionary *options = @{(NSString *)kCGImageDestinationLossyCompressionQuality: [NSNumber numberWithFloat:1.0]};
  115. NSData *imageData = [self.imageContext JPEGRepresentationOfImage:image
  116. colorSpace:image.colorSpace
  117. options:options];
  118. return imageData;
  119. }
  120. @end