調用系統相機錄像,壓縮保存到相冊(附仿微信視頻錄製demo)

使用系統相機錄像,使用的AVFoundation框架。首先了解一下框架的使用。
一、錄製的相關類有:
1、AVCaptureSession
媒體(音、視頻)捕獲會話,負責把捕獲的音視頻數據輸出到輸出設備中。一個AVCaptureSession可以有多個輸入輸出。
2、AVCaptureDevice
輸入設備,包括麥克風、攝像頭,通過該對象可以設置物理設備的一些屬性(例如相機聚焦、白平衡等)。
3、AVCaptureDeviceInput
設備輸入數據管理對象,可以根據AVCaptureDevice創建對應AVCaptureDeviceInput對象,該對象將會被添加到AVCaptureSession中管理。
4、AVCaptureOutput
輸出數據管理對象,用於接收各類輸出數據,通常使用對應的子類AVCaptureAudioDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput,該對象將會被添加到AVCaptureSession中管理。注意:前面幾個對象的輸出數據都是NSData類型,而AVCaptureFileOutput代表數據以文件形式輸出,類似的,AVCcaptureFileOutput也不會直接創建使用,通常會使用其子類:AVCaptureAudioFileOutput、AVCaptureMovieFileOutput。當把一個輸入或者輸出添加到AVCaptureSession之後AVCaptureSession就會在所有相符的輸入、輸出設備之間建立連接(AVCaptionConnection)。
5、AVCaptureVideoPreviewLayer
相機拍攝預覽圖層,是CALayer的子類,使用該對象可以實時查看拍照或視頻錄製效果,創建該對象需要指定對應的AVCaptureSession對象。

二、使用AVFoundation拍照和錄製視頻的一般步驟如下:
1、創建AVCaptureSession對象。
2、使用AVCaptureDevice的靜態方法獲得需要使用的設備,例如拍照和錄像就需要獲得攝像頭設備,錄音就要獲得麥克風設備。
3、利用輸入設備AVCaptureDevice初始化AVCaptureDeviceInput對象。
4、初始化輸出數據管理對象,如果要拍照就初始化AVCaptureStillImageOutput對象;如果拍攝視頻就初始化AVCaptureMovieFileOutput對象。
5、將數據輸入對象AVCaptureDeviceInput、數據輸出對象AVCaptureOutput添加到媒體會話管理對象AVCaptureSession中。
6、創建視頻預覽圖層AVCaptureVideoPreviewLayer並指定媒體會話,添加圖層到顯示容器中,調用AVCaptureSession的startRuning方法開始捕獲。
7、將捕獲的音頻或視頻數據輸出到指定文件。

介紹完錄製視頻,要介紹一下播放器的使用。使用的也是AVFoundation框架中的類是AVPlayer
AVPlayer本身並不能顯示視頻,而且它也不像MPMoviePlayerController有一個view屬性。如果AVPlayer要顯示必須創建一個播放器層AVPlayerLayer用於展示,播放器層繼承於CALayer,有了AVPlayerLayer之添加到控制器視圖的layer中即可。要使用AVPlayer首先了解一下幾個常用的類:

1、AVAsset:主要用於獲取多媒體信息,是一個抽象類,不能直接使用。

2、AVURLAsset:AVAsset的子類,可以根據一個URL路徑創建一個包含媒體信息的AVURLAsset對象。

3、AVPlayerItem:一個媒體資源管理對象,管理者視頻的一些基本信息和狀態,一個AVPlayerItem對應着一個視頻資源。

簡單介紹AVFoundation框架之後,放上一個自己寫的小demo,類似於微信的視頻錄製。能保存到相冊。效果圖如下:
這裏寫圖片描述
這裏寫圖片描述

具體代碼如下:
ViewController.m中:

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import "UserDefine.h"

typedef NS_ENUM(NSInteger,VideoStatus){
    VideoStatusEnded = 0,
    VideoStatusStarted
};

@interface ViewController ()<AVCaptureFileOutputRecordingDelegate>

{   //拍攝視頻相關
    AVCaptureSession * _captureSession;/**< 是一個會話對象,是設備音頻/視頻整個錄製期間的管理者. */
    AVCaptureDevice *_videoDevice;/**< 視頻設備 */
    AVCaptureDevice *_audioDevice;/**< 音頻設備 */
    AVCaptureDeviceInput *_videoInput;/**< 視頻輸入 */
    AVCaptureDeviceInput *_audioInput;/**< 音頻輸入 */
    AVCaptureMovieFileOutput *_movieOutput;/**< 視頻輸出 */
    AVCaptureVideoPreviewLayer *_captureVideoPreviewLayer;/**< 預覽拍攝過程中的圖像 */

    //播放相關
    AVPlayer *_player;/**< 播放器對象 */
    AVPlayerItem *_playItem;/**< 一個媒體資源管理對象,管理者視頻的一些基本信息和狀態,一個AVPlayerItem對應着一個視頻資源 */
    AVPlayerLayer *_playerLayer;
    BOOL _isPlaying;

}

@property (strong, nonatomic) UIView *recordingView;/**<  */

@property (strong, nonatomic) UIButton *recordingButton;/**< 錄製按鈕 */

@property (strong, nonatomic) UILabel *timeLabel;/**< 倒計時時間 */

@property (strong, nonatomic) UIButton *playButton;/**< 播放按鈕 */

@property (nonatomic,assign) VideoStatus status;

@property (nonatomic,assign) BOOL canSave;

@property (nonatomic,strong) CADisplayLink *link;

@property (strong, nonatomic) NSURL *videoUrl;/**< 視頻URL */
@end

@implementation ViewController
static float CountdownTime = 15 * 60;//倒計時時間,時間爲15s,* 60 因爲CADisplayLink 1/60秒刷新一次
- (void)viewDidLoad {
    [super viewDidLoad];
    [self setUILayout];
    [self getAuthorization];
    //對視頻播放完進行監聽
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:)name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
}
-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    //移除通知
    [[NSNotificationCenter defaultCenter]removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
#pragma mark - ****************  界面佈局
-(void)setUILayout{
    self.view.backgroundColor = RGBColor(245, 245, 245);
    CGFloat jianGe = 20;
    CGFloat btnH = 30;
    CGFloat btnW = 120;
    CGFloat lblH = 40;
    CGFloat lblW = 120;
    CGFloat boFangH = 160;
    _recordingView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_W, SCREEN_H/2)];
    _recordingView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:_recordingView];

    _timeLabel = [[UILabel alloc]initWithFrame:CGRectMake(self.view.center.x - lblW/2, _recordingView.frame.size.height + _recordingView.frame.origin.y + lblH, lblW, lblH)];
    _timeLabel.hidden = NO;
    _timeLabel.text = [NSString stringWithFormat:@"倒計時:%d秒",(int)(CountdownTime/60)];
    _timeLabel.textAlignment = NSTextAlignmentCenter;
    _timeLabel.font = [UIFont systemFontOfSize:18.0];
    _timeLabel.textColor = RGBColor(217, 28, 26);
    [self.view addSubview:_timeLabel];

    _recordingButton = [UIButton buttonWithType:UIButtonTypeCustom];
    _recordingButton.frame = CGRectMake(self.view.center.x - btnW/2, _timeLabel.frame.size.height + _timeLabel.frame.origin.y + jianGe, btnW, btnH);
    [_recordingButton setTitle:@"開始錄製" forState:UIControlStateNormal];
    [_recordingButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    _recordingButton.backgroundColor = RGBColor(217, 28, 26);
    _recordingButton.titleLabel.font = [UIFont fontWithName:@"STHeitiSC-Light" size:14.0];
    _recordingButton.layer.cornerRadius = 3;
    [_recordingButton addTarget:self action:@selector(recordButton:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:_recordingButton];

    _playButton = [UIButton buttonWithType:UIButtonTypeCustom];
    _playButton.frame = CGRectMake(0, 0, boFangH, boFangH);
    _playButton.center = _recordingView.center;
    [_playButton setImage:[UIImage imageNamed:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
    _playButton.hidden = YES;
    [_playButton addTarget:self action:@selector(playButton:) forControlEvents:UIControlEventTouchUpInside];
    [_recordingView addSubview:_playButton];

}
#pragma mark - **************** Button 方法
/**
 *  點擊錄製
 *
 *  @param sender
 */
-(void)recordButton:(UIButton *)sender{
    if ([sender.titleLabel.text isEqualToString:@"開始錄製"]) {
        [self startAnimation];
    }else{
        [self saveVideo:_videoUrl];
    }

}
/**
 *  點擊播放
 *
 *  @param sender
 */
-(void)playButton:(UIButton *)sender{
    [_player play];
    _playButton.hidden = YES;
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
#pragma mark - **************** 視頻錄製相關
#pragma mark -- 獲取授權
-(void)getAuthorization{
    //此處獲取攝像頭授權
    switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {
        case AVAuthorizationStatusAuthorized://已授權可以使用
        {
            NSLog(@"授權成功!");
            [self setupAVCaptureInfo];
        }
            break;
        case AVAuthorizationStatusNotDetermined://未授權
        {
            //則再次請求授權
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                if (granted) {//授權成功
                    [self setupAVCaptureInfo];
                    return;
                }else{
                    //授權失敗
                    return;
                }

            }];
        }
            break;
        default:    //用戶拒絕授權/未授權
            break;
    }
}
#pragma mark - 設置相關信息
-(void)setupAVCaptureInfo{
    [self addSession];
    //開始配置視頻的會話對象
    [_captureSession beginConfiguration];
    [self addVideo];
    [self addAudio];
    [self addPreviewLayer];
    //提交配置
    [_captureSession commitConfiguration];
    //開啓會話----> 不等於開始錄製
    [_captureSession startRunning];
}
/**
 *  設置視頻的會話對象
 */
-(void)addSession{
    _captureSession = [[AVCaptureSession alloc]init];
    //設置視頻分辨率
    //注意,這個地方設置的模式/分辨率大小將影響後面的拍攝質量
    if ([_captureSession canSetSessionPreset:AVAssetExportPreset640x480]) {
        [_captureSession setSessionPreset:AVAssetExportPreset640x480];
    }
}
/**
 *  設置視頻設備
 */
-(void)addVideo{
    _videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];//AVCaptureDevicePositionBack -- 後攝像頭
    [self addVideoInput];
    [self addMovieOutput];
}
/**
 *  設置視頻輸入對象
 */
-(void)addVideoInput{
    NSError *videoError;
    //視頻輸入對象
    //根據輸入設備初始化輸入對象,用戶獲取輸入數據
    _videoInput = [[AVCaptureDeviceInput alloc]initWithDevice:_videoDevice error:&videoError];
    if (videoError) {
        NSLog(@"-------取得攝像頭設備時出錯---%@",[videoError localizedDescription]);
        return;
    }
    //將視頻輸入對象添加到會話(AVCaptureSession)中
    if ([_captureSession canAddInput:_videoInput]) {
        [_captureSession addInput:_videoInput];
    }
}
/**
 *  設置視頻輸出對象
 */
-(void)addMovieOutput{
    //拍攝視頻輸出對象
    //初始化輸出設備對象,用戶獲取輸出數據
    _movieOutput = [[AVCaptureMovieFileOutput alloc] init];

    if ([_captureSession canAddOutput:_movieOutput]) {
        [_captureSession addOutput:_movieOutput];
        //設置連接管理對象
        AVCaptureConnection *captureConnection = [_movieOutput connectionWithMediaType:AVMediaTypeVideo];
        //視頻穩定設置
        if ([captureConnection isVideoStabilizationSupported]) {
            captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
        }
        //視頻旋轉方向的設置
       captureConnection.videoScaleAndCropFactor = captureConnection.videoMaxScaleAndCropFactor;
    }
}
/**
 *  設置音頻設備
 */
-(void)addAudio{
    NSError *audioError;
    //添加一個音頻設備
    _audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    // 音頻輸入對象
    _audioInput = [[AVCaptureDeviceInput alloc]initWithDevice:_audioDevice error:&audioError];
    if (audioError) {
        NSLog(@"取得錄音設備時出錯 ------ %@",audioError);
        return;
    }
    //將音頻輸入對象添加到會話 (AVCaptureSession) 中
    if ([_captureSession canAddInput:_audioInput]) {
        [_captureSession addInput:_audioInput];
    }
}
/**
 *  設置預覽層
 */
-(void)addPreviewLayer{
    [self.view layoutIfNeeded];

    //通過會話(AVCaptureSession)創建預覽圖層
    _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:_captureSession];
    _captureVideoPreviewLayer.frame = self.view.layer.bounds;
    //如果預覽圖層和視頻方向不一致,可以修改這個
    _captureVideoPreviewLayer.connection.videoOrientation = [_movieOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation;
    //設置captureVideoPreviewLayer在父視圖中的位置
    _captureVideoPreviewLayer.position = CGPointMake(self.view.frame.size.width*0.5,self.recordingView.frame.size.height*0.5);

    //顯示在視圖表面的圖層
    CALayer *layer = self.recordingView.layer;
    layer.masksToBounds = YES;
    [self.view layoutIfNeeded];
    [layer addSublayer:_captureVideoPreviewLayer];
}
-(void)startAnimation{
    if (self.status == VideoStatusEnded) {
        self.status = VideoStatusStarted;
        [UIView animateWithDuration:0.5 animations:^{

        } completion:^(BOOL finished) {
            [self stopLink];
            [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        }];
    }
}
-(void)stopAnimation{
    if (self.status == VideoStatusStarted) {
        self.status = VideoStatusEnded;
        [self stopLink];
        [self stopRecord];
        _playButton.hidden = NO;

        [UIView animateWithDuration:0.5 animations:^{
            [_recordingButton setTitle:@"保存到相冊" forState:UIControlStateNormal];

        } completion:^(BOOL finished) {

        }];
    }
}

-(CADisplayLink *)link{
    if (!_link) {
        _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(refresh:)];
        [self startRecord];
    }
    return _link;
}
-(void)stopLink{
    _link.paused = YES;
    [_link invalidate];
    _link = nil;
}
-(void)refresh:(CADisplayLink *)link{
    if (CountdownTime <= 0) {
        CountdownTime = 15 * 60;
        [self recordComplete];
        [self stopAnimation];
        _timeLabel.hidden = YES;
        return;
    }
    CountdownTime -= 1;
    NSLog(@"%f",CountdownTime);
    _timeLabel.text = [NSString stringWithFormat:@"倒計時:%d秒",(int)(CountdownTime/60)];
}
/**
 *  獲取錄製視頻的地址
 *
 *  @return outPutFileURL   
 */
- (NSURL *)outPutFileURL
{
    return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"outPut.mov"]];
}

- (void)startRecord
{
    [_movieOutput startRecordingToOutputFileURL:[self outPutFileURL] recordingDelegate:self];
}
- (void)stopRecord
{
    // 取消視頻拍攝
    [_movieOutput stopRecording];
}

- (void)recordComplete
{
    self.canSave = YES;
}
//這個在完全退出小視頻時調用
- (void)quit
{
    [_captureSession stopRunning];
}

- (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(@"---- 錄製結束 ---%@-%@ ",outputFileURL,captureOutput.outputFileURL);
    if (outputFileURL.absoluteString.length == 0 && captureOutput.outputFileURL.absoluteString.length == 0 ) {
        return;
    }
    if (self.canSave) {
        _videoUrl = outputFileURL;
        self.canSave = NO;
        [self creatPlayView];
    }
}

#pragma mark - 獲取攝像頭-->前/後 
-(AVCaptureDevice *)deviceWithMediaType:(NSString *)mediaType preferringPosition:(AVCaptureDevicePosition)position{
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType];
    AVCaptureDevice *captureDevice = devices.firstObject;

    for (AVCaptureDevice *device in devices) {
        if (device.position == position) {
            captureDevice = device;
            break;
        }
    }
    return captureDevice;
}
#pragma mark - **************** 播放相關
-(void)creatPlayView{
    NSLog(@"%@",_videoUrl);
    [_captureVideoPreviewLayer removeFromSuperlayer];
    [self.view layoutIfNeeded];
    _playItem = [AVPlayerItem playerItemWithURL:self.videoUrl];
    _player = [AVPlayer playerWithPlayerItem:_playItem];
    _playerLayer =[AVPlayerLayer playerLayerWithPlayer:_player];
    _playerLayer.frame = _recordingView.frame;
    _playerLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//視頻填充模式
    _playerLayer.position = CGPointMake(self.view.frame.size.width*0.5,self.recordingView.frame.size.height*0.5);
    CALayer *layer = self.recordingView.layer;
    layer.masksToBounds = true;
    [self.view layoutIfNeeded];

    [layer addSublayer:_playerLayer];
    [self.recordingView bringSubviewToFront:_playButton];
}
#pragma mark - 視頻播放通知回調
-(void)playbackFinished:(NSNotification *)notification
{
    [_player seekToTime:CMTimeMake(0, 1)];
    _playButton.hidden = NO;
}
#pragma mark - **************** 壓縮保存
- (NSURL *)compressedURL
{
    return [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"compressed.mp4"]]];
}

- (CGFloat)fileSize:(NSURL *)path
{
    return [[NSData dataWithContentsOfURL:path] length]/1024.00 /1024.00;
}

// 壓縮視頻
-(void)saveVideoWithUrl:(NSURL *)url
{
    NSLog(@"開始壓縮,壓縮前大小 %f MB",[self fileSize:url]);

    AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
    if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {

        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPreset640x480];
        exportSession.outputURL = [self compressedURL];
        //優化網絡
        exportSession.shouldOptimizeForNetworkUse = true;
        //轉換後的格式
        exportSession.outputFileType = AVFileTypeMPEG4;
        //異步導出
        [exportSession exportAsynchronouslyWithCompletionHandler:^{
            // 如果導出的狀態爲完成
            if ([exportSession status] == AVAssetExportSessionStatusCompleted) {
                NSLog(@"壓縮完畢,壓縮後大小 %f MB",[self fileSize:[self compressedURL]]);
                [self saveVideo:[self compressedURL]];
            }else{
                NSLog(@"當前壓縮進度:%f",exportSession.progress);
            }

        }];
    }
}


- (void)saveVideo:(NSURL *)outputFileURL
{
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    [library writeVideoAtPathToSavedPhotosAlbum:outputFileURL
                                completionBlock:^(NSURL *assetURL, NSError *error) {
                                    if (error) {
                                        NSLog(@"保存視頻失敗:%@",error);
                                    } else {
                                        NSLog(@"保存視頻到相冊成功");
                                    }
                                }];
}
@end

附上demo下載地址:http://download.csdn.net/detail/qq_34195670/9589592
github下載地址:https://github.com/goingmyway1/RecordingVideoDemo

以上如有錯誤,請留言指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章