Benutzerdefinierte AVVideoCompositing-Klasse funktioniert nicht wie erwartet
Ich versuche, einen CIFilter auf ein AVAsset anzuwenden und ihn dann mit dem angewendeten Filter zu speichern. Die Art, wie ich das mache, ist die Verwendung einesAVAssetExportSession
mitvideoComposition
auf ein @ setzAVMutableVideoComposition
Objekt mit einem benutzerdefiniertenAVVideoCompositing
Klasse.
Ich stelle auch das @ einstructions
von meinenAVMutableVideoComposition
-Objekt zu einer benutzerdefinierten Kompositionsanweisungsklasse (gemäßAVMutableVideoCompositionInstruction
). Dieser Klasse wird zusammen mit einigen anderen unwichtigen Variablen eine Track-ID übergeben.
Leider bin ich auf ein Problem gestoßen - dasstartVideoCompositionRequest:
-Funktion in meiner benutzerdefinierten Video-Compositor-Klasse (gemäßAVVideoCompositing
) wird nicht korrekt aufgerufen.
Wenn ich das @ einstelpassthroughTrackID
-Variable meiner benutzerdefinierten Anweisungsklasse für die Spur-ID, diestartVideoCompositionRequest(request)
Funktion in meinemAVVideoCompositing
wird nicht aufgerufen.
Yet, wenn ich das @ nicht einstelpassthroughTrackID
-Variable meiner benutzerdefinierten Anweisungsklasse, diestartVideoCompositionRequest(request)
ist angerufen, aber nicht richtig - drucktrequest.sourceTrackIDs
ergibt ein leeres Array undrequest.sourceFrameByTrackID(trackID)
ergibt einen Nullwert.
Etwas Interessantes, das ich fand, war, dass diecancelAllPendingVideoCompositionRequests:
ie @ -Funktion wird immer zweimal aufgerufen, wenn versucht wird, das Video mit Filtern zu exportieren. Es wird entweder einmal vor @ aufgerufstartVideoCompositionRequest:
und einmal danach oder nur zweimal hintereinander, wennstartVideoCompositionRequest:
wird nicht aufgerufen.
Ich habe drei Klassen zum Exportieren des Videos mit Filtern erstellt. Hier ist die Utility-Klasse, die im Grunde nur ein @ enthäexport
Funktion und ruft den gesamten erforderlichen Code auf
class VideoFilterExport{
let asset: AVAsset
init(asset: AVAsset){
self.asset = asset
}
func export(toURL url: NSURL, callback: (url: NSURL?) -> Void){
guard let track: AVAssetTrack = self.asset.tracksWithMediaType(AVMediaTypeVideo).first else{callback(url: nil); return}
let composition = AVMutableComposition()
let compositionTrack = composition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)
do{
try compositionTrack.insertTimeRange(track.timeRange, ofTrack: track, atTime: kCMTimeZero)
}
catch _{callback(url: nil); return}
let videoComposition = AVMutableVideoComposition(propertiesOfAsset: composition)
videoComposition.customVideoCompositorClass = VideoFilterCompositor.self
videoComposition.frameDuration = CMTimeMake(1, 30)
videoComposition.renderSize = compositionTrack.naturalSize
let instruction = VideoFilterCompositionInstruction(trackID: compositionTrack.trackID)
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.asset.duration)
videoComposition.instructions = [instruction]
let session: AVAssetExportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetMediumQuality)!
session.videoComposition = videoComposition
session.outputURL = url
session.outputFileType = AVFileTypeMPEG4
session.exportAsynchronouslyWithCompletionHandler(){
callback(url: url)
}
}
}
Hier sind die anderen beiden Klassen - ich werde sie beide in einen Codeblock setzen, um diesen Beitrag zu verkürzen
// Video Filter Composition Instruction Class - from what I gather,
// AVVideoCompositionInstruction is used only to pass values to
// the AVVideoCompositing class
class VideoFilterCompositionInstruction : AVMutableVideoCompositionInstruction{
let trackID: CMPersistentTrackID
let filters: ImageFilterGroup
let context: CIContext
// When I leave this line as-is, startVideoCompositionRequest: isn't called.
// When commented out, startVideoCompositionRequest(request) is called, but there
// are no valid CVPixelBuffers provided by request.sourceFrameByTrackID(below value)
override var passthroughTrackID: CMPersistentTrackID{get{return self.trackID}}
override var requiredSourceTrackIDs: [NSValue]{get{return []}}
override var containsTweening: Bool{get{return false}}
init(trackID: CMPersistentTrackID, filters: ImageFilterGroup, context: CIContext){
self.trackID = trackID
self.filters = filters
self.context = context
super.init()
//self.timeRange = timeRange
self.enablePostProcessing = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// My custom AVVideoCompositing class. This is where the problem lies -
// although I don't know if this is the root of the problem
class VideoFilterCompositor : NSObject, AVVideoCompositing{
var requiredPixelBufferAttributesForRenderContext: [String : AnyObject] = [
kCVPixelBufferPixelFormatTypeKey as String : NSNumber(unsignedInt: kCVPixelFormatType_32BGRA), // The video is in 32 BGRA
kCVPixelBufferOpenGLESCompatibilityKey as String : NSNumber(bool: true),
kCVPixelBufferOpenGLCompatibilityKey as String : NSNumber(bool: true)
]
var sourcePixelBufferAttributes: [String : AnyObject]? = [
kCVPixelBufferPixelFormatTypeKey as String : NSNumber(unsignedInt: kCVPixelFormatType_32BGRA),
kCVPixelBufferOpenGLESCompatibilityKey as String : NSNumber(bool: true),
kCVPixelBufferOpenGLCompatibilityKey as String : NSNumber(bool: true)
]
let renderQueue = dispatch_queue_create("co.getblix.videofiltercompositor.renderingqueue", DISPATCH_QUEUE_SERIAL)
override init(){
super.init()
}
func startVideoCompositionRequest(request: AVAsynchronousVideoCompositionRequest){
// This code block is never executed when the
// passthroughTrackID variable is in the above class
autoreleasepool(){
dispatch_async(self.renderQueue){
guard let instruction = request.videoCompositionInstruction as? VideoFilterCompositionInstruction else{
request.finishWithError(NSError(domain: "getblix.co", code: 760, userInfo: nil))
return
}
guard let pixels = request.sourceFrameByTrackID(instruction.passthroughTrackID) else{
// This code block is executed when I comment out the
// passthroughTrackID variable in the above class
request.finishWithError(NSError(domain: "getblix.co", code: 761, userInfo: nil))
return
}
// I have not been able to get the code to reach this point
// This function is either not called, or the guard
// statement above executes
let image = CIImage(CVPixelBuffer: pixels)
let filtered: CIImage = //apply the filter here
let width = CVPixelBufferGetWidth(pixels)
let height = CVPixelBufferGetHeight(pixels)
let format = CVPixelBufferGetPixelFormatType(pixels)
var newBuffer: CVPixelBuffer?
CVPixelBufferCreate(kCFAllocatorDefault, width, height, format, nil, &newBuffer)
if let buffer = newBuffer{
instruction.context.render(filtered, toCVPixelBuffer: buffer)
request.finishWithComposedVideoFrame(buffer)
}
else{
request.finishWithComposedVideoFrame(pixels)
}
}
}
}
func renderContextChanged(newRenderContext: AVVideoCompositionRenderContext){
// I don't have any code in this block
}
// This is interesting - this is called twice,
// Once before startVideoCompositionRequest is called,
// And once after. In the case when startVideoCompositionRequest
// Is not called, this is simply called twice in a row
func cancelAllPendingVideoCompositionRequests(){
dispatch_barrier_async(self.renderQueue){
print("Cancelled")
}
}
}
Ich habe auf @ gesuc Apples AVCustomEdit Beispielprojekt viel zur Orientierung, aber ich kann anscheinend keinen Grund finden, warum dies geschieht.
Wie kann ich das @ bekommrequest.sourceFrameByTrackID:
-Funktion zum korrekten Aufrufen und Bereitstellen eines gültigenCVPixelBuffer
für jeden Frame?