【iOS】視頻錄像相關(guān)功能調(diào)研(一)

1绢掰、實(shí)現(xiàn)視頻錄像的幾種方式

  • UIImagePickerController
  • AVCaptureSession + AVCaptureMovieFileOutput
  • AVCaptureSession + AVAssetWriter

UIImagePickerController系統(tǒng)封裝好的UI闹究,直接輸出視頻文件
AVCaptureSession + AVCaptureMovieFileOutput 支持自定義UI,直接輸出視頻文件
AVCaptureSession + AVAssetWriter支持自定義UI唱较,輸出的是視頻幀和音頻幀,需要自己處理硼一,拼接成視頻文件

2遵倦、系統(tǒng)封裝好的 UIImagePickerController

2.1 UIImagePickerController方式

是目前集成相機(jī)最簡(jiǎn)單的方式,但是不知道自定義相機(jī)额嘿,這是一個(gè)封裝了完整視頻捕獲管線和相機(jī) UI 的 view controller瘸恼。

2.1.1 info.plist 設(shè)置

Privacy - Microphone Usage Description 是否允許設(shè)備調(diào)用您的麥克風(fēng)?
Privacy - Camera Usage Description 是否允許設(shè)備調(diào)用您的相機(jī)?

2.1.2 是否支持相機(jī)錄制

在實(shí)例化相機(jī)之前册养,首先要檢查設(shè)備是否支持相機(jī)錄制:

        /// 判斷是否支持錄像
        if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
            if let availableMediaTypes = UIImagePickerController.availableMediaTypes(for: UIImagePickerController.SourceType.camera) {
                if !availableMediaTypes.contains("public.movie") {
                    print("不支持錄像")
                    return
                }
            }
        }

2.1.3 權(quán)限確認(rèn)

視頻權(quán)限和音頻權(quán)限確認(rèn)

        /// 視頻權(quán)限
        AVCaptureDevice.requestAccess(for: AVMediaType.video) {[weak self] (granted) in
            guard let weakSelf = self else {
                return
            }
            if !granted {
                print("無權(quán)限訪問相機(jī)")
                return
            }
            
            // 錄音權(quán)限
            AVCaptureDevice.requestAccess(for: AVMediaType.audio) {[weak self] (granted) in
                guard let weakSelf = self else {
                    return
                }
                
                if !granted {
                    print("無權(quán)限訪問麥克風(fēng)")
                    return
                }
                // 進(jìn)入錄像頁面
                weakSelf.present(weakSelf.pickerController, animated: true, completion: nil)
            }
        }

2.1.4 創(chuàng)建UIImagePickerController 對(duì)象

然后創(chuàng)建一個(gè) UIImagePickerController 對(duì)象东帅,設(shè)置好代理便于進(jìn)一步處理錄制好的視頻 (比如存到相冊(cè)) 以及對(duì)于用戶關(guān)閉相機(jī)作出響應(yīng):

    lazy var pickerController: UIImagePickerController = {
        let pickerController = UIImagePickerController()
        // 設(shè)置圖像選取控制器的來源模式為相機(jī)模式 相機(jī)、相冊(cè)
        pickerController.sourceType = UIImagePickerController.SourceType.camera
        // 設(shè)置相機(jī)的類型 public.image  public.movie
        pickerController.mediaTypes = ["public.movie",]
        // 設(shè)置攝像頭 前捕儒、后
        pickerController.cameraDevice = UIImagePickerController.CameraDevice.rear;
        // 設(shè)置攝像頭閃光燈模式
        // pickerController.cameraFlashMode = UIImagePickerController.CameraFlashMode.auto
        // 設(shè)置攝像圖像品質(zhì)
        pickerController.videoQuality = UIImagePickerController.QualityType.typeHigh
        // 設(shè)置最長攝像時(shí)間
        pickerController.videoMaximumDuration = 30
        // 允許用戶進(jìn)行編輯
        pickerController.allowsEditing = false
        // 設(shè)置委托對(duì)象
        pickerController.delegate = self
        return pickerController
    }()

2.1.5 UIImagePickerControllerDelegate代理的實(shí)現(xiàn)

extension RecordVideoViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        let mediaType = info[UIImagePickerController.InfoKey.mediaType] as! String

        if mediaType == "public.movie" {
            // 獲取視頻文件的url
            let mediaURL = info[UIImagePickerController.InfoKey.mediaURL] as! NSURL
            // 視頻文件的地址
            let pathString = mediaURL.relativePath
            print("視頻地址:" + pathString!)
            
            DispatchQueue.global().async {
                //判斷能不能保存到相簿
                if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(pathString!)) {
                    //保存視頻到相簿
                    UISaveVideoAtPathToSavedPhotosAlbum(pathString!, self,  #selector(self.saveVideo(videoPath:didFinishSavingWithError:contextInfo:)), nil)
                }
                DispatchQueue.main.async {
                    picker.dismiss(animated: true, completion: nil)
                }
            }
            
        }
    }
    
    @objc private func saveVideo(videoPath:String, didFinishSavingWithError error: NSError?, contextInfo: AnyObject) {
        if error != nil{
            print("保存視頻 失敗")
        }else{
            print("保存視頻 成功")
        }
    }
}

3冰啃、自定義相機(jī) AVFoundation

AVFoundation 中關(guān)于視頻捕獲的主要的類是 AVCaptureSession。它負(fù)責(zé)調(diào)配影音輸入與輸出之間的數(shù)據(jù)流

3.1 AVCaptureSession + AVCaptureMovieFileOutput方式

3.1.1 info.plist 設(shè)置

Privacy - Microphone Usage Description 是否允許設(shè)備調(diào)用您的麥克風(fēng)刘莹?
Privacy - Camera Usage Description 是否允許設(shè)備調(diào)用您的相機(jī)?

3.1.2 權(quán)限確認(rèn)

視頻權(quán)限和音頻權(quán)限確認(rèn)

        /// 視頻權(quán)限
        AVCaptureDevice.requestAccess(for: AVMediaType.video) {[weak self] (granted) in
            guard let weakSelf = self else {
                return
            }
            if !granted {
                print("無權(quán)限訪問相機(jī)")
                return
            }
            
            // 錄音權(quán)限
            AVCaptureDevice.requestAccess(for: AVMediaType.audio) {[weak self] (granted) in
                guard let weakSelf = self else {
                    return
                }
                
                if !granted {
                    print("無權(quán)限訪問麥克風(fēng)")
                    return
                }
                
            }
        }

3.1.3 創(chuàng)建AVCaptureSession

使用一個(gè) capture session,你需要先實(shí)例化焚刚,添加輸入與輸出点弯,設(shè)置分辨率,接著啟動(dòng)從輸入到輸出之間的數(shù)據(jù)流:

    /// 視頻捕獲會(huì)話
    let captureSession = AVCaptureSession()

3.1.4 添加視頻輸入設(shè)備

    //MARK: 添加視頻輸入設(shè)備
    func addInputVideo() {
        self.captureSession.beginConfiguration()

        let videoDevice = AVCaptureDevice.default(for: AVMediaType.video)!
        let videoInput = try? AVCaptureDeviceInput(device: videoDevice)
        
        if self.captureSession.canAddInput(videoInput!) {
            self.captureSession.addInput(videoInput!)
        }
        
        self.captureSession.commitConfiguration()
    }

3.1.5 添加音頻輸入設(shè)備

    //MARK: 添加音頻輸入設(shè)備
    func addInputAudio() {
        self.captureSession.beginConfiguration()
        
        let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
        let audioInput = try? AVCaptureDeviceInput(device: audioDevice!)
        
        if self.captureSession.canAddInput(audioInput!) {
            self.captureSession.addInput(audioInput!);
        }
        self.captureSession.commitConfiguration()
    }

3.1.6 設(shè)置分辨率

    //MARK: 設(shè)置分辨率
    func setPreset() {
        self.captureSession.beginConfiguration()
        if self.captureSession.canSetSessionPreset(AVCaptureSession.Preset.hd1280x720) {
            self.captureSession.sessionPreset = AVCaptureSession.Preset.hd1280x720
        }
        self.captureSession.commitConfiguration()
    }
    

3.1.7 設(shè)置輸出

    //MARK: 設(shè)置輸出
    func setOutput() {
        self.captureSession.beginConfiguration()
        
        if let captureConnection = self.fileOutput.connection(with: AVMediaType.video) {
            // 防止抖動(dòng)
            if captureConnection.isVideoStabilizationSupported {
                captureConnection.preferredVideoStabilizationMode = .auto
            }
            // 預(yù)覽圖層和視頻方向保存一直
            captureConnection.videoOrientation = (self.videoLayer.connection?.videoOrientation)!
        }
        // 視頻時(shí)長默認(rèn)10秒矿咕,此設(shè)置不受限制
        self.fileOutput.movieFragmentInterval = CMTime.invalid
        if  self.captureSession.canAddOutput(self.fileOutput) {
            self.captureSession.addOutput(self.fileOutput)
        }
        self.captureSession.commitConfiguration()
    }

3.1.8 設(shè)置顯示采集畫面抢肛,并開始采集

    /// 攝像頭采集畫面
    lazy var videoLayer: AVCaptureVideoPreviewLayer = {
        let videoLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
        videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        videoLayer.masksToBounds = true
        return videoLayer
    }()
    //使用AVCaptureVideoPreviewLayer可以將攝像頭的拍攝的實(shí)時(shí)畫面顯示在ViewController上
        DispatchQueue.main.async {
            weakSelf.videoLayer.frame = weakSelf.view.bounds
            weakSelf.view.layer.addSublayer(weakSelf.videoLayer)
            weakSelf.captureSession.startRunning()
            //創(chuàng)建按鈕
            weakSelf.setUI()
        }

3.1.9 開始錄制

    //MARK: 開始錄制
    @objc func starRecordVideo() {
        
        if  !self.isRecording {
            //設(shè)置錄像的保存地址
            let filePath = self.getNewPath(videoTyle: AVFileType.mp4)
            let fileURL = URL(fileURLWithPath: filePath)
            //啟動(dòng)視頻編碼輸出
            fileOutput.startRecording(to:fileURL, recordingDelegate:  self )
            
            //記錄狀態(tài):錄像中...
            self.isRecording = true
            //開始狼钮、結(jié)束按鈕顏色改變
            self.starButton.backgroundColor = UIColor.lightGray
            self.starButton.isEnabled = false
            
            self.stopButton.backgroundColor = UIColor.blue
            self.stopButton.isEnabled = true
        }
    }

3.1.10 結(jié)束錄制

    //MARK: 結(jié)束錄制
    @objc func stopRecordVideo() {
        if self.isRecording {
            //停止視頻編碼輸出
            fileOutput.stopRecording()
            
            //記錄狀態(tài):錄像結(jié)束
            self .isRecording =  false
            
            //開始、結(jié)束按鈕顏色改變
            self.starButton.backgroundColor = UIColor.red
            self.starButton.isEnabled = true
            
            self.stopButton.backgroundColor = UIColor.lightGray
            self.stopButton.isEnabled = false
        }
    }

3.1.11 AVCaptureFileOutputRecordingDelegate

//MARK: AVCaptureFileOutputRecordingDelegate
extension RecordVideo2ViewController:AVCaptureFileOutputRecordingDelegate
{
    // 開始錄制
    func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) {
        
    }
    // 結(jié)束錄制
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        // 獲取視頻文件大小
        self.getVideoSize(videoUrl: outputFileURL)
        // 獲取視頻文件時(shí)長
        self.getVideoLength(videoUrl: outputFileURL)
        // 保存相冊(cè)一份捡絮,便于測(cè)試
        self.saveVideoToAlbum(videoUrl: outputFileURL)
        // 獲取指定時(shí)間的幀
        self.getImage(videoUrl: outputFileURL, cmtime: CMTimeMake(value: 1, timescale: 1), width: 300)
        
        // 壓縮視頻
        let newPath = self.getNewPath(videoTyle: AVFileType.mov)
        print(newPath)
        self.convertVideo(inputURL: outputFileURL, outputURL: URL(fileURLWithPath: newPath), presetName: AVAssetExportPresetMediumQuality) { (success) in
            if success {
                print("壓縮成功")
            }else{
                print("壓縮失敗")
            }
        }
    }
}

3.2 AVCaptureSession + AVAssetWriter方式

對(duì)視頻進(jìn)一步了解熬芜,可以使用AVCaptureVideoDataOutputAVCaptureAudioDataOutput來會(huì)各自捕獲視頻和音頻的樣本緩存,而不是AVCaptureMovieFileOutpu福稳。
接著我們可以使用他們的代理AVCaptureVideoDataOutputSampleBufferDelegateAVCaptureAudioDataOutputSampleBufferDelegate涎拉,可以對(duì)采樣緩沖進(jìn)行處理 (比如給視頻加濾鏡),或者保持原樣傳送的圆。
然后使用 AVAssetWriter 對(duì)象可以將樣本緩存寫入文件

3.2.1 info.plist 設(shè)置

Privacy - Microphone Usage Description 是否允許設(shè)備調(diào)用您的麥克風(fēng)鼓拧?
Privacy - Camera Usage Description 是否允許設(shè)備調(diào)用您的相機(jī)?

3.2.2 權(quán)限確認(rèn)

視頻權(quán)限和音頻權(quán)限確認(rèn)

        /// 視頻權(quán)限
        AVCaptureDevice.requestAccess(for: AVMediaType.video) {[weak self] (granted) in
            guard let weakSelf = self else {
                return
            }
            if !granted {
                print("無權(quán)限訪問相機(jī)")
                return
            }
            
            // 錄音權(quán)限
            AVCaptureDevice.requestAccess(for: AVMediaType.audio) {[weak self] (granted) in
                guard let weakSelf = self else {
                    return
                }
                
                if !granted {
                    print("無權(quán)限訪問麥克風(fēng)")
                    return
                }
                
            }
        }

3.2.3 創(chuàng)建AVCaptureSession

使用一個(gè) capture session,你需要先實(shí)例化越妈,添加輸入與輸出季俩,設(shè)置分辨率,接著啟動(dòng)從輸入到輸出之間的數(shù)據(jù)流:

    /// 視頻捕獲會(huì)話
    let captureSession = AVCaptureSession()

3.2.4 添加視頻輸入設(shè)備

    //MARK: 添加視頻輸入設(shè)備
    func addInputVideo() {
        self.captureSession.beginConfiguration()

        let videoDevice = AVCaptureDevice.default(for: AVMediaType.video)!
        let videoInput = try? AVCaptureDeviceInput(device: videoDevice)
        
        if self.captureSession.canAddInput(videoInput!) {
            self.captureSession.addInput(videoInput!)
        }
        
        self.captureSession.commitConfiguration()
    }

3.2.5 添加音頻輸入設(shè)備

    //MARK: 添加音頻輸入設(shè)備
    func addInputAudio() {
        self.captureSession.beginConfiguration()
        
        let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
        let audioInput = try? AVCaptureDeviceInput(device: audioDevice!)
        
        if self.captureSession.canAddInput(audioInput!) {
            self.captureSession.addInput(audioInput!);
        }
        self.captureSession.commitConfiguration()
    }

3.2.6 設(shè)置分辨率

    //MARK: 設(shè)置分辨率
    func setPreset() {
        self.captureSession.beginConfiguration()
        if self.captureSession.canSetSessionPreset(AVCaptureSession.Preset.hd1280x720) {
            self.captureSession.sessionPreset = AVCaptureSession.Preset.hd1280x720
        }
        self.captureSession.commitConfiguration()
    }
    

3.2.7 添加視頻輸出梅掠、音頻輸出

    //MARK: 添加視頻輸出酌住、音頻輸出
    func addOutputVideoAndAudio() {
        self.captureSession.beginConfiguration()
        
        self.videoDataOutput.setSampleBufferDelegate(self, queue: self.sessionQueue!)
        if self.captureSession.canAddOutput(self.videoDataOutput) {
            self.captureSession.addOutput(self.videoDataOutput)
        }
        
        self.audioDataOutput.setSampleBufferDelegate(self, queue: self.sessionQueue)
        if self.captureSession.canAddOutput(self.audioDataOutput) {
            self.captureSession.addOutput(self.audioDataOutput)
        }
        
        self.captureSession.commitConfiguration()
    }

3.2.8 設(shè)置顯示采集畫面,并開始采集

    /// 攝像頭采集畫面
    lazy var videoLayer: AVCaptureVideoPreviewLayer = {
        let videoLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
        videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        videoLayer.masksToBounds = true
        return videoLayer
    }()
    //使用AVCaptureVideoPreviewLayer可以將攝像頭的拍攝的實(shí)時(shí)畫面顯示在ViewController上
        DispatchQueue.main.async {
            weakSelf.videoLayer.frame = weakSelf.view.bounds
            weakSelf.view.layer.addSublayer(weakSelf.videoLayer)
            weakSelf.captureSession.startRunning()
            //創(chuàng)建按鈕
            weakSelf.setUI()
        }

3.2.9 開始錄制

    //MARK: 開始錄制
    @objc func starRecordVideo() {
        
        if  !self.isRecording {
            print("開始錄制")
            //設(shè)置錄像的保存地址
            self.videoPath = self.getNewPath(videoTyle: AVFileType.mp4)
            print(self.videoPath)
            setAssetWriter(videoPath: self.videoPath)
            
            //記錄狀態(tài):錄像中...
            self.isRecording = true
            //開始阎抒、結(jié)束按鈕顏色改變
            self.starButton.backgroundColor = UIColor.lightGray
            self.starButton.isEnabled = false
            
            self.stopButton.backgroundColor = UIColor.blue
            self.stopButton.isEnabled = true
        }
        
    }

3.2.10 設(shè)置AVAssetWrite

    //MARK: 設(shè)置AssetWrite
    func setAssetWriter(videoPath: String) {
        //設(shè)置錄像的保存地址
        let fileURL = URL(fileURLWithPath: videoPath)
        
        if let assetWriter = try? AVAssetWriter.init(url: fileURL, fileType: AVFileType.mp4) {
            self.assetWriter = assetWriter
              
            var width = UIScreen.main.bounds.size.height
            
            var height = UIScreen.main.bounds.size.width
            //寫入視頻大小
            let numPixels = width*height
            //每像素比特
            let bitsPerPixel:CGFloat = 12.0;
            let bitsPerSecond = numPixels * bitsPerPixel;
            if (false) // 是否是劉海屏
            {
                width = UIScreen.main.bounds.size.height - 146;
                height = UIScreen.main.bounds.size.width;
            }
                
            let compressionProperties = [
                // 視頻尺寸*比率酪我,10.1相當(dāng)于AVCaptureSessionPresetHigh,數(shù)值越大挠蛉,顯示越精細(xì)
                AVVideoAverageBitRateKey : bitsPerSecond,
                // 設(shè)置輸出幀率
                AVVideoExpectedSourceFrameRateKey : 15,
                // 關(guān)鍵幀最大間隔祭示,1為每個(gè)都是關(guān)鍵幀,數(shù)值越大壓縮率越高
                AVVideoMaxKeyFrameIntervalKey : 15,
                // 畫面質(zhì)量
                AVVideoProfileLevelKey : AVVideoProfileLevelH264BaselineAutoLevel
            ] as [String : Any]
            
            let videoCompressionSettings = [
                AVVideoCodecKey : AVVideoCodecH264,
                AVVideoWidthKey : width * 2,
                AVVideoHeightKey : height * 2,
//                AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill,
                AVVideoCompressionPropertiesKey : compressionProperties
            ] as [String : Any]
            
            self.assetWriterVideoInput = AVAssetWriterInput.init(mediaType: AVMediaType.video, outputSettings: videoCompressionSettings)
            //expectsMediaDataInRealTime 必須設(shè)為yes谴古,需要從capture session 實(shí)時(shí)獲取數(shù)據(jù)
            self.assetWriterVideoInput?.expectsMediaDataInRealTime = true
            
            // 音頻設(shè)置
            let audioCompressionSettings = [
                // 每個(gè)聲道的比特率
                AVEncoderBitRatePerChannelKey : 28000,
                // 設(shè)置錄音格式
                AVFormatIDKey : kAudioFormatMPEG4AAC,
                // 設(shè)置通道,單聲道质涛,雙聲道  mp3 必須雙聲道
                AVNumberOfChannelsKey : 1,
                // 設(shè)置錄音采樣率,8000是電話采樣率掰担,對(duì)于一般錄音已經(jīng)夠了
                AVSampleRateKey : 22050,
                // 每個(gè)采樣點(diǎn)位數(shù),分為8汇陆、16、24带饱、32
                AVLinearPCMBitDepthKey: 16,
                // 質(zhì)量
                AVEncoderAudioQualityKey: AVAudioQuality.medium
            ] as [String : Any]
            
            self.assetWriterAudioInput = AVAssetWriterInput.init(mediaType: AVMediaType.audio, outputSettings: audioCompressionSettings)
            self.assetWriterAudioInput?.expectsMediaDataInRealTime = true
            if self.assetWriter!.canAdd(self.assetWriterVideoInput!) {
                self.assetWriter?.add(self.assetWriterVideoInput!)
            }
            
            if self.assetWriter!.canAdd(self.assetWriterAudioInput!) {
                self.assetWriter?.add(self.assetWriterAudioInput!)
            }
            
            self.canWrite = false
            
        } else {
            print("加載AVAssetWriter失敗")
        }
    }

3.2.12 視頻和音頻每一幀處理

extension RecordVideo3ViewController: AVCaptureVideoDataOutputSampleBufferDelegate,AVCaptureAudioDataOutputSampleBufferDelegate
{
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {        
        autoreleasepool {
            if !isRecording {
                return
            }
            if connection == self.videoDataOutput.connection(with: AVMediaType.video) {
                objc_sync_enter(self)
                self.appendSampleBuffer(sampleBuffer: sampleBuffer, mediaType: AVMediaType.video)
                objc_sync_exit(self)
            }
            if connection == self.audioDataOutput.connection(with: AVMediaType.audio) {
                objc_sync_enter(self)
                self.appendSampleBuffer(sampleBuffer: sampleBuffer, mediaType: AVMediaType.audio)
                objc_sync_exit(self)
            }
        }
    }
    func appendSampleBuffer(sampleBuffer:CMSampleBuffer, mediaType:AVMediaType) {
        autoreleasepool {
            if (!self.canWrite && mediaType == AVMediaType.video) {
                print("開始寫入AVAssetWriter")
                self.assetWriter?.startWriting()
                self.assetWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
                self.canWrite = true
            }
            
            //寫入視頻數(shù)據(jù)
            if mediaType == AVMediaType.video {
                if self.assetWriterVideoInput!.isReadyForMoreMediaData {
                    let success = self.assetWriterVideoInput!.append(sampleBuffer)
                    if !success {
                        //停止錄像
                        objc_sync_enter(self)
                        self.stopRecordVideo()
                        objc_sync_exit(self)
                    }
                }
            }
            
            //寫入視頻數(shù)據(jù)
            if mediaType == AVMediaType.audio {
                if self.assetWriterAudioInput!.isReadyForMoreMediaData {
                    let success = self.assetWriterAudioInput!.append(sampleBuffer)
                    if !success {
                        //停止錄像
                        objc_sync_enter(self)
                        self.stopRecordVideo()
                        objc_sync_exit(self)
                    }
                }
            }
        }
    }
}

3.2.13 結(jié)束錄制

    //MARK: 結(jié)束錄制
    @objc func stopRecordVideo() {
        if self.isRecording {
            print("結(jié)束錄制")
            //停止視頻編碼輸出
            if self.assetWriter != nil && self.assetWriter?.status == AVAssetWriter.Status.writing {
                self.assetWriter?.finishWriting { [weak self] in
                    guard let weakSelf = self else {
                        return
                    }
                    weakSelf.canWrite = false
                    weakSelf.assetWriter = nil
                    weakSelf.assetWriterAudioInput = nil
                    weakSelf.assetWriterVideoInput = nil
                }
            }
            
            //記錄狀態(tài):錄像結(jié)束
            self .isRecording =  false
            
            //開始毡代、結(jié)束按鈕顏色改變
            self.starButton.backgroundColor = UIColor.red
            self.starButton.isEnabled = true
            
            self.stopButton.backgroundColor = UIColor.lightGray
            self.stopButton.isEnabled = false
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市勺疼,隨后出現(xiàn)的幾起案子教寂,更是在濱河造成了極大的恐慌,老刑警劉巖执庐,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酪耕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡轨淌,警方通過查閱死者的電腦和手機(jī)迂烁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門看尼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盟步,你說我怎么就攤上這事藏斩。” “怎么了却盘?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵狰域,是天一觀的道長。 經(jīng)常有香客問我谷炸,道長北专,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任旬陡,我火速辦了婚禮拓颓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘描孟。我一直安慰自己驶睦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布匿醒。 她就那樣靜靜地躺著场航,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廉羔。 梳的紋絲不亂的頭發(fā)上溉痢,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音憋他,去河邊找鬼孩饼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛竹挡,可吹牛的內(nèi)容都是我干的镀娶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼揪罕,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼梯码!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起好啰,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤轩娶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后框往,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罢坝,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年搅窿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘁酿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡男应,死狀恐怖闹司,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沐飘,我是刑警寧澤游桩,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站耐朴,受9級(jí)特大地震影響借卧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜筛峭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一铐刘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧影晓,春花似錦镰吵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饵婆,卻和暖如春勺馆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侨核。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工草穆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芹关。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓续挟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親侥衬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诗祸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容