blob: 00143e909fd0aa02fe771f80d790e9677848799a [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
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020011#import "RTCFileVideoCapturer.h"
denicija0d4d57f2017-06-02 07:15:14 -070012
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020013#import "base/RTCLogging.h"
14#import "base/RTCVideoFrameBuffer.h"
15#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
denicija0d4d57f2017-06-02 07:15:14 -070016
Daniela012b56b2017-11-15 13:15:24 +010017NSString *const kRTCFileVideoCapturerErrorDomain = @"org.webrtc.RTCFileVideoCapturer";
18
19typedef NS_ENUM(NSInteger, RTCFileVideoCapturerErrorCode) {
20 RTCFileVideoCapturerErrorCode_CapturerRunning = 2000,
21 RTCFileVideoCapturerErrorCode_FileNotFound
22};
23
24typedef NS_ENUM(NSInteger, RTCFileVideoCapturerStatus) {
25 RTCFileVideoCapturerStatusNotInitialized,
26 RTCFileVideoCapturerStatusStarted,
27 RTCFileVideoCapturerStatusStopped
28};
29
denicija0d4d57f2017-06-02 07:15:14 -070030@implementation RTCFileVideoCapturer {
31 AVAssetReader *_reader;
32 AVAssetReaderTrackOutput *_outTrack;
Daniela012b56b2017-11-15 13:15:24 +010033 RTCFileVideoCapturerStatus _status;
denicija0d4d57f2017-06-02 07:15:14 -070034 CMTime _lastPresentationTime;
35 dispatch_queue_t _frameQueue;
Daniela012b56b2017-11-15 13:15:24 +010036 NSURL *_fileURL;
denicija0d4d57f2017-06-02 07:15:14 -070037}
38
Daniela012b56b2017-11-15 13:15:24 +010039- (void)startCapturingFromFileNamed:(NSString *)nameOfFile
40 onError:(RTCFileVideoCapturerErrorBlock)errorBlock {
41 if (_status == RTCFileVideoCapturerStatusStarted) {
42 NSError *error =
43 [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
44 code:RTCFileVideoCapturerErrorCode_CapturerRunning
45 userInfo:@{NSUnderlyingErrorKey : @"Capturer has been started."}];
46
47 errorBlock(error);
48 return;
49 } else {
50 _status = RTCFileVideoCapturerStatusStarted;
51 }
52
denicija0d4d57f2017-06-02 07:15:14 -070053 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
denicija0d4d57f2017-06-02 07:15:14 -070054 NSString *pathForFile = [self pathForFileName:nameOfFile];
55 if (!pathForFile) {
Daniela012b56b2017-11-15 13:15:24 +010056 NSString *errorString =
57 [NSString stringWithFormat:@"File %@ not found in bundle", nameOfFile];
58 NSError *error = [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
59 code:RTCFileVideoCapturerErrorCode_FileNotFound
60 userInfo:@{NSUnderlyingErrorKey : errorString}];
61 errorBlock(error);
denicija0d4d57f2017-06-02 07:15:14 -070062 return;
63 }
64
65 _lastPresentationTime = CMTimeMake(0, 0);
66
Daniela012b56b2017-11-15 13:15:24 +010067 _fileURL = [NSURL fileURLWithPath:pathForFile];
68 [self setupReaderOnError:errorBlock];
denicija0d4d57f2017-06-02 07:15:14 -070069 });
70}
71
Daniela012b56b2017-11-15 13:15:24 +010072- (void)setupReaderOnError:(RTCFileVideoCapturerErrorBlock)errorBlock {
73 AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_fileURL options:nil];
74
75 NSArray *allTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
76 NSError *error = nil;
77
78 _reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
79 if (error) {
80 errorBlock(error);
81 return;
82 }
83
84 NSDictionary *options = @{
85 (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
86 };
87 _outTrack =
88 [[AVAssetReaderTrackOutput alloc] initWithTrack:allTracks.firstObject outputSettings:options];
89 [_reader addOutput:_outTrack];
90
91 [_reader startReading];
92 RTCLog(@"File capturer started reading");
93 [self readNextBuffer];
94}
denicija0d4d57f2017-06-02 07:15:14 -070095- (void)stopCapture {
Daniela012b56b2017-11-15 13:15:24 +010096 _status = RTCFileVideoCapturerStatusStopped;
denicija0d4d57f2017-06-02 07:15:14 -070097 RTCLog(@"File capturer stopped.");
98}
99
100#pragma mark - Private
101
102- (nullable NSString *)pathForFileName:(NSString *)fileName {
103 NSArray *nameComponents = [fileName componentsSeparatedByString:@"."];
104 if (nameComponents.count != 2) {
105 return nil;
106 }
107
108 NSString *path =
109 [[NSBundle mainBundle] pathForResource:nameComponents[0] ofType:nameComponents[1]];
110 return path;
111}
112
113- (dispatch_queue_t)frameQueue {
114 if (!_frameQueue) {
115 _frameQueue = dispatch_queue_create("org.webrtc.filecapturer.video", DISPATCH_QUEUE_SERIAL);
116 dispatch_set_target_queue(_frameQueue,
117 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
118 }
119 return _frameQueue;
120}
121
122- (void)readNextBuffer {
Daniela012b56b2017-11-15 13:15:24 +0100123 if (_status == RTCFileVideoCapturerStatusStopped) {
denicija0d4d57f2017-06-02 07:15:14 -0700124 [_reader cancelReading];
125 _reader = nil;
126 return;
127 }
128
Daniela012b56b2017-11-15 13:15:24 +0100129 if (_reader.status == AVAssetReaderStatusCompleted) {
130 [_reader cancelReading];
131 _reader = nil;
132 [self setupReaderOnError:nil];
133 return;
134 }
135
denicija0d4d57f2017-06-02 07:15:14 -0700136 CMSampleBufferRef sampleBuffer = [_outTrack copyNextSampleBuffer];
137 if (!sampleBuffer) {
138 [self readNextBuffer];
139 return;
140 }
141 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(sampleBuffer) ||
142 !CMSampleBufferDataIsReady(sampleBuffer)) {
Peter Hanspersd9b64cd2018-01-12 16:16:18 +0100143 CFRelease(sampleBuffer);
denicija0d4d57f2017-06-02 07:15:14 -0700144 [self readNextBuffer];
145 return;
146 }
147
148 [self publishSampleBuffer:sampleBuffer];
149}
150
151- (void)publishSampleBuffer:(CMSampleBufferRef)sampleBuffer {
152 CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
153 Float64 presentationDifference =
154 CMTimeGetSeconds(CMTimeSubtract(presentationTime, _lastPresentationTime));
155 _lastPresentationTime = presentationTime;
156 int64_t presentationDifferenceRound = lroundf(presentationDifference * NSEC_PER_SEC);
157
158 __block dispatch_source_t timer = [self createStrictTimer];
159 // Strict timer that will fire |presentationDifferenceRound| ns from now and never again.
160 dispatch_source_set_timer(timer,
161 dispatch_time(DISPATCH_TIME_NOW, presentationDifferenceRound),
162 DISPATCH_TIME_FOREVER,
163 0);
164 dispatch_source_set_event_handler(timer, ^{
165 dispatch_source_cancel(timer);
166 timer = nil;
167
168 CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
169 if (!pixelBuffer) {
170 CFRelease(sampleBuffer);
171 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
172 [self readNextBuffer];
173 });
174 return;
175 }
176
Anders Carlssone5960ce2017-06-22 15:26:30 +0200177 RTCCVPixelBuffer *rtcPixelBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBuffer];
denicija0d4d57f2017-06-02 07:15:14 -0700178 NSTimeInterval timeStampSeconds = CACurrentMediaTime();
179 int64_t timeStampNs = lroundf(timeStampSeconds * NSEC_PER_SEC);
180 RTCVideoFrame *videoFrame =
Anders Carlssone5960ce2017-06-22 15:26:30 +0200181 [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuffer rotation:0 timeStampNs:timeStampNs];
denicija0d4d57f2017-06-02 07:15:14 -0700182 CFRelease(sampleBuffer);
183
184 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
185 [self readNextBuffer];
186 });
187
188 [self.delegate capturer:self didCaptureVideoFrame:videoFrame];
189 });
190 dispatch_activate(timer);
191}
192
193- (dispatch_source_t)createStrictTimer {
194 dispatch_source_t timer = dispatch_source_create(
195 DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, [self frameQueue]);
196 return timer;
197}
198
199- (void)dealloc {
200 [self stopCapture];
201}
202
203@end