iOS三種錄制視頻方式詳細(xì)對比

先附上參考資料

http://www.itdecent.cn/p/16cb14f53933

https://developer.apple.com/library/content/samplecode/AVSimpleEditoriOS/Introduction/Intro.html

https://github.com/objcio/VideoCaptureDemo

https://github.com/gsixxxx/DTSmallVideo

https://github.com/AndyFightting/VideoRecord

卷首吐槽語

這還是第一次接觸自定義界面錄制視頻,包括各種參數(shù)的設(shè)置,不得不說,錄制視頻這塊,各種類,各種方法,蠻復(fù)雜的,網(wǎng)上的資料也是各種雜亂,想要弄清楚還真是得費(fèi)一番功夫,我參考了大量資料,根據(jù)自己的思路整理了一遍,按照我的思路來,保證你看一遍就會(huì),我這里只是簡單的錄制,壓縮,剪裁,導(dǎo)出等功能,不設(shè)計(jì)濾鏡,添加背景音樂,合并,字幕等等,重要的是這個(gè)流程,主流程會(huì)了,其他也就是錦上添花了。

先附上dome demo地址

我的blog看更加方便,左側(cè)有目錄
點(diǎn)擊進(jìn)入我的blog文章地址

腦圖

方便大家對三中錄制方式有一個(gè)大概的了解,看一下這張圖片。


基本屬性與類
基本屬性與類

第一種采用系統(tǒng)的錄制較為簡單,詳細(xì)介紹后面兩種。

效果圖

1
1

2
2

3
3
4
4
5
5

6
6

demo中把三種方式單獨(dú)分開,便于學(xué)習(xí)。支持閃光燈,切換鏡頭,錄制不同尺寸的視頻等。

1.UIImagePickerController

這種方式只能設(shè)置一些簡單參數(shù),自定義程度不高,只能自定義界面上的操作按鈕,還有視頻的畫質(zhì)等。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    if ([self isVideoRecordingAvailable]) {
        return;
    }
    self.sourceType = UIImagePickerControllerSourceTypeCamera;
    self.mediaTypes = @[(NSString *)kUTTypeMovie];
    self.delegate = self;
    
    //隱藏系統(tǒng)自帶UI
    self.showsCameraControls = NO;
    //設(shè)置攝像頭
    [self switchCameraIsFront:NO];
    //設(shè)置視頻畫質(zhì)類別
    self.videoQuality = UIImagePickerControllerQualityTypeMedium;
    //設(shè)置散光燈類型
    self.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto;
    //設(shè)置錄制的最大時(shí)長
    self.videoMaximumDuration = 20;
}
- (BOOL)isVideoRecordingAvailable
{
    if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){
        NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
        if([availableMediaTypes containsObject:(NSString *)kUTTypeMovie]){
            return YES;
        }
    }
    return NO;
}

- (void)switchCameraIsFront:(BOOL)front
{
    if (front) {
        if([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]){
            [self setCameraDevice:UIImagePickerControllerCameraDeviceFront];
            
        }
    } else {
        if([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear]){
            [self setCameraDevice:UIImagePickerControllerCameraDeviceRear];
            
        }
    }
}

2.AVCaptureSession+AVCaptureMovieFileOutput

流程:

1. 創(chuàng)建捕捉會(huì)話
2. 設(shè)置視頻的輸入
3. 設(shè)置音頻的輸入
4. 輸出源設(shè)置,這里視頻,音頻數(shù)據(jù)會(huì)合并到一起輸出,在代理方法中國也可以單獨(dú)拿到視頻或者音頻數(shù)據(jù),給AVCaptureMovieFileOutput指定路徑,開始錄制之后就會(huì)向這個(gè)路徑寫入數(shù)據(jù)
5. 添加視頻預(yù)覽層
6. 開始采集數(shù)據(jù),這個(gè)時(shí)候還沒有寫入數(shù)據(jù),用戶點(diǎn)擊錄制后就可以開始寫入數(shù)據(jù)

0. 創(chuàng)建捕捉會(huì)話

 self.session = [[AVCaptureSession alloc] init];
    if ([_session canSetSessionPreset:AVCaptureSessionPreset640x480]) {//設(shè)置分辨率
        _session.sessionPreset=AVCaptureSessionPreset640x480;
    }

1. 視頻的輸入

   - (void)setUpVideo
{
    // 1.1 獲取視頻輸入設(shè)備(攝像頭)
    AVCaptureDevice *videoCaptureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
    
    // 視頻 HDR (高動(dòng)態(tài)范圍圖像)
    // videoCaptureDevice.videoHDREnabled = YES;
    // 設(shè)置最大,最小幀速率
    //videoCaptureDevice.activeVideoMinFrameDuration = CMTimeMake(1, 60);
    // 1.2 創(chuàng)建視頻輸入源
    NSError *error=nil;
    self.videoInput= [[AVCaptureDeviceInput alloc] initWithDevice:videoCaptureDevice error:&error];
    // 1.3 將視頻輸入源添加到會(huì)話
    if ([self.session canAddInput:self.videoInput]) {
        [self.session addInput:self.videoInput];
        
    }
}

2. 音頻的輸入

// 2.1 獲取音頻輸入設(shè)備
    AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
    NSError *error=nil;
    // 2.2 創(chuàng)建音頻輸入源
    self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
    // 2.3 將音頻輸入源添加到會(huì)話
    if ([self.session canAddInput:self.audioInput]) {
        [self.session addInput:self.audioInput];
    }

3.輸出源設(shè)置

- (void)setUpFileOut
{
    // 3.1初始化設(shè)備輸出對象,用于獲得輸出數(shù)據(jù)
    self.FileOutput=[[AVCaptureMovieFileOutput alloc]init];
    
    // 3.2設(shè)置輸出對象的一些屬性
    AVCaptureConnection *captureConnection=[self.FileOutput connectionWithMediaType:AVMediaTypeVideo];
    //設(shè)置防抖
    //視頻防抖 是在 iOS 6 和 iPhone 4S 發(fā)布時(shí)引入的功能。到了 iPhone 6,增加了更強(qiáng)勁和流暢的防抖模式,被稱為影院級的視頻防抖動(dòng)。相關(guān)的 API 也有所改動(dòng) (目前為止并沒有在文檔中反映出來,不過可以查看頭文件)。防抖并不是在捕獲設(shè)備上配置的,而是在 AVCaptureConnection 上設(shè)置。由于不是所有的設(shè)備格式都支持全部的防抖模式,所以在實(shí)際應(yīng)用中應(yīng)事先確認(rèn)具體的防抖模式是否支持:
    if ([captureConnection isVideoStabilizationSupported ]) {
        captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
    }
    //預(yù)覽圖層和視頻方向保持一致
    captureConnection.videoOrientation = [self.previewlayer connection].videoOrientation;
    
    // 3.3將設(shè)備輸出添加到會(huì)話中
    if ([_session canAddOutput:_FileOutput]) {
        [_session addOutput:_FileOutput];
    }
}

4. 視頻預(yù)覽層

一進(jìn)入視頻錄制界面,這個(gè)時(shí)候 session就已經(jīng)在采集數(shù)據(jù)了,并把數(shù)據(jù)顯示在預(yù)覽層上,用戶選擇錄制后,再將采集到的數(shù)據(jù)寫入文件。

    - (void)setUpPreviewLayerWithType:(FMVideoViewType )type
{
    CGRect rect = CGRectZero;
    switch (type) {
        case Type1X1:
            rect = CGRectMake(0, 0, kScreenWidth, kScreenWidth);
            break;
        case Type4X3:
            rect = CGRectMake(0, 0, kScreenWidth, kScreenWidth*4/3);
            break;
        case TypeFullScreen:
            rect = [UIScreen mainScreen].bounds;
            break;
        default:
            rect = [UIScreen mainScreen].bounds;
            break;
    }
    self.previewlayer.frame = rect;
    [_superView.layer insertSublayer:self.previewlayer atIndex:0];
}

5. 開始采集畫面

[self.session startRunning];

6.開始錄制

- (void)writeDataTofile
{
    NSString *videoPath = [self createVideoFilePath];
    self.videoUrl = [NSURL fileURLWithPath:videoPath];
    [self.FileOutput startRecordingToOutputFileURL:self.videoUrl recordingDelegate:self];
    
}

3.AVCaptureSession+AVAssetWriter

流程:

1. 創(chuàng)建捕捉會(huì)話
2. 設(shè)置視頻的輸入 和 輸出
3. 設(shè)置音頻的輸入 和 輸出
4. 添加視頻預(yù)覽層
5. 開始采集數(shù)據(jù),這個(gè)時(shí)候還沒有寫入數(shù)據(jù),用戶點(diǎn)擊錄制后就可以開始寫入數(shù)據(jù)
6. 初始化AVAssetWriter, 我們會(huì)拿到視頻和音頻的數(shù)據(jù)流,用AVAssetWriter寫入文件,這一步需要我們自己實(shí)現(xiàn)。

1. 創(chuàng)建捕捉會(huì)話

需要確保在同一個(gè)隊(duì)列,最好隊(duì)列只創(chuàng)建一次

 self.session = [[AVCaptureSession alloc] init];
    if ([_session canSetSessionPreset:AVCaptureSessionPreset640x480]) {//設(shè)置分辨率
        _session.sessionPreset=AVCaptureSessionPreset640x480;
    }

2.設(shè)置視頻的輸入 和 輸出

- (void)setUpVideo
{
    // 2.1 獲取視頻輸入設(shè)備(攝像頭)
    AVCaptureDevice *videoCaptureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
    // 2.2 創(chuàng)建視頻輸入源
    NSError *error=nil;
    self.videoInput= [[AVCaptureDeviceInput alloc] initWithDevice:videoCaptureDevice error:&error];
    // 2.3 將視頻輸入源添加到會(huì)話
    if ([self.session canAddInput:self.videoInput]) {
        [self.session addInput:self.videoInput];
    }
    
    self.videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    self.videoOutput.alwaysDiscardsLateVideoFrames = YES; //立即丟棄舊幀,節(jié)省內(nèi)存,默認(rèn)YES
    [self.videoOutput setSampleBufferDelegate:self queue:self.videoQueue];
    if ([self.session canAddOutput:self.videoOutput]) {
        [self.session addOutput:self.videoOutput];
    }
    
}

3. 設(shè)置音頻的輸入 和 輸出

- (void)setUpAudio
{
    // 2.2 獲取音頻輸入設(shè)備
    AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
    NSError *error=nil;
    // 2.4 創(chuàng)建音頻輸入源
    self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
    // 2.6 將音頻輸入源添加到會(huì)話
    if ([self.session canAddInput:self.audioInput]) {
        [self.session addInput:self.audioInput];
    }
    
    self.audioOutput = [[AVCaptureAudioDataOutput alloc] init];
    [self.audioOutput setSampleBufferDelegate:self queue:self.videoQueue];
    if([self.session canAddOutput:self.audioOutput]) {
        [self.session addOutput:self.audioOutput];
    }
    
}

4. 添加視頻預(yù)覽層

  - (void)setUpPreviewLayerWithType:(FMVideoViewType )type
{
    CGRect rect = CGRectZero;
    switch (type) {
        case Type1X1:
            rect = CGRectMake(0, 0, kScreenWidth, kScreenWidth);
            break;
        case Type4X3:
            rect = CGRectMake(0, 0, kScreenWidth, kScreenWidth*4/3);
            break;
        case TypeFullScreen:
            rect = [UIScreen mainScreen].bounds;
            break;
        default:
            rect = [UIScreen mainScreen].bounds;
            break;
    }
    
    self.previewlayer.frame = rect;
    [_superView.layer insertSublayer:self.previewlayer atIndex:0];
}

5. 開始采集畫面

   [self.session startRunning];

6. 初始化AVAssetWriter

AVAssetWriter 寫入數(shù)據(jù)的過程需要在子線程中執(zhí)行,并且每次寫入數(shù)據(jù)都需要保證在同一個(gè)線程。

- (void)setUpWriter
{
    self.videoUrl = [[NSURL alloc] initFileURLWithPath:[self createVideoFilePath]];
    self.writeManager = [[AVAssetWriteManager alloc] initWithURL:self.videoUrl viewType:_viewType];
    self.writeManager.delegate = self;
    
}

7.拿到數(shù)據(jù)流后處理

視頻數(shù)據(jù)和音頻數(shù)據(jù)需要分開處理

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    @autoreleasepool {
        
        //視頻
        if (connection == [self.videoOutput connectionWithMediaType:AVMediaTypeVideo]) {
            
            if (!self.writeManager.outputVideoFormatDescription) {
                @synchronized(self) {
                    CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
                    self.writeManager.outputVideoFormatDescription = formatDescription;
                }
            } else {
                @synchronized(self) {
                    if (self.writeManager.writeState == FMRecordStateRecording) {
                        [self.writeManager appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeVideo];
                    }
                    
                }
            }
            
            
        }
        
        //音頻
        if (connection == [self.audioOutput connectionWithMediaType:AVMediaTypeAudio]) {
            if (!self.writeManager.outputAudioFormatDescription) {
                @synchronized(self) {
                    CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
                    self.writeManager.outputAudioFormatDescription = formatDescription;
                }
            }
            @synchronized(self) {
                
                if (self.writeManager.writeState == FMRecordStateRecording) {
                    [self.writeManager appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio];
                }
                
            }
            
        }
    }
    
}

我們拿到最原始的數(shù)據(jù)以后,可以對其進(jìn)行各種參數(shù)的設(shè)置

- (void)setUpWriter
{
    self.assetWriter = [AVAssetWriter assetWriterWithURL:self.videoUrl fileType:AVFileTypeMPEG4 error:nil];
    //寫入視頻大小
    NSInteger numPixels = self.outputSize.width * self.outputSize.height;
    //每像素比特
    CGFloat bitsPerPixel = 6.0;
    NSInteger bitsPerSecond = numPixels * bitsPerPixel;
    
    // 碼率和幀率設(shè)置
    NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(bitsPerSecond),
                                             AVVideoExpectedSourceFrameRateKey : @(30),
                                             AVVideoMaxKeyFrameIntervalKey : @(30),
                                             AVVideoProfileLevelKey : AVVideoProfileLevelH264BaselineAutoLevel };
    
    //視頻屬性
    self.videoCompressionSettings = @{ AVVideoCodecKey : AVVideoCodecH264,
                                       AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill,
                                       AVVideoWidthKey : @(self.outputSize.height),
                                       AVVideoHeightKey : @(self.outputSize.width),
                                       AVVideoCompressionPropertiesKey : compressionProperties };

    _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoCompressionSettings];
    //expectsMediaDataInRealTime 必須設(shè)為yes,需要從capture session 實(shí)時(shí)獲取數(shù)據(jù)
    _assetWriterVideoInput.expectsMediaDataInRealTime = YES;
    _assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
    
    
    // 音頻設(shè)置
    self.audioCompressionSettings = @{ AVEncoderBitRatePerChannelKey : @(28000),
                                       AVFormatIDKey : @(kAudioFormatMPEG4AAC),
                                       AVNumberOfChannelsKey : @(1),
                                       AVSampleRateKey : @(22050) };
    
    
    _assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioCompressionSettings];
    _assetWriterAudioInput.expectsMediaDataInRealTime = YES;
    
    
    if ([_assetWriter canAddInput:_assetWriterVideoInput]) {
        [_assetWriter addInput:_assetWriterVideoInput];
    }else {
        NSLog(@"AssetWriter videoInput append Failed");
    }
    if ([_assetWriter canAddInput:_assetWriterAudioInput]) {
        [_assetWriter addInput:_assetWriterAudioInput];
    }else {
        NSLog(@"AssetWriter audioInput Append Failed");
    }
    
    
    self.writeState = FMRecordStateRecording;
}

設(shè)置好參數(shù)以后,就可以寫入文件了。AVAssetWriter數(shù)據(jù)寫入的過程有點(diǎn)復(fù)雜,demo中我新建AVAssetWriteManager分離出AVAssetWriter,單獨(dú)處理寫數(shù)據(jù),這樣邏輯會(huì)清晰一點(diǎn)。

fileOut和writer的相同點(diǎn)和不同點(diǎn)

從上面的兩個(gè)流程大致可以看出來,
相同點(diǎn):數(shù)據(jù)采集都在AVCaptureSession中進(jìn)行,視頻和音頻的輸入都一樣,畫面的預(yù)覽一致。
不同點(diǎn):
輸出不一致, AVCaptureMovieFileOutput 只需要一個(gè)輸出即可,指定一個(gè)文件路后,視頻和音頻會(huì)寫入到指定路徑,不需要其他復(fù)雜的操作。
AVAssetWriter 需要 AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 兩個(gè)單獨(dú)的輸出,拿到各自的輸出數(shù)據(jù)后,然后自己進(jìn)行相應(yīng)的處理。

可配參數(shù)不一致,AVAssetWriter可以配置更多的參數(shù)。

視頻剪裁不一致,AVCaptureMovieFileOutput 如果要剪裁視頻,因?yàn)橄到y(tǒng)已經(jīng)把數(shù)據(jù)寫到文件中了,我們需要從文件中獨(dú)到一個(gè)完整的視頻,然后處理;而AVAssetWriter我們拿到的是數(shù)據(jù)流,還沒有合成視頻,對數(shù)據(jù)流進(jìn)行處理,所以兩則剪裁方式也是不一樣。

其他添加背景音樂,水印等也是不一樣的,這里沒有涉及就不介紹了。到這里也差不多了,文章也有點(diǎn)長了。這些是我自己整理資料總結(jié)出來的,不排除會(huì)有一些錯(cuò)誤之處,供大家學(xué)習(xí)參考,希望有所收獲。如果方便,還請為我star一個(gè),也算是對我的支持。

demo地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容