TUICameraViewController.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. // Created by Tencent on 2023/06/09.
  2. // Copyright © 2023 Tencent. All rights reserved.
  3. #import "TUICameraViewController.h"
  4. #import <AVFoundation/AVFoundation.h>
  5. #import <AssetsLibrary/AssetsLibrary.h>
  6. #import <CoreMedia/CMMetadata.h>
  7. #import <Photos/Photos.h>
  8. #import "TUICameraView.h"
  9. #import "TUICaptureImagePreviewController.h"
  10. #import "TUICaptureVideoPreviewViewController.h"
  11. #import <TIMCommon/TIMCommonModel.h>
  12. #import <TIMCommon/TIMDefine.h>
  13. #import "TUICameraManager.h"
  14. #import "TUICaptureTimer.h"
  15. #import "TUIMotionManager.h"
  16. #import "TUIMovieManager.h"
  17. @interface TUICameraViewController () <AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate, TUICameraViewDelegate> {
  18. AVCaptureSession *_session;
  19. AVCaptureDeviceInput *_deviceInput;
  20. AVCaptureConnection *_videoConnection;
  21. AVCaptureConnection *_audioConnection;
  22. AVCaptureVideoDataOutput *_videoOutput;
  23. AVCaptureStillImageOutput *_imageOutput;
  24. BOOL _recording;
  25. }
  26. @property(nonatomic, strong) TUICameraView *cameraView;
  27. @property(nonatomic, strong) TUIMovieManager *movieManager;
  28. @property(nonatomic, strong) TUICameraManager *cameraManager;
  29. @property(nonatomic, strong) TUIMotionManager *motionManager;
  30. @property(nonatomic, strong) AVCaptureDevice *activeCamera;
  31. @property(nonatomic, strong) AVCaptureDevice *inactiveCamera;
  32. @property(nonatomic) BOOL isFirstShow;
  33. @property(nonatomic) BOOL lastPageBarHidden;
  34. @end
  35. @implementation TUICameraViewController
  36. - (instancetype)init {
  37. self = [super init];
  38. if (self) {
  39. _motionManager = [[TUIMotionManager alloc] init];
  40. _cameraManager = [[TUICameraManager alloc] init];
  41. _type = TUICameraMediaTypePhoto;
  42. _aspectRatio = TUICameraViewAspectRatio16x9;
  43. _videoMaximumDuration = 15.0;
  44. _videoMinimumDuration = 3.0;
  45. _isFirstShow = YES;
  46. }
  47. return self;
  48. }
  49. - (void)viewDidLoad {
  50. [super viewDidLoad];
  51. self.cameraView = [[TUICameraView alloc] initWithFrame:self.view.bounds];
  52. self.cameraView.type = self.type;
  53. self.cameraView.aspectRatio = self.aspectRatio;
  54. self.cameraView.delegate = self;
  55. self.cameraView.maxVideoCaptureTimeLimit = self.videoMaximumDuration;
  56. [self.view addSubview:self.cameraView];
  57. NSError *error;
  58. [self setupSession:&error];
  59. if (!error) {
  60. [self.cameraView.previewView setCaptureSessionsion:_session];
  61. [self startCaptureSession];
  62. } else {
  63. // NSAssert1(NO, @"Camera Initialize Failed : %@", error.localizedDescription);
  64. // [self showErrorStr:error.localizedDescription];
  65. }
  66. }
  67. - (void)dealloc {
  68. [self stopCaptureSession];
  69. }
  70. - (void)viewWillAppear:(BOOL)animated {
  71. [super viewWillAppear:animated];
  72. if (self.isFirstShow) {
  73. self.isFirstShow = NO;
  74. self.lastPageBarHidden = self.navigationController.navigationBarHidden;
  75. }
  76. self.navigationController.navigationBarHidden = YES;
  77. }
  78. - (void)willMoveToParentViewController:(UIViewController *)parent {
  79. [super willMoveToParentViewController:parent];
  80. if (!parent) {
  81. self.navigationController.navigationBarHidden = self.lastPageBarHidden;
  82. }
  83. }
  84. #pragma mark - - Input Device
  85. - (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {
  86. NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
  87. for (AVCaptureDevice *device in devices) {
  88. if (device.position == position) {
  89. return device;
  90. }
  91. }
  92. return nil;
  93. }
  94. - (AVCaptureDevice *)activeCamera {
  95. return _deviceInput.device;
  96. }
  97. - (AVCaptureDevice *)inactiveCamera {
  98. AVCaptureDevice *device = nil;
  99. if ([[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count] > 1) {
  100. if ([self activeCamera].position == AVCaptureDevicePositionBack) {
  101. device = [self cameraWithPosition:AVCaptureDevicePositionFront];
  102. } else {
  103. device = [self cameraWithPosition:AVCaptureDevicePositionBack];
  104. }
  105. }
  106. return device;
  107. }
  108. #pragma mark - - Configuration
  109. - (void)setupSession:(NSError **)error {
  110. _session = [[AVCaptureSession alloc] init];
  111. _session.sessionPreset = AVCaptureSessionPresetHigh;
  112. [self setupSessionInputs:error];
  113. [self setupSessionOutputs:error];
  114. }
  115. - (void)setupSessionInputs:(NSError **)error {
  116. AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  117. AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
  118. if (videoInput) {
  119. if ([_session canAddInput:videoInput]) {
  120. [_session addInput:videoInput];
  121. }
  122. }
  123. _deviceInput = videoInput;
  124. if (_type == TUICameraMediaTypeVideo) {
  125. AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
  126. AVCaptureDeviceInput *audioIn = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:error];
  127. if ([_session canAddInput:audioIn]) {
  128. [_session addInput:audioIn];
  129. }
  130. }
  131. }
  132. - (void)setupSessionOutputs:(NSError **)error {
  133. dispatch_queue_t captureQueue = dispatch_queue_create("com.tui.captureQueue", DISPATCH_QUEUE_SERIAL);
  134. AVCaptureVideoDataOutput *videoOut = [[AVCaptureVideoDataOutput alloc] init];
  135. [videoOut setAlwaysDiscardsLateVideoFrames:YES];
  136. [videoOut setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]}];
  137. [videoOut setSampleBufferDelegate:self queue:captureQueue];
  138. if ([_session canAddOutput:videoOut]) {
  139. [_session addOutput:videoOut];
  140. }
  141. _videoOutput = videoOut;
  142. _videoConnection = [videoOut connectionWithMediaType:AVMediaTypeVideo];
  143. if (_type == TUICameraMediaTypeVideo) {
  144. AVCaptureAudioDataOutput *audioOut = [[AVCaptureAudioDataOutput alloc] init];
  145. [audioOut setSampleBufferDelegate:self queue:captureQueue];
  146. if ([_session canAddOutput:audioOut]) {
  147. [_session addOutput:audioOut];
  148. }
  149. _audioConnection = [audioOut connectionWithMediaType:AVMediaTypeAudio];
  150. }
  151. AVCaptureStillImageOutput *imageOutput = [[AVCaptureStillImageOutput alloc] init];
  152. imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};
  153. if ([_session canAddOutput:imageOutput]) {
  154. [_session addOutput:imageOutput];
  155. }
  156. _imageOutput = imageOutput;
  157. }
  158. #pragma mark - - Session Control
  159. - (void)startCaptureSession {
  160. if (!_session.isRunning) {
  161. [_session startRunning];
  162. }
  163. }
  164. - (void)stopCaptureSession {
  165. if (_session.isRunning) {
  166. [_session stopRunning];
  167. }
  168. }
  169. #pragma mark - - Camera Operation
  170. - (void)zoomAction:(TUICameraView *)cameraView factor:(CGFloat)factor {
  171. NSError *error = [_cameraManager zoom:[self activeCamera] factor:factor];
  172. if (error) NSLog(@"%@", error);
  173. }
  174. - (void)focusAction:(TUICameraView *)cameraView point:(CGPoint)point handle:(void (^)(NSError *))handle {
  175. NSError *error = [_cameraManager focus:[self activeCamera] point:point];
  176. handle(error);
  177. NSLog(@"%f", [self activeCamera].activeFormat.videoMaxZoomFactor);
  178. }
  179. - (void)exposAction:(TUICameraView *)cameraView point:(CGPoint)point handle:(void (^)(NSError *))handle {
  180. NSError *error = [_cameraManager expose:[self activeCamera] point:point];
  181. handle(error);
  182. }
  183. - (void)autoFocusAndExposureAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle {
  184. NSError *error = [_cameraManager resetFocusAndExposure:[self activeCamera]];
  185. handle(error);
  186. }
  187. - (void)flashLightAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle {
  188. BOOL on = [_cameraManager flashMode:[self activeCamera]] == AVCaptureFlashModeOn;
  189. AVCaptureFlashMode mode = on ? AVCaptureFlashModeOff : AVCaptureFlashModeOn;
  190. NSError *error = [_cameraManager changeFlash:[self activeCamera] mode:mode];
  191. handle(error);
  192. }
  193. - (void)torchLightAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle {
  194. BOOL on = [_cameraManager torchMode:[self activeCamera]] == AVCaptureTorchModeOn;
  195. AVCaptureTorchMode mode = on ? AVCaptureTorchModeOff : AVCaptureTorchModeOn;
  196. NSError *error = [_cameraManager changeTorch:[self activeCamera] model:mode];
  197. handle(error);
  198. }
  199. - (void)swicthCameraAction:(TUICameraView *)cameraView handle:(void (^)(NSError *))handle {
  200. NSError *error;
  201. AVCaptureDevice *videoDevice = [self inactiveCamera];
  202. AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
  203. if (videoInput) {
  204. CATransition *animation = [CATransition animation];
  205. animation.type = @"oglFlip";
  206. animation.subtype = kCATransitionFromLeft;
  207. animation.duration = 0.5;
  208. [self.cameraView.previewView.layer addAnimation:animation forKey:@"flip"];
  209. AVCaptureFlashMode mode = [_cameraManager flashMode:[self activeCamera]];
  210. _deviceInput = [_cameraManager switchCamera:_session old:_deviceInput new:videoInput];
  211. _videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
  212. [_cameraManager changeFlash:[self activeCamera] mode:mode];
  213. }
  214. handle(error);
  215. }
  216. #pragma mark - - Taking Photo
  217. - (void)takePhotoAction:(TUICameraView *)cameraView {
  218. AVCaptureConnection *connection = [_imageOutput connectionWithMediaType:AVMediaTypeVideo];
  219. if (connection.isVideoOrientationSupported) {
  220. connection.videoOrientation = [self currentVideoOrientation];
  221. }
  222. [_imageOutput captureStillImageAsynchronouslyFromConnection:connection
  223. completionHandler:^(CMSampleBufferRef _Nullable imageDataSampleBuffer, NSError *_Nullable error) {
  224. if (error) {
  225. [self showErrorStr:error.localizedDescription];
  226. return;
  227. }
  228. NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
  229. UIImage *image = [[UIImage alloc] initWithData:imageData];
  230. TUICaptureImagePreviewController *vc = [[TUICaptureImagePreviewController alloc] initWithImage:image];
  231. [self.navigationController pushViewController:vc animated:YES];
  232. __weak __typeof(self) weakSelf = self;
  233. vc.commitBlock = ^{
  234. __strong __typeof(weakSelf) strongSelf = weakSelf;
  235. UIGraphicsBeginImageContext(CGSizeMake(image.size.width, image.size.height));
  236. [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
  237. UIImage *convertToUpImage = UIGraphicsGetImageFromCurrentImageContext();
  238. UIGraphicsEndImageContext();
  239. NSData *data = UIImageJPEGRepresentation(convertToUpImage, 0.75);
  240. [strongSelf.delegate cameraViewController:strongSelf didFinishPickingMediaWithImageData:data];
  241. [strongSelf popViewControllerAnimated:YES];
  242. };
  243. vc.cancelBlock = ^{
  244. __strong __typeof(weakSelf) strongSelf = weakSelf;
  245. [strongSelf.navigationController popViewControllerAnimated:YES];
  246. };
  247. }];
  248. }
  249. - (void)cancelAction:(TUICameraView *)cameraView {
  250. [self.delegate cameraViewControllerDidCancel:self];
  251. [self popViewControllerAnimated:YES];
  252. }
  253. - (void)pictureLibAction:(TUICameraView *)cameraView {
  254. @weakify(self);
  255. [self.delegate cameraViewControllerDidPictureLib:self
  256. finishCallback:^{
  257. @strongify(self);
  258. [self popViewControllerAnimated:NO];
  259. }];
  260. }
  261. #pragma mark - - Record
  262. - (void)startRecordVideoAction:(TUICameraView *)cameraView {
  263. /**
  264. * Recreate each time to avoid Crash caused by unreleased previous information
  265. */
  266. _movieManager = [[TUIMovieManager alloc] init];
  267. _recording = YES;
  268. _movieManager.currentDevice = [self activeCamera];
  269. _movieManager.currentOrientation = [self currentVideoOrientation];
  270. @weakify(self);
  271. [_movieManager start:^(NSError *_Nonnull error) {
  272. @strongify(self);
  273. @weakify(self);
  274. dispatch_async(dispatch_get_main_queue(), ^{
  275. @strongify(self);
  276. if (error) [self showErrorStr:error.localizedDescription];
  277. });
  278. }];
  279. }
  280. - (void)stopRecordVideoAction:(TUICameraView *)cameraView RecordDuration:(CGFloat)duration {
  281. _recording = NO;
  282. @weakify(self);
  283. [_movieManager stop:^(NSURL *_Nonnull url, NSError *_Nonnull error) {
  284. @strongify(self);
  285. @weakify(self);
  286. dispatch_async(dispatch_get_main_queue(), ^{
  287. @strongify(self);
  288. if (duration < self.videoMinimumDuration) {
  289. [self showErrorStr:TIMCommonLocalizableString(TUIKitMoreVideoCaptureDurationTip)];
  290. } else if (error) {
  291. [self showErrorStr:error.localizedDescription];
  292. } else {
  293. TUICaptureVideoPreviewViewController *videoPreviewController = [[TUICaptureVideoPreviewViewController alloc] initWithVideoURL:url];
  294. [self.navigationController pushViewController:videoPreviewController animated:YES];
  295. @weakify(self);
  296. videoPreviewController.commitBlock = ^{
  297. @strongify(self);
  298. [self.delegate cameraViewController:self didFinishPickingMediaWithVideoURL:url];
  299. [self popViewControllerAnimated:YES];
  300. };
  301. videoPreviewController.cancelBlock = ^{
  302. @strongify(self);
  303. [self.navigationController popViewControllerAnimated:YES];
  304. };
  305. }
  306. });
  307. }];
  308. }
  309. #pragma mark - - AVCaptureVideoDataOutputSampleBufferDelegate
  310. - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
  311. if (_recording) {
  312. [_movieManager writeData:connection video:_videoConnection audio:_audioConnection buffer:sampleBuffer];
  313. }
  314. }
  315. #pragma mark - - Others
  316. - (AVCaptureVideoOrientation)currentVideoOrientation {
  317. AVCaptureVideoOrientation orientation;
  318. switch (self.motionManager.deviceOrientation) {
  319. case UIDeviceOrientationPortrait:
  320. orientation = AVCaptureVideoOrientationPortrait;
  321. break;
  322. case UIDeviceOrientationLandscapeLeft:
  323. orientation = AVCaptureVideoOrientationLandscapeRight;
  324. break;
  325. case UIDeviceOrientationLandscapeRight:
  326. orientation = AVCaptureVideoOrientationLandscapeLeft;
  327. break;
  328. case UIDeviceOrientationPortraitUpsideDown:
  329. orientation = AVCaptureVideoOrientationPortraitUpsideDown;
  330. break;
  331. default:
  332. orientation = AVCaptureVideoOrientationPortrait;
  333. break;
  334. }
  335. return orientation;
  336. }
  337. - (void)didReceiveMemoryWarning {
  338. [super didReceiveMemoryWarning];
  339. // Dispose of any resources that can be recreated.
  340. }
  341. - (void)popViewControllerAnimated:(BOOL)animated {
  342. NSUInteger index = [self.navigationController.viewControllers indexOfObject:self];
  343. index--;
  344. UIViewController *lastVC = nil;
  345. if (index > 0 && index < self.navigationController.viewControllers.count) {
  346. lastVC = self.navigationController.viewControllers[index];
  347. }
  348. self.navigationController.navigationBarHidden = self.lastPageBarHidden;
  349. if (lastVC) {
  350. [self.navigationController popToViewController:lastVC animated:animated];
  351. } else {
  352. [self.navigationController popViewControllerAnimated:animated];
  353. }
  354. }
  355. - (void)showErrorStr:(NSString *)errStr {
  356. [TUITool makeToast:errStr duration:1 position:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0)];
  357. }
  358. @end