blob: 07cb2c6668624d56237fcc350109f3e3ec516a2f [file] [log] [blame]
denicija0d4d57f2017-06-02 07:15:14 -07001/**
2 * Copyright 2017 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Daniela5adcd192017-11-07 13:57:11 +010011#import "WebRTC/RTCFileVideoCapturer.h"
denicija0d4d57f2017-06-02 07:15:14 -070012
13#import "WebRTC/RTCLogging.h"
Anders Carlssone5960ce2017-06-22 15:26:30 +020014#import "WebRTC/RTCVideoFrameBuffer.h"
denicija0d4d57f2017-06-02 07:15:14 -070015
Daniela5adcd192017-11-07 13:57:11 +010016NSString *const kRTCFileVideoCapturerErrorDomain = @"org.webrtc.RTCFileVideoCapturer";
17
18typedef NS_ENUM(NSInteger, RTCFileVideoCapturerErrorCode) {
19 RTCFileVideoCapturerErrorCode_CapturerRunning = 2000,
20 RTCFileVideoCapturerErrorCode_FileNotFound
21};
22
23typedef NS_ENUM(NSInteger, RTCFileVideoCapturerStatus) {
24 RTCFileVideoCapturerStatusNotInitialized,
25 RTCFileVideoCapturerStatusStarted,
26 RTCFileVideoCapturerStatusStopped
27};
28
denicija0d4d57f2017-06-02 07:15:14 -070029@implementation RTCFileVideoCapturer {
30 AVAssetReader *_reader;
31 AVAssetReaderTrackOutput *_outTrack;
Daniela5adcd192017-11-07 13:57:11 +010032 RTCFileVideoCapturerStatus _status;
denicija0d4d57f2017-06-02 07:15:14 -070033 CMTime _lastPresentationTime;
34 dispatch_queue_t _frameQueue;
Daniela5adcd192017-11-07 13:57:11 +010035 NSURL *_fileURL;
denicija0d4d57f2017-06-02 07:15:14 -070036}
37
Daniela5adcd192017-11-07 13:57:11 +010038- (void)startCapturingFromFileNamed:(NSString *)nameOfFile
39 onError:(RTCFileVideoCapturerErrorBlock)errorBlock {
40 if (_status == RTCFileVideoCapturerStatusStarted) {
41 NSError *error =
42 [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
43 code:RTCFileVideoCapturerErrorCode_CapturerRunning
44 userInfo:@{NSUnderlyingErrorKey : @"Capturer has been started."}];
45
46 errorBlock(error);
47 return;
48 } else {
49 _status = RTCFileVideoCapturerStatusStarted;
50 }
51
denicija0d4d57f2017-06-02 07:15:14 -070052 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
denicija0d4d57f2017-06-02 07:15:14 -070053 NSString *pathForFile = [self pathForFileName:nameOfFile];
54 if (!pathForFile) {
Daniela5adcd192017-11-07 13:57:11 +010055 NSString *errorString =
56 [NSString stringWithFormat:@"File %@ not found in bundle", nameOfFile];
57 NSError *error = [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
58 code:RTCFileVideoCapturerErrorCode_FileNotFound
59 userInfo:@{NSUnderlyingErrorKey : errorString}];
60 errorBlock(error);
denicija0d4d57f2017-06-02 07:15:14 -070061 return;
62 }
63
64 _lastPresentationTime = CMTimeMake(0, 0);
65
Daniela5adcd192017-11-07 13:57:11 +010066 _fileURL = [NSURL fileURLWithPath:pathForFile];
67 [self setupReaderOnError:errorBlock];
denicija0d4d57f2017-06-02 07:15:14 -070068 });
69}
70
Daniela5adcd192017-11-07 13:57:11 +010071- (void)setupReaderOnError:(RTCFileVideoCapturerErrorBlock)errorBlock {
72 AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_fileURL options:nil];
73
74 NSArray *allTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
75 NSError *error = nil;
76
77 _reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
78 if (error) {
79 errorBlock(error);
80 return;
81 }
82
83 NSDictionary *options = @{
84 (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
85 };
86 _outTrack =
87 [[AVAssetReaderTrackOutput alloc] initWithTrack:allTracks.firstObject outputSettings:options];
88 [_reader addOutput:_outTrack];
89
90 [_reader startReading];
91 RTCLog(@"File capturer started reading");
92 [self readNextBuffer];
93}
denicija0d4d57f2017-06-02 07:15:14 -070094- (void)stopCapture {
Daniela5adcd192017-11-07 13:57:11 +010095 _status = RTCFileVideoCapturerStatusStopped;
denicija0d4d57f2017-06-02 07:15:14 -070096 RTCLog(@"File capturer stopped.");
97}
98
99#pragma mark - Private
100
101- (nullable NSString *)pathForFileName:(NSString *)fileName {
102 NSArray *nameComponents = [fileName componentsSeparatedByString:@"."];
103 if (nameComponents.count != 2) {
104 return nil;
105 }
106
107 NSString *path =
108 [[NSBundle mainBundle] pathForResource:nameComponents[0] ofType:nameComponents[1]];
109 return path;
110}
111
112- (dispatch_queue_t)frameQueue {
113 if (!_frameQueue) {
114 _frameQueue = dispatch_queue_create("org.webrtc.filecapturer.video", DISPATCH_QUEUE_SERIAL);
115 dispatch_set_target_queue(_frameQueue,
116 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
117 }
118 return _frameQueue;
119}
120
121- (void)readNextBuffer {
Daniela5adcd192017-11-07 13:57:11 +0100122 if (_status == RTCFileVideoCapturerStatusStopped) {
denicija0d4d57f2017-06-02 07:15:14 -0700123 [_reader cancelReading];
124 _reader = nil;
125 return;
126 }
127
Daniela5adcd192017-11-07 13:57:11 +0100128 if (_reader.status == AVAssetReaderStatusCompleted) {
129 [_reader cancelReading];
130 _reader = nil;
131 [self setupReaderOnError:nil];
132 return;
133 }
134
denicija0d4d57f2017-06-02 07:15:14 -0700135 CMSampleBufferRef sampleBuffer = [_outTrack copyNextSampleBuffer];
136 if (!sampleBuffer) {
137 [self readNextBuffer];
138 return;
139 }
140 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(sampleBuffer) ||
141 !CMSampleBufferDataIsReady(sampleBuffer)) {
142 [self readNextBuffer];
143 return;
144 }
145
146 [self publishSampleBuffer:sampleBuffer];
147}
148
149- (void)publishSampleBuffer:(CMSampleBufferRef)sampleBuffer {
150 CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
151 Float64 presentationDifference =
152 CMTimeGetSeconds(CMTimeSubtract(presentationTime, _lastPresentationTime));
153 _lastPresentationTime = presentationTime;
154 int64_t presentationDifferenceRound = lroundf(presentationDifference * NSEC_PER_SEC);
155
156 __block dispatch_source_t timer = [self createStrictTimer];
157 // Strict timer that will fire |presentationDifferenceRound| ns from now and never again.
158 dispatch_source_set_timer(timer,
159 dispatch_time(DISPATCH_TIME_NOW, presentationDifferenceRound),
160 DISPATCH_TIME_FOREVER,
161 0);
162 dispatch_source_set_event_handler(timer, ^{
163 dispatch_source_cancel(timer);
164 timer = nil;
165
166 CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
167 if (!pixelBuffer) {
168 CFRelease(sampleBuffer);
169 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
170 [self readNextBuffer];
171 });
172 return;
173 }
174
Anders Carlssone5960ce2017-06-22 15:26:30 +0200175 RTCCVPixelBuffer *rtcPixelBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBuffer];
denicija0d4d57f2017-06-02 07:15:14 -0700176 NSTimeInterval timeStampSeconds = CACurrentMediaTime();
177 int64_t timeStampNs = lroundf(timeStampSeconds * NSEC_PER_SEC);
178 RTCVideoFrame *videoFrame =
Anders Carlssone5960ce2017-06-22 15:26:30 +0200179 [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuffer rotation:0 timeStampNs:timeStampNs];
denicija0d4d57f2017-06-02 07:15:14 -0700180 CFRelease(sampleBuffer);
181
182 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
183 [self readNextBuffer];
184 });
185
186 [self.delegate capturer:self didCaptureVideoFrame:videoFrame];
187 });
188 dispatch_activate(timer);
189}
190
191- (dispatch_source_t)createStrictTimer {
192 dispatch_source_t timer = dispatch_source_create(
193 DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, [self frameQueue]);
194 return timer;
195}
196
197- (void)dealloc {
198 [self stopCapture];
199}
200
201@end