13

AVAsset에 CIFilter를 적용한 다음 필터를 적용한 상태로 저장하려고합니다. 내가이 작업을 수행하는 방법은 AVVideoCompositing 클래스의 객체로 설정된 videoComposition으로 AVAssetExportSession을 사용하는 것입니다.사용자 지정 AVVideoCompositing 클래스가 예상대로 작동하지 않습니다.

AVMutableVideoComposition 개체는 사용자 지정 합성 명령 클래스 (AVMutableVideoCompositionInstruction 준수)로 설정됩니다. 이 클래스에는 몇 가지 중요하지 않은 변수와 함께 트랙 ID가 전달됩니다.

불행히도 문제가 생겼습니다. 맞춤 비디오 컴포저 클래스 (AVVideoCompositing 준수)의 startVideoCompositionRequest: 함수가 올바르게 호출되지 않았습니다.

사용자 정의 명령어 클래스의 passthroughTrackID 변수를 트랙 ID로 설정하면 AVVideoCompositing에있는 startVideoCompositionRequest(request) 함수가 호출되지 않습니다.

그러나, 나는 내 사용자 지정 명령 클래스의 passthroughTrackID 변수를 설정하지 마십시오 startVideoCompositionRequest(request)라는이지만, 제대로하지 - 빈 배열 request.sourceTrackIDs 결과 및 전무 값 request.sourceFrameByTrackID(trackID) 결과를 인쇄.

내가 발견 한 흥미로운 점은 필터를 사용하여 비디오를 내보낼 때 cancelAllPendingVideoCompositionRequests: 함수가 항상 두 번 호출된다는 것입니다. startVideoCompositionRequest: 전에 한 번 호출되거나 한 번만 호출되거나 startVideoCompositionRequest:이 호출되지 않은 경우에는 한 번만 호출됩니다.

필자는 필터를 사용하여 비디오를 내보내는 세 가지 클래스를 만들었습니다. 여기에 기본적으로 그냥 export 기능을 포함하고 필요한 모든 코드를 호출하는 유틸리티 클래스는,이다

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) 
     } 
    } 
} 

여기에 다른 두 종류의 -이 게시하기 위해 하나 개의 코드 블록에 둘 다 놓을 게요 짧은

// 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") 
     } 
    } 
} 

나는 이것에 관해서 많은 조언을 해왔다. 그러나 나는 왜 이런 일이 일어나고 있는지 발견 할 수 없다.

request.sourceFrameByTrackID: 함수를 올바르게 호출하여 각 프레임에 대해 유효한 CVPixelBuffer을 제공하려면 어떻게해야합니까?

답변

6

그것은 사용자 AVVideoCompositionInstruction 클래스의 requiredSourceTrackIDs 변수 (의문에 VideoFilterCompositionInstruction)이

override var requiredSourceTrackIDs: [NSValue]{ 
    get{ 
    return [ 
     NSNumber(value: Int(self.trackID)) 
    ] 
    } 
} 

그래서 최종 사용자 조성물 명령 클래스

인 트랙 아이디를 포함하는 배열로 설정되어야한다는 것을 밝혀
class VideoFilterCompositionInstruction : AVMutableVideoCompositionInstruction{ 
    let trackID: CMPersistentTrackID 
    let filters: [CIFilter] 
    let context: CIContext 

    override var passthroughTrackID: CMPersistentTrackID{get{return self.trackID}} 
    override var requiredSourceTrackIDs: [NSValue]{get{return [NSNumber(value: Int(self.trackID))]}} 
    override var containsTweening: Bool{get{return false}} 

    init(trackID: CMPersistentTrackID, filters: [CIFilter], context: CIContext){ 
     self.trackID = trackID 
     self.filters = filters 
     self.context = context 

     super.init() 

     self.enablePostProcessing = true 
    } 

    required init?(coder aDecoder: NSCoder){ 
     fatalError("init(coder:) has not been implemented") 
    } 
} 

이 유틸리티 is also on GitHub

+0

안녕하세요. github에서 샘플 프로젝트를 시도했지만 제 쪽에서 작동하지 않습니다. func startRequest (_ asyncVideoCompositionRequest : AVAsynchronousVideoCompositionRequest)가 AVVideoCompositing에서 호출되지 않습니다. – Sam

+0

잘못된 점을 알고 있습니까? 제안 된대로 모든 것을 구현했습니다. – Sam

+0

스위프트 3에서이 기능을 사용해 보셨습니까? – Sam

3

로에 대한 코드의 모든 당신이 언급 한 바에 따르면, passthroughTrackID은 필터링하려는 트랙을 반환하는 것이 올바른 접근법이 아니므로 필터링 할 트랙을 requiredSourceTrackIDs에서 반환해야합니다. (그리고 일단 당신이 그렇게하면, 당신은 passthroughTrackID에서 그것을 반환해도 상관 없습니다.) 확실히 passthroughTrackIDrequiredSourceTrackIDs 것이이 방식으로 작동하는 이유의 나머지 질문 ...

워드 프로세서 대답하기 위해 애플의 명확한 기록은 지금까지 수 없습니다.

이 명령의 기간 동안, 비디오 구성 결과가 하나라면 ... 힌트를 (강조 추가) (File a bug about it 그들은.이 향상 될 수 있습니다)하지만 전자의 설명에 자세히 보면, 거기에 이 속성은 해당 트랙 ID를 반환합니다. 명령어의 지속 기간 동안 컴포 지터가 실행되지 않고 올바른 소스 프레임이 대신 사용됩니다. 당신이
을 처리하지 않고 을 통해 하나의 트랙을 전달하는 명령 클래스를 만들 때

그래서, 당신은 passthroughTrackID를 사용합니다.

이미지 처리를 수행하려는 경우 컴포지션이없는 단일 트랙 인 경우에도 해당 트랙을 requiredSourceTrackIDs에 지정하십시오.

+0

안녕하세요, 자세히 설명해 주시겠습니까? github 프로젝트를 swift3와 함께 사용하려면 어떻게해야합니까? – Sam