iOS 視訊的錄製、合成以及播放
阿新 • • 發佈:2019-02-17
根據專案要求,視訊可以暫停然後繼續錄製。選擇了視訊合成。後面有連結
匯入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