SwiftUI中PhotosPicker选择视频后文件体积大于原文件的问题排查
你好呀,我来帮你捋捋这个问题~首先你的现象很明确:用PhotosPicker选视频后拿到的文件是8.9MB,比原文件的5.3MB大,但用UIImagePickerController就能拿到和原文件一样大小的,这其实是PhotosPicker的默认转码行为导致的,咱们来一步步分析原因和解决办法:
问题根源:自动转码导致体积变大
iOS的PhotosPicker在通过Transferable协议处理视频时,默认会把视频转码成系统通用兼容的格式(比如把高效的HEVC/H.265编码转成兼容性更广的H.264编码)。而H.264的压缩率比HEVC低,所以转码后的文件体积自然会变大。反观UIImagePickerController,它是直接获取相册里的原文件URL,没有经过转码步骤,所以大小和原文件一致。
另外还要提一句你代码里的小问题:在Transferable的importing闭包里,你只是创建了目标URL,但没有执行实际的文件复制操作,这样后续访问copy这个URL会找不到文件哦,正确的复制逻辑应该加上文件拷贝的代码(不过即使加上,复制的也是转码后的文件,体积还是会大)。
解决方案:获取原文件,绕过转码
要拿到和原文件大小一致的视频,咱们可以直接获取相册里的原资产(PHAsset),然后从中提取原文件的URL,完全绕过Transferable的转码流程。修改你ContentView里的onChange代码就行:
.onChange(of: selectedItem) { _ in Task { do { // 加载原始PHAsset if let asset = try await selectedItem?.loadOriginalAsset() { let options = PHVideoRequestOptions() options.isNetworkAccessAllowed = true // 如果视频存在iCloud上,需要开启这个 options.version = .original // 指定获取原版本视频 // 请求原视频的AVAsset PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { avAsset, audioMix, info in if let urlAsset = avAsset as? AVURLAsset { let originalURL = urlAsset.url print("原文件大小:\(originalURL.fileSize)") // 把原文件复制到你的应用目录(可选,根据需求来) let destinationURL = URL.documentsDirectory.appending(path: "abc.mp4") do { // 先删除已存在的文件(避免覆盖报错) if FileManager.default.fileExists(atPath: destinationURL.path) { try FileManager.default.removeItem(at: destinationURL) } try FileManager.default.copyItem(at: originalURL, to: destinationURL) print("复制完成,目标文件大小:\(destinationURL.fileSize)") } catch { print("复制文件出错:\(error.localizedDescription)") } } } } } catch { print("加载资产出错:\(error.localizedDescription)") } } }
补充:如果坚持用Transferable怎么办?
如果你不想用PHAsset的方式,也可以尝试给FileRepresentation指定更精确的视频类型,比如直接指定HEVC的contentType,让系统不要转码:
static var transferRepresentation: some TransferRepresentation { FileRepresentation(contentType: .videoHEVC) { movie in SentTransferredFile(movie.url) } importing: { received in print("Importing \(received)") print("receive \(received.file.fileSize)") let copyURL = URL.documentsDirectory.appending(path: "abc.mp4") // 加上实际的复制操作 try FileManager.default.copyItem(at: received.file, to: copyURL) return Self.init(url: copyURL) } }
不过这个方法有局限性:如果用户选择的视频不是HEVC编码的,就会失败,所以还是推荐第一种获取原PHAsset的方式更稳妥。
备注:内容来源于stack exchange,提问作者William Hu

