Неправильное преобразование при использовании AVComposition и AVVideoComposition

Я создаю AVMutableComposition. Мне нужно, чтобы актив был перевернут по горизонтали, поэтому я устанавливаю преобразование дорожки композиции следующим образом:

compositionTrack.preferredTransform = assetTrack.preferredTransform.scaledBy(x: -1, y: 1)
    

Если я экспортирую это (я использую AVAssetPreset960x640 в качестве предустановки), это работает, как ожидалось.

Однако мне также нужно добавить AVMutableVideoComposition оверлей для рендеринга с этим копированием. Этот оверлей не следует переворачивать по горизонтали. Уточняю это так:

// Create video composition
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = videoSize
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(
    postProcessingAsVideoLayer: videoLayer,
    in: outputLayer
)

let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRange(
  start: .zero,
  duration: composition.duration
)
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack)
layerInstruction.setTransform(assetTrack.preferredTransform, at: .zero)
    
instruction.layerInstructions = [layerInstruction]
videoComposition.instructions = [instruction]

Когда я экспортирую видео с этим, оно не переворачивается по горизонтали. Он останавливает выполнение primaryTransform, примененного к композиции. Предположительно, причина в этой строке:

layerInstruction.setTransform(assetTrack.preferredTransform, at: .zero)

Если вместо этого я установил преобразование слоя на assetTrack.preferredTransform.scaledBy(x: -1, y: 1) или compositionTrack.preferredTransform, при экспорте я получаю черный экран.

У Apple есть эти документы, объясняющие преобразования в AVVideoComposition. Если я правильно понял, они говорят, что я должен просто установить преобразование инструкции слоя. Если я сделаю это - применив преобразование к инструкции слоя, а не к композиции, - у меня все равно будет черный экран.

Почему это так и как это исправить?


person Tometoyou    schedule 12.10.2020    source источник


Ответы (1)


Изменить: преобразование, которое сработало, было

CGAffineTransform(a: -1.0, b: 0.0, c: 0.0, d: 1.0, tx: videoSize.width, ty: 0.0)

Итак, я столкнулся с этой проблемой некоторое время назад после этого RayWenderlich учебник (ваш код тоже выглядит очень знакомым!). Проблема заключалась в том, что assetTrack.preferredTransform был ненадежен, на некоторых видео он работал, а на других был черный экран.

Вместо

let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack)
layerInstruction.setTransform(assetTrack.preferredTransform, at: .zero)

Попробуй это:

func compositionLayerInstruction(for track: AVCompositionTrack, assetTrack: AVAssetTrack, orientation: UIImage.Orientation) -> AVMutableVideoCompositionLayerInstruction {

    let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
    
    var transform = CGAffineTransform.identity
    let assetSize = assetTrack.naturalSize
    
    /// you should be able to play with these values to make a horizontal flip (try changing `a` and `d`)
    switch orientation {
    case .up:
        transform = CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0)
    case .down:
        transform = CGAffineTransform(a: -1, b: 0, c: 0, d: -1, tx: assetSize.width, ty: assetSize.height)
    case .left:
        transform = CGAffineTransform(a: 0, b: -1, c: 1, d: 0, tx: 0, ty: assetSize.width)
    case .right:
        transform = CGAffineTransform(a: 0, b: 1, c: -1, d: 0, tx: assetSize.height, ty: 0)
     default:
        print("Unsupported orientation")
    }
    
    instruction.setTransform(transform, at: .zero)
    
    return instruction
}

let layerInstruction = compositionLayerInstruction(
    for: compositionTrack, assetTrack: assetTrack, orientation: videoInfo.orientation)
/// assetTrack is the original video

Я основал это на другом ответе на SO, но не могу вспомнить, где.

Вот вспомогательная функция, чтобы получить orientation

/// adjust the video orientation is the source has a different orientation
private func orientation(from transform: CGAffineTransform) -> (orientation: UIImage.Orientation, isPortrait: Bool) {
    var assetOrientation = UIImage.Orientation.up
    var isPortrait = false
    if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 {
        assetOrientation = .right
        isPortrait = true
    } else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 {
        assetOrientation = .left
        isPortrait = true
    } else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 {
        assetOrientation = .up
    } else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 {
        assetOrientation = .down
    }
    return (assetOrientation, isPortrait)
}

Полное использование:

/// get adjusted orientation of the video
let videoInfo = orientation(from: assetTrack.preferredTransform)
let videoSize: CGSize

if videoInfo.isPortrait {
    videoSize = CGSize(
    width: assetTrack.naturalSize.height,
    height: assetTrack.naturalSize.width)
} else {
    videoSize = assetTrack.naturalSize
}


 /// the video
let videoLayer = CALayer()
videoLayer.frame = CGRect(origin: .zero, size: videoSize)
        
/// overlay where you add graphics and other stuff
let overlayLayer = CALayer()
overlayLayer.frame = CGRect(origin: .zero, size: videoSize)

let outputLayer = CALayer()
outputLayer.frame = CGRect(origin: .zero, size: videoSize)
outputLayer.addSublayer(videoLayer)
outputLayer.addSublayer(overlayLayer)

let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = videoSize
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(
postProcessingAsVideoLayer: videoLayer,
in: outputLayer)

// MARK: - previous `let layerInstruction` code goes here...

instruction.layerInstructions = [layerInstruction]

/// export session code here...
person aheze    schedule 12.10.2020
comment
Да, я тоже следил за этим руководством. С помощью комбинации вашего ответа и некоторого поиска в Google я пришел к выводу, что реальное преобразование должно быть таким: CGAffineTransform(a: -1.0, b: 0.0, c: 0.0, d: 1.0, tx: videoSize.width, ty: 0.0). Это должно применяться только к инструкции слоя, а не к композиции. Возможно, стоит обновить свой ответ для всех, кто читает в будущем :) - person Tometoyou; 13.10.2020
comment
Хороший! Что вы имеете в виду, когда говорите, что применяется только к инструкции слоя, а не к композитному треку? Разве это не так? Конечно, я обновлю свой ответ. - person aheze; 13.10.2020
comment
Кроме того, при использовании CGAffineTransform(a: -1.0, b: 0.0, c: 0.0, d: 1.0, tx: videoSize.width, ty: 0.0) не требуется переключение orientation? - person aheze; 13.10.2020