如何使用AVFoundation同时录制和播放捕获的视频,延迟几秒钟? [英] How to simultaneously record and play captured video using AVFoundation with a few seconds delay?

查看:105
本文介绍了如何使用AVFoundation同时录制和播放捕获的视频,延迟几秒钟?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在考虑让我的Swift iOS应用程序记录一段视频并在同一屏幕上播放,延迟30秒。

I'm looking into making my Swift iOS app record a video and play it back on the same screen with 30 seconds delay.

我一直在使用官方示例以录制视频。然后我添加了一个按钮,在屏幕上的单独视图中使用AVPlayer触发 self.movi​​eFileOutput?.outputFileURL 。它接近我想要的但显然它会在写入磁盘的文件结束时停止播放,并且在写入下一个缓冲的块时不会继续播放。

I've been using an official example to record a video. Then I added a button that would trigger playing self.movieFileOutput?.outputFileURL using AVPlayer in a separate view on the screen. It's close to what I want but obviously it stops playing once it comes to the end of the file written to the disk and does not proceed when the next buffered chunk is written.

我可以每隔30秒停止视频录制并保存每个文件的URL,这样我就可以播放它,但这意味着视频捕获和播放会中断。

I could stop the video recording every 30 seconds and save the URL for each file so I can play it back but that means that there would be interruptions in video capture and playback.

如何让视频录制从不停止,播放始终在屏幕上,我想要任何延迟?

How can I make video recording never stop and playback always be on the screen with any delay I want?

我看到了类似的问题,所有的答案都指向了在AVFoundation docs。我无法找到如何让AVFoundation在录制时将可预测的视频块从内存写入磁盘。

I've seen a similar question and all the answers pointed at AVFoundation docs. I couldn't find how to make AVFoundation to write predictable chunks of video from memory to disk when recording.

推荐答案

你可以实现通过录制30个视频块然后将它们排入 AVQueuePlayer 进行无缝播放,你想要什么。使用macOS上的 AVCaptureFileOutput 录制视频块非常容易,但遗憾的是,在iOS上你不能创建新的块而不丢帧,所以你必须使用wordier,lower level AVAssetWriter API:

You can achieve what you want by recording 30s chunks of video, then enqueueing them to an AVQueuePlayer for seamless playback. Recording the video chunks would be very easy with AVCaptureFileOutput on macOS, but sadly, on iOS you cannot create new chunks without dropping frames, so you have to use the wordier, lower level 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)")
        }
    }
}

ps 30秒前看到你在做什么真是太好奇了。你究竟在做什么?

p.s. it's very curious to see what you were doing 30 seconds ago. What exactly are you making?

这篇关于如何使用AVFoundation同时录制和播放捕获的视频,延迟几秒钟?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆