Es ist ein ziemlich langer Prozess. Sitzst du gemütlich? Dann beginne ich ...
Als Teil meiner VideoEffects Projekt habe ich eine Klasse namens FilteredVideoVendor erstellt, die gefilterte Bilderframes aus einer Filmdatei verkauft.
Die erste Aufgabe der Herstellerklasse ist eigentlich von einer URL durch die Schaltfläche „Ladens“ in der Systemsteuerung geliefert, einen Film zu öffnen:
func openMovie(url: NSURL){
player = AVPlayer(URL: url)
guard let player = player,
currentItem = player.currentItem,
videoTrack = currentItem.asset.tracksWithMediaType(AVMediaTypeVideo).first else {
fatalError("** unable to access item **")
}
currentURL = url
failedPixelBufferForItemTimeCount = 0
currentItem.addOutput(videoOutput)
videoTransform = CGAffineTransformInvert(videoTrack.preferredTransform)
player.muted = true
}
Es gibt ein paar interessanten hier Punkte: Erstens, ich zurücksetzen, um eine Variable mit dem Namen failedPixelBufferForItemTimeCount
- das ist eine Abhilfe für das, was ich denke, um ein Fehler in AVFoundation mit Videos, die gelegentlich ohne offensichtliche Fehler laden fehlschlagen würden. Zweitens, um Landscape- und Portrait-Videos zu unterstützen, erstelle ich eine invertierte Version der bevorzugten Transformation der Videospur.
Der Verkäufer enthält ein CADisplayLink
die step(_:)
ruft:
func step(link: CADisplayLink) {
guard let player = player,
currentItem = player.currentItem else {
return
}
let itemTime = videoOutput.itemTimeForHostTime(CACurrentMediaTime())
displayVideoFrame(itemTime)
let normalisedTime = Float(itemTime.seconds/currentItem.asset.duration.seconds)
delegate?.vendorNormalisedTimeUpdated(normalisedTime)
if normalisedTime >= 1.0
{
paused = true
}
}
Mit dem CADisplayLink
, berechne ich die Zeit für die AVPlayerItem
basierend auf CACurrentMediaTime
. Die normalisierte Zeit (d. H. Zwischen 0 und 1) wird berechnet, indem die Zeit des Spielerelements durch die Aktivitätsdauer dividiert wird, dies wird von den UI-Komponenten verwendet, um die Position der Scrub-Leiste während der Wiedergabe einzustellen. Erstellen ein CIImage
aus dem Rahmen des Films bei itemTime
in displayVideoFrame(_:)
getan:
func displayVideoFrame(time: CMTime) {
guard let player = player,
currentItem = player.currentItem where player.status == .ReadyToPlay && currentItem.status == .ReadyToPlay else {
return
}
if videoOutput.hasNewPixelBufferForItemTime(time) {
failedPixelBufferForItemTimeCount = 0
var presentationItemTime = kCMTimeZero
guard let pixelBuffer = videoOutput.copyPixelBufferForItemTime(
time,
itemTimeForDisplay: &presentationItemTime) else {
return
}
unfilteredImage = CIImage(CVImageBuffer: pixelBuffer)
displayFilteredImage()
}
else if let currentURL = currentURL where !paused {
failedPixelBufferForItemTimeCount += 1
if failedPixelBufferForItemTimeCount > 12 {
openMovie(currentURL)
}
}
}
Bevor einen Pixelpuffer aus dem Videoausgang kopieren, ich brauche verfügbar ist zu gewährleisten. Wenn das alles gut ist, ist es ein einfacher Schritt, aus diesem Pixelpuffer einen CIImage
zu erstellen. Wenn jedoch hasNewPixelBufferForItemTime(_:)
zu oft versagt (12 scheint zu funktionieren), gehe ich davon aus AVFoundation hat leise ausgefallen und ich den Film wieder zu öffnen.
Mit dem bevölkerten CIImage
, wende ich einen Filter und Rück das gerenderte Ergebnis zurück an den Delegaten (falls vorhanden) (die die Hauptansicht ist) angezeigt werden:
func displayFilteredImage() {
guard let unfilteredImage = unfilteredImage,
videoTransform = videoTransform else {
return
}
let ciImage: CIImage
if let ciFilter = ciFilter {
ciFilter.setValue(unfilteredImage, forKey: kCIInputImageKey)
ciImage = ciFilter.outputImage!.imageByApplyingTransform(videoTransform)
}
else {
ciImage = unfilteredImage.imageByApplyingTransform(videoTransform)
}
let cgImage = ciContext.createCGImage(
ciImage,
fromRect: ciImage.extent)
delegate?.finalOutputUpdated(UIImage(CGImage: cgImage))
}
Wenn Sie möchten, schreibe einen gefilterten Film zurück in das Dateisystem, ich bespreche das in this blog post.
Simon