Сбой AVFoundation при экспорте видео с текстовым слоем

В свободное время я разрабатываю приложение для редактирования видео для iOS.

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

Я проверил и создал точно такой же коммит, который я успешно загрузил в TestFlight тогда (и он работал на нескольких устройствах без сбоев), так что, возможно, это проблема с последним Xcode/iOS SDK, который я обновлял с тех пор?

Сбой кода на _xpc_api_misuse в потоке:

com.apple.coremedia.basicvideocompositor.output

Навигатор отладки:

введите описание изображения здесь

На момент сбоя в навигаторе отладки было более 70 потоков, поэтому, возможно, что-то не так и приложение использует слишком много потоков (никогда не видел такого количества).


Мое приложение накладывает «водяной знак» на экспортированное видео с помощью текстового слоя. Поигравшись, я обнаружил, что сбоя можно избежать, если я закомментирую код водяного знака:

    guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
        return failure(ProjectError.failedToCreateExportSession)
    }
    guard let documents = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {
        return failure(ProjectError.temporaryOutputDirectoryNotFound)
    }
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd_HHmmss"
    let fileName = dateFormatter.string(from: Date())
    let fileExtension = "mov"
    let fileURL = documents.appendingPathComponent(fileName).appendingPathExtension(fileExtension)
    exporter.outputURL = fileURL

    exporter.outputFileType = AVFileType.mov
    exporter.shouldOptimizeForNetworkUse = true // check if needed

    // OFFENDING BLOCK (commenting out averts crash)
    if addWaterMark {
        let frame = CGRect(origin: .zero, size: videoComposition.renderSize)
        let watermark = WatermarkLayer(frame: frame)
        let parentLayer = CALayer()
        let videoLayer = CALayer()

        parentLayer.frame = frame
        videoLayer.frame = frame
        parentLayer.addSublayer(videoLayer)
        parentLayer.addSublayer(watermark)
        videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
    }
    // END OF OFFENDING BLOCK

    exporter.videoComposition = videoComposition

    exporter.exportAsynchronously {
    // etc.

Код слоя водяного знака:

class WatermarkLayer: CATextLayer {

    private let defaultFontSize: CGFloat = 48

    private let rightMargin: CGFloat = 10
    private let bottomMargin: CGFloat = 10

    init(frame: CGRect) {
        super.init()
        guard let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String else {
            fatalError("!!!")
        }
        self.foregroundColor = CGColor.srgb(r: 255, g: 255, b: 255, a: 0.5)
        self.backgroundColor = CGColor.clear
        self.string = String(format: String.watermarkFormat, appName)
        self.font = CTFontCreateWithName(String.watermarkFontName as CFString, defaultFontSize, nil)
        self.fontSize = defaultFontSize
        self.shadowOpacity = 0.75
        self.alignmentMode = .right
        self.frame = frame
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented. Use init(frame:) instead.")
    }

    override func draw(in ctx: CGContext) {
        let height = self.bounds.size.height
        let fontSize = self.fontSize
        let yDiff = (height-fontSize) - fontSize/10 - bottomMargin // Bottom (minus margin)

        ctx.saveGState()
        ctx.translateBy(x: -rightMargin, y: yDiff)
        super.draw(in: ctx)
        ctx.restoreGState()
    }
}

Есть идеи, что может произойти?

Возможно, мой код делает что-то не так, что каким-то образом «получило пропуск» в предыдущем SDK из-за какой-то ошибки Apple, которая была исправлена, или «дыры» реализации, которая была заткнута?


ОБНОВЛЕНИЕ: я скачал Пример проекта Рэя Вендерлиха для редактирования видео и попытался добавить «субтитры» к видео (мне пришлось настроить слишком старый проект, чтобы он компилировался под Xcode 11).

И вот, вылетает точно так же.


ОБНОВЛЕНИЕ 2: я попробовал устройство (iPhone 8 с последней версией iOS 13.5), работает, без сбоев. Однако симуляторы для iOS 13.5 вылетают. Когда я первоначально разместил вопрос (iOS 13.4?), Я уверен, что это был сбой как на устройстве, так и в симуляторе.

Я загружаю симуляторы iOS 12.0, чтобы проверить, но до него еще несколько гигабайт...

введите описание изображения здесь


person Nicolas Miari    schedule 03.04.2020    source источник
comment
Я также разместил вопрос на форумах Apple (forums.developer.apple.com/message/ 422504#422504), чтобы он мог собирать перекати-поле.   -  person Nicolas Miari    schedule 31.05.2020
comment
Вы решили свою проблему? Я получаю ту же проблему.   -  person gstream79    schedule 03.07.2020
comment
@gstream79 В последнее время не прикасался к коду, занят своей основной работой. См. «Обновление 2» выше, чтобы узнать о последних изменениях.   -  person Nicolas Miari    schedule 03.07.2020
comment
Сбой на обоих устройствах, симулятор для iOS 13.4, 13.5   -  person gstream79    schedule 03.07.2020


Ответы (3)


У меня такая же проблема. Запустился после iOS 13.4 и отображается только на симуляторе (устройство работает нормально). Если я закомментирую parentLayer.addSublayer(videoLayer), приложение не вылетит, но экспортированное видео не будет желаемым результатом.

person Jeremy Kelleher    schedule 09.04.2020
comment
Мое приложение падает на симуляторе и устройстве. - person Nicolas Miari; 09.04.2020
comment
Я тоже могу предотвратить сбой, если закомментирую ту же самую строку (удалю подслой видео, который работает в координации с текстовым слоем), но на выходе видео будет черный экран. - person Nicolas Miari; 31.05.2020
comment
На вашем устройстве также установлена ​​версия 13.4 (или более поздняя) и проблема не возникает? - person Nicolas Miari; 31.05.2020
comment
да аппарат на 13.4 и работает нормально. Симулятор все еще падает на Xcode 11.5 - person Jeremy Kelleher; 01.06.2020
comment
Все еще происходит на симуляторе Xcode 12 и iOS 14.0 :( - person christophercotton; 17.10.2020
comment
Есть новости по этому поводу? Все еще сбой Xcode 12.3, iOS 14.3 :(( - person AcidicSkittles; 06.01.2021

Встречаются те же проблемы, но только на симуляторе (Xcode 12.4 (12D4e)).

После некоторых исследований я обнаружил, что причиной этого сбоя является AVVideoCompositionCoreAnimationTool.

+videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:inLayer:

И я исправил это, заменив его одним из приведенных ниже (но нам нужно обработать instruction.layerInstructions таким образом):

+videoCompositionCoreAnimationToolWithAdditionalLayer:asTrackID:

Ниже приведен пример кода, который работает как на реальном устройстве, так и на симуляторе (поскольку OP не пометил Swift явно, я просто скопирую сюда свой образец Objective-C):

...

// Prepare watermark layer
CALayer *watermarkLayer = ...;
CMPersistentTrackID watermarkLayerTrackID = [asset unusedTrackID];
// !!! NOTE#01: Use as additional layer here instead of animation layer.
videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithAdditionalLayer:watermarkLayer asTrackID:watermarkLayerTrackID];
  
// Create video composition instruction
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);

// - Watermark layer instruction
// !!! NOTE#02: Make this instruction track watermark layer by the `trackID`.
AVMutableVideoCompositionLayerInstruction *watermarkLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstruction];
watermarkLayerInstruction.trackID = watermarkLayerTrackID;

// - Video track layer instruction
AVAssetTrack *videoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
AVMutableVideoCompositionLayerInstruction *videoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];

// Watermark layer above video layer here.
instruction.layerInstructions = @[
  watermarkLayerInstruction,
  videoLayerInstruction,
];
  
videoComposition.instructions = @[instruction];

// Export the video w/ watermark.
AVAssetExportSession *exportSession = ...;
...
exportSession.videoComposition = videoComposition;

...
  

И кстати, если вам просто нужно добавить изображение в качестве водяного знака, другое решение с использованием AVVideoComposition

-videoCompositionWithAsset:applyingCIFiltersWithHandler:

также хорошо работает как на реальном устройстве, так и на симуляторе, но я протестировал его и обнаружил, что он медленнее. Кажется, этот способ больше подходит для видеоблендера/фильтра.

person Kjuly    schedule 03.06.2021
comment
На форумах разработчиков Apple есть похожий вопрос: Сбой при экспорте видео с наложением текста, ответил и там. - person Kjuly; 03.06.2021

Это исправило это для меня в iOS 14.5:

public static var isSimulator: Bool {
  #if targetEnvironment(simulator)
  true
  #else
  false
  #endif
}

// ...

let export = AVAssetExportSession(
  asset: composition,
  presetName: isSimulator ? AVAssetExportPresetPassthrough : AVAssetExportPresetHighestQuality
)

редактировать: на самом деле не отображается, как на реальном устройстве. Правки просто игнорируются...

person yspreen    schedule 24.05.2021
comment
Спасибо, @yspreen. Это не устраняет проблему, а просто обходит ее при работе на симуляторе, что является хорошим началом, поскольку сбой, похоже, происходит только на симуляторе. При этом, по крайней мере, он не падает и не прерывает разработку. - person Dan; 29.05.2021
comment
Да, я заметил это после публикации. Изменил мой ответ сейчас - person yspreen; 01.06.2021