| /** |
| * 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 |