先說一下我的視頻錄制的思路:
1.首先創建一個VC專門用來渲染和顯示視頻的控制器
2.在viewWillAppear中做好如下初始化準備錄音步驟:
#import "RecordVideoViewController.h"
//音視頻
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
//文件
#import "FCFileManager.h"
//網絡
#import "DRDAPIPostCall.h"
//提示
#import "UIView+Toast.h"
#define RGBA_COLOR(R, G, B, A) [UIColor colorWithRed:((R) / 255.0f) green:((G) / 255.0f) blue:((B) / 255.0f) alpha:A]
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);
@interface RecordVideoViewController ()<AVCaptureFileOutputRecordingDelegate> //視頻文件輸出代理
@property (strong,nonatomic) AVCaptureSession *captureSession; //負責輸入和輸出設備之間的數據傳遞
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput; //負責從AVCaptureDevice獲得輸入數據
@property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput; //視頻輸出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機拍攝預覽圖層
@property (assign,nonatomic) BOOL enableRotation; //是否允許旋轉(注意在視頻錄制過程中禁止屏幕旋轉)
@property (assign,nonatomic) CGRect *lastBounds; //旋轉的前大小
@property (assign,nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier;//后臺任務標識
@property (weak, nonatomic) IBOutlet UIView *viewContainer; //本控制器
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標
@property (weak, nonatomic) IBOutlet UIProgressView *videoProgress; //加載進度條
@property (weak, nonatomic) IBOutlet UIButton *recordBtn; //錄屏按鈕
@property (weak, nonatomic) IBOutlet UILabel *recoderTipsLabel; //錄屏時間顯示標簽
@property (strong,nonatomic) NSTimer *mTimerForRecord; //錄屏定時器
@end
@implementation RecordVideoViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//文字顏色
[_recoderTipsLabel setTextColor:RGBA_COLOR(32, 192, 227, 1)];
}
- (IBAction)backButtonAction:(id)sender {
//停止錄視頻
[self.captureMovieFileOutput stopRecording];//停止錄制
//關閉監聽
[self removeNotification];
//關閉定時器
[_mTimerForRecord invalidate];
_mTimerForRecord = nil;
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//初始化會話
_captureSession=[[AVCaptureSession alloc]init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) {//設置分辨率
_captureSession.sessionPreset=AVCaptureSessionPreset1920x1080;
}
//獲得輸入設備
AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
if (!captureDevice) {
NSLog(@"取得后置攝像頭時出現問題.");
return;
}
//添加一個音頻輸入設備
AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
NSError *error=nil;
//根據輸入設備初始化設備輸入對象,用于獲得輸入數據
_captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
if (error) {
NSLog(@"取得設備輸入對象時出錯,錯誤原因:%@",error.localizedDescription);
return;
}
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];
NSLog(@"前:%i",[_captureVideoPreviewLayer connection].videoOrientation);
//新增加
[_captureVideoPreviewLayer connection].videoOrientation = (AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation];
NSLog(@"后:%i",[_captureVideoPreviewLayer connection].videoOrientation);
CALayer *layer=self.viewContainer.layer;
layer.masksToBounds=YES;
_captureVideoPreviewLayer.frame=layer.bounds;
NSLog(@"%@",NSStringFromCGRect(layer.bounds));
_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
//將視頻預覽層添加到界面中
//[layer addSublayer:_captureVideoPreviewLayer];
[layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
_enableRotation=NO;
[self addNotificationToCaptureDevice:captureDevice];
[self addGenstureRecognizer];
}
#pragma mark - 計時器
- (void)startTimeLabel
{
_recoderTipsLabel.text = @"00:00";
_mTimerForRecord = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(toSetRecFlags) userInfo:nil repeats:YES]; //定時改變錄像標
[[NSRunLoop currentRunLoop] addTimer:_mTimerForRecord forMode:NSRunLoopCommonModes];
[_mTimerForRecord setFireDate:[NSDate distantPast]];
}
- (void)stopTimeLabel
{
[_mTimerForRecord setFireDate:[NSDate distantFuture]];
[_mTimerForRecord invalidate];
_mTimerForRecord = nil;
_recoderTipsLabel.text = @"00:00";
}
- (void)toSetRecFlags
{
NSInteger value = [[[_recoderTipsLabel.text componentsSeparatedByString:@":"] objectAtIndex:0] integerValue]*60 + [[[_recoderTipsLabel.text componentsSeparatedByString:@":"] objectAtIndex:1] integerValue];
value++;
NSString *minute;
NSString *second;
if (value/60 > 9)
{
minute = [NSString stringWithFormat:@"%d",value/60];
}
else
{
minute = [NSString stringWithFormat:@"0%d",value/60];
}
if (value%60 > 9)
{
second = [NSString stringWithFormat:@"%d",value%60];
}
else
{
second = [NSString stringWithFormat:@"0%d",value%60];
}
[_recoderTipsLabel setTextColor:RGBA_COLOR(32, 192, 227, 1)];//RGBA(32, 192, 227, 1)//RGBA(201, 201, 201, 1)
[_recoderTipsLabel setText:[NSString stringWithFormat:@"%@:%@",minute,second]];
}
#pragma mark 上傳視頻
- (IBAction)uploadVideoAction:(id)sender
{
_videoProgress.hidden = NO;
NSString *outputFielPath = [FCFileManager pathForTemporaryDirectoryWithPath:@"user_video.mp4"];
DRDAPIPostCall *apiPost = [[DRDAPIPostCall alloc] init];
[apiPost setApiCompletionHandler:^(id _Nonnull responseObject, NSError * _Nullable error) {
if (error) {
[self.view makeToast:[error description] duration:2.0 position:CSToastPositionCenter];
}
}];
[apiPost setApiRequestConstructingBodyBlock:^(id<DRDMultipartFormData> _Nonnull formData) {
NSData *videoData = [NSData dataWithContentsOfFile:outputFielPath];
[formData appendPartWithFileData:videoData
name:@"user_video"
fileName:@"user_video"
mimeType:@"video/mp4"];
}];
[apiPost setApiProgressBlock:^(NSProgress * _Nullable progress) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.videoProgress.progress = progress.fractionCompleted;
weakSelf.recoderTipsLabel.text = [NSString stringWithFormat:@"上傳進度: %.2f%%", progress.fractionCompleted * 100];
});
}];
[apiPost start];
}
#pragma mark 錄屏
-(BOOL)shouldAutorotate{
return NO;
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self.captureSession startRunning];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.captureSession stopRunning];
}
//屏幕旋轉時調整視頻預覽圖層的方向
//-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
// AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];
// captureConnection.videoOrientation=(AVCaptureVideoOrientation)toInterfaceOrientation;
//}
//旋轉后重新設置大小
//-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
// _captureVideoPreviewLayer.frame=self.viewContainer.bounds;
//}
-(void)dealloc{
[self removeNotification];
}
//切換攝像頭
- (IBAction)toggleCapture:(id)sender {
AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
AVCaptureDevicePosition currentPosition=[currentDevice position];
[self removeNotificationFromCaptureDevice:currentDevice];
AVCaptureDevice *toChangeDevice;
AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
toChangePosition=AVCaptureDevicePositionBack;
}
toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
[self addNotificationToCaptureDevice:toChangeDevice];
//獲得要調整的設備輸入對象
AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
//改變會話的配置前一定要先開啟配置,配置完成后提交配置改變
[self.captureSession beginConfiguration];
//移除原有輸入對象
[self.captureSession removeInput:self.captureDeviceInput];
[self.captureSession setSessionPreset:AVCaptureSessionPresetHigh];
//添加新的輸入對象
BOOL isAdded = [self.captureSession canAddInput:toChangeDeviceInput];
if (isAdded) {
[self.captureSession addInput:toChangeDeviceInput];
self.captureDeviceInput=toChangeDeviceInput;
}else{
[self.captureSession addInput:self.captureDeviceInput];
}
//提交會話配置
[self.captureSession commitConfiguration];
}
- (IBAction)startVideo:(id)sender {
//根據設備輸出獲得連接
AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
//根據連接取得設備輸出的數據
if (![self.captureMovieFileOutput isRecording]) {
self.enableRotation=NO;
//開始計時
[self startTimeLabel];
//切換UI圖標
[_recordBtn setImage:[UIImage imageNamed:@"RecoderBtn_Selected.png"] forState:UIControlStateNormal];
//如果支持多任務則則開始多任務
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
}
//預覽圖層和視頻方向保持一致
captureConnection.videoOrientation=[self.captureVideoPreviewLayer connection].videoOrientation;
NSLog(@"%i",[self.captureVideoPreviewLayer connection].videoOrientation);
NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:@"user_video.mp4"];
if ([FCFileManager existsItemAtPath:outputFielPath]) {
[FCFileManager removeItemAtPath:outputFielPath];
}
NSLog(@"save path is :%@",outputFielPath);
NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
[self.captureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
}
else{
[self.captureMovieFileOutput stopRecording];//停止錄制
[_recordBtn setImage:[UIImage imageNamed:@"RecoderBtn_Defalut.png"] forState:UIControlStateNormal];//切換UI圖標
[self stopTimeLabel];//結束計時
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - 視頻輸出代理
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
NSLog(@"開始錄制...");
}
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
NSLog(@"視頻錄制完成.");
//視頻錄入完成之后在后臺將視頻存儲到相簿
self.enableRotation=YES;
UIBackgroundTaskIdentifier lastBackgroundTaskIdentifier=self.backgroundTaskIdentifier;
self.backgroundTaskIdentifier=UIBackgroundTaskInvalid;
ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
[assetsLibrary writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"保存視頻到相簿過程中發生錯誤,錯誤信息:%@",error.localizedDescription);
}
if (lastBackgroundTaskIdentifier!=UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:lastBackgroundTaskIdentifier];
}
NSLog(@"成功保存視頻到相簿.");
}];
}
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
[connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
}
#pragma mark - 通知
/**
* 給輸入設備添加通知
*/
-(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
//注意添加區域改變捕獲通知必須首先設置設備允許捕獲
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
captureDevice.subjectAreaChangeMonitoringEnabled=YES;
}];
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
//捕獲區域發生改變
[notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
-(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
* 移除所有通知
*/
-(void)removeNotification{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
-(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
//會話出錯
[notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}
/**
* 設備連接成功
*
* @param notification 通知對象
*/
-(void)deviceConnected:(NSNotification *)notification{
NSLog(@"設備已連接...");
}
/**
* 設備連接斷開
*
* @param notification 通知對象
*/
-(void)deviceDisconnected:(NSNotification *)notification{
NSLog(@"設備已斷開.");
}
/**
* 捕獲區域改變
*
* @param notification 通知對象
*/
-(void)areaChange:(NSNotification *)notification{
NSLog(@"捕獲區域改變...");
}
/**
* 會話出錯
*
* @param notification 通知對象
*/
-(void)sessionRuntimeError:(NSNotification *)notification{
NSLog(@"會話發生錯誤.");
}
#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;
}
/**
* 改變設備屬性的統一操作方法
*
* @param propertyChange 屬性改變操作
*/
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
NSError *error;
//注意改變設備屬性前一定要首先調用lockForConfiguration:調用完之后使用unlockForConfiguration方法解鎖
if ([captureDevice lockForConfiguration:&error]) {
propertyChange(captureDevice);
[captureDevice unlockForConfiguration];
}else{
NSLog(@"設置設備屬性過程發生錯誤,錯誤信息:%@",error.localizedDescription);
}
}
/**
* 設置閃光燈模式
*
* @param flashMode 閃光燈模式
*/
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFlashModeSupported:flashMode]) {
[captureDevice setFlashMode:flashMode];
}
}];
}
/**
* 設置聚焦模式
*
* @param focusMode 聚焦模式
*/
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:focusMode];
}
}];
}
/**
* 設置曝光模式
*
* @param exposureMode 曝光模式
*/
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:exposureMode];
}
}];
}
/**
* 設置聚焦點
*
* @param point 聚焦點
*/
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
}
if ([captureDevice isFocusPointOfInterestSupported]) {
[captureDevice setFocusPointOfInterest:point];
}
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
}
if ([captureDevice isExposurePointOfInterestSupported]) {
[captureDevice setExposurePointOfInterest:point];
}
}];
}
/**
* 添加點按手勢,點按時聚焦
*/
-(void)addGenstureRecognizer{
UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
[self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
CGPoint point= [tapGesture locationInView:self.viewContainer];
//將UI坐標轉化為攝像頭坐標
CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
[self setFocusCursorWithPoint:point];
[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}
/**
* 設置聚焦光標位置
*
* @param point 光標位置
*/
-(void)setFocusCursorWithPoint:(CGPoint)point{
self.focusCursor.center=point;
self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
self.focusCursor.alpha=1.0;
[UIView animateWithDuration:1.0 animations:^{
self.focusCursor.transform=CGAffineTransformIdentity;
} completion:^(BOOL finished) {
self.focusCursor.alpha=0;
}];
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
@end
遇到的主要問題:
我是ipad頁面,當我翻轉ipad的時候,該頁面沒有跟著轉動,導致當我拿反pad的時候頁面是個反的。
解決的辦法就是上面的渲染頁面我把它做了以下的操作:
[_captureVideoPreviewLayer connection].videoOrientation = (AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation];