Как одновременно записывать и воспроизводить снятое видео с помощью AVFoundation с задержкой в несколько секунд?
Я ищу, чтобы сделать мой Swift iOS app записывать видео и воспроизводить его на том же экране с задержкой 30 секунд.
я использовал официальный пример для записи видео. Затем я добавил кнопку, которая вызовет воспроизведение self.movieFileOutput?.outputFileURL
использование AVPlayer в отдельном представлении на экране. Это близко к тому, что я хочу, но, очевидно, он перестает играть, как только доходит до конца файла, записанного на диск, и не продолжается, когда следующий буферизованный кусок написанный.
Я мог бы остановить запись видео каждые 30 секунд и сохранить URL для каждого файла, чтобы я мог воспроизвести его, но это означает, что будут перерывы в захвате и воспроизведении видео.
Как я могу сделать видеозапись никогда не останавливаться и воспроизведение всегда будет на экране с любой задержкой, которую я хочу?
Я видел аналогичный вопрос, и все ответы указывали на документы AVFoundation. Я не мог найти, как сделать AVFoundation, чтобы написать предсказуемые куски видео из памяти на диск при записи.
1 ответов
вы можете достичь того, что вы хотите, записывая 30s куски видео, а затем enqueueing их в AVQueuePlayer
для плавного воспроизведения. Запись видео куски будет очень легко с AVCaptureFileOutput
на macOS, но, к сожалению, на iOS вы не можете создавать новые куски без удаления фреймов, поэтому вам нужно использовать 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)")
}
}
}
p.s. очень любопытно посмотреть, что вы делали 30 секунд назад. Что именно вы делаете?