coh*_*air 4 camera video-capture video-processing avfoundation ios
我已经进行了大量研究,但由于多种原因尚未找到可行的解决方案,我将在下面概述。
在我的 iOS 应用程序中,我需要三个视图来无限期地显示设备相机的延迟实时预览。
例如,视图1将显示相机视图,延迟5秒,视图2将显示相同的相机视图,延迟20秒,视图3将显示相同的相机视图,延迟30秒。
这将用于记录您自己执行某种活动,例如锻炼练习,然后在几秒钟后观察自己,以完善您的特定练习形式。
我尝试并研究了几种不同的解决方案,但都有问题。
AVFoundation和AVCaptureMovieFileOutput:AVCaptureSession和AVCaptureMovieFileOutput将短片录制到设备存储中。需要短剪辑,因为您无法播放 URL 中的视频,并同时写入同一 URL。AVPlayer个AVPlayerLayer实例,全部按所需的时间延迟播放录制的短片。AVPlayer.replaceCurrentItem(_:),剪辑之间存在非常明显的延迟。这需要一个平稳的过渡。AVPlayer由于设备限制不要创建多个实例。我还没有找到证实或否认这一说法的信息。E:根据 Jake G 的评论 -AVPlayer对于 iPhone 5 及更新版本来说 10 次就可以了。AVFoundation和AVCaptureVideoDataOutput:AVCaptureSession和AVCaptureVideoDataOutput来通过委托方法流式传输和处理摄像机输入的每一帧didOutputSampleBuffer。GLKViewWithBounds)。AVPlayer这解决了来自 的多个实例的问题Solution 1.。didOutputSampleBuffer. 也许有一种方法可以在不损失质量的情况下将每个帧压缩 x1000,这样我就可以将这些数据保留在内存中。如果存在这样的方法,我还没有找到它。如果有一种方法可以同时读取和写入文件,我相信以下解决方案将是理想的。
UIView。 当然,这仍然存在存储空间的问题。如果帧存储为压缩的 JPEG 图像,我们讨论的是较低质量的 2 分钟视频需要多个 GB 的存储空间。
自接受答案以来情况发生了变化。现在有一种分段的替代方案AVCaptureMovieFileOutput,当您创建新分段时,它不会在 iOS 上丢帧,而该替代方案就是AVAssetWriter!
从 iOS 14 开始,AVAssetWriter可以创建分段的 MPEG4,本质上是内存中的 mpeg 4 文件。它适用于 HLS 流应用程序,但它也是一种非常方便的缓存视频和音频内容的方法。
Takayuki Mizuno 在 WWDC 2020 会议上使用 AVAssetWriter 创作分段 MPEG-4 内容描述了这一新功能。
有了碎片化的 mp4 ,通过将片段写入磁盘并使用几秒以所需的时间偏移播放它们,AVAssetWriter创建解决此问题的方案并不难。mp4AVQueuePlayer
AVAssetWriter因此,这将是第四种解决方案:捕获摄像机流并使用 的输出配置文件将其作为分段mp4 写入磁盘,并使用s 和.mpeg4AppleHLS以不同的延迟播放视频。AVQueuePlayerAVPlayerLayers
如果您需要支持 iOS 13 及更低版本,则必须替换分段AVAssetWriter,这很快就会变得技术性,特别是如果您也想编写音频。谢谢水野贵之!
import UIKit
import AVFoundation
import UniformTypeIdentifiers
class ViewController: UIViewController {
let playbackDelays:[Int] = [5, 20, 30]
let segmentDuration = CMTime(value: 2, timescale: 1)
var assetWriter: AVAssetWriter!
var videoInput: AVAssetWriterInput!
var startTime: CMTime!
var writerStarted = false
let session = AVCaptureSession()
var segment = 0
var outputDir: URL!
var initializationData = Data()
var layers: [AVPlayerLayer] = []
var players: [AVQueuePlayer] = []
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0..<playbackDelays.count {
let player = AVQueuePlayer()
player.automaticallyWaitsToMinimizeStalling = false
let layer = AVPlayerLayer(player: player)
layer.videoGravity = .resizeAspectFill
layers.append(layer)
players.append(player)
view.layer.addSublayer(layer)
}
outputDir = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first!
assetWriter = AVAssetWriter(contentType: UTType.mpeg4Movie)
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS // fragmented mp4 output!
assetWriter.preferredOutputSegmentInterval = segmentDuration
assetWriter.initialSegmentStartTime = .zero
assetWriter.delegate = self
let videoOutputSettings: [String : Any] = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: 1024,
AVVideoHeightKey: 720
]
videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
videoInput.expectsMediaDataInRealTime = true
assetWriter.add(videoInput)
// capture session
let videoDevice = AVCaptureDevice.default(for: .video)!
let videoInput = try! AVCaptureDeviceInput(device: videoDevice)
session.addInput(videoInput)
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
session.addOutput(videoOutput)
session.startRunning()
}
override func viewDidLayoutSubviews() {
let size = view.bounds.size
let layerWidth = size.width / CGFloat(layers.count)
for i in 0..<layers.count {
let layer = layers[i]
layer.frame = CGRect(x: CGFloat(i)*layerWidth, y: 0, width: layerWidth, height: size.height)
}
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
}
extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
if startTime == nil {
let success = assetWriter.startWriting()
assert(success)
startTime = sampleBuffer.presentationTimeStamp
assetWriter.startSession(atSourceTime: startTime)
}
if videoInput.isReadyForMoreMediaData {
videoInput.append(sampleBuffer)
}
}
}
extension ViewController: AVAssetWriterDelegate {
func assetWriter(_ writer: AVAssetWriter, didOutputSegmentData segmentData: Data, segmentType: AVAssetSegmentType) {
print("segmentType: \(segmentType.rawValue) - size: \(segmentData.count)")
switch segmentType {
case .initialization:
initializationData = segmentData
case .separable:
let fileURL = outputDir.appendingPathComponent(String(format: "%.4i.mp4", segment))
segment += 1
let mp4Data = initializationData + segmentData
try! mp4Data.write(to: fileURL)
let asset = AVAsset(url: fileURL)
for i in 0..<players.count {
let player = players[i]
let playerItem = AVPlayerItem(asset: asset)
player.insert(playerItem, after: nil)
if player.rate == 0 && player.status == .readyToPlay {
let hostStartTime: CMTime = startTime + CMTime(value: CMTimeValue(playbackDelays[i]), timescale: 1)
player.preroll(atRate: 1) { prerolled in
guard prerolled else { return }
player.setRate(1, time: .invalid, atHostTime: hostStartTime)
}
}
}
@unknown default:
break
}
}
}
Run Code Online (Sandbox Code Playgroud)
结果看起来像这样
而且性能还算合理:我的 2019 款 iPod 的 CPU 利用率为 10-14%,内存为 38MB。
| 归档时间: |
|
| 查看次数: |
1403 次 |
| 最近记录: |