blob: 207a21d8c0d3f6915d228af4cc21d2bd80500a15 [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
Jiawei Ou4aeb35b2018-11-09 13:55:45 -080030@interface RTCFileVideoCapturer ()
31@property(nonatomic, assign) CMTime lastPresentationTime;
32@property(nonatomic, strong) NSURL *fileURL;
33@end
34
denicija0d4d57f2017-06-02 07:15:14 -070035@implementation RTCFileVideoCapturer {
36 AVAssetReader *_reader;
37 AVAssetReaderTrackOutput *_outTrack;
Daniela012b56b2017-11-15 13:15:24 +010038 RTCFileVideoCapturerStatus _status;
denicija0d4d57f2017-06-02 07:15:14 -070039 dispatch_queue_t _frameQueue;
40}
41
Jiawei Ou4aeb35b2018-11-09 13:55:45 -080042@synthesize lastPresentationTime = _lastPresentationTime;
43@synthesize fileURL = _fileURL;
44
Daniela012b56b2017-11-15 13:15:24 +010045- (void)startCapturingFromFileNamed:(NSString *)nameOfFile
46 onError:(RTCFileVideoCapturerErrorBlock)errorBlock {
47 if (_status == RTCFileVideoCapturerStatusStarted) {
48 NSError *error =
49 [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
50 code:RTCFileVideoCapturerErrorCode_CapturerRunning
51 userInfo:@{NSUnderlyingErrorKey : @"Capturer has been started."}];
52
53 errorBlock(error);
54 return;
55 } else {
56 _status = RTCFileVideoCapturerStatusStarted;
57 }
58
denicija0d4d57f2017-06-02 07:15:14 -070059 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
denicija0d4d57f2017-06-02 07:15:14 -070060 NSString *pathForFile = [self pathForFileName:nameOfFile];
61 if (!pathForFile) {
Daniela012b56b2017-11-15 13:15:24 +010062 NSString *errorString =
63 [NSString stringWithFormat:@"File %@ not found in bundle", nameOfFile];
64 NSError *error = [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
65 code:RTCFileVideoCapturerErrorCode_FileNotFound
66 userInfo:@{NSUnderlyingErrorKey : errorString}];
67 errorBlock(error);
denicija0d4d57f2017-06-02 07:15:14 -070068 return;
69 }
70
Jiawei Ou4aeb35b2018-11-09 13:55:45 -080071 self.lastPresentationTime = CMTimeMake(0, 0);
denicija0d4d57f2017-06-02 07:15:14 -070072
Jiawei Ou4aeb35b2018-11-09 13:55:45 -080073 self.fileURL = [NSURL fileURLWithPath:pathForFile];
Daniela012b56b2017-11-15 13:15:24 +010074 [self setupReaderOnError:errorBlock];
denicija0d4d57f2017-06-02 07:15:14 -070075 });
76}
77
Daniela012b56b2017-11-15 13:15:24 +010078- (void)setupReaderOnError:(RTCFileVideoCapturerErrorBlock)errorBlock {
79 AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_fileURL options:nil];
80
81 NSArray *allTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
82 NSError *error = nil;
83
84 _reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
85 if (error) {
86 errorBlock(error);
87 return;
88 }
89
90 NSDictionary *options = @{
91 (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
92 };
93 _outTrack =
94 [[AVAssetReaderTrackOutput alloc] initWithTrack:allTracks.firstObject outputSettings:options];
95 [_reader addOutput:_outTrack];
96
97 [_reader startReading];
98 RTCLog(@"File capturer started reading");
99 [self readNextBuffer];
100}
denicija0d4d57f2017-06-02 07:15:14 -0700101- (void)stopCapture {
Daniela012b56b2017-11-15 13:15:24 +0100102 _status = RTCFileVideoCapturerStatusStopped;
denicija0d4d57f2017-06-02 07:15:14 -0700103 RTCLog(@"File capturer stopped.");
104}
105
106#pragma mark - Private
107
108- (nullable NSString *)pathForFileName:(NSString *)fileName {
109 NSArray *nameComponents = [fileName componentsSeparatedByString:@"."];
110 if (nameComponents.count != 2) {
111 return nil;
112 }
113
114 NSString *path =
115 [[NSBundle mainBundle] pathForResource:nameComponents[0] ofType:nameComponents[1]];
116 return path;
117}
118
119- (dispatch_queue_t)frameQueue {
120 if (!_frameQueue) {
121 _frameQueue = dispatch_queue_create("org.webrtc.filecapturer.video", DISPATCH_QUEUE_SERIAL);
122 dispatch_set_target_queue(_frameQueue,
123 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
124 }
125 return _frameQueue;
126}
127
128- (void)readNextBuffer {
Daniela012b56b2017-11-15 13:15:24 +0100129 if (_status == RTCFileVideoCapturerStatusStopped) {
denicija0d4d57f2017-06-02 07:15:14 -0700130 [_reader cancelReading];
131 _reader = nil;
132 return;
133 }
134
Daniela012b56b2017-11-15 13:15:24 +0100135 if (_reader.status == AVAssetReaderStatusCompleted) {
136 [_reader cancelReading];
137 _reader = nil;
138 [self setupReaderOnError:nil];
139 return;
140 }
141
denicija0d4d57f2017-06-02 07:15:14 -0700142 CMSampleBufferRef sampleBuffer = [_outTrack copyNextSampleBuffer];
143 if (!sampleBuffer) {
144 [self readNextBuffer];
145 return;
146 }
147 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(sampleBuffer) ||
148 !CMSampleBufferDataIsReady(sampleBuffer)) {
Peter Hanspersd9b64cd2018-01-12 16:16:18 +0100149 CFRelease(sampleBuffer);
denicija0d4d57f2017-06-02 07:15:14 -0700150 [self readNextBuffer];
151 return;
152 }
153
154 [self publishSampleBuffer:sampleBuffer];
155}
156
157- (void)publishSampleBuffer:(CMSampleBufferRef)sampleBuffer {
158 CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
159 Float64 presentationDifference =
160 CMTimeGetSeconds(CMTimeSubtract(presentationTime, _lastPresentationTime));
161 _lastPresentationTime = presentationTime;
162 int64_t presentationDifferenceRound = lroundf(presentationDifference * NSEC_PER_SEC);
163
164 __block dispatch_source_t timer = [self createStrictTimer];
165 // Strict timer that will fire |presentationDifferenceRound| ns from now and never again.
166 dispatch_source_set_timer(timer,
167 dispatch_time(DISPATCH_TIME_NOW, presentationDifferenceRound),
168 DISPATCH_TIME_FOREVER,
169 0);
170 dispatch_source_set_event_handler(timer, ^{
171 dispatch_source_cancel(timer);
172 timer = nil;
173
174 CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
175 if (!pixelBuffer) {
176 CFRelease(sampleBuffer);
177 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
178 [self readNextBuffer];
179 });
180 return;
181 }
182
Anders Carlssone5960ce2017-06-22 15:26:30 +0200183 RTCCVPixelBuffer *rtcPixelBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBuffer];
denicija0d4d57f2017-06-02 07:15:14 -0700184 NSTimeInterval timeStampSeconds = CACurrentMediaTime();
185 int64_t timeStampNs = lroundf(timeStampSeconds * NSEC_PER_SEC);
186 RTCVideoFrame *videoFrame =
Anders Carlssone5960ce2017-06-22 15:26:30 +0200187 [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuffer rotation:0 timeStampNs:timeStampNs];
denicija0d4d57f2017-06-02 07:15:14 -0700188 CFRelease(sampleBuffer);
189
190 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
191 [self readNextBuffer];
192 });
193
194 [self.delegate capturer:self didCaptureVideoFrame:videoFrame];
195 });
196 dispatch_activate(timer);
197}
198
199- (dispatch_source_t)createStrictTimer {
200 dispatch_source_t timer = dispatch_source_create(
201 DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, [self frameQueue]);
202 return timer;
203}
204
205- (void)dealloc {
206 [self stopCapture];
207}
208
209@end