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
相关产品推荐
相关产品推荐

