2017-08-26 28 views
10

我正在研究如何讓我的Swift iOS應用程序錄制視頻,並在30秒的延遲時間內在同一屏幕上播放。如何使用AVFoundation以幾秒鐘的時間同時錄製和播放捕獲的視頻?

我一直在使用official example來錄製視頻。然後我添加了一個按鈕,在屏幕的另一個視圖中觸發使用AVPlayer播放self.movieFileOutput?.outputFileURL。它接近我想要的,但很明顯,一旦它寫入到磁盤的文件結束時停止播放,並且在下一個緩衝區塊被寫入時不會繼續播放。

我可以每30秒停止一次視頻錄製並保存每個文件的URL,以便我可以播放它,但這意味着視頻捕獲和播放會出現中斷。

如何讓視頻錄製永不停止,並且播放始終在屏幕上隨時都有我想要的?

我見過類似的問題,所有的答案指向AVFoundation文檔。我無法找到如何使AVFoundation在錄製時將可預測的視頻塊從內存寫入磁盤。

+0

可能重複[同時錄製視頻和播放視頻](https://stackoverflow.com/questions/7707427/record-video-and-play-video-at-the-same-time) – 2017-08-26 23:36:33

回答

4

您可以通過錄制30s的視頻塊,然後將它們排列到AVQueuePlayer以實現無縫播放,從而實現您想要的效果。錄製視頻塊會很容易AVCaptureFileOutput在MacOS,但遺憾的是,在iOS你不能沒有丟幀創建新的數據塊,所以你必須使用wordier,下級AVAssetWriter API:

import UIKit 
import AVFoundation 

// TODO: delete old videos 
// TODO: audio 

class ViewController: UIViewController { 
    // capture 
    let captureSession = AVCaptureSession() 

    // playback 
    let player = AVQueuePlayer() 
    var playerLayer: AVPlayerLayer! = nil 

    // output. sadly not AVCaptureMovieFileOutput 
    var assetWriter: AVAssetWriter! = nil 
    var assetWriterInput: AVAssetWriterInput! = nil 

    var chunkNumber = 0 
    var chunkStartTime: CMTime! = nil 
    var chunkOutputURL: URL! = nil 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     playerLayer = AVPlayerLayer(player: player) 
     view.layer.addSublayer(playerLayer) 

     // inputs 
     let videoCaptureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) 
     let videoInput = try! AVCaptureDeviceInput(device: videoCaptureDevice) 
     captureSession.addInput(videoInput) 

     // outputs 
     // iOS AVCaptureFileOutput/AVCaptureMovieFileOutput still don't support dynamically 
     // switching files (?) so we have to re-implement with AVAssetWriter 
     let videoOutput = AVCaptureVideoDataOutput() 
     // TODO: probably something else 
     videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) 
     captureSession.addOutput(videoOutput) 

     captureSession.startRunning() 
    } 

    override func viewDidLayoutSubviews() { 
     super.viewDidLayoutSubviews() 
     playerLayer.frame = view.layer.bounds 
    } 

    func createWriterInput(for presentationTimeStamp: CMTime) { 
     let fileManager = FileManager.default 
     chunkOutputURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("chunk\(chunkNumber).mov") 
     try? fileManager.removeItem(at: chunkOutputURL) 

     assetWriter = try! AVAssetWriter(outputURL: chunkOutputURL, fileType: AVFileTypeQuickTimeMovie) 
     // TODO: get dimensions from image CMSampleBufferGetImageBuffer(sampleBuffer) 
     let outputSettings: [String: Any] = [AVVideoCodecKey:AVVideoCodecH264, AVVideoWidthKey: 1920, AVVideoHeightKey: 1080] 
     assetWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: outputSettings) 
     assetWriterInput.expectsMediaDataInRealTime = true 
     assetWriter.add(assetWriterInput) 

     chunkNumber += 1 
     chunkStartTime = presentationTimeStamp 

     assetWriter.startWriting() 
     assetWriter.startSession(atSourceTime: chunkStartTime) 
    } 
} 

extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { 
    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { 
     let presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) 

     if assetWriter == nil { 
      createWriterInput(for: presentationTimeStamp) 
     } else { 
      let chunkDuration = CMTimeGetSeconds(CMTimeSubtract(presentationTimeStamp, chunkStartTime)) 

      if chunkDuration > 30 { 
       assetWriter.endSession(atSourceTime: presentationTimeStamp) 

       // make a copy, as finishWriting is asynchronous 
       let newChunkURL = chunkOutputURL! 
       let chunkAssetWriter = assetWriter! 

       chunkAssetWriter.finishWriting { 
        print("finishWriting says: \(chunkAssetWriter.status.rawValue, chunkAssetWriter.error)") 
        print("queuing \(newChunkURL)") 
        self.player.insert(AVPlayerItem(url: newChunkURL), after: nil) 
        self.player.play() 
       } 
       createWriterInput(for: presentationTimeStamp) 
      } 
     } 

     if !assetWriterInput.append(sampleBuffer) { 
      print("append says NO: \(assetWriter.status.rawValue, assetWriter.error)") 
     } 
    } 
} 

附: 30秒前看到你在做什麼非常好奇。你究竟在做什麼?

+1

謝謝爲你答覆,馬上試一試。我正在嘗試不同的方法來幫助不同種類的運動中的人馬上回顧他們的技巧。 – Maklaus

相關問題