gpt4 book ai didi

iOS - 创建多个时间延迟的实时相机预览 View

转载 作者:塔克拉玛干 更新时间:2023-11-02 20:30:13 29 4
gpt4 key购买 nike

我已经进行了大量的研究,但由于多种原因仍未能找到可行的解决方案,我将在下面概述这些原因。


问题

在我的 iOS 应用中,我想要三个 View 无限期地显示设备相机的延迟实时预览。

例如, View 1 将显示相机 View ,延迟 5 秒, View 2 将显示相同的相机 View ,延迟 20 秒, View 3 将显示相同的相机 View ,延迟 30 秒。

这将用于记录您自己进行的某种事件,例如锻炼,然后在几秒钟后观察您自己,以完善给定锻炼的形式。

尝试过的解决方案

我尝试并研究了几个不同的解决方案,但都存在问题。

1。使用 AVFoundationAVCaptureMovieFileOutput:

  • 使用 AVCaptureSessionAVCaptureMovieFileOutput 将短片录制到设备存储中。短片是必需的,因为您不能从一个 URL 播放视频,并同时写入同一个 URL。
  • 有 3 个 AVPlayerAVPlayerLayer 实例,它们都以所需的时间延迟播放录制的短片。
  • 问题:
    1. 使用 AVPlayer.replaceCurrentItem(_:) 切换剪辑时,剪辑之间有非常明显的延迟。这需要平稳过渡。
    2. 虽然老了,一个评论here由于设备限制,建议不要创建多个 AVPlayer 实例。我无法找到确认或否认此声明的信息。 E:来自 Jake G 的评论 - 10 个 AVPlayer 实例对于 iPhone 5 和更新版本是可以的。

2。使用 AVFoundationAVCaptureVideoDataOutput:

  • 使用 AVCaptureSessionAVCaptureVideoDataOutput 通过 didOutputSampleBuffer 委托(delegate)方法流式传输和处理相机馈送的每一帧。
  • 在 OpenGL View (例如 GLKViewWithBounds)上绘制每一帧。这解决了解决方案 1. 中的多个 AVPlayer 实例的问题。
  • 问题:存储每一帧以便稍后显示它们需要大量内存(这在 iOS 设备上不可行)或磁盘空间。如果我想以每秒 30 帧的速度存储一个 2 分钟的视频,如果直接从 didOutputSampleBuffer 复制,则总共有 3600 帧,总计超过 12GB。也许有一种方法可以将每一帧压缩 x1000 而不会降低质量,这样我就可以将这些数据保存在内存中。如果有这样的方法,我还没找到。

可能的第三种解决方案

如果有办法同时读取和写入文件,我相信下面的解决方案将是理想的。

  • 将视频录制为循环流。例如,对于 2 分钟的视频缓冲区,我将创建一个文件输出流来写入两分钟的帧。一旦达到 2 分钟标记,流将从头开始重新开始,覆盖原始帧。
  • 随着这个文件输出流持续运行,我将在同一个录制的视频文件上有 3 个输入流。每个流将指向流中的不同帧(有效地落后于写入流 X 秒)。然后每一帧将显示在输入流各自的 UIView 上。

当然,这还有存储空间的问题。如果帧存储为压缩的 JPEG 图像,我们谈论的是质量较低的 2 分钟视频需要数 GB 的存储空间。

问题

  1. 有人知道实现我想要的目标的有效方法吗?
  2. 如何解决我已经尝试过的解决方案中的一些问题?

最佳答案

自接受答案以来,情况发生了变化。现在有一个分段的 AVCaptureMovieFileOutput 的替代方案,它在您创建新分段时不会在 iOS 上丢帧,而替代方案是 AVAssetWriter!

从 iOS 14 开始,AVAssetWriter 可以创建碎片化的 MPEG4,它们本质上是内存中的 mpeg 4 文件。专为 HLS 流应用程序而设计,但它也是一种非常方便的缓存视频和音频内容的方法。

Takayuki Mizuno 在 WWDC 2020 session 上描述了这项新功能 Author fragmented MPEG-4 content with AVAssetWriter .

手头有一个碎片化的 mp4 AVAssetWriter,通过将 mp4 片段写入磁盘并用使用多个 AVQueuePlayer 的所需时间偏移量。

因此这将是第四种解决方案:使用 AVAssetWriter.mpeg4AppleHLS 输出配置文件捕获相机流并将其作为碎片 mp4 写入磁盘并播放视频使用 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
}
}
}

结果是这样的

4 clocks visible, 1 in background, 3 on ipad, with delays of 5, 20 and 30 seconds between them

并且性能合理:我的 2019 款 iPod 占用 10-14% 的 CPU 和 38MB 的内存。

关于iOS - 创建多个时间延迟的实时相机预览 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48892328/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com