能否无画质损失录制AVPlayer视频的用户操作过程?
实现AVPlayer操作过程的无损录制方案
直接使用系统屏幕录制API会因为默认的高压缩编码导致画质损失,要实现无损录制,核心思路是直接捕获AVPlayer的原始视频帧,叠加操作UI后用无损编码合成视频,以下是具体实现方案:
方案一:原始帧捕获+UI合成+无损编码输出
1. 捕获AVPlayer的原始视频帧
通过AVPlayerItemVideoOutput获取未压缩的视频帧,避免屏幕录制的压缩损耗:
// 配置视频输出的像素格式,选择无损格式 let pixelBufferAttributes: [String: Any] = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, kCVPixelBufferWidthKey as String: videoWidth, kCVPixelBufferHeightKey as String: videoHeight ] let videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: pixelBufferAttributes) playerItem.add(videoOutput) // 定时获取帧(和视频帧率同步) Timer.scheduledTimer(withTimeInterval: 1/30, repeats: true) { timer in let itemTime = playerItem.currentTime() if videoOutput.hasNewPixelBuffer(forItemTime: itemTime) { guard let pixelBuffer = videoOutput.copyPixelBuffer(forItemTime: itemTime, itemTimeForDisplay: nil) else { return } // 拿到原始pixelBuffer,下一步合成UI self.combineVideoFrameWithUI(pixelBuffer: pixelBuffer, timestamp: itemTime) } }
2. 叠加操作UI到视频帧
将播放器的控制界面(自定义控件或系统AVPlayerViewController的控制层)渲染为图像,合成到原始视频帧上:
func combineVideoFrameWithUI(pixelBuffer: CVPixelBuffer, timestamp: CMTime) { // 将UI视图转为CGImage UIGraphicsBeginImageContextWithOptions(playerView.bounds.size, false, UIScreen.main.scale) playerView.drawHierarchy(in: playerView.bounds, afterScreenUpdates: true) guard let uiImage = UIGraphicsGetImageFromCurrentImageContext() else { UIGraphicsEndImageContext() return } UIGraphicsEndImageContext() // 将CGImage和原始pixelBuffer合成(用Metal替代CoreImage可提升性能) let ciImage = CIImage(cgImage: uiImage.cgImage!) let context = CIContext(options: [.useSoftwareRenderer: false]) context.render(ciImage, to: pixelBuffer) // 将合成后的帧写入视频文件 writePixelBufferToAssetWriter(pixelBuffer: pixelBuffer, timestamp: timestamp) }
3. 用AVAssetWriter输出无损视频
配置AVAssetWriter使用无损编码格式(比如Apple ProRes),保证输出画质无损失:
// 初始化AVAssetWriter let outputURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("recorded_video.mov") guard let assetWriter = try? AVAssetWriter(outputURL: outputURL, fileType: .mov) else { return } // 配置视频输入,使用Apple ProRes无损编码 let videoOutputSettings: [String: Any] = [ AVVideoCodecKey: AVVideoCodecType.appleProRes4444, AVVideoWidthKey: videoWidth, AVVideoHeightKey: videoHeight ] let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings) videoInput.expectsMediaDataInRealTime = true assetWriter.add(videoInput) // 写入合成后的pixelBuffer func writePixelBufferToAssetWriter(pixelBuffer: CVPixelBuffer, timestamp: CMTime) { if assetWriter.status == .unknown { assetWriter.startWriting() assetWriter.startSession(atSourceTime: timestamp) } if videoInput.isReadyForMoreMediaData { videoInput.append(pixelBuffer, withPresentationTime: timestamp) } }
关键注意事项
- 性能优化:处理原始帧和UI合成会占用较多CPU/GPU,建议用Metal替代CoreImage做合成,或者在后台队列处理帧数据,避免主线程阻塞。
- 编码选择:Apple ProRes是苹果生态下的无损编码,兼容性好;如果需要跨平台,也可以选择未压缩的AVI格式,但文件体积会很大。
- 音频录制:如果需要同时录制视频的音频,可以通过
AVPlayerItemAudioOutput捕获原始音频帧,和视频同步写入到AVAssetWriter。
备选方案:记录操作指令而非视频
如果不需要生成视频文件,只是需要回放操作过程,可以记录用户的操作事件(比如播放/暂停的时间点、进度拖动的目标时间),回放时直接控制AVPlayer执行这些指令,这种方式完全没有画质损失,但无法生成可分享的视频文件。
内容的提问来源于stack exchange,提问作者Westley
相关产品推荐
相关产品推荐

