iOS視頻流開發(fā)(3)— 錄制

AVFoundation 開發(fā)

當(dāng)接到拍照或者視頻錄制需求的時(shí)候,很多同學(xué)會(huì)選擇UIImagePickerController因?yàn)樗庋b的非常好,幾個(gè)簡單的設(shè)置就可以開始使用。但是同MPMoviePlayerController一樣,由于它的高度封裝性,它的適用性就比較差,比如以下幾個(gè)場景:

  • 自定義拍照界面,相信我無論P(yáng)D還是UED他們一定會(huì)要求你改掉系統(tǒng)原生界面的
  • 為了使拍出來的照片或者視頻更鮮亮,可以使用Torch硬件。但是用ImagePicker就無法控制了,要看系統(tǒng)娘的心情。
  • UIImagePickerController還有一個(gè)比較磨人的問題,就是當(dāng)照相機(jī)控制器被壓入棧的時(shí)候是不會(huì)關(guān)閉的,如果我們有一些后續(xù)操作如圖片編輯、打標(biāo),這個(gè)時(shí)候取景框是一直工作的,對內(nèi)存和電量都是極大的浪費(fèi)。甚至?xí)?dǎo)致memory warning。
  • 有時(shí)候我們需要的不是一個(gè)全屏的取景框,需要的可能只是一個(gè)區(qū)域,比如在相冊列表中,加入一個(gè)實(shí)時(shí)取景框區(qū)域表示拍照。

這個(gè)時(shí)候我們就需要使用AVFoundation進(jìn)行視頻流的獲取。

AVFoundation框架簡介

!

AVFoundation是基于CoreAudio CoreMedia CoreAnimation的庫,提供一組Objective-C接口,所以支持ARC,不需要程序員管理引用計(jì)數(shù)。他提供了必要的服務(wù)以便于程序員可以在iOS、OS X上進(jìn)行基于時(shí)間的視頻音頻開發(fā)工作??梢苑奖愕膶uickTime影片和MPEG-4等媒體格式文件進(jìn)行播放、拍攝、編輯和編碼工作。
上文的AVPlayer和AVQueuePlayer也屬于AVFoundation框架。

AVFoundation工作模型

!

AVCaptureSession:

媒體捕獲會(huì)話,負(fù)責(zé)將輸入的音頻、視頻數(shù)據(jù)輸出到輸出設(shè)備中。一個(gè)AVCaptureSession可以有多個(gè)輸入設(shè)備和輸出設(shè)備。實(shí)際上是在輸入源和輸出源之間充當(dāng)中介者。

AVCaptureDevice:

捕獲視頻、音頻信息的設(shè)備,如攝像頭、麥克風(fēng)。通過該對象可以對物理設(shè)備進(jìn)行功能設(shè)置,如對焦、曝光、白平衡、閃光燈、火炬

AVCaptureDeviceInput:

輸入源,這個(gè)對象需要跟AVCaptureDevice對象綁定,并添加到AVCaptureSession工作。

AVCaptureOutput:

輸出源,用于接受輸出的多媒體數(shù)據(jù)或文件。主要子類有:

  • AVCaptureVideoDataOutput 獲取視頻數(shù)據(jù)
  • AVCaptureAudioDataOutput 獲取音頻數(shù)據(jù)
  • AVCaptureStillImageOutput 獲取靜態(tài)圖片數(shù)據(jù)
  • AVCaptureFileOutput 獲取多媒體文件
  • AVCaptureMovieFileOutput 獲取視頻文件,是AVCaptureFileOutput的子類。
  • AVCaptureAudioFileOutput 獲取音頻文件,是AVCaptureFileOutput的子類。

AVCaptureConnection:

當(dāng)把輸入源和輸出源添加到AVCaptureSession之后AVCaptureSession就會(huì)在所有相符的輸入源和輸出源之間建立連接。通過連接可以調(diào)整視頻的方向,視頻錄制時(shí)的穩(wěn)定模式,音量等屬性

AVCaptureVideoPreviewLayer:

呈現(xiàn)捕獲的視頻流的層,是CALayer子類。用于實(shí)時(shí)取景,這個(gè)層上呈現(xiàn)的效果就是最后實(shí)際輸出的效果。PreviewLayer創(chuàng)建的時(shí)候必須綁定一個(gè)AVCaptureSession對象。

視頻流捕獲編程步驟

視頻流捕獲初始化

  • 1、獲取照相機(jī)訪問權(quán)限
  • 2、創(chuàng)建session
  • 3、設(shè)置session
  • 4、添加輸入源到session
  • 5、添加輸出源到session
  • 6、設(shè)置預(yù)覽layer

視頻流捕獲啟動(dòng)

  • 1、啟動(dòng)session
  • 2、停止session

攝像設(shè)備設(shè)置

  • 1、設(shè)置曝光
  • 2、設(shè)置對焦
  • 3、設(shè)置白平衡
  • 4、設(shè)置火炬

多媒體文件輸出

  • 1、圖片輸出
  • 2、視頻輸出
  • 3、音頻輸出
  • 4、音頻視頻混合文件movie輸出

下面將詳細(xì)介紹視頻流捕獲的編程工作

視頻流的創(chuàng)建和啟動(dòng)

無論是拍照還是錄制視頻都需要?jiǎng)?chuàng)建和啟動(dòng)視頻流。創(chuàng)建和啟動(dòng)視頻

1、獲取照相機(jī)訪問權(quán)限
  • iOS6.x以及之前的iOS版本(包括iOS6),應(yīng)用都可以獲取照相機(jī)不需要用戶授權(quán),可以直接進(jìn)行視頻流的初始化和獲取工作。
  • iOS7.0開始,APP第一次使用照相機(jī)的時(shí)候需要用戶授權(quán),所以要先進(jìn)行權(quán)限判斷,如果沒有權(quán)限需要獲取照相機(jī)訪問權(quán)限。
    //iOS7之后的版本需要照相機(jī)訪問權(quán)限
    if (IsIOS7AndHigher && self.isDeviceAuthorized == NO)
    {
        NSString *mediaType = AVMediaTypeVideo;
        @weakify(self);
        self.handlerBlock = ^(BOOL granted) {
            @strongify(self);
            self.isDeviceAuthorized = granted;
            if (granted)
            {
                [self initAndRunCameraDeviceWithPosition:self.devicePosition];
            }
            else
            {
                [self startRunningDidFinish:NO];
            }
        };
        //獲取權(quán)限
        [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:self.handlerBlock];
    }
    else //iOS7之前的版本直接進(jìn)行初始化工作
    {
        self.isDeviceAuthorized = YES;
        [self initAndRunCameraDeviceWithPosition:self.devicePosition];
    }
2、創(chuàng)建session
AVCaptureSession *session = [[AVCaptureSession alloc] init];
3、設(shè)置session

因?yàn)閟ession是可以重用的,所以在設(shè)置之前必須確保是stop的。

//設(shè)置session
if (self.session)
{
    [self.session stopRunning];
}
self.session = session;
        
//設(shè)置session呈現(xiàn)的尺寸
[self setSessionPresent];

//設(shè)置會(huì)話呈現(xiàn)的取景尺寸
- (void)setSessionPresent
{
    if (self.model == CICameraCaptureModelVideo)
    {
        switch (self.qualityType) {
            case CICameraDeviceVideoQualityTypeHigh:
                [self.session setSessionPreset:AVCaptureSessionPresetHigh];
                break;
            case CICameraDeviceVideoQualityTypeMedium:
                [self.session setSessionPreset:AVCaptureSessionPresetMedium];
            case CICameraDeviceVideoQualityTypeLow:
                [self.session setSessionPreset:AVCaptureSessionPresetLow];
            default:
                break;
        }
    }
    else
    {
        [self.session setSessionPreset:AVCaptureSessionPresetPhoto];
    }
}
4、添加輸入源到session
//設(shè)備輸入添加到會(huì)話
- (BOOL)addDeviceInputToSession:(AVCaptureDevicePosition)position
{
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    AVCaptureDevice *videoDevice = [devices firstObject];
    
    for (AVCaptureDevice *device in devices)
    {
        if ([device position] == position)
        {
            videoDevice = device;
            break;
        }
    }
    
    NSError *error = nil;

    //添加視頻輸入
    AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
    
    if (error)
    {
        NSLog(@"獲取視頻輸入錯(cuò)誤:%@", error.localizedDescription);
        return NO;
    }
    
    if ([self.session canAddInput:videoDeviceInput])
    {
        [self.session addInput:videoDeviceInput];
        [self setVideoDeviceInput:videoDeviceInput];
    }
    
    //添加音頻輸入
    if (self.model == CICameraCaptureModelVideo && self.needRecordAudio)
    {
        AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
        AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
        
        if (error)
        {
            NSLog(@"獲取音頻輸入錯(cuò)誤:%@", error.localizedDescription);
            return NO;
        }
        
        if ([self.session canAddInput:audioDeviceInput])
        {
            [self.session addInput:audioDeviceInput];
        }
    }
    
    return YES;
}

5、添加輸出源到session
- (void)addDeviceOutputToSession
{

    if (self.model == CICameraCaptureModelPhoto) //獲取靜態(tài)圖片
    {
        AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
        if ([self.session canAddOutput:stillImageOutput])
        {
            [stillImageOutput setOutputSettings:@{AVVideoCodecKey : AVVideoCodecJPEG}];
            [self.session addOutput:stillImageOutput];
            [self setStillImageOutput:stillImageOutput];
        }
        
        [self setDeviceModeWithFocusPoint:CGPointMake(0.5, 0.5)];
    }
    else if (self.model == CICameraCaptureModelVideo) //獲取視頻流
    {
        AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
        if ([self.session canAddOutput:movieFileOutput])
        {
            [self.session addOutput:movieFileOutput];
            
            AVCaptureConnection *videoConnection = [movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
            self.movieFileOutput = movieFileOutput;
            if ([videoConnection isVideoStabilizationSupported]) {
                videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
            }
        }
        
    }
}
6、設(shè)置預(yù)覽layer
- (void)setupPreview
{
    UIView *preview = [self.dataSource cameraDeviceVideoPreview];
    
    AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
    previewLayer.frame = preview.bounds;
    previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    WeakSelf
    dispatch_async(dispatch_get_main_queue(), ^{
        StrongSelf
        [strongSelf.previewLayer removeFromSuperlayer];
        strongSelf.previewLayer = previewLayer;
        [preview.layer insertSublayer:strongSelf.previewLayer atIndex:0];
    });
    
}
7、啟動(dòng)session

啟動(dòng)session之后預(yù)覽界面就會(huì)有畫面呈現(xiàn),因?yàn)閱?dòng)是個(gè)比較耗時(shí)的操作,所以一般會(huì)加入過渡動(dòng)畫

[self.session startRunning];
8、停止session

當(dāng)不需要實(shí)時(shí)取景的時(shí)候可以關(guān)閉session,比如拍照視圖被壓入視圖棧下面,當(dāng)拍照視圖從新到視圖棧頂端的時(shí)候可以再將session start。這樣既不會(huì)對內(nèi)存和電量造成不良影響,也不需要視頻流重新初始化。

[self.session stopRunning];

到此為止一個(gè)視頻流的創(chuàng)建和啟動(dòng)已經(jīng)完成了。當(dāng)然我們最重要捕獲視頻流,所以要進(jìn)行視頻流的捕獲。在此之前,為了獲得更好的效果,我們還要進(jìn)行一些設(shè)置,如設(shè)置對焦、曝光、白平衡和火炬,以便我們能得到更好的拍攝效果。

攝像設(shè)備的設(shè)置

切換攝像頭
- (void)changeDevicePosition
{
    if (self.isDeviceAuthorized)
    {
        AVCaptureDevice *currentVideoDevice = [self.videoDeviceInput device];
        AVCaptureDevicePosition preferredPosition = AVCaptureDevicePositionUnspecified;
        AVCaptureDevicePosition currentPosition = [currentVideoDevice position];
        
        switch (currentPosition)
        {
            case AVCaptureDevicePositionUnspecified:
                preferredPosition = AVCaptureDevicePositionBack;
                break;
            case AVCaptureDevicePositionBack:
                preferredPosition = AVCaptureDevicePositionFront;
                break;
            case AVCaptureDevicePositionFront:
                preferredPosition = AVCaptureDevicePositionBack;
                break;
        }
        
        self.devicePosition = preferredPosition;
        [self startDevice];
    }
}
設(shè)置對焦、曝光、白平衡、火炬(torch)

對這些設(shè)備狀態(tài)的設(shè)定可以在session啟動(dòng)之后。在設(shè)置之前一定要鎖定,lockForConfiguration:。設(shè)置完后,調(diào)用unlockForConfiguration解除鎖定?;鹁媸茿pple一個(gè)獨(dú)特的功能,它會(huì)為攝像頭提供一個(gè)LED光源,光源的亮度是可以調(diào)節(jié)的,相比閃光燈,torch可以提供持續(xù)的光源,對在黑夜錄制清晰的視頻幫助巨大。

+ (void)setDevice:(AVCaptureDevice *)device deviceMode:(CICameraDeviceMode *)mode
{
    NSError *error = nil;
    if ([device lockForConfiguration:&error])
    {
        //對焦
        if ([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:mode.focusMode])
        {
            [device setFocusMode:mode.focusMode];
            [device setFocusPointOfInterest:mode.focusPoint];
        }
        
        //曝光
        if ([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:mode.exposureMode])
        {
            [device setExposureMode:mode.exposureMode];
            [device setExposurePointOfInterest:mode.exposurePoint];
        }
        
        //白平衡
        if ([device isWhiteBalanceModeSupported:mode.whiteBalanceMode])
        {
            [device setWhiteBalanceMode:mode.whiteBalanceMode];
        }
        
        //火炬
        if ([device hasTorch] && [device isTorchModeSupported:mode.torchMode])
        {
            [device setTorchMode:mode.torchMode];
        }
        
        [device setSubjectAreaChangeMonitoringEnabled:mode.monitorSubjectAreaChange];
        [device unlockForConfiguration];
    }
    else
    {
        NSLog(@"%@", error);
    }
}

視頻流輸出文件

獲取靜態(tài)圖片

輸出時(shí)注意connection的方向設(shè)置

    if (self.isDeviceAuthorized && self.session.isRunning)
    {
        @weakify(self)
        dispatch_async([self sessionQueue], ^{
            @strongify(self)
            
            AVCaptureConnection *videoConnection = [[self stillImageOutput] connectionWithMediaType:AVMediaTypeVideo];
            
            if ([videoConnection isVideoOrientationSupported]) {
                [videoConnection setVideoOrientation:[self videoOrientation:[UIDevice currentDevice].orientation]];
            }
            
            [[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
                
                UIImage *image = nil;
                if (imageDataSampleBuffer)
                {
                    NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
                    
                    if (imageData)
                    {
                        image = [[UIImage alloc] initWithData:imageData];
                    }
                }
                
                if ([self.delegate respondsToSelector:@selector(snapStillImageDidFinish:)])
                {
                    [self.delegate snapStillImageDidFinish:image];
                }
            }];
        });
    }

獲取視頻

開始獲取視頻流

視頻的方向在這個(gè)時(shí)候設(shè)置,在拍攝期間不能更改。
視頻的保存路徑也在這個(gè)時(shí)機(jī)決定。

- (void)startRecordingWithFilePath:(NSString *)filePath
{
    if (self.isDeviceAuthorized && self.session.isRunning && filePath.length) {
        WeakSelf
        dispatch_async([self sessionQueue], ^{
            StrongSelf
            
            AVCaptureConnection *videoConnection = [strongSelf.movieFileOutput connectionWithMediaType:AVMediaTypeAudio];
            
            videoConnection.videoOrientation = [strongSelf videoOrientation:[UIDevice currentDevice].orientation];
            
            NSURL *fileURL = [NSURL fileURLWithPath:filePath];
            
            [strongSelf.movieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:self];
            
            strongSelf.isRecording = YES;
            
        });
    }
}
結(jié)束獲取視頻流

通過AVCaptureMovieFileOutput可以獲得一個(gè)完整的音視頻混合movie文件

- (void)stopRecording
{
    WeakSelf
    dispatch_async([self sessionQueue], ^{
        StrongSelf
        if ([strongSelf.movieFileOutput isRecording])
        {
            [strongSelf.movieFileOutput stopRecording];
            strongSelf.isRecording = NO;
        }
    });
}
視頻錄制delegate

視頻錄制相關(guān)的代理,可以查看AVCaptureFileOutputRecordingDelegate文件。比較常用的是,開始錄制回調(diào)和錄制完成回調(diào)

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections
{
    if ([self.delegate respondsToSelector:@selector(videoRecordDidStarted:)]) {
        [self.delegate videoRecordDidStarted:fileURL];
    }
}

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
{
    if ([self.delegate respondsToSelector:@selector(videoRecordDidFinished:)] && !error && outputFileURL)
    {
        [self.delegate videoRecordDidFinished:outputFileURL];
    }
}

獲取照片和獲取視頻的異同

無論是拍照還是錄制視頻都需要?jiǎng)?chuàng)建和啟動(dòng)視頻流。上面已經(jīng)說的很清楚了。不同點(diǎn)只有以下幾點(diǎn):

  • 無論獲取照片還是獲取視頻,都需要預(yù)覽,所以都需要視頻輸入設(shè)備和相應(yīng)的視頻輸入源。通過 [ [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] firstObject];獲取視頻輸入設(shè)備-攝像頭
  • 視頻需要音頻,所以比照片要多一個(gè)輸入設(shè)備。通過[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]獲得音頻輸入設(shè)備-麥克風(fēng)。
  • 照片的輸出設(shè)備同視頻不同,照片的輸出對象AVCaptureStillImageOutput,視頻的輸出對象是AVCaptureMovieFileOutput。
  • 照片輸出后可以直接將照片對象的指針傳給調(diào)用方使用,視頻只能將視頻文件的地址傳給調(diào)用方。

CameraDeviceFramework

這個(gè)framework已經(jīng)將視頻流捕獲、攝像頭設(shè)置、視頻獲取封裝,并配有Demo。
gitlab地址:http://gitlab.alibaba-inc.com/xunfeng.zy/CameraDeviceFramework

最后介紹一下UIImagePickerController的開發(fā)

UIImagePickerController支持拍照和視頻錄制,還可以用來選取照片。
用UIImagePickerController拍照或者錄制視頻有以下幾個(gè)步驟:

1、創(chuàng)建UIImagePickerController對象。

2、設(shè)置源類型sourceType,類型有:

  • UIImagePickerControllerSourceTypePhotoLibrary:照片庫
    ,默認(rèn)值。
  • UIImagePickerControllerSourceTypeCamera:攝像頭
  • UIImagePickerControllerSourceTypeSavedPhotosAlbum:相冊

3、設(shè)置媒體類型mediaType。類型有:

  • kUTTypeImage:照片
  • kUTTypeVideo:無聲視頻
  • kUTTypeMovie:有聲視頻

4、指定工作模式,類型有:

  • UIImagePickerControllerCameraCaptureModePhoto:拍照模式
  • UIImagePickerControllerCameraCaptureModeVideo:視頻模式

5、設(shè)置視頻質(zhì)量videoQuality,如果是拍照則忽略

  • UIImagePickerControllerQualityTypeHigh
  • UIImagePickerControllerQualityTypeMedium
  • UIImagePickerControllerQualityTypeLow
    也可以按照尺寸選擇,其實(shí)在部分機(jī)型下High同1280x720是等價(jià)的。
  • UIImagePickerControllerQualityTypeIFrame1280x720
  • UIImagePickerControllerQualityTypeIFrame960x540
  • UIImagePickerControllerQualityType640x480

6、設(shè)置攝像頭、閃光燈

7、展示UIImagePickerController

8、視頻錄制完成后在代理方法中處理視頻并保存。照片同理

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

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

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