1. 程式人生 > >iOS 視訊的錄製、合成以及播放

iOS 視訊的錄製、合成以及播放

根據專案要求,視訊可以暫停然後繼續錄製。選擇了視訊合成。後面有連結

匯入AVFoundation庫檔案用於支援:

AVCaptureSession 連結輸入輸出裝置
AVCaptureDeviceInput 從裝置獲取輸入裝置
AVCaptureMovieFileOutput 視訊輸出
AVCaptureVideoPreviewLayer 錄製預覽layer

AVMutableComposition 根據MediaType返回音視訊軌道
AVMutableVideoComposition 設施輸出內容屬性,大小,幀數。。。
AVAssetTrack 素材通道
AVURLAsset 獲取音視訊資訊
AVAssetExportSession 輸出視訊

匯入MediaPlayer庫用於支援:
MPMoviePlayerController 視訊播放控制元件

info.plist檔案需要新增下面內容獲取相應許可權
Privacy - Camera Usage Description (是否允許此App使用你的相機?)
Privacy - Microphone Usage Description (是否允許此App使用你的麥克風?)

視訊合成過程需要時間,這裡沒做處理,可以根據自己需要新增一個UIActivityIndicatorView

//
//  ViewController.m
//  VideoRecordText
//
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h> #import <MediaPlayer/MediaPlayer.h> #define WIDTH [UIScreen mainScreen].bounds.size.width #define HEIGHT [UIScreen mainScreen].bounds.size.height @interface ViewController ()<AVCaptureFileOutputRecordingDelegate> @property (strong,nonatomic
) AVCaptureSession *captureSession;//負責輸入和輸出設定之間的資料傳遞 @property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負責從AVCaptureDevice獲得輸入資料 @property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput;//視訊輸出流 @property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機拍攝預覽圖層 @property (nonatomic,strong)NSString * fileName;//資料夾名 @property (nonatomic,assign)int fileNum;//檔名 @property (nonatomic,strong)NSMutableArray * fileUrlArr;//檔案路徑陣列 @property (nonatomic,strong)MPMoviePlayerController * mediaV;//播放器 @property (nonatomic,strong)UIView * playBcgV;//播放頁面 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _fileUrlArr = [[NSMutableArray alloc]init]; _fileName = [NSString stringWithFormat:@"%d",(int)[[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970]]; _fileNum = 0; [self createPlayBcgV];//播放檢視 [self createUI]; // Do any additional setup after loading the view, typically from a nib. } -(void)createUI{ UIView * layerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, HEIGHT-140)]; layerView.backgroundColor = [UIColor blackColor]; //初始化會話 _captureSession=[[AVCaptureSession alloc]init]; [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { if (granted) { }else{ NSLog(@"未獲取攝像頭許可權"); } }]; //獲得輸入裝置(視訊) AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得後置攝像頭 if (!captureDevice) { NSLog(@"取得後置攝像頭時出現問題."); return; } NSError *error=nil; //根據輸入裝置初始化裝置輸入物件,用於獲得輸入資料 _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error]; if (error) { NSLog(@"取得視訊裝置輸入物件時出錯,錯誤原因:%@",error.localizedDescription); return; } //新增一個輸入裝置(音訊) AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]; AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error]; if (error) { NSLog(@"取得音訊裝置輸入物件時出錯,錯誤原因:%@",error.localizedDescription); return; } //初始化裝置輸出物件,用於獲得輸出資料 _captureMovieFileOutput=[[AVCaptureMovieFileOutput alloc]init]; //將裝置輸入新增到會話中 if ([_captureSession canAddInput:_captureDeviceInput]) { [_captureSession addInput:_captureDeviceInput]; [_captureSession addInput:audioCaptureDeviceInput]; AVCaptureConnection *captureConnection=[_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo]; if ([captureConnection isVideoStabilizationSupported ]) { captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto; } } //將裝置輸出新增到會話中 if ([_captureSession canAddOutput:_captureMovieFileOutput]) { [_captureSession addOutput:_captureMovieFileOutput]; } //建立視訊預覽層,用於實時展示攝像頭狀態 _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession]; _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; CALayer *layer= layerView.layer; layer.masksToBounds=YES; _captureVideoPreviewLayer.frame = CGRectMake(0, 0, WIDTH, HEIGHT); //將視訊預覽層新增到介面中 [layer addSublayer:_captureVideoPreviewLayer]; [self.captureSession startRunning]; [self.view addSubview:layerView]; [self createButton]; } #pragma mark --------------------清空按鈕------------------- -(void)clearBtnClick:(UIButton *)btn{ NSLog(@"清空"); UIButton * button = (UIButton *)[self.view viewWithTag:100]; if (button.selected) { return; } NSFileManager* fileManager=[NSFileManager defaultManager]; NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *createPath = [NSString stringWithFormat:@"%@/myVidio", pathDocuments]; // 判斷資料夾是否存在,如果存在,清空 if ([[NSFileManager defaultManager] fileExistsAtPath:createPath]) { [fileManager removeItemAtPath:createPath error:nil]; } } #pragma mark --------------------視訊錄製------------------- -(void)recordStartOrPause:(UIButton *)btn{ btn.selected = !btn.selected; if (btn.selected) {//點選開始方法 NSLog(@"錄製"); NSString * pathDocument = [self checkPath]; [_fileUrlArr addObject:pathDocument]; [_captureMovieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:pathDocument] recordingDelegate:self]; _fileNum ++; }else{//停止方法 NSLog(@"停止"); [_captureMovieFileOutput stopRecording]; } } -(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{ NSLog(@"錄製完成"); } #pragma mark --------------------錄製完成------------------- -(void)finishBtnClick:(UIButton *)btn{//點選進行視訊合成操作並跳轉到PlayViewController UIButton * button = (UIButton *)[self.view viewWithTag:100]; if (button.selected) { return; } NSLog(@"完成"); if (_fileUrlArr.count < 1) { return; } //合成後視訊出處路徑 NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *createPath = [NSString stringWithFormat:@"%@/myVidio/%@/%@.mp4", pathDocuments,_fileName,_fileName]; if (_fileUrlArr.count > 1) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init]; AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; CMTime totalDuration = kCMTimeZero; for (int i = 0; i < _fileUrlArr.count; i++) { AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:_fileUrlArr[i]]]; NSError *erroraudio = nil;//獲取AVAsset中的音訊 或者視訊 AVAssetTrack *assetAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject];//向通道內加入音訊或者視訊 // BOOL ba = [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:assetAudioTrack atTime:totalDuration error:&erroraudio]; // NSLog(@"erroraudio:%@%d",erroraudio,ba); NSError *errorVideo = nil; AVAssetTrack *assetVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo]firstObject]; // BOOL bl = [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:assetVideoTrack atTime:totalDuration error:&errorVideo]; // NSLog(@"errorVideo:%@%d",errorVideo,bl); totalDuration = CMTimeAdd(totalDuration, asset.duration); videoComposition.frameDuration = CMTimeMake(1, 30); //視訊輸出尺寸 videoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.height*(HEIGHT/(HEIGHT-140))); AVMutableVideoCompositionInstruction * avMutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; [avMutableVideoCompositionInstruction setTimeRange:CMTimeRangeMake(kCMTimeZero, [mixComposition duration])]; AVMutableVideoCompositionLayerInstruction * avMutableVideoCompositionLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:assetAudioTrack]; [avMutableVideoCompositionLayerInstruction setTransform:assetVideoTrack.preferredTransform atTime:kCMTimeZero]; avMutableVideoCompositionInstruction.layerInstructions = [NSArray arrayWithObject:avMutableVideoCompositionLayerInstruction]; videoComposition.instructions = [NSArray arrayWithObject:avMutableVideoCompositionInstruction]; } NSFileManager* fileManager=[NSFileManager defaultManager]; BOOL blHave=[[NSFileManager defaultManager] fileExistsAtPath:createPath]; if (blHave) { [fileManager removeItemAtPath:createPath error:nil]; } NSURL *mergeFileURL = [NSURL fileURLWithPath:createPath]; // NSLog(@"starvideorecordVC: 345 outpath = %@",outpath); AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetMediumQuality]; exporter.outputURL = mergeFileURL; exporter.videoComposition = videoComposition; exporter.outputFileType = AVFileTypeMPEG4; exporter.shouldOptimizeForNetworkUse = YES; [exporter exportAsynchronouslyWithCompletionHandler:^{ NSLog(@" exporter%@",exporter.error); if (exporter.status == AVAssetExportSessionStatusCompleted) { dispatch_async(dispatch_get_main_queue(), ^{ _playBcgV.hidden = NO; [self.view bringSubviewToFront:_playBcgV]; _mediaV.contentURL = mergeFileURL; [_mediaV prepareToPlay]; [_mediaV play]; }); } }]; }); }else{ _playBcgV.hidden = NO; [self.view bringSubviewToFront:_playBcgV]; _mediaV.contentURL = [NSURL fileURLWithPath:_fileUrlArr[0]]; [_mediaV prepareToPlay]; [_mediaV play]; } } #pragma mark - -----------------獲取攝像頭裝置--------------------- /** * 取得指定位置的攝像頭 * * @param position 攝像頭位置 * * @return 攝像頭裝置 */ -(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{ NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for (AVCaptureDevice *camera in cameras) { if ([camera position]==position) { return camera; } } return nil; } #pragma mark --------------------檢查檔案路徑------------------ -(NSString *)checkPath{ NSFileManager* fileManager=[NSFileManager defaultManager]; NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *createPath = [NSString stringWithFormat:@"%@/myVidio/%@", pathDocuments,_fileName]; // 判斷資料夾是否存在,如果不存在,則建立 if (![[NSFileManager defaultManager] fileExistsAtPath:createPath]) { [fileManager createDirectoryAtPath:createPath withIntermediateDirectories:YES attributes:nil error:nil]; } NSString *pathDocument = [NSString stringWithFormat:@"%@/%d.mp4",createPath,_fileNum]; BOOL blHave=[[NSFileManager defaultManager] fileExistsAtPath:pathDocument]; if (blHave) { [fileManager removeItemAtPath:pathDocument error:nil]; } return pathDocument; } #pragma mark ---------------------建立控制按鈕---------------------- -(void)createButton{ UIButton * recordBtn = [UIButton buttonWithType:UIButtonTypeCustom]; recordBtn.frame = CGRectMake(30, HEIGHT-100, WIDTH/3-40, 40); [recordBtn setTitle:@"開始" forState:UIControlStateNormal]; [recordBtn setTitle:@"停止" forState:UIControlStateSelected]; recordBtn.tag = 100; recordBtn.backgroundColor = [UIColor blackColor]; recordBtn.layer.cornerRadius = 20; recordBtn.clipsToBounds = YES; [recordBtn addTarget:self action:@selector(recordStartOrPause:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:recordBtn]; UIButton * finishBtn = [UIButton buttonWithType:UIButtonTypeCustom]; finishBtn.frame = CGRectMake(WIDTH/3+20, HEIGHT-100, WIDTH/3-40, 40); [finishBtn setTitle:@"完成" forState:UIControlStateNormal]; finishBtn.backgroundColor = [UIColor blackColor]; finishBtn.layer.cornerRadius = 20; finishBtn.clipsToBounds = YES; [finishBtn addTarget:self action:@selector(finishBtnClick:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:finishBtn]; UIButton * clearBtn = [UIButton buttonWithType:UIButtonTypeCustom]; clearBtn.frame = CGRectMake(WIDTH/3 * 2 + 10, HEIGHT-100, WIDTH/3-40, 40); [clearBtn setTitle:@"清空" forState:UIControlStateNormal]; clearBtn.backgroundColor = [UIColor blackColor]; clearBtn.layer.cornerRadius = 20; clearBtn.clipsToBounds = YES; [clearBtn addTarget:self action:@selector(clearBtnClick:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:clearBtn]; } #pragma mark ---------------------建立播放檢視---------------------- -(void)createPlayBcgV{ _playBcgV = [[UIView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, HEIGHT)]; _playBcgV.backgroundColor = [UIColor blackColor]; _mediaV = [[MPMoviePlayerController alloc]init]; _mediaV.view.frame = CGRectMake(0, 0, WIDTH, HEIGHT-120); _mediaV.view.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; [_playBcgV addSubview:_mediaV.view]; UIButton * hideBtn = [UIButton buttonWithType:UIButtonTypeCustom]; hideBtn.frame = CGRectMake(30, HEIGHT-100, WIDTH-60, 40); [hideBtn setTitle:@"隱藏" forState:UIControlStateNormal]; hideBtn.backgroundColor = [UIColor whiteColor]; [hideBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; hideBtn.layer.cornerRadius = 20; hideBtn.clipsToBounds = YES; [hideBtn addTarget:self action:@selector(hideBtnClick:) forControlEvents:UIControlEventTouchUpInside]; [_playBcgV addSubview:hideBtn]; _playBcgV.hidden = YES; [self.view addSubview:_playBcgV]; } #pragma mark ---------------------隱藏播放控制器--------------------- -(void)hideBtnClick:(UIButton *)btn{ NSLog(@"隱藏"); [_mediaV stop]; _playBcgV.hidden = YES; } @end