/** * Copyright 2017 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #import "RTCFileVideoCapturer.h" #import "base/RTCLogging.h" #import "base/RTCVideoFrameBuffer.h" #import "components/video_frame_buffer/RTCCVPixelBuffer.h" #include "rtc_base/system/gcd_helpers.h" NSString *const kRTCFileVideoCapturerErrorDomain = @"org.webrtc.RTC_OBJC_TYPE(RTCFileVideoCapturer)"; typedef NS_ENUM(NSInteger, RTCFileVideoCapturerErrorCode) { RTCFileVideoCapturerErrorCode_CapturerRunning = 2000, RTCFileVideoCapturerErrorCode_FileNotFound }; typedef NS_ENUM(NSInteger, RTCFileVideoCapturerStatus) { RTCFileVideoCapturerStatusNotInitialized, RTCFileVideoCapturerStatusStarted, RTCFileVideoCapturerStatusStopped }; @interface RTC_OBJC_TYPE (RTCFileVideoCapturer) () @property(nonatomic, assign) CMTime lastPresentationTime; @property(nonatomic, strong) NSURL *fileURL; @end @implementation RTC_OBJC_TYPE (RTCFileVideoCapturer) { AVAssetReader *_reader; AVAssetReaderTrackOutput *_outTrack; RTCFileVideoCapturerStatus _status; dispatch_queue_t _frameQueue; } @synthesize lastPresentationTime = _lastPresentationTime; @synthesize fileURL = _fileURL; - (void)startCapturingFromFileNamed:(NSString *)nameOfFile onError:(RTCFileVideoCapturerErrorBlock)errorBlock { if (_status == RTCFileVideoCapturerStatusStarted) { NSError *error = [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain code:RTCFileVideoCapturerErrorCode_CapturerRunning userInfo:@{NSUnderlyingErrorKey : @"Capturer has been started."}]; errorBlock(error); return; } else { _status = RTCFileVideoCapturerStatusStarted; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *pathForFile = [self pathForFileName:nameOfFile]; if (!pathForFile) { NSString *errorString = [NSString stringWithFormat:@"File %@ not found in bundle", nameOfFile]; NSError *error = [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain code:RTCFileVideoCapturerErrorCode_FileNotFound userInfo:@{NSUnderlyingErrorKey : errorString}]; errorBlock(error); return; } self.lastPresentationTime = CMTimeMake(0, 0); self.fileURL = [NSURL fileURLWithPath:pathForFile]; [self setupReaderOnError:errorBlock]; }); } - (void)setupReaderOnError:(RTCFileVideoCapturerErrorBlock)errorBlock { AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_fileURL options:nil]; NSArray *allTracks = [asset tracksWithMediaType:AVMediaTypeVideo]; NSError *error = nil; _reader = [[AVAssetReader alloc] initWithAsset:asset error:&error]; if (error) { errorBlock(error); return; } NSDictionary *options = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) }; _outTrack = [[AVAssetReaderTrackOutput alloc] initWithTrack:allTracks.firstObject outputSettings:options]; [_reader addOutput:_outTrack]; [_reader startReading]; RTCLog(@"File capturer started reading"); [self readNextBuffer]; } - (void)stopCapture { _status = RTCFileVideoCapturerStatusStopped; RTCLog(@"File capturer stopped."); } #pragma mark - Private - (nullable NSString *)pathForFileName:(NSString *)fileName { NSArray *nameComponents = [fileName componentsSeparatedByString:@"."]; if (nameComponents.count != 2) { return nil; } NSString *path = [[NSBundle mainBundle] pathForResource:nameComponents[0] ofType:nameComponents[1]]; return path; } - (dispatch_queue_t)frameQueue { if (!_frameQueue) { _frameQueue = RTCDispatchQueueCreateWithTarget( "org.webrtc.filecapturer.video", DISPATCH_QUEUE_SERIAL, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); } return _frameQueue; } - (void)readNextBuffer { if (_status == RTCFileVideoCapturerStatusStopped) { [_reader cancelReading]; _reader = nil; return; } if (_reader.status == AVAssetReaderStatusCompleted) { [_reader cancelReading]; _reader = nil; [self setupReaderOnError:nil]; return; } CMSampleBufferRef sampleBuffer = [_outTrack copyNextSampleBuffer]; if (!sampleBuffer) { [self readNextBuffer]; return; } if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(sampleBuffer) || !CMSampleBufferDataIsReady(sampleBuffer)) { CFRelease(sampleBuffer); [self readNextBuffer]; return; } [self publishSampleBuffer:sampleBuffer]; } - (void)publishSampleBuffer:(CMSampleBufferRef)sampleBuffer { CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); Float64 presentationDifference = CMTimeGetSeconds(CMTimeSubtract(presentationTime, _lastPresentationTime)); _lastPresentationTime = presentationTime; int64_t presentationDifferenceRound = lroundf(presentationDifference * NSEC_PER_SEC); __block dispatch_source_t timer = [self createStrictTimer]; // Strict timer that will fire `presentationDifferenceRound` ns from now and never again. dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, presentationDifferenceRound), DISPATCH_TIME_FOREVER, 0); dispatch_source_set_event_handler(timer, ^{ dispatch_source_cancel(timer); timer = nil; CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); if (!pixelBuffer) { CFRelease(sampleBuffer); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self readNextBuffer]; }); return; } RTC_OBJC_TYPE(RTCCVPixelBuffer) *rtcPixelBuffer = [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBuffer]; NSTimeInterval timeStampSeconds = CACurrentMediaTime(); int64_t timeStampNs = lroundf(timeStampSeconds * NSEC_PER_SEC); RTC_OBJC_TYPE(RTCVideoFrame) *videoFrame = [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:rtcPixelBuffer rotation:0 timeStampNs:timeStampNs]; CFRelease(sampleBuffer); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self readNextBuffer]; }); [self.delegate capturer:self didCaptureVideoFrame:videoFrame]; }); dispatch_activate(timer); } - (dispatch_source_t)createStrictTimer { dispatch_source_t timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, [self frameQueue]); return timer; } - (void)dealloc { [self stopCapture]; } @end