AVFoundation 框架初探究(二) AVFoundation 框架初探究(一) iOS 视频播放方式整理

 

 

接着第一篇总结html


 

      系列第一篇地址:AVFoundation 框架初探究(一)git

      在第一篇的文章中,咱们总结了主要有下面几个点的知识:github

      一、对AVFoundation框架总体的一个认识缓存

      二、AVSpeechSynthesizer这个文字转音频类微信

      三、AVAudioPlayer音频播放类session

      四、AVAudioRecorder音频录制类框架

      五、AVAudioSession音频会话处理类 ide

      上面第一篇说的内容,大体都是关于上面总结的,接着说说咱们这第二篇总结什么?其实刚开始的时候,我是想按照《AVFoundation开发秘籍》的内容总结的,但我又以为上面第一篇写的内容大体其实都是音频的,那咱们这第二篇是否是总结视频的内容会更好一点,多媒体的处理,最主要的也就是音频和视频了,在接触了第一篇的音频以后,趁热打铁在把视频的总结出来,这样就大体上让咱们认识了一下这个AVFoundation,全部这篇文章就决定再也不按照书中的知识点去总结,直接总结视频的内容,固然这并非说说中关于其余的讨论咱们就不总结了,既然是系列的文章,按咱们在说完视频以后再接着回来总结书中的知识。布局

      本文Demo地址 post

 

视频的播放


 

      在这个系列最开始的时候咱们有总结过视频播放的几个方式,因此关于AVPlayerItem、AVPlayerLayer、AVPlayer这几个播放类相关的定义、使用等等的咱们就再也不说了, 有须要的能够看看咱们前面总结的文章 :

      iOS 视频播放方式整理

      上面写的也只是最基础的视频的播放功能,在后面涉及到其余功能的时候咱们再仔细的总结,说说今天咱们针对视频这一块要总结的重点内容,视频的录制。

 

视频录制  AVCaptureSession + AVCaptureMovieFileOutput


 

      咱们先把利用AVCaptureSession + AVCaptureMovieFileOutput录制视频的整个流程整理出来,而后咱们对照着整个流程,总结这整个流程当中的点点滴滴:

      一、初始化 AVCaptureSession 获得一个捕捉会话对象。

      二、经过 AVCaptureDevice 的类方法 defaultDeviceWithMediaType 区别 MediaType 获得 AVCaptureDevice 对象。

      三、获得上面的 AVCaptureDevice 对象以后,就是咱们的 AVCaptureDeviceInput 输入对象了。把咱们的输入对象添加到 AVCaptureSession ,固然这里输入对象是要区分音频和视频对象的,这个具体的代码里面咱们说。

      四、有了输入固然也就有 AVCaptureMovieFileOutput,把它添加给AVCaptureSession对象。

      五、经过咱们初始化的AVCaptureMovieFileOutput的connectionWithMediaType方法获得一个AVCaptureConnection对象,ACCaptureConnection能够控制input到output的数据传输也能够设置视频录制的一些属性。

      六、也是经过前面获得的AVCaptureSession对象初始化获得一个AVCaptureVideoPreviewLayer对象,用来预览咱们要录制的视频画面,注意这个时候咱们的视频录制尚未开始。

      七、如今看看AVCaptureSession对象,你就发现输入输出以及Connection还有预览层都有了,那就让它 startRunning。

      八、好了,用咱们的AVCaptureMovieFileOutput 的 startRecordingToOutputFileURL 开始录制吧。

      九、录制到知足你的需求时候记得让你startRunning的AVCaptureSession 经过 stopRunning休息了,让你的AVCaptureMovieFileOutput也能够stopRecording。这样整个过程就结束了!

      上面的过程咱们就把使用AVCaptureSession + AVCaptureMovieFileOutput录制视频的过程说的清楚了,有些细节咱们也提过了,咱们看看下面咱们的Demo效果,因为是在真机测试的就简单截两张图。具体的能够运行Demo看看:

 

                                                  

                                             录制                                                            播放

(说点题外的,也是无心中发现用摄像头对着X的前置摄像头的时候真的看到有红点闪烁,这也就说网上说的住酒店的时候你能够用摄像头扫描黑暗的房间能够看到有没有针孔摄像头是有道理的!^_^生活小常识,给常常出差住酒店的伙伴!)                                  

经过上面的这两张效果图就大概的展现出了一个录制与播放的过程,下面就是咱们的重点了,解读总结一下关于AVCaptureSession + AVCaptureMovieFileOutput的代码:

 

代码解读第一步:      

 self.captureSession = ({
        // 分辨率设置
        AVCaptureSession *session = [[AVCaptureSession alloc] init];
        // 先判断这个设备是否支持设置你要设置的分辨率
        if ([session canSetSessionPreset:AVCaptureSessionPresetMedium]) {
                
                /*
                 下面是对你能设置的预设图片的质量和分辨率的说明
                 AVCaptureSessionPresetHigh      High 最高的录制质量,每台设备不一样
                 AVCaptureSessionPresetMedium    Medium 基于无线分享的,实际值可能会改变
                 AVCaptureSessionPresetLow       LOW 基于3g分享的
                 AVCaptureSessionPreset640x480   640x480 VGA
                 AVCaptureSessionPreset1280x720  1280x720 720p HD
                 AVCaptureSessionPresetPhoto     Photo 完整的照片分辨率,不支持视频输出
                 */
                [session setSessionPreset:AVCaptureSessionPresetMedium];
        }
        session;
});

NOTE: 我在Demo中有写清楚为何咱们能够利用 self.captureSession =({ })的方式写,有兴趣的能够看看。我也是学习中看到才上网查为何能这样写的,长见识!

 

解读代码第2、三步:

-(BOOL)SetSessioninputs:(NSError *)error{
    
    // capture 捕捉 捕获
    /*
       视频输入类
       AVCaptureDevice 捕获设备类
       AVCaptureDeviceInput 捕获设备输入类
    */
    AVCaptureDevice * captureDevice   = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput * videoInput = [AVCaptureDeviceInput deviceInputWithDevice: captureDevice error: &error];
    
    if (!videoInput) {
        return NO;
    }
    
    // 给捕获会话类添加输入捕获设备
    if ([self.captureSession canAddInput:videoInput]) {
        
        [self.captureSession addInput:videoInput];
    }else{
        return NO;
    }
    /*
      添加音频捕获设备
     */
    AVCaptureDevice * audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    AVCaptureDeviceInput * audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
    
    if (!audioDevice) {
        
        return NO;
    }
    if ([self.captureSession canAddInput:audioInput]) {
        
        [self.captureSession addInput:audioInput];
    }
    return YES;
}

NOTE:这段代码须要注意的地方就是 captureSession  addInput 的时候最好就是先利用 canAddInput 进行判断,看是否能添加,为了代码的健壮。咱们接着看!

 

解读代码第4、五步:

    // 初始化一个设备输出对象
    self.captureMovieFileOutput = ({
        
        //输出一个电影文件
        /*
         a.AVCaptureMovieFileOutput  输出一个电影文件
         b.AVCaptureVideoDataOutput  输出处理视频帧被捕获
         c.AVCaptureAudioDataOutput  输出音频数据被捕获
         d.AVCaptureStillImageOutput 捕获元数据
         */
        
        AVCaptureMovieFileOutput * output = [[AVCaptureMovieFileOutput alloc]init];
        
        /*
         一个ACCaptureConnection能够控制input到output的数据传输。
         */
        AVCaptureConnection * connection = [output connectionWithMediaType:AVMediaTypeVideo];
        
        if ([connection isVideoMirroringSupported]) {

            /*
             视频防抖 是在 iOS 6 和 iPhone 4S 发布时引入的功能。到了 iPhone 6,增长了更强劲和流畅的防抖模式,被称为影院级的视频防抖动。相关的 API 也有所改动 (目前为止并无在文档中反映出来,不过能够查看头文件)。防抖并非在捕获设备上配置的,而是在 AVCaptureConnection 上设置。因为不是全部的设备格式都支持所有的防抖模式,因此在实际应用中应事先确认具体的防抖模式是否支持:
             
             typedef NS_ENUM(NSInteger, AVCaptureVideoStabilizationMode) {
             AVCaptureVideoStabilizationModeOff       = 0,
             AVCaptureVideoStabilizationModeStandard  = 1,
             AVCaptureVideoStabilizationModeCinematic = 2,
             AVCaptureVideoStabilizationModeAuto      = -1,  自动
             } NS_AVAILABLE_IOS(8_0) __TVOS_PROHIBITED;
             */
            connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
            //预览图层和视频方向保持一致
            connection.videoOrientation = [self.captureVideoPreviewLayer connection].videoOrientation;
        }
        
        if ([self.captureSession canAddOutput:output]) {
            
            [self.captureSession addOutput:output];
        }
        
        output;
    });

NOTE: 前面咱们也有说这个Connection,除了给输入和输出创建链接以外,还有一些录制属性是能够设置的,就像咱们在代码中介绍的那样,具体的在代码注释中写的很详细,你们能够看代码。

 

解读代码第六步:

 /*
     用于展现录制的画面
    */
    self.captureVideoPreviewLayer = ({
        
        AVCaptureVideoPreviewLayer * preViewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
        preViewLayer.frame = CGRectMake(10, 50, 355, 355);
        
        /*
         AVLayerVideoGravityResizeAspect:保留长宽比,未填充部分会有黑边
         AVLayerVideoGravityResizeAspectFill:保留长宽比,填充全部的区域
         AVLayerVideoGravityResize:拉伸填满全部的空间
        */
        preViewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        [self.view.layer addSublayer:preViewLayer];
        self.view.layer.masksToBounds = YES;
        preViewLayer;
    });

NOTE: 这里的AVCaptureVideoPreviewLayer对象利用 initWithSession: 初始化的时候这个session就是咱们前面初始化的session。

 

解读代码......没了!剩下的开始和结束的就没有什么好说的了,还有一个点值得咱们说说就是:  AVCaptureFileOutputRecordingDelegate 你看它的名字就知道是什么了,它就是咱们AVCaptureMovieFileOutput的代理,看看个代理里面的方法,首先这个代理是在咱们的开始录制方法里面设置的:

- (void)startRecordingToOutputFileURL:(NSURL*)outputFileURL recordingDelegate:(id<AVCaptureFileOutputRecordingDelegate>)delegate

就是这个开始的方法,最后的AVCaptureFileOutputRecordingDelegate就是咱们须要注意的代理,咱们看这个代理里面的方法解释:

@protocol AVCaptureFileOutputRecordingDelegate <NSObject>
@optional

 @method captureOutput:didStartRecordingToOutputFileAtURL:fromConnections:
 @abstract
 //开始往file里面写数据
 Informs the delegate when the output has started writing to a file.
 
 @param captureOutput
 The capture file output that started writing the file.
 @param fileURL
 The file URL of the file that is being written.
 @param connections
 An array of AVCaptureConnection objects attached to the file output that provided the data that is being written to the file.
 
 @discussion
 //方法在给输出文件当中写数据的时候开始调用 若是在开始写数据的时候有错误  方法就不会被调用  但 captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error: and captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:
 //这两个方法老是会被调用,即便没有数据写入
 
 This method is called when the file output has started writing data to a file. If an error condition prevents any data from being written, this method may not be called. captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error: and captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: will always be called, even if no data is written.
 Clients 顾客;客户端;委托方   specific特殊  efficient 有效的
 Clients should not assume that this method will be called on a specific thread, and should also try to make this method as efficient as possible.
方法:
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections;

 @method captureOutput:didPauseRecordingToOutputFileAtURL:fromConnections:
 @abstract 摘要
 
 没当客户端成功的暂停了录制时候这个方法就会被调用
 Called whenever the output is recording to a file and successfully pauses the recording at the request of the client.
 
 @param captureOutput
 The capture file output that has paused its file recording.
 @param fileURL
 The file URL of the file that is being written.
 @param connections
 attached 附加   provided 提供
 An array of AVCaptureConnection objects attached to the file output that provided the data that is being written to the file.
 
 @discussion  下面的谈论告诉咱们你要是调用了stop方法,这个代理方法是不会被调用的
 Delegates can use this method to be informed when a request to pause recording is actually respected. It is safe for delegates to change what the file output is currently doing (starting a new file, for example) from within this method. If recording to a file is stopped, either manually or due to an error, this method is not guaranteed to be called, even if a previous call to pauseRecording was made.
 
 Clients should not assume that this method will be called on a specific thread, and should also try to make this method as efficient as possible.
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didPauseRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections NS_AVAILABLE(10_7, NA);

 @method captureOutput:didResumeRecordingToOutputFileAtURL:fromConnections:
 @abstract 这个摘要告诉咱们的是这个方法在你暂停完以后成功的回复了录制就会进这个代理方法
 Called whenever the output, at the request of the client, successfully resumes a file recording that was paused.
 
 @param captureOutput
 The capture file output that has resumed(从新开始) its paused file recording.
 @param fileURL
 The file URL of the file that is being written.
 @param connections
 An array of AVCaptureConnection objects attached to the file output that provided the data that is being written to the file.
 
 @discussion
 Delegates can use this method to be informed(通知) when a request to resume recording is actually respected. It is safe for delegates to change what the file output is currently doing (starting a new file, for example) from within this method. If recording to a file is stopped, either manually or due to an error, this method is not guaranteed(确保、有保证) to be called, even if a previous call to resumeRecording was made.
 
 Clients should not assume that this method will be called on a specific thread, and should also try to make this method as efficient as possible.
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didResumeRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections NS_AVAILABLE(10_7, NA);

 @method captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:
 @abstract  这个方法在录制即将要结束的时候就会被调用
 Informs the delegate when the output will stop writing new samples to a file.
 
 @param captureOutput
 The capture file output that will finish writing the file.
 @param fileURL
 The file URL of the file that is being written.
 @param connections
 An array of AVCaptureConnection objects attached to the file output that provided the data that is being written to the file.
 @param error
 An error describing what caused the file to stop recording, or nil if there was no error.
 
 @discussion
 This method is called when the file output will stop recording new samples to the file at outputFileURL, either because startRecordingToOutputFileURL:recordingDelegate: or stopRecording were called, or because an error, described by the error parameter, occurred (if no error occurred, the error parameter will be nil). This method will always be called for each recording request, even if no data is successfully written to the file.
 
 Clients should not assume that this method will be called on a specific thread, and should also try to make this method as efficient as possible.
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput willFinishRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections error:(NSError *)error NS_AVAILABLE(10_7, NA);

 下面是必须实现的代理方法,就是录制成功结束的时候调用的方法
 @required

 @method captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:
 @abstract
 Informs the delegate when all pending data has been written to an output file.
 
 @param captureOutput
 The capture file output that has finished writing the file.
 @param fileURL
 The file URL of the file that has been written.
 @param connections
 An array of AVCaptureConnection objects attached to the file output that provided the data that was written to the file.
 @param error
 An error describing what caused the file to stop recording, or nil if there was no error.
 
 @discussion
 This method is called when the file output has finished writing all data to a file whose recording was stopped, either because startRecordingToOutputFileURL:recordingDelegate: or stopRecording were called, or because an error, described by the error parameter, occurred (if no error occurred, the error parameter will be nil). This method will always be called for each recording request, even if no data is successfully written to the file.
 
 Clients should not assume that this method will be called on a specific thread.
 Delegates are required to implement this method.

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error;
@end

以上就是咱们总结的关于AVCaptureSession + AVCaptureMovieFileOutput录制视频咱们须要注意的一些地方,可能直接这样分开的看代码和文章感受不太友好,可读性比较差,其实最好的就是跟着文章文字内容读,具体的代码看Demo。至于咱们这里写的具体的代码内容,推荐仍是看Demo会好一点。毕竟Demo里面全都有! 

 

 

视频录制 AVCaptureSession + AVAssetWriter  


 

      上面说了AVCaptureSession + AVCaptureMovieFileOutput,如今说说咱们的AVCaptureSession + AVAssetWriter,这个过程比起咱们前面提到的是要复杂的,先来一个大概的归纳,而后把它在解析一下:

      一、建录制会话

      二、设置视频的输入 和 输出

      三、设置音频的输入 和 输出

      四、添加视频预览层

      五、开始采集数据,这个时候尚未写入数据,用户点击录制后就能够开始写入数据

      六、初始化AVAssetWriter, 咱们会拿到视频和音频的数据流,用AVAssetWriter写入文件,这一步须要咱们本身实现。

      整个大概的过程咱们能够整理成这六点,看着好像比前面的要简单,其实比前面的是要复杂的。咱们再仔细把这六步拆分一下,你就知道这六步涉及到的内容是要比前面的多一点的:

      

      一、初始化须要的线程队列(这个后面你能够了解到为何须要这些队列)

      二、初始化AVCaptureSession录制会话

      三、须要一个视频流的输入类: 利用AVCaptureDevice  录制设备类,根据 AVMediaType 初始化 AVCaptureDeviceInput  录制输入设备类,是要分音频和视频的,这点和前面的相似。把他们添加到录制会话里面。

      四、初始化 AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput ,把它们添加到AVCaptureSession对象,根据你初始化的线程设置setSampleBufferDelegate代理对象

      五、根据AVCaptureSession获得一个AVCaptureVideoPreviewLayer预览曾对象,用于预览你的拍摄画面

      六、初始化AVAssetWrite 再给AVSssetWrite经过addInput添加AVAssetWriterInput,AVAssetWriterInput也是根据AVMediaType分为video和audio,这个是重点!!!有许多参数须要设置!

      七、经过 AVCaptureSession startRunning 开始采集数据,采集到的数据就会走你设置的输出对象AVCaptureAudioDataOutput的代理,代理会遵照AVCaptureVideoDataOutputSampleBufferDelegate协议。你须要在这个协议的方法里面去开始经过 AVAssetWriter 对象 startWriting 开始写入数据

      八、当写完数据以后就会走AVAssetWriter的finishWritingWithCompletionHandler方法,在这里你就能够拿到你录制的视频去作其余的处理了!

      九、咱们再Demo中使用了Photos框架,这个也是必要从新学习的。 

      咱们和前面的同样,一步步的解析一下上面每一步的代码:

 

      解读代码第一步:      

#pragma mark --
#pragma mark -- initDispatchQueue
-(void)initDispatchQueue{
   
        // 视频队列
        self.videoDataOutputQueue = dispatch_queue_create(CAPTURE_SESSION_QUEUE_VIDEO, DISPATCH_QUEUE_SERIAL);
        /*
          解释:
          用到dispatch_set_target_queue是为了改变self.videoDataOutputQueue串行队列的优先级,要是咱们不使用dispatch_set_target_queue
          咱们建立的队列执行的优先级都与默认优先级的Global Dispatch queue相同
         */
        dispatch_set_target_queue(self.videoDataOutputQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
        
        // 音频队列
        self.audioDataOutputQueue = dispatch_queue_create(CAPTURE_SESSION_QUEUE_AUDIO, DISPATCH_QUEUE_SERIAL);
        
        // WRITER队列
        self.writingQueue = dispatch_queue_create(CAPTURE_SESSION_QUEUE_ASSET_WRITER, DISPATCH_QUEUE_SERIAL );  
}

 

      解读代码第2、三步: 

#pragma mark --
#pragma mark -- 初始化AVCaptureDevice 以及 AVCaptureDeviceInput
-(BOOL)SetSessioninputs:(NSError *)error{
        
        // 具体的为何能这样写,以及代码里面一些变量的具体的含义参考LittieVideoController
        self.captureSession = ({
                
                AVCaptureSession * captureSession = [[AVCaptureSession alloc]init];
                if ([captureSession canSetSessionPreset:AVCaptureSessionPresetMedium]) {
                        
                        [captureSession canSetSessionPreset:AVCaptureSessionPresetMedium];
                }
                captureSession;
        });

        // capture 捕捉 捕获
        /*
          视频输入类
          AVCaptureDevice 捕获设备类
          AVCaptureDeviceInput 捕获设备输入类
         */
        
        AVCaptureDevice * videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        AVCaptureDeviceInput * videoInput = [AVCaptureDeviceInput deviceInputWithDevice: videoDevice error: &error];
        
        if (!videoInput) {
                return NO;
        }
        
        // 给捕获会话类添加输入捕获设备
        if ([self.captureSession canAddInput:videoInput]) {
                
                [self.captureSession addInput:videoInput];
        }else{
                return NO;
        }
        
        /*
          添加音频捕获设备
         */
        AVCaptureDevice * audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
        AVCaptureDeviceInput * audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
        
        if (!audioDevice) {
                
                return NO;
        }
        
        if ([self.captureSession canAddInput:audioInput]) {
                
                [self.captureSession addInput:audioInput];
        }
        return YES;
}

 

      解读代码第4、五步: 

#pragma mark --
#pragma mark -- 初始化AVCaptureSession 以及 AVCaptureVideoDataOutput AVCaptureAudioDataOutput

-(void)captureSessionAddOutputSession{

        // 视频videoDataOutput
        self.videoDataOutput = ({
        
                AVCaptureVideoDataOutput * videoDataOutput = [[AVCaptureVideoDataOutput alloc]init];
                videoDataOutput.videoSettings = nil;
                videoDataOutput.alwaysDiscardsLateVideoFrames = NO;
                videoDataOutput;
             
        });
        
        // Sample样品 Buffer 缓冲
        [self.videoDataOutput setSampleBufferDelegate:self queue:self.videoDataOutputQueue];
        self.videoDataOutput.alwaysDiscardsLateVideoFrames = YES; //当即丢弃旧帧,节省内存,默认YES
      
        if ([self.captureSession canAddOutput:self.videoDataOutput]) {
                
                [self.captureSession addOutput:self.videoDataOutput];
        }
        // 音频audioDataOutput
        self.audioDataOutput = ({
        
                AVCaptureAudioDataOutput * audioDataOutput = [[AVCaptureAudioDataOutput alloc]init];
                audioDataOutput;
        
        });
        [self.audioDataOutput setSampleBufferDelegate:self queue:self.audioDataOutputQueue];
        if ([self.captureSession canAddOutput:self.audioDataOutput]) {
                
                [self.captureSession addOutput:self.audioDataOutput];
        }
        /*
         用于展现录制的画面
         */
        self.captureVideoPreviewLayer = ({
                
                AVCaptureVideoPreviewLayer * preViewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
                preViewLayer.frame = CGRectMake(10, 50, 355, 355);
                
                /*
                 AVLayerVideoGravityResizeAspect:保留长宽比,未填充部分会有黑边
                 AVLayerVideoGravityResizeAspectFill:保留长宽比,填充全部的区域
                 AVLayerVideoGravityResize:拉伸填满全部的空间
                 */
                preViewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
                [self.view.layer addSublayer:preViewLayer];
                self.view.layer.masksToBounds = YES;
                preViewLayer;
        });
}

NOTE: 注意这里的 setSampleBufferDelegate 这个方法,经过这个方法有两点你就理解了,一是为何咱们须要队列。二就是为何咱们处理采集到的视频、音频数据的时候是在这个 AVCaptureVideoDataOutputSampleBufferDelegate协议的方法里面。

 

   解读代码第六步:(重点,要说的都在代码注释里面)

#pragma mark --
#pragma mark -- 初始化AVAssetWriterInput
-(void)initAssetWriterInputAndOutput{
        
        NSError * error;
        self.assetWriter = ({
        
                AVAssetWriter * assetWrite = [[AVAssetWriter alloc]initWithURL:[NSURL fileURLWithPath:self.dataDirectory] fileType:AVFileTypeMPEG4 error:&error];
                NSParameterAssert(assetWrite);
                assetWrite;
        });
        
        //每像素比特
        CGSize outputSize = CGSizeMake(355, 355);
        NSInteger numPixels = outputSize.width * outputSize.height;
        CGFloat   bitsPerPixel = 6.0;
        NSInteger bitsPerSecond = numPixels * bitsPerPixel;
        // [NSNumber numberWithDouble:128.0*1024.0]
        
        /*
         AVVideoCompressionPropertiesKey   硬编码参数
         
         AVVideoAverageBitRateKey          视频尺寸*比率
         AVVideoMaxKeyFrameIntervalKey     关键帧最大间隔,1为每一个都是关键帧,数值越大压缩率越高
         AVVideoExpectedSourceFrameRateKey 帧率
         
         */
        NSDictionary * videoCpmpressionDic = @{AVVideoAverageBitRateKey:@(bitsPerSecond),
                                               AVVideoExpectedSourceFrameRateKey:@(30),
                                               AVVideoMaxKeyFrameIntervalKey : @(30),
                                               AVVideoProfileLevelKey : AVVideoProfileLevelH264BaselineAutoLevel };
   
        /*
         AVVideoScalingModeKey 填充模式 Scaling 缩放
         AVVideoCodecKey       编码格式
         */
        NSDictionary * videoCompressionSettings = @{ AVVideoCodecKey : AVVideoCodecH264,
                                                    AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill,
                                                    AVVideoWidthKey  : @(outputSize.height),
                                                    AVVideoHeightKey : @(outputSize.width),
                                                    AVVideoCompressionPropertiesKey : videoCpmpressionDic };
        //Compression 压缩
        if ([self.assetWriter canApplyOutputSettings:videoCompressionSettings forMediaType:AVMediaTypeVideo]) {
                
                self.videoWriterInput = ({
                        
                        AVAssetWriterInput * input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoCompressionSettings];
                        NSParameterAssert(input);
                         //expectsMediaDataInRealTime 必须设为yes,须要从capture session 实时获取数据
                        input.expectsMediaDataInRealTime = YES;
                        input.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
                        input;
                });
                
                
                if ([self.assetWriter canAddInput:self.videoWriterInput]) {
                        
                        [self.assetWriter addInput:self.videoWriterInput];
                }
        }
        
        // 下面这些属性设置会影响到语音是否能被正常的录入
        // Channel 频道
        // AudioChannelLayout acl;
        
        // void * memset(void *s,int c,size_t n)总的做用:将已开辟内存空间 s 的首 n 个字节的值设为值 c
        // bzero() 会将内存块(字符串)的前n个字节清零,其原型为:void bzero(void *s, int n)
        
        //bzero(&acl, sizeof(acl));
        //AVChannelLayoutKey:[NSData dataWithBytes: &acl length: sizeof(acl)],
        /*
         
         AVAudioRecorder 录音类这个后面说
         能够设置的一些属性 :
         <1>AVNumberOfChannelsKey    通道数
         <2>AVSampleRateKey          采样率 通常用44100
         <3>AVLinearPCMBitDepthKey   比特率 通常设16 32
         <4>AVEncoderAudioQualityKey 质量
         <5>AVEncoderBitRateKey      比特采样率 通常是128000
         <6>AVChannelLayoutKey       通道布局值是一个包含AudioChannelLayout的NSData对象
         */
        
        NSDictionary * audioSettings = @{  AVFormatIDKey:@(kAudioFormatMPEG4AAC) ,
                                           AVEncoderBitRatePerChannelKey:@(64000),
                                           AVSampleRateKey:@(44100.0),
                                           AVNumberOfChannelsKey:@(1)};
        
        if ([self.assetWriter canApplyOutputSettings:audioSettings forMediaType:AVMediaTypeAudio]) {
                
                self.audioWriterInput = ({
                
                        AVAssetWriterInput * input = [[AVAssetWriterInput alloc]initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
                        
                        //Parameter 参数 系数 参量
                        //NSParameterAssert注意条件书写不支持逻辑或语法
                        
                        /*
                         注意它和NSAssert的区别
                         在NSAssert中你是能够写逻辑判断的语句的。好比:
                         NSAssert(count>10, @"总数必须大于10"); 这条语句中要是count<=10 就会报错
                         */
                        
                        NSParameterAssert(input);
                        input.expectsMediaDataInRealTime = YES;
                        input;
                
                });
                
                if ([self.assetWriter canAddInput:self.audioWriterInput]) {
                        
                        [self.assetWriter addInput:self.audioWriterInput];
                }
        }
        self.writeState = FMRecordStateRecording;
}

      后面的开始和结束的部分咱们就不在说了,重点仍是!!! 看Demo,由于这些注释Demo里面全都有,边看代码看注释应该效果会更好,比这样直白的看着文章效果确定要好!最后咱们比较一下上面的两种录制方式: 

 

 

AVCaptureMovieFileOutput 和 AVAssetWriter  方式比较


 

      相同点:数据采集都在AVCaptureSession中进行,视频和音频的输入都同样,画面的预览一致。

      不一样点:输出不一致

      AVCaptureMovieFileOutput 只须要一个输出便可,指定一个文件路后,视频和音频会写入到指定路径,不须要其余复杂的操做。

      AVAssetWriter 须要 AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 两个单独的输出,拿到各自的输出数据后,而后本身进行相应的处理。可配参数不一致,AVAssetWriter能够配置更多的参数。

      视频剪裁不一致,AVCaptureMovieFileOutput 若是要剪裁视频,由于系统已经把数据写到文件中了,咱们须要从文件中独到一个完整的视频,而后处理;而AVAssetWriter咱们拿到的是数据流,尚未合成视频,对数据流进行处理,因此两则剪裁方式也是不同。

      咱们再说说第一种方式,在微信官方优化视频录制文章中有这样一段话:

      “因而用AVCaptureMovieFileOutput(640*480)直接生成视频文件,拍视频很流畅。然而录制的6s视频大小有2M+,再用MMovieDecoder+MMovieWriter压缩至少要7~8s,影响聊天窗口发小视频的速度。”

      这段话也反应出了第一种方式的缺点!而后在我看这类资料的时候,又看到这样一段话:

      “若是你想要对影音输出有更多的操做,你可使用 AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 而不是咱们上节讨论的 AVCaptureMovieFileOutput。 这些输出将会各自捕获视频和音频的样本缓存,接着发送到它们的代理。代理要么对采样缓冲进行处理 (好比给视频加滤镜),要么保持原样传送。使用 AVAssetWriter 对象能够将样本缓存写入文件

      这样就把这两种之间的优劣进行了一个比较,但愿看到这文章的每个同行都能有收获吧。  

      个人博客即将同步至腾讯云+社区,邀请你们一同入驻。

 

      Demo地址

相关文章
相关标签/搜索