Экспорт AVAssetExportSession занимает много времени

Моя цель - позволить пользователю выбирать видео из фотографий, а затем позволить ему добавлять к нему метки.

Вот что у меня есть:

let audioAsset = AVURLAsset(url: selectedVideoURL)
let videoAsset = AVURLAsset(url: selectedVideoURL)
let mixComposition = AVMutableComposition()
let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
let clipVideoTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo)[0]
let clipAudioTrack = audioAsset.tracks(withMediaType: AVMediaTypeAudio)[0]
do {
    try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: clipVideoTrack, at: kCMTimeZero)
    try compositionAudioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, audioAsset.duration), of: clipAudioTrack, at: kCMTimeZero)
    compositionVideoTrack.preferredTransform = clipVideoTrack.preferredTransform
} catch {
    print(error)
}
var videoSize = clipVideoTrack.naturalSize
if isVideoPortrait(asset: videoAsset) {
    videoSize = CGSize(width: videoSize.height, height: videoSize.width)
}
let parentLayer = CALayer()
let videoLayer = CALayer()
parentLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
parentLayer.addSublayer(videoLayer)

// adding label
let helloLabelLayer = CATextLayer()
helloLabelLayer.string = "Hello"
helloLabelLayer.font = "Signika-Semibold" as CFTypeRef?
helloLabelLayer.fontSize = 30.0
helloLabelLayer.contentsScale = mainScreen.scale
helloLabelLayer.alignmentMode = kCAAlignmentNatural
helloLabelLayer.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 50.0)
parentLayer.addSublayer(helloLabelLayer)

// creating composition
let videoComp = AVMutableVideoComposition()
videoComp.renderSize = videoSize
videoComp.frameDuration = CMTimeMake(1, 30)
videoComp.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer)

let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mixComposition.duration)
let layerInstruction = videoCompositionInstructionForTrack(track: compositionVideoTrack, asset: videoAsset)
instruction.layerInstructions = [layerInstruction]
videoComp.instructions = [instruction]
if let assetExport = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPreset640x480) {
    let filename = NSTemporaryDirectory().appending("video.mov")

    if FileManager.default.fileExists(atPath: filename) {
    do {
        try FileManager.default.removeItem(atPath: filename)
    } catch {
        print(error)
    }
}

let url = URL(fileURLWithPath: filename)
assetExport.outputURL = url
assetExport.outputFileType = AVFileTypeMPEG4
assetExport.videoComposition = videoComp
print(NSDate().timeIntervalSince1970)
assetExport.exportAsynchronously {
    print(NSDate().timeIntervalSince1970)
    let library = ALAssetsLibrary()
    library.writeVideoAtPath(toSavedPhotosAlbum: url, completionBlock: {
        (url, error) in
        switch assetExport.status {
            case AVAssetExportSessionStatus.failed:
                p("failed \(assetExport.error)")
            case AVAssetExportSessionStatus.cancelled:
                p("cancelled \(assetExport.error)")
            default:
                p("complete")
                p(NSDate().timeIntervalSince1970)
                if FileManager.default.fileExists(atPath: filename) {
                    do {
                        try FileManager.default.removeItem(atPath: filename)
                    } catch {
                        p(error)
                    }
                }
                print("Exported")                                    
        }
    })
}

Реализация функции isVideoPortrait:

func isVideoPortrait(asset: AVAsset) -> Bool {
    var isPortrait = false
    let tracks = asset.tracks(withMediaType: AVMediaTypeVideo)
    if tracks.count > 0 {
        let videoTrack = tracks[0]
        let t = videoTrack.preferredTransform
        if t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0 {
            isPortrait = true
        }
        if t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0 {
            isPortrait = true
        }
        if t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0 {
            isPortrait = false
        }
        if t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0 {
            isPortrait = false
        }
    }
    return isPortrait
}

И последняя функция для video composition layer instruction:

func videoCompositionInstructionForTrack(track: AVCompositionTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction {
    let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
    let assetTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0]
    let transform = assetTrack.preferredTransform
    instruction.setTransform(transform, at: kCMTimeZero)
    return instruction
}

Код работает хорошо, выходное видео имеет метку, но если я выберу 1-минутное видео, экспорт займет 28 секунд.

Я искал его и пытался удалить layerInsctuction преобразование, но безрезультатно.

Пробовал добавлять: assetExport.shouldOptimizeForNetworkUse = false тоже никакого эффекта.

Кроме того, пытался установить AVAssetExportPresetPassthrough для AVAssetExportSession, в этом случае видео экспортируется с 1 секундой, но метки исчезли.

Любая помощь будет оценена по достоинству, потому что я застрял. Спасибо за ваше время.


person mkz    schedule 30.11.2016    source источник
comment
Вы делаете две трудоемкие вещи: экспорт и копирование в фотоальбом. Какой из них занимает много времени?   -  person matt    schedule 30.11.2016
comment
Также: когда что-то занимает много времени и вы хотите знать, почему, используйте Инструменты. Вы пробовали это? Что оно тебе сказало?   -  person matt    schedule 30.11.2016
comment
@matt В основном экспорт занимает много времени. Копирование в фотоальбом занимает 1 секунду. Я еще не использовал инструменты, должен ли я использовать для этого Монитор активности при запуске инструментов?   -  person mkz    schedule 30.11.2016
comment
Вы должны профилировать время.   -  person matt    schedule 30.11.2016
comment
@matt Я использовал Time Profile, запустил приложение и выполнил все обычные шаги. Вот результат, но он немного сложный. Помогите, что мне искать?   -  person mkz    schedule 30.11.2016
comment
Вам нужно продолжать открывать основной поток (поскольку он занимает больше всего времени), а затем продолжать открывать все, что занимает больше всего времени, пока вы не дойдете до своего кода. После этого вы сможете увидеть, на что уходит время, и даже увидеть представление своего кода, в котором каждая строка отмечена тем, насколько это занимает много времени. Это просто фантастика! Посмотрите сессию 418 WWDC 2016, чтобы получить хорошее вступление.   -  person matt    schedule 30.11.2016


Ответы (2)


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

Это делается с помощью словаря, примененного к видеонастройкам AssetExporter, для этого мне пришлось использовать фреймворк под названием SDAVAssetExportSession.

Затем, изменив настройки видео, я мог поиграть с качеством, чтобы получить оптимальное качество / скорость.

    let compression = [AVVideoAverageBitRateKey : 2097152(DESIRED_BITRATE),AVVideoProfileLevelKey : AVVideoProfileLevelH264BaselineAutoLevel]

    let videoSettings = [AVVideoCodecKey : AVVideoCodecH264, AVVideoWidthKey : maxWidth, AVVideoHeightKey : maxHeight, AVVideoCompressionPropertiesKey:compression]

Только так я мог ускорить процесс.

person Sean Lintern    schedule 19.12.2016
comment
Спасибо, это помогло мне сократить время вдвое. - person mkz; 19.12.2016
comment
Я не думаю, что вам нужно использовать для этого структуру (если я что-то не упустил), вы можете получить пресеты из AVAssetExportSession.exportPresets(compatibleWith: yourVideoAsset), а затем передать их вашему экземпляру AVAssetExportSession. - person AdjunctProfessorFalcon; 06.03.2018
comment
Я считаю, что эти конкретные ключи несовместимы с AVAssetExportSession, вы должны использовать более низкий AVAssetWriter, что и делает этот файл под капотом. - person Sean Lintern; 06.03.2018
comment
Кто-нибудь нашел какое-либо решение для сокращения времени, затрачиваемого exportAsynchronously на экспорт видео/аудио. У меня уходит много времени на кодирование больших аудио продолжительностью 5 часов и выше. Я пробовал использовать AVAssetExportSession и SDAVAssetExportSession, но оба они дают одинаковые результаты. :( - person Ali Raza; 27.02.2021
comment
@AliRaza Это зависит от того, записываете ли вы видео / аудио, а затем перекодируете его, чтобы уменьшить размер, или просто перекодируете существующие статические файлы? - person Sean Lintern; 03.03.2021
comment
@SeanLintern Я записываю звук, который, как мне кажется, не кодируется во время записи. Когда я нажимаю кнопку «Готово», для ее кодирования требуется время, которое увеличивается с длиной записанного звука. - person Ali Raza; 04.03.2021
comment
@AliRaza, тогда вам нужно изменить качество записи с помощью SDAVAssetExportSession - person Sean Lintern; 07.03.2021
comment
@SeanLintern Я уже уменьшил его до минимума. Это улучшило время кодирования, но все еще требует времени. По этой причине я не могу полностью сохранить звук, когда звонит телефон, потому что для кодирования требуется время. - person Ali Raza; 07.03.2021

Это не имеет прямого отношения к вашему вопросу, но ваш код здесь обратный:

assetExport.exportAsynchronously {
    let library = ALAssetsLibrary()
    library.writeVideoAtPath(toSavedPhotosAlbum: url, completionBlock: {
        switch assetExport.status {

Нет нет нет. Сначала вы завершаете экспорт активов. Затем вы можете снова скопировать куда-нибудь еще, если это то, что вы хотите сделать. Итак, это должно быть так:

assetExport.exportAsynchronously {
    switch assetExport.status {
    case .completed:
        let library = ALAssetsLibrary()
        library.writeVideoAtPath...

Другие комментарии:

  • ALAssetsLibrary мертв. Это не способ копирования в библиотеку фотографий пользователя. Используйте рамки для фотографий.

  • Ваш исходный код очень странный, потому что есть много других случаев, которые вы не тестируете. Вы просто предполагаете, что default означает .completed. Это опасно.

person matt    schedule 30.11.2016
comment
Без проблем. Мне жаль, что это не настоящий ответ; Я, вероятно, придется удалить его. :) Но не было возможности сказать все это в комментарии. - person matt; 30.11.2016