You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS中AVAssetExportSession首次运行卡顿的原因与解决方法咨询

问题原因分析

首次调用AVAssetExportSession.export()时卡顿的核心原因是媒体编解码器与框架资源的首次初始化阻塞:

  • 第一次使用AVAssetExportSession时,系统需要加载视频编解码器、初始化媒体处理相关的底层框架,这些操作包含磁盘IO、资源加载等同步任务,默认会占用主线程资源,导致1-2秒的卡顿。
  • 后续调用时,相关资源已经被缓存到内存中,初始化流程直接复用缓存,因此不会再触发阻塞。
解决方案

1. 提前预加载媒体资源

在App启动阶段或用户即将触发压缩操作的场景(比如进入视频编辑页面时),在后台线程提前完成编解码器/ExportSession的初始化,把阻塞操作提前消化:

// 示例:App启动后预加载
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    Task.detached {
        // 使用一个本地的小dummy视频文件完成预初始化
        guard let dummyVideoURL = Bundle.main.url(forResource: "dummy_video", withExtension: "mp4") else { return }
        let dummyAsset = AVAsset(url: dummyVideoURL)
        _ = AVAssetExportSession(asset: dummyAsset, presetName: AVAssetExportPresetMediumQuality)
    }
    return true
}

2. 强制将ExportSession的创建与导出移到后台线程

确保整个ExportSession的创建、配置和导出操作都在脱离主线程上下文的后台Task中执行,避免初始化步骤意外跑在主线程:

// 修正后的压缩代码
Task.detached(priority: .background) { [weak self] in
    guard let self = self,
          let exportSession = AVAssetExportSession(asset: self.singleVideoComposition, presetName: AVAssetExportPresetMediumQuality) else {
        // 切回主线程执行回调
        DispatchQueue.main.async {
            self?.completion(nil)
        }
        return
    }
    
    exportSession.outputFileType = .mp4
    exportSession.timeRange = self.videoTrack.timeRange
    
    await exportSession.export()
    
    // 结果回调必须回到主线程
    DispatchQueue.main.async {
        if exportSession.status == .completed {
            self.completion(self.outputURL)
        } else {
            self.completion(nil)
        }
    }
}

3. 替换为AVAssetWriter(进阶方案)

如果需要更精细的编码控制,可以使用AVAssetWriter手动实现视频压缩,它的初始化和编码流程默认在后台执行,不会出现首次阻塞主线程的问题,但代码复杂度会更高:

// 简化示例:AVAssetWriter基础压缩流程
Task.detached(priority: .background) { [weak self] in
    guard let self = self else { return }
    let asset = self.singleVideoComposition
    guard let videoTrack = asset.tracks(withMediaType: .video).first,
          let audioTrack = asset.tracks(withMediaType: .audio).first else {
        DispatchQueue.main.async { self.completion(nil) }
        return
    }
    
    let outputURL = self.outputURL
    try? FileManager.default.removeItem(at: outputURL)
    
    guard let writer = try? AVAssetWriter(outputURL: outputURL, fileType: .mp4) else {
        DispatchQueue.main.async { self.completion(nil) }
        return
    }
    
    // 配置视频输入
    let videoOutputSettings = [
        AVVideoCodecKey: AVVideoCodecType.h264,
        AVVideoWidthKey: videoTrack.naturalSize.width,
        AVVideoHeightKey: videoTrack.naturalSize.height
    ] as [String: Any]
    let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
    videoInput.transform = videoTrack.preferredTransform
    writer.add(videoInput)
    
    // 配置音频输入
    let audioOutputSettings = [
        AVFormatIDKey: kAudioFormatMPEG4AAC,
        AVNumberOfChannelsKey: 2,
        AVSampleRateKey: 44100.0,
        AVEncoderBitRateKey: 128000
    ] as [String: Any]
    let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioOutputSettings)
    writer.add(audioInput)
    
    // 开始写入
    writer.startWriting()
    writer.startSession(atSourceTime: .zero)
    
    // 处理视频数据
    let videoReader = try? AVAssetReaderTrackOutput(track: videoTrack, outputSettings: nil)
    let audioReader = try? AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
    
    // 省略数据读取与写入的具体逻辑...
    
    // 完成后回调
    DispatchQueue.main.async {
        self.completion(outputURL)
    }
}

内容的提问来源于stack exchange,提问作者Eugene Alexeev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 19:20:09