我已经进行了大量的研究,但由于多种原因仍未能找到可行的解决方案,我将在下面概述这些原因。
在我的 iOS 应用中,我想要三个 View 无限期地显示设备相机的延迟实时预览。
例如, View 1 将显示相机 View ,延迟 5 秒, View 2 将显示相同的相机 View ,延迟 20 秒, View 3 将显示相同的相机 View ,延迟 30 秒。
这将用于记录您自己进行的某种事件,例如锻炼,然后在几秒钟后观察您自己,以完善给定锻炼的形式。
我尝试并研究了几个不同的解决方案,但都存在问题。
AVFoundation 和 AVCaptureMovieFileOutput:AVCaptureSession 和 AVCaptureMovieFileOutput 将短片录制到设备存储中。短片是必需的,因为您不能从一个 URL 播放视频,并同时写入同一个 URL。AVPlayer 和 AVPlayerLayer 实例,它们都以所需的时间延迟播放录制的短片。AVPlayer.replaceCurrentItem(_:) 切换剪辑时,剪辑之间有非常明显的延迟。这需要平稳过渡。AVPlayer 实例。我无法找到确认或否认此声明的信息。 E:来自 Jake G 的评论 - 10 个 AVPlayer 实例对于 iPhone 5 和更新版本是可以的。AVFoundation 和 AVCaptureVideoDataOutput:AVCaptureSession 和 AVCaptureVideoDataOutput 通过 didOutputSampleBuffer 委托(delegate)方法流式传输和处理相机馈送的每一帧。GLKViewWithBounds)上绘制每一帧。这解决了解决方案 1. 中的多个 AVPlayer 实例的问题。didOutputSampleBuffer 复制,则总共有 3600 帧,总计超过 12GB。也许有一种方法可以将每一帧压缩 x1000 而不会降低质量,这样我就可以将这些数据保存在内存中。如果有这样的方法,我还没找到。如果有办法同时读取和写入文件,我相信下面的解决方案将是理想的。
UIView 上。 当然,这还有存储空间的问题。如果帧存储为压缩的 JPEG 图像,我们谈论的是质量较低的 2 分钟视频需要数 GB 的存储空间。
最佳答案
自接受答案以来,情况发生了变化。现在有一个分段的 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 写入磁盘并播放视频使用 AVQueuePlayer 和 AVPlayerLayers 具有不同的延迟。
如果您需要支持 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
}
}
}
结果是这样的
并且性能合理:我的 2019 款 iPod 占用 10-14% 的 CPU 和 38MB 的内存。
关于iOS - 创建多个时间延迟的实时相机预览 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48892328/
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake
我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2