| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- // Created by Tencent on 2023/06/09.
- // Copyright © 2023 Tencent. All rights reserved.
- #import "TUIMovieManager.h"
- #import <TIMCommon/TIMDefine.h>
- @interface TUIMovieManager () {
- BOOL _readyToRecordVideo;
- BOOL _readyToRecordAudio;
- dispatch_queue_t _movieWritingQueue;
- NSURL *_movieURL;
- AVAssetWriter *_movieWriter;
- AVAssetWriterInput *_movieAudioInput;
- AVAssetWriterInput *_movieVideoInput;
- }
- @end
- @implementation TUIMovieManager
- - (instancetype)init {
- self = [super init];
- if (self) {
- _movieWritingQueue = dispatch_queue_create("com.tui.Movie.Writing.Queue", DISPATCH_QUEUE_SERIAL);
- _movieURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"TUICaptureTempMovie.mp4"]];
- _referenceOrientation = AVCaptureVideoOrientationPortrait;
- }
- return self;
- }
- - (void)start:(void (^)(NSError *error))handle {
- @weakify(self);
- dispatch_async(_movieWritingQueue, ^{
- @strongify(self);
- [self removeFile:self->_movieURL];
- NSError *error;
- if (!self->_movieWriter) {
- self->_movieWriter = [[AVAssetWriter alloc] initWithURL:self->_movieURL fileType:AVFileTypeMPEG4 error:&error];
- }
- handle(error);
- });
- }
- - (void)stop:(void (^)(NSURL *url, NSError *error))handle {
- @weakify(self);
- dispatch_async(_movieWritingQueue, ^{
- @strongify(self);
- self->_readyToRecordVideo = NO;
- self->_readyToRecordAudio = NO;
- if (self->_movieWriter && self->_movieWriter.status == AVAssetWriterStatusWriting) {
- @weakify(self);
- [self->_movieWriter finishWritingWithCompletionHandler:^() {
- @strongify(self);
- @weakify(self);
- dispatch_async(dispatch_get_main_queue(), ^{
- @strongify(self);
- if (self->_movieWriter.status == AVAssetWriterStatusCompleted) {
- handle(self->_movieURL, nil);
- } else {
- handle(nil, self->_movieWriter.error);
- }
- self->_movieWriter = nil;
- });
- }];
- } else {
- [self->_movieWriter cancelWriting];
- self->_movieWriter = nil;
- dispatch_async(dispatch_get_main_queue(), ^{
- handle(nil, [NSError errorWithDomain:@"com.tui.Movie.Writing" code:0 userInfo:@{NSLocalizedDescriptionKey : @"AVAssetWriter status error"}]);
- });
- }
- });
- }
- - (void)writeData:(AVCaptureConnection *)connection video:(AVCaptureConnection *)video audio:(AVCaptureConnection *)audio buffer:(CMSampleBufferRef)buffer {
- CFRetain(buffer);
- @weakify(self);
- dispatch_async(_movieWritingQueue, ^{
- @strongify(self);
- if (connection == video) {
- if (!self->_readyToRecordVideo) {
- self->_readyToRecordVideo = [self setupAssetWriterVideoInput:CMSampleBufferGetFormatDescription(buffer)] == nil;
- }
- if ([self inputsReadyToRecord]) {
- [self writeSampleBuffer:buffer ofType:AVMediaTypeVideo];
- }
- } else if (connection == audio) {
- if (!self->_readyToRecordAudio) {
- self->_readyToRecordAudio = [self setupAssetWriterAudioInput:CMSampleBufferGetFormatDescription(buffer)] == nil;
- }
- if ([self inputsReadyToRecord]) {
- [self writeSampleBuffer:buffer ofType:AVMediaTypeAudio];
- }
- }
- CFRelease(buffer);
- });
- }
- - (void)writeSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(NSString *)mediaType {
- if (_movieWriter.status == AVAssetWriterStatusUnknown) {
- if ([_movieWriter startWriting]) {
- [_movieWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
- } else {
- NSLog(@"%@", _movieWriter.error);
- }
- }
- if (_movieWriter.status == AVAssetWriterStatusWriting) {
- if (mediaType == AVMediaTypeVideo) {
- if (!_movieVideoInput.isReadyForMoreMediaData) {
- return;
- }
- if (![_movieVideoInput appendSampleBuffer:sampleBuffer]) {
- NSLog(@"%@", _movieWriter.error);
- }
- } else if (mediaType == AVMediaTypeAudio) {
- if (!_movieAudioInput.isReadyForMoreMediaData) {
- return;
- }
- if (![_movieAudioInput appendSampleBuffer:sampleBuffer]) {
- NSLog(@"%@", _movieWriter.error);
- }
- }
- }
- }
- - (BOOL)inputsReadyToRecord {
- return _readyToRecordVideo && _readyToRecordAudio;
- }
- - (NSError *)setupAssetWriterAudioInput:(CMFormatDescriptionRef)currentFormatDescription {
- size_t aclSize = 0;
- const AudioStreamBasicDescription *currentASBD = CMAudioFormatDescriptionGetStreamBasicDescription(currentFormatDescription);
- const AudioChannelLayout *channelLayout = CMAudioFormatDescriptionGetChannelLayout(currentFormatDescription, &aclSize);
- NSData *dataLayout = aclSize > 0 ? [NSData dataWithBytes:channelLayout length:aclSize] : [NSData data];
- NSDictionary *settings = @{
- AVFormatIDKey : [NSNumber numberWithInteger:kAudioFormatMPEG4AAC],
- AVSampleRateKey : [NSNumber numberWithFloat:currentASBD->mSampleRate],
- AVChannelLayoutKey : dataLayout,
- AVNumberOfChannelsKey : [NSNumber numberWithInteger:currentASBD->mChannelsPerFrame],
- AVEncoderBitRatePerChannelKey : [NSNumber numberWithInt:64000]
- };
- if ([_movieWriter canApplyOutputSettings:settings forMediaType:AVMediaTypeAudio]) {
- _movieAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:settings];
- _movieAudioInput.expectsMediaDataInRealTime = YES;
- if ([_movieWriter canAddInput:_movieAudioInput]) {
- [_movieWriter addInput:_movieAudioInput];
- } else {
- return _movieWriter.error;
- }
- } else {
- return _movieWriter.error;
- }
- return nil;
- }
- - (NSError *)setupAssetWriterVideoInput:(CMFormatDescriptionRef)currentFormatDescription {
- CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(currentFormatDescription);
- NSUInteger numPixels = dimensions.width * dimensions.height;
- CGFloat bitsPerPixel = numPixels < (640 * 480) ? 4.05 : 11.0;
- NSDictionary *compression =
- @{AVVideoAverageBitRateKey : [NSNumber numberWithInteger:numPixels * bitsPerPixel], AVVideoMaxKeyFrameIntervalKey : [NSNumber numberWithInteger:30]};
- NSDictionary *settings = @{
- AVVideoCodecKey : AVVideoCodecH264,
- AVVideoWidthKey : [NSNumber numberWithInteger:dimensions.width],
- AVVideoHeightKey : [NSNumber numberWithInteger:dimensions.height],
- AVVideoCompressionPropertiesKey : compression
- };
- if ([_movieWriter canApplyOutputSettings:settings forMediaType:AVMediaTypeVideo]) {
- _movieVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
- _movieVideoInput.expectsMediaDataInRealTime = YES;
- _movieVideoInput.transform = [self transformFromCurrentVideoOrientationToOrientation:self.referenceOrientation];
- if ([_movieWriter canAddInput:_movieVideoInput]) {
- [_movieWriter addInput:_movieVideoInput];
- } else {
- return _movieWriter.error;
- }
- } else {
- return _movieWriter.error;
- }
- return nil;
- }
- - (CGAffineTransform)transformFromCurrentVideoOrientationToOrientation:(AVCaptureVideoOrientation)orientation {
- CGFloat orientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:orientation];
- CGFloat videoOrientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:self.currentOrientation];
- CGFloat angleOffset;
- if (self.currentDevice.position == AVCaptureDevicePositionBack) {
- angleOffset = videoOrientationAngleOffset - orientationAngleOffset + M_PI_2;
- } else {
- angleOffset = orientationAngleOffset - videoOrientationAngleOffset + M_PI_2;
- }
- CGAffineTransform transform = CGAffineTransformMakeRotation(angleOffset);
- return transform;
- }
- - (CGFloat)angleOffsetFromPortraitOrientationToOrientation:(AVCaptureVideoOrientation)orientation {
- CGFloat angle = 0.0;
- switch (orientation) {
- case AVCaptureVideoOrientationPortrait:
- angle = 0.0;
- break;
- case AVCaptureVideoOrientationPortraitUpsideDown:
- angle = M_PI;
- break;
- case AVCaptureVideoOrientationLandscapeRight:
- angle = -M_PI_2;
- break;
- case AVCaptureVideoOrientationLandscapeLeft:
- angle = M_PI_2;
- break;
- }
- return angle;
- }
- - (void)removeFile:(NSURL *)fileURL {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSString *filePath = fileURL.path;
- if ([fileManager fileExistsAtPath:filePath]) {
- NSError *error;
- BOOL success = [fileManager removeItemAtPath:filePath error:&error];
- if (!success) {
- NSAssert(NO, error.localizedDescription);
- NSLog(@"Failed to delete file:%@", error);
- } else {
- NSLog(@"Succeed to delete file");
- }
- }
- }
- @end
|