xref: /aosp_15_r20/external/webrtc/sdk/objc/components/capturer/RTCFileVideoCapturer.m (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1*d9f75844SAndroid Build Coastguard Worker/**
2*d9f75844SAndroid Build Coastguard Worker *  Copyright 2017 The WebRTC Project Authors. All rights reserved.
3*d9f75844SAndroid Build Coastguard Worker *
4*d9f75844SAndroid Build Coastguard Worker *  Use of this source code is governed by a BSD-style license
5*d9f75844SAndroid Build Coastguard Worker *  that can be found in the LICENSE file in the root of the source
6*d9f75844SAndroid Build Coastguard Worker *  tree. An additional intellectual property rights grant can be found
7*d9f75844SAndroid Build Coastguard Worker *  in the file PATENTS.  All contributing project authors may
8*d9f75844SAndroid Build Coastguard Worker *  be found in the AUTHORS file in the root of the source tree.
9*d9f75844SAndroid Build Coastguard Worker */
10*d9f75844SAndroid Build Coastguard Worker
11*d9f75844SAndroid Build Coastguard Worker#import "RTCFileVideoCapturer.h"
12*d9f75844SAndroid Build Coastguard Worker
13*d9f75844SAndroid Build Coastguard Worker#import "base/RTCLogging.h"
14*d9f75844SAndroid Build Coastguard Worker#import "base/RTCVideoFrameBuffer.h"
15*d9f75844SAndroid Build Coastguard Worker#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
16*d9f75844SAndroid Build Coastguard Worker#include "rtc_base/system/gcd_helpers.h"
17*d9f75844SAndroid Build Coastguard Worker
18*d9f75844SAndroid Build Coastguard WorkerNSString *const kRTCFileVideoCapturerErrorDomain =
19*d9f75844SAndroid Build Coastguard Worker    @"org.webrtc.RTC_OBJC_TYPE(RTCFileVideoCapturer)";
20*d9f75844SAndroid Build Coastguard Worker
21*d9f75844SAndroid Build Coastguard Workertypedef NS_ENUM(NSInteger, RTCFileVideoCapturerErrorCode) {
22*d9f75844SAndroid Build Coastguard Worker  RTCFileVideoCapturerErrorCode_CapturerRunning = 2000,
23*d9f75844SAndroid Build Coastguard Worker  RTCFileVideoCapturerErrorCode_FileNotFound
24*d9f75844SAndroid Build Coastguard Worker};
25*d9f75844SAndroid Build Coastguard Worker
26*d9f75844SAndroid Build Coastguard Workertypedef NS_ENUM(NSInteger, RTCFileVideoCapturerStatus) {
27*d9f75844SAndroid Build Coastguard Worker  RTCFileVideoCapturerStatusNotInitialized,
28*d9f75844SAndroid Build Coastguard Worker  RTCFileVideoCapturerStatusStarted,
29*d9f75844SAndroid Build Coastguard Worker  RTCFileVideoCapturerStatusStopped
30*d9f75844SAndroid Build Coastguard Worker};
31*d9f75844SAndroid Build Coastguard Worker
32*d9f75844SAndroid Build Coastguard Worker@interface RTC_OBJC_TYPE (RTCFileVideoCapturer)
33*d9f75844SAndroid Build Coastguard Worker() @property(nonatomic, assign) CMTime lastPresentationTime;
34*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) NSURL *fileURL;
35*d9f75844SAndroid Build Coastguard Worker@end
36*d9f75844SAndroid Build Coastguard Worker
37*d9f75844SAndroid Build Coastguard Worker@implementation RTC_OBJC_TYPE (RTCFileVideoCapturer) {
38*d9f75844SAndroid Build Coastguard Worker  AVAssetReader *_reader;
39*d9f75844SAndroid Build Coastguard Worker  AVAssetReaderTrackOutput *_outTrack;
40*d9f75844SAndroid Build Coastguard Worker  RTCFileVideoCapturerStatus _status;
41*d9f75844SAndroid Build Coastguard Worker  dispatch_queue_t _frameQueue;
42*d9f75844SAndroid Build Coastguard Worker}
43*d9f75844SAndroid Build Coastguard Worker
44*d9f75844SAndroid Build Coastguard Worker@synthesize lastPresentationTime = _lastPresentationTime;
45*d9f75844SAndroid Build Coastguard Worker@synthesize fileURL = _fileURL;
46*d9f75844SAndroid Build Coastguard Worker
47*d9f75844SAndroid Build Coastguard Worker- (void)startCapturingFromFileNamed:(NSString *)nameOfFile
48*d9f75844SAndroid Build Coastguard Worker                            onError:(RTCFileVideoCapturerErrorBlock)errorBlock {
49*d9f75844SAndroid Build Coastguard Worker  if (_status == RTCFileVideoCapturerStatusStarted) {
50*d9f75844SAndroid Build Coastguard Worker    NSError *error =
51*d9f75844SAndroid Build Coastguard Worker        [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
52*d9f75844SAndroid Build Coastguard Worker                            code:RTCFileVideoCapturerErrorCode_CapturerRunning
53*d9f75844SAndroid Build Coastguard Worker                        userInfo:@{NSUnderlyingErrorKey : @"Capturer has been started."}];
54*d9f75844SAndroid Build Coastguard Worker
55*d9f75844SAndroid Build Coastguard Worker    errorBlock(error);
56*d9f75844SAndroid Build Coastguard Worker    return;
57*d9f75844SAndroid Build Coastguard Worker  } else {
58*d9f75844SAndroid Build Coastguard Worker    _status = RTCFileVideoCapturerStatusStarted;
59*d9f75844SAndroid Build Coastguard Worker  }
60*d9f75844SAndroid Build Coastguard Worker
61*d9f75844SAndroid Build Coastguard Worker  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
62*d9f75844SAndroid Build Coastguard Worker    NSString *pathForFile = [self pathForFileName:nameOfFile];
63*d9f75844SAndroid Build Coastguard Worker    if (!pathForFile) {
64*d9f75844SAndroid Build Coastguard Worker      NSString *errorString =
65*d9f75844SAndroid Build Coastguard Worker          [NSString stringWithFormat:@"File %@ not found in bundle", nameOfFile];
66*d9f75844SAndroid Build Coastguard Worker      NSError *error = [NSError errorWithDomain:kRTCFileVideoCapturerErrorDomain
67*d9f75844SAndroid Build Coastguard Worker                                           code:RTCFileVideoCapturerErrorCode_FileNotFound
68*d9f75844SAndroid Build Coastguard Worker                                       userInfo:@{NSUnderlyingErrorKey : errorString}];
69*d9f75844SAndroid Build Coastguard Worker      errorBlock(error);
70*d9f75844SAndroid Build Coastguard Worker      return;
71*d9f75844SAndroid Build Coastguard Worker    }
72*d9f75844SAndroid Build Coastguard Worker
73*d9f75844SAndroid Build Coastguard Worker    self.lastPresentationTime = CMTimeMake(0, 0);
74*d9f75844SAndroid Build Coastguard Worker
75*d9f75844SAndroid Build Coastguard Worker    self.fileURL = [NSURL fileURLWithPath:pathForFile];
76*d9f75844SAndroid Build Coastguard Worker    [self setupReaderOnError:errorBlock];
77*d9f75844SAndroid Build Coastguard Worker  });
78*d9f75844SAndroid Build Coastguard Worker}
79*d9f75844SAndroid Build Coastguard Worker
80*d9f75844SAndroid Build Coastguard Worker- (void)setupReaderOnError:(RTCFileVideoCapturerErrorBlock)errorBlock {
81*d9f75844SAndroid Build Coastguard Worker  AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_fileURL options:nil];
82*d9f75844SAndroid Build Coastguard Worker
83*d9f75844SAndroid Build Coastguard Worker  NSArray *allTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
84*d9f75844SAndroid Build Coastguard Worker  NSError *error = nil;
85*d9f75844SAndroid Build Coastguard Worker
86*d9f75844SAndroid Build Coastguard Worker  _reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
87*d9f75844SAndroid Build Coastguard Worker  if (error) {
88*d9f75844SAndroid Build Coastguard Worker    errorBlock(error);
89*d9f75844SAndroid Build Coastguard Worker    return;
90*d9f75844SAndroid Build Coastguard Worker  }
91*d9f75844SAndroid Build Coastguard Worker
92*d9f75844SAndroid Build Coastguard Worker  NSDictionary *options = @{
93*d9f75844SAndroid Build Coastguard Worker    (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
94*d9f75844SAndroid Build Coastguard Worker  };
95*d9f75844SAndroid Build Coastguard Worker  _outTrack =
96*d9f75844SAndroid Build Coastguard Worker      [[AVAssetReaderTrackOutput alloc] initWithTrack:allTracks.firstObject outputSettings:options];
97*d9f75844SAndroid Build Coastguard Worker  [_reader addOutput:_outTrack];
98*d9f75844SAndroid Build Coastguard Worker
99*d9f75844SAndroid Build Coastguard Worker  [_reader startReading];
100*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"File capturer started reading");
101*d9f75844SAndroid Build Coastguard Worker  [self readNextBuffer];
102*d9f75844SAndroid Build Coastguard Worker}
103*d9f75844SAndroid Build Coastguard Worker- (void)stopCapture {
104*d9f75844SAndroid Build Coastguard Worker  _status = RTCFileVideoCapturerStatusStopped;
105*d9f75844SAndroid Build Coastguard Worker  RTCLog(@"File capturer stopped.");
106*d9f75844SAndroid Build Coastguard Worker}
107*d9f75844SAndroid Build Coastguard Worker
108*d9f75844SAndroid Build Coastguard Worker#pragma mark - Private
109*d9f75844SAndroid Build Coastguard Worker
110*d9f75844SAndroid Build Coastguard Worker- (nullable NSString *)pathForFileName:(NSString *)fileName {
111*d9f75844SAndroid Build Coastguard Worker  NSArray *nameComponents = [fileName componentsSeparatedByString:@"."];
112*d9f75844SAndroid Build Coastguard Worker  if (nameComponents.count != 2) {
113*d9f75844SAndroid Build Coastguard Worker    return nil;
114*d9f75844SAndroid Build Coastguard Worker  }
115*d9f75844SAndroid Build Coastguard Worker
116*d9f75844SAndroid Build Coastguard Worker  NSString *path =
117*d9f75844SAndroid Build Coastguard Worker      [[NSBundle mainBundle] pathForResource:nameComponents[0] ofType:nameComponents[1]];
118*d9f75844SAndroid Build Coastguard Worker  return path;
119*d9f75844SAndroid Build Coastguard Worker}
120*d9f75844SAndroid Build Coastguard Worker
121*d9f75844SAndroid Build Coastguard Worker- (dispatch_queue_t)frameQueue {
122*d9f75844SAndroid Build Coastguard Worker  if (!_frameQueue) {
123*d9f75844SAndroid Build Coastguard Worker    _frameQueue = RTCDispatchQueueCreateWithTarget(
124*d9f75844SAndroid Build Coastguard Worker        "org.webrtc.filecapturer.video",
125*d9f75844SAndroid Build Coastguard Worker        DISPATCH_QUEUE_SERIAL,
126*d9f75844SAndroid Build Coastguard Worker        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
127*d9f75844SAndroid Build Coastguard Worker  }
128*d9f75844SAndroid Build Coastguard Worker  return _frameQueue;
129*d9f75844SAndroid Build Coastguard Worker}
130*d9f75844SAndroid Build Coastguard Worker
131*d9f75844SAndroid Build Coastguard Worker- (void)readNextBuffer {
132*d9f75844SAndroid Build Coastguard Worker  if (_status == RTCFileVideoCapturerStatusStopped) {
133*d9f75844SAndroid Build Coastguard Worker    [_reader cancelReading];
134*d9f75844SAndroid Build Coastguard Worker    _reader = nil;
135*d9f75844SAndroid Build Coastguard Worker    return;
136*d9f75844SAndroid Build Coastguard Worker  }
137*d9f75844SAndroid Build Coastguard Worker
138*d9f75844SAndroid Build Coastguard Worker  if (_reader.status == AVAssetReaderStatusCompleted) {
139*d9f75844SAndroid Build Coastguard Worker    [_reader cancelReading];
140*d9f75844SAndroid Build Coastguard Worker    _reader = nil;
141*d9f75844SAndroid Build Coastguard Worker    [self setupReaderOnError:nil];
142*d9f75844SAndroid Build Coastguard Worker    return;
143*d9f75844SAndroid Build Coastguard Worker  }
144*d9f75844SAndroid Build Coastguard Worker
145*d9f75844SAndroid Build Coastguard Worker  CMSampleBufferRef sampleBuffer = [_outTrack copyNextSampleBuffer];
146*d9f75844SAndroid Build Coastguard Worker  if (!sampleBuffer) {
147*d9f75844SAndroid Build Coastguard Worker    [self readNextBuffer];
148*d9f75844SAndroid Build Coastguard Worker    return;
149*d9f75844SAndroid Build Coastguard Worker  }
150*d9f75844SAndroid Build Coastguard Worker  if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(sampleBuffer) ||
151*d9f75844SAndroid Build Coastguard Worker      !CMSampleBufferDataIsReady(sampleBuffer)) {
152*d9f75844SAndroid Build Coastguard Worker    CFRelease(sampleBuffer);
153*d9f75844SAndroid Build Coastguard Worker    [self readNextBuffer];
154*d9f75844SAndroid Build Coastguard Worker    return;
155*d9f75844SAndroid Build Coastguard Worker  }
156*d9f75844SAndroid Build Coastguard Worker
157*d9f75844SAndroid Build Coastguard Worker  [self publishSampleBuffer:sampleBuffer];
158*d9f75844SAndroid Build Coastguard Worker}
159*d9f75844SAndroid Build Coastguard Worker
160*d9f75844SAndroid Build Coastguard Worker- (void)publishSampleBuffer:(CMSampleBufferRef)sampleBuffer {
161*d9f75844SAndroid Build Coastguard Worker  CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
162*d9f75844SAndroid Build Coastguard Worker  Float64 presentationDifference =
163*d9f75844SAndroid Build Coastguard Worker      CMTimeGetSeconds(CMTimeSubtract(presentationTime, _lastPresentationTime));
164*d9f75844SAndroid Build Coastguard Worker  _lastPresentationTime = presentationTime;
165*d9f75844SAndroid Build Coastguard Worker  int64_t presentationDifferenceRound = lroundf(presentationDifference * NSEC_PER_SEC);
166*d9f75844SAndroid Build Coastguard Worker
167*d9f75844SAndroid Build Coastguard Worker  __block dispatch_source_t timer = [self createStrictTimer];
168*d9f75844SAndroid Build Coastguard Worker  // Strict timer that will fire `presentationDifferenceRound` ns from now and never again.
169*d9f75844SAndroid Build Coastguard Worker  dispatch_source_set_timer(timer,
170*d9f75844SAndroid Build Coastguard Worker                            dispatch_time(DISPATCH_TIME_NOW, presentationDifferenceRound),
171*d9f75844SAndroid Build Coastguard Worker                            DISPATCH_TIME_FOREVER,
172*d9f75844SAndroid Build Coastguard Worker                            0);
173*d9f75844SAndroid Build Coastguard Worker  dispatch_source_set_event_handler(timer, ^{
174*d9f75844SAndroid Build Coastguard Worker    dispatch_source_cancel(timer);
175*d9f75844SAndroid Build Coastguard Worker    timer = nil;
176*d9f75844SAndroid Build Coastguard Worker
177*d9f75844SAndroid Build Coastguard Worker    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
178*d9f75844SAndroid Build Coastguard Worker    if (!pixelBuffer) {
179*d9f75844SAndroid Build Coastguard Worker      CFRelease(sampleBuffer);
180*d9f75844SAndroid Build Coastguard Worker      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
181*d9f75844SAndroid Build Coastguard Worker        [self readNextBuffer];
182*d9f75844SAndroid Build Coastguard Worker      });
183*d9f75844SAndroid Build Coastguard Worker      return;
184*d9f75844SAndroid Build Coastguard Worker    }
185*d9f75844SAndroid Build Coastguard Worker
186*d9f75844SAndroid Build Coastguard Worker    RTC_OBJC_TYPE(RTCCVPixelBuffer) *rtcPixelBuffer =
187*d9f75844SAndroid Build Coastguard Worker        [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBuffer];
188*d9f75844SAndroid Build Coastguard Worker    NSTimeInterval timeStampSeconds = CACurrentMediaTime();
189*d9f75844SAndroid Build Coastguard Worker    int64_t timeStampNs = lroundf(timeStampSeconds * NSEC_PER_SEC);
190*d9f75844SAndroid Build Coastguard Worker    RTC_OBJC_TYPE(RTCVideoFrame) *videoFrame =
191*d9f75844SAndroid Build Coastguard Worker        [[RTC_OBJC_TYPE(RTCVideoFrame) alloc] initWithBuffer:rtcPixelBuffer
192*d9f75844SAndroid Build Coastguard Worker                                                    rotation:0
193*d9f75844SAndroid Build Coastguard Worker                                                 timeStampNs:timeStampNs];
194*d9f75844SAndroid Build Coastguard Worker    CFRelease(sampleBuffer);
195*d9f75844SAndroid Build Coastguard Worker
196*d9f75844SAndroid Build Coastguard Worker    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
197*d9f75844SAndroid Build Coastguard Worker      [self readNextBuffer];
198*d9f75844SAndroid Build Coastguard Worker    });
199*d9f75844SAndroid Build Coastguard Worker
200*d9f75844SAndroid Build Coastguard Worker    [self.delegate capturer:self didCaptureVideoFrame:videoFrame];
201*d9f75844SAndroid Build Coastguard Worker  });
202*d9f75844SAndroid Build Coastguard Worker  dispatch_activate(timer);
203*d9f75844SAndroid Build Coastguard Worker}
204*d9f75844SAndroid Build Coastguard Worker
205*d9f75844SAndroid Build Coastguard Worker- (dispatch_source_t)createStrictTimer {
206*d9f75844SAndroid Build Coastguard Worker  dispatch_source_t timer = dispatch_source_create(
207*d9f75844SAndroid Build Coastguard Worker      DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, [self frameQueue]);
208*d9f75844SAndroid Build Coastguard Worker  return timer;
209*d9f75844SAndroid Build Coastguard Worker}
210*d9f75844SAndroid Build Coastguard Worker
211*d9f75844SAndroid Build Coastguard Worker- (void)dealloc {
212*d9f75844SAndroid Build Coastguard Worker  [self stopCapture];
213*d9f75844SAndroid Build Coastguard Worker}
214*d9f75844SAndroid Build Coastguard Worker
215*d9f75844SAndroid Build Coastguard Worker@end
216